diff options
author | Rob Mensching <rob@firegiant.com> | 2017-09-17 15:35:20 -0700 |
---|---|---|
committer | Rob Mensching <rob@firegiant.com> | 2017-09-17 16:00:11 -0700 |
commit | d3d3649a68cb1fa589fdd987a6690dbd5d671f0d (patch) | |
tree | 44fe37ee352b7e3a355cc1e08b1d7d5988c14cc0 /src | |
parent | a62610d23d6feb98be3b1e529a4e81b19d77d9d8 (diff) | |
download | wix-d3d3649a68cb1fa589fdd987a6690dbd5d671f0d.tar.gz wix-d3d3649a68cb1fa589fdd987a6690dbd5d671f0d.tar.bz2 wix-d3d3649a68cb1fa589fdd987a6690dbd5d671f0d.zip |
Initial code commit
Diffstat (limited to 'src')
205 files changed, 80284 insertions, 0 deletions
diff --git a/src/Directory.Build.props b/src/Directory.Build.props new file mode 100644 index 00000000..25cb6d36 --- /dev/null +++ b/src/Directory.Build.props | |||
@@ -0,0 +1,20 @@ | |||
1 | <?xml version="1.0" encoding="utf-8"?> | ||
2 | <!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. --> | ||
3 | |||
4 | <Project> | ||
5 | <PropertyGroup> | ||
6 | <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> | ||
7 | <BaseIntermediateOutputPath>$(MSBuildThisFileDirectory)..\build\obj\$(MSBuildProjectName)\</BaseIntermediateOutputPath> | ||
8 | <OutputPath>$(MSBuildThisFileDirectory)..\build\$(Configuration)\</OutputPath> | ||
9 | |||
10 | <Authors>WiX Toolset Team</Authors> | ||
11 | <Company>WiX Toolset</Company> | ||
12 | <Copyright>Copyright (c) .NET Foundation and contributors. All rights reserved.</Copyright> | ||
13 | </PropertyGroup> | ||
14 | |||
15 | <PropertyGroup> | ||
16 | <WixToolsetRootFolder>$(MSBuildThisFileDirectory)..\..\</WixToolsetRootFolder> | ||
17 | </PropertyGroup> | ||
18 | |||
19 | <Import Project="Custom.Build.props" Condition=" Exists('Custom.Build.props') " /> | ||
20 | </Project> | ||
diff --git a/src/WixToolset.BuildTasks/AssemblyInfo.cs b/src/WixToolset.BuildTasks/AssemblyInfo.cs new file mode 100644 index 00000000..ae52fce8 --- /dev/null +++ b/src/WixToolset.BuildTasks/AssemblyInfo.cs | |||
@@ -0,0 +1,7 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
3 | using System.Reflection; | ||
4 | using System.Runtime.InteropServices; | ||
5 | |||
6 | [assembly: AssemblyCulture("")] | ||
7 | [assembly: ComVisible(false)] | ||
diff --git a/src/WixToolset.BuildTasks/BuildException.cs b/src/WixToolset.BuildTasks/BuildException.cs new file mode 100644 index 00000000..953134ba --- /dev/null +++ b/src/WixToolset.BuildTasks/BuildException.cs | |||
@@ -0,0 +1,26 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
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 | } | ||
diff --git a/src/WixToolset.BuildTasks/Candle.cs b/src/WixToolset.BuildTasks/Candle.cs new file mode 100644 index 00000000..82b15838 --- /dev/null +++ b/src/WixToolset.BuildTasks/Candle.cs | |||
@@ -0,0 +1,199 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
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 | } | ||
diff --git a/src/WixToolset.BuildTasks/Common.cs b/src/WixToolset.BuildTasks/Common.cs new file mode 100644 index 00000000..803e9d14 --- /dev/null +++ b/src/WixToolset.BuildTasks/Common.cs | |||
@@ -0,0 +1,41 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
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 | } | ||
diff --git a/src/WixToolset.BuildTasks/ConvertReferences.cs b/src/WixToolset.BuildTasks/ConvertReferences.cs new file mode 100644 index 00000000..fe137633 --- /dev/null +++ b/src/WixToolset.BuildTasks/ConvertReferences.cs | |||
@@ -0,0 +1,93 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
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 | } | ||
diff --git a/src/WixToolset.BuildTasks/CreateItemAvoidingInference.cs b/src/WixToolset.BuildTasks/CreateItemAvoidingInference.cs new file mode 100644 index 00000000..84816cac --- /dev/null +++ b/src/WixToolset.BuildTasks/CreateItemAvoidingInference.cs | |||
@@ -0,0 +1,60 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
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 | } | ||
diff --git a/src/WixToolset.BuildTasks/CreateProjectReferenceDefineConstants.cs b/src/WixToolset.BuildTasks/CreateProjectReferenceDefineConstants.cs new file mode 100644 index 00000000..5eae0850 --- /dev/null +++ b/src/WixToolset.BuildTasks/CreateProjectReferenceDefineConstants.cs | |||
@@ -0,0 +1,270 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
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 | } | ||
diff --git a/src/WixToolset.BuildTasks/DoIt-Compile.cs b/src/WixToolset.BuildTasks/DoIt-Compile.cs new file mode 100644 index 00000000..f89078fe --- /dev/null +++ b/src/WixToolset.BuildTasks/DoIt-Compile.cs | |||
@@ -0,0 +1,192 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
3 | #if false | ||
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 | ||
diff --git a/src/WixToolset.BuildTasks/DoIt.cs b/src/WixToolset.BuildTasks/DoIt.cs new file mode 100644 index 00000000..7688342c --- /dev/null +++ b/src/WixToolset.BuildTasks/DoIt.cs | |||
@@ -0,0 +1,233 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
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 | } | ||
diff --git a/src/WixToolset.BuildTasks/FileSearchHelperMethods.cs b/src/WixToolset.BuildTasks/FileSearchHelperMethods.cs new file mode 100644 index 00000000..6cc804eb --- /dev/null +++ b/src/WixToolset.BuildTasks/FileSearchHelperMethods.cs | |||
@@ -0,0 +1,60 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
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 | } | ||
diff --git a/src/WixToolset.BuildTasks/GenerateCompileWithObjectPath.cs b/src/WixToolset.BuildTasks/GenerateCompileWithObjectPath.cs new file mode 100644 index 00000000..06c8b98a --- /dev/null +++ b/src/WixToolset.BuildTasks/GenerateCompileWithObjectPath.cs | |||
@@ -0,0 +1,146 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
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 | } | ||
diff --git a/src/WixToolset.BuildTasks/GetCabList.cs b/src/WixToolset.BuildTasks/GetCabList.cs new file mode 100644 index 00000000..e97538af --- /dev/null +++ b/src/WixToolset.BuildTasks/GetCabList.cs | |||
@@ -0,0 +1,87 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
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 | } | ||
diff --git a/src/WixToolset.BuildTasks/GetLooseFileList.cs b/src/WixToolset.BuildTasks/GetLooseFileList.cs new file mode 100644 index 00000000..bd403426 --- /dev/null +++ b/src/WixToolset.BuildTasks/GetLooseFileList.cs | |||
@@ -0,0 +1,230 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
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 | } | ||
diff --git a/src/WixToolset.BuildTasks/GlobalSuppressions.cs b/src/WixToolset.BuildTasks/GlobalSuppressions.cs new file mode 100644 index 00000000..65c34c13 --- /dev/null +++ b/src/WixToolset.BuildTasks/GlobalSuppressions.cs | |||
@@ -0,0 +1,8 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
3 | [assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1020:AvoidNamespacesWithFewTypes", Scope = "namespace", Target = "WixToolset")] | ||
4 | |||
5 | [assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Msi", Scope = "namespace", Target = "WixToolset.BuildTasks")] | ||
6 | [assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "wix")] | ||
7 | [assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Wix")] | ||
8 | [assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "wix")] | ||
diff --git a/src/WixToolset.BuildTasks/Insignia.cs b/src/WixToolset.BuildTasks/Insignia.cs new file mode 100644 index 00000000..ba30963a --- /dev/null +++ b/src/WixToolset.BuildTasks/Insignia.cs | |||
@@ -0,0 +1,118 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
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 | } | ||
diff --git a/src/WixToolset.BuildTasks/Light.cs b/src/WixToolset.BuildTasks/Light.cs new file mode 100644 index 00000000..b7d0b4f7 --- /dev/null +++ b/src/WixToolset.BuildTasks/Light.cs | |||
@@ -0,0 +1,488 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
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 | } | ||
diff --git a/src/WixToolset.BuildTasks/Lit.cs b/src/WixToolset.BuildTasks/Lit.cs new file mode 100644 index 00000000..1df964ae --- /dev/null +++ b/src/WixToolset.BuildTasks/Lit.cs | |||
@@ -0,0 +1,178 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
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 | } | ||
diff --git a/src/WixToolset.BuildTasks/Pyro.cs b/src/WixToolset.BuildTasks/Pyro.cs new file mode 100644 index 00000000..f6b069da --- /dev/null +++ b/src/WixToolset.BuildTasks/Pyro.cs | |||
@@ -0,0 +1,140 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
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 | } | ||
diff --git a/src/WixToolset.BuildTasks/RefreshBundleGeneratedFile.cs b/src/WixToolset.BuildTasks/RefreshBundleGeneratedFile.cs new file mode 100644 index 00000000..5445e0cd --- /dev/null +++ b/src/WixToolset.BuildTasks/RefreshBundleGeneratedFile.cs | |||
@@ -0,0 +1,132 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
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 | } | ||
diff --git a/src/WixToolset.BuildTasks/RefreshGeneratedFile.cs b/src/WixToolset.BuildTasks/RefreshGeneratedFile.cs new file mode 100644 index 00000000..fdfc4774 --- /dev/null +++ b/src/WixToolset.BuildTasks/RefreshGeneratedFile.cs | |||
@@ -0,0 +1,118 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
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 | } | ||
diff --git a/src/WixToolset.BuildTasks/ReplaceString.cs b/src/WixToolset.BuildTasks/ReplaceString.cs new file mode 100644 index 00000000..e5041923 --- /dev/null +++ b/src/WixToolset.BuildTasks/ReplaceString.cs | |||
@@ -0,0 +1,54 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
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 | } | ||
diff --git a/src/WixToolset.BuildTasks/ResolveWixReferences.cs b/src/WixToolset.BuildTasks/ResolveWixReferences.cs new file mode 100644 index 00000000..9b8cfe6f --- /dev/null +++ b/src/WixToolset.BuildTasks/ResolveWixReferences.cs | |||
@@ -0,0 +1,212 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
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 | } | ||
diff --git a/src/WixToolset.BuildTasks/TaskBase.cs b/src/WixToolset.BuildTasks/TaskBase.cs new file mode 100644 index 00000000..3d58fc06 --- /dev/null +++ b/src/WixToolset.BuildTasks/TaskBase.cs | |||
@@ -0,0 +1,65 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
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 | } | ||
diff --git a/src/WixToolset.BuildTasks/Torch.cs b/src/WixToolset.BuildTasks/Torch.cs new file mode 100644 index 00000000..e18ed315 --- /dev/null +++ b/src/WixToolset.BuildTasks/Torch.cs | |||
@@ -0,0 +1,159 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
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 | } | ||
diff --git a/src/WixToolset.BuildTasks/WixAssignCulture.cs b/src/WixToolset.BuildTasks/WixAssignCulture.cs new file mode 100644 index 00000000..7a03dc47 --- /dev/null +++ b/src/WixToolset.BuildTasks/WixAssignCulture.cs | |||
@@ -0,0 +1,231 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
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 | } | ||
diff --git a/src/WixToolset.BuildTasks/WixCommandLineBuilder.cs b/src/WixToolset.BuildTasks/WixCommandLineBuilder.cs new file mode 100644 index 00000000..9a6a005d --- /dev/null +++ b/src/WixToolset.BuildTasks/WixCommandLineBuilder.cs | |||
@@ -0,0 +1,180 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
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 | } | ||
diff --git a/src/WixToolset.BuildTasks/WixToolTask.cs b/src/WixToolset.BuildTasks/WixToolTask.cs new file mode 100644 index 00000000..2e5e8705 --- /dev/null +++ b/src/WixToolset.BuildTasks/WixToolTask.cs | |||
@@ -0,0 +1,406 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
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 | } | ||
diff --git a/src/WixToolset.BuildTasks/WixToolset.BuildTasks.csproj b/src/WixToolset.BuildTasks/WixToolset.BuildTasks.csproj new file mode 100644 index 00000000..34a1a9f5 --- /dev/null +++ b/src/WixToolset.BuildTasks/WixToolset.BuildTasks.csproj | |||
@@ -0,0 +1,38 @@ | |||
1 | <?xml version="1.0" encoding="utf-8"?> | ||
2 | <!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. --> | ||
3 | |||
4 | <Project Sdk="Microsoft.NET.Sdk"> | ||
5 | <PropertyGroup> | ||
6 | <TargetFramework>net462</TargetFramework> | ||
7 | <Description></Description> | ||
8 | <Title>WiX Toolset MSBuild Tasks</Title> | ||
9 | </PropertyGroup> | ||
10 | |||
11 | <PropertyGroup> | ||
12 | <!-- <CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies> --> | ||
13 | <NoWarn>NU1701</NoWarn> | ||
14 | </PropertyGroup> | ||
15 | |||
16 | <ItemGroup> | ||
17 | <Content Include="redirect.wix.targets" CopyToOutputDirectory="PreserveNewest" /> | ||
18 | <Content Include="redirect.wix.ca.targets" CopyToOutputDirectory="PreserveNewest" /> | ||
19 | <Content Include="wix.targets" CopyToOutputDirectory="PreserveNewest" /> | ||
20 | <Content Include="wix.ca.targets" CopyToOutputDirectory="PreserveNewest" /> | ||
21 | <Content Include="wix.harvest.targets" CopyToOutputDirectory="PreserveNewest" /> | ||
22 | <Content Include="wix.signing.targets" CopyToOutputDirectory="PreserveNewest" /> | ||
23 | </ItemGroup> | ||
24 | |||
25 | <ItemGroup> | ||
26 | <ProjectReference Include="..\WixToolset.Core\WixToolset.Core.csproj" /> | ||
27 | </ItemGroup> | ||
28 | |||
29 | <ItemGroup> | ||
30 | <PackageReference Include="WixToolset.Dtf.WindowsInstaller" Version="4.0.*" /> | ||
31 | </ItemGroup> | ||
32 | |||
33 | <ItemGroup> | ||
34 | <PackageReference Include="Nerdbank.GitVersioning" Version="2.0.41" PrivateAssets="all" /> | ||
35 | <PackageReference Include="Microsoft.Build.Tasks.Core" Version="14.3" PrivateAssets="all" Condition="'$(TargetFramework)'=='net462' " /> | ||
36 | <PackageReference Include="Microsoft.Build.Tasks.Core" Version="15.3.409" PrivateAssets="all" Condition="'$(TargetFramework)'=='netstandard2.0' " /> | ||
37 | </ItemGroup> | ||
38 | </Project> | ||
diff --git a/src/WixToolset.BuildTasks/heatdirectory.cs b/src/WixToolset.BuildTasks/heatdirectory.cs new file mode 100644 index 00000000..1d5f104a --- /dev/null +++ b/src/WixToolset.BuildTasks/heatdirectory.cs | |||
@@ -0,0 +1,103 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
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 | } | ||
diff --git a/src/WixToolset.BuildTasks/heatfile.cs b/src/WixToolset.BuildTasks/heatfile.cs new file mode 100644 index 00000000..69e11b88 --- /dev/null +++ b/src/WixToolset.BuildTasks/heatfile.cs | |||
@@ -0,0 +1,95 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
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 | } | ||
diff --git a/src/WixToolset.BuildTasks/heatproject.cs b/src/WixToolset.BuildTasks/heatproject.cs new file mode 100644 index 00000000..8620ffa3 --- /dev/null +++ b/src/WixToolset.BuildTasks/heatproject.cs | |||
@@ -0,0 +1,108 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
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 | } | ||
diff --git a/src/WixToolset.BuildTasks/heattask.cs b/src/WixToolset.BuildTasks/heattask.cs new file mode 100644 index 00000000..bf0a2ad3 --- /dev/null +++ b/src/WixToolset.BuildTasks/heattask.cs | |||
@@ -0,0 +1,121 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
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 | } | ||
diff --git a/src/WixToolset.BuildTasks/redirect.wix.ca.targets b/src/WixToolset.BuildTasks/redirect.wix.ca.targets new file mode 100644 index 00000000..74e6ec3c --- /dev/null +++ b/src/WixToolset.BuildTasks/redirect.wix.ca.targets | |||
@@ -0,0 +1,11 @@ | |||
1 | <?xml version="1.0" encoding="utf-8" ?> | ||
2 | <!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. --> | ||
3 | |||
4 | |||
5 | <Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="4.0"> | ||
6 | <PropertyGroup> | ||
7 | <WixInstallFolder>$([MSBuild]::GetRegistryValueFromView('HKEY_LOCAL_MACHINE\SOFTWARE\WiX Toolset\v4', 'InstallFolder', null, RegistryView.Registry32))</WixInstallFolder> | ||
8 | </PropertyGroup> | ||
9 | |||
10 | <Import Project="$(WixInstallFolder)sdk\wix.ca.targets" /> | ||
11 | </Project> | ||
diff --git a/src/WixToolset.BuildTasks/redirect.wix.targets b/src/WixToolset.BuildTasks/redirect.wix.targets new file mode 100644 index 00000000..b40c4c36 --- /dev/null +++ b/src/WixToolset.BuildTasks/redirect.wix.targets | |||
@@ -0,0 +1,11 @@ | |||
1 | <?xml version="1.0" encoding="utf-8"?> | ||
2 | <!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. --> | ||
3 | |||
4 | |||
5 | <Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="4.0"> | ||
6 | <PropertyGroup> | ||
7 | <WixInstallFolder>$([MSBuild]::GetRegistryValueFromView('HKEY_LOCAL_MACHINE\SOFTWARE\WiX Toolset\v4', 'InstallFolder', null, RegistryView.Registry32))</WixInstallFolder> | ||
8 | </PropertyGroup> | ||
9 | |||
10 | <Import Project="$(WixInstallFolder)bin\wix.targets" /> | ||
11 | </Project> | ||
diff --git a/src/WixToolset.BuildTasks/wix.ca.targets b/src/WixToolset.BuildTasks/wix.ca.targets new file mode 100644 index 00000000..4578c2d8 --- /dev/null +++ b/src/WixToolset.BuildTasks/wix.ca.targets | |||
@@ -0,0 +1,123 @@ | |||
1 | <?xml version="1.0" encoding="utf-8" ?> | ||
2 | <!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. --> | ||
3 | |||
4 | |||
5 | <Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="4.0"> | ||
6 | |||
7 | <Import Project="$(CustomBeforeWixCATargets)" Condition=" '$(CustomBeforeWixCATargets)' != '' and Exists('$(CustomBeforeWixCATargets)')" /> | ||
8 | |||
9 | <PropertyGroup> | ||
10 | <WixCATargetsImported>true</WixCATargetsImported> | ||
11 | |||
12 | <TargetCAFileName Condition=" '$(TargetCAFileName)' == '' ">$(TargetName).CA$(TargetExt)</TargetCAFileName> | ||
13 | |||
14 | <WixSdkPath Condition=" '$(WixSdkPath)' == '' ">$(MSBuildThisFileDirectory)</WixSdkPath> | ||
15 | <WixSdkX86Path Condition=" '$(WixSdkX86Path)' == '' ">$(WixSdkPath)x86\</WixSdkX86Path> | ||
16 | <WixSdkX64Path Condition=" '$(WixSdkX64Path)' == '' ">$(WixSdkPath)x64\</WixSdkX64Path> | ||
17 | |||
18 | <MakeSfxCA Condition=" '$(MakeSfxCA)' == '' ">$(WixSdkPath)MakeSfxCA.exe</MakeSfxCA> | ||
19 | <SfxCADll Condition=" '$(SfxCADll)' == '' and '$(Platform)' == 'x64' ">$(WixSdkX64Path)SfxCA.dll</SfxCADll> | ||
20 | <SfxCADll Condition=" '$(SfxCADll)' == '' ">$(WixSdkX86Path)SfxCA.dll</SfxCADll> | ||
21 | </PropertyGroup> | ||
22 | |||
23 | <!-- | ||
24 | ================================================================================================== | ||
25 | PackCustomAction | ||
26 | |||
27 | Creates an MSI managed custom action package that includes the custom action assembly, | ||
28 | local assembly dependencies, and project content files. | ||
29 | |||
30 | [IN] | ||
31 | @(IntermediateAssembly) - Managed custom action assembly. | ||
32 | @(Content) - Project items of type Content will be included in the package. | ||
33 | $(CustomActionContents) - Optional space-delimited list of additional files to include. | ||
34 | |||
35 | [OUT] | ||
36 | $(IntermediateOutputPath)$(TargetCAFileName) - Managed custom action package with unmanaged stub. | ||
37 | ================================================================================================== | ||
38 | --> | ||
39 | <Target Name="PackCustomAction" | ||
40 | Inputs="@(IntermediateAssembly);@(Content);$(CustomActionContents)" | ||
41 | Outputs="$(IntermediateOutputPath)$(TargetCAFileName)"> | ||
42 | |||
43 | <!-- Find all referenced items marked CopyLocal, but exclude non-binary files. --> | ||
44 | <ItemGroup> | ||
45 | <CustomActionReferenceContents Include="@(ReferenceCopyLocalPaths)" | ||
46 | Condition=" '%(Extension)' == '.dll' or '%(Extension)' == '.exe' " /> | ||
47 | <CustomActionReferenceContents Include="@(ReferenceComWrappersToCopyLocal)" | ||
48 | Condition=" '%(Extension)' == '.dll' or '%(Extension)' == '.exe' " /> | ||
49 | |||
50 | <!-- include PDBs for Debug only --> | ||
51 | <CustomActionReferenceContents Include="@(IntermediateAssembly->'%(RootDir)%(Directory)%(Filename).pdb')" | ||
52 | Condition=" Exists('%(RootDir)%(Directory)%(Filename).pdb') and '$(Configuration)' == 'Debug' " /> | ||
53 | <CustomActionReferenceContents Include="@(ReferenceCopyLocalPaths)" | ||
54 | Condition=" '%(Extension)' == '.pdb' and '$(Configuration)' == 'Debug' " /> | ||
55 | <CustomActionReferenceContents Include="@(ReferenceComWrappersToCopyLocal)" | ||
56 | Condition=" '%(Extension)' == '.pdb' and '$(Configuration)' == 'Debug' " /> | ||
57 | </ItemGroup> | ||
58 | |||
59 | <!-- | ||
60 | Items to include in the CA package: | ||
61 | - Reference assemblies marked CopyLocal | ||
62 | - Project items of type Content | ||
63 | - Additional items in the CustomActionContents property | ||
64 | --> | ||
65 | <PropertyGroup> | ||
66 | <CustomActionContents>@(CustomActionReferenceContents);@(Content->'%(FullPath)');$(CustomActionContents)</CustomActionContents> | ||
67 | </PropertyGroup> | ||
68 | |||
69 | <ItemGroup> | ||
70 | <IntermediateCAAssembly Include="@(IntermediateAssembly->'%(FullPath)')" /> | ||
71 | <IntermediateCAPackage Include="@(IntermediateAssembly->'%(RootDir)%(Directory)$(TargetCAFileName)')" /> | ||
72 | </ItemGroup> | ||
73 | |||
74 | <!-- Run the MakeSfxCA.exe CA packaging tool. --> | ||
75 | <Exec Command='"$(MakeSfxCA)" "@(IntermediateCAPackage)" "$(SfxCADll)" "@(IntermediateCAAssembly)" "$(CustomActionContents)"' | ||
76 | WorkingDirectory="$(ProjectDir)" /> | ||
77 | |||
78 | <!-- Add modules to be copied to output dir. --> | ||
79 | <ItemGroup> | ||
80 | <AddModules Include="@(IntermediateCAPackage)" /> | ||
81 | </ItemGroup> | ||
82 | </Target> | ||
83 | |||
84 | <!-- | ||
85 | ================================================================================================== | ||
86 | CleanCustomAction | ||
87 | |||
88 | Cleans the .CA.dll binary created by the PackCustomAction target. | ||
89 | |||
90 | ================================================================================================== | ||
91 | --> | ||
92 | <Target Name="CleanCustomAction"> | ||
93 | <Delete Files="$(IntermediateOutputPath)$(TargetCAFileName)" | ||
94 | TreatErrorsAsWarnings="true" /> | ||
95 | </Target> | ||
96 | |||
97 | <!-- | ||
98 | ================================================================================================== | ||
99 | AfterCompile (redefinition) | ||
100 | |||
101 | Calls the PackCustomAction target after compiling. | ||
102 | Overrides the empty AfterCompile target from Microsoft.Common.targets. | ||
103 | |||
104 | ================================================================================================== | ||
105 | --> | ||
106 | <Target Name="AfterCompile" | ||
107 | DependsOnTargets="PackCustomAction" /> | ||
108 | |||
109 | <!-- | ||
110 | ================================================================================================== | ||
111 | BeforeClean (redefinition) | ||
112 | |||
113 | Calls the CleanCustomAction target before cleaning. | ||
114 | Overrides the empty AfterCompile target from Microsoft.Common.targets. | ||
115 | |||
116 | ================================================================================================== | ||
117 | --> | ||
118 | <Target Name="BeforeClean" | ||
119 | DependsOnTargets="CleanCustomAction" /> | ||
120 | |||
121 | <Import Project="$(CustomAfterWixCATargets)" Condition=" '$(CustomAfterWixCATargets)' != '' and Exists('$(CustomAfterWixCATargets)')" /> | ||
122 | |||
123 | </Project> | ||
diff --git a/src/WixToolset.BuildTasks/wix.harvest.targets b/src/WixToolset.BuildTasks/wix.harvest.targets new file mode 100644 index 00000000..e94dfcea --- /dev/null +++ b/src/WixToolset.BuildTasks/wix.harvest.targets | |||
@@ -0,0 +1,511 @@ | |||
1 | <?xml version="1.0" encoding="utf-8"?> | ||
2 | <!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. --> | ||
3 | |||
4 | |||
5 | <Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> | ||
6 | |||
7 | <!-- These properties can be overridden to support non-default installations. --> | ||
8 | <PropertyGroup> | ||
9 | <WixTargetsPath Condition=" '$(WixTargetsPath)' == '' ">$(MSBuildThisFileFullPath)</WixTargetsPath> | ||
10 | <WixTasksPath Condition=" '$(WixTasksPath)' == '' ">$(WixTargetsPath)WixTasks.dll</WixTasksPath> | ||
11 | </PropertyGroup> | ||
12 | |||
13 | <!-- These tasks are extensions for harvesting WiX source code from other sources. --> | ||
14 | <UsingTask TaskName="HeatFile" AssemblyFile="$(WixTasksPath)" /> | ||
15 | <UsingTask TaskName="HeatDirectory" AssemblyFile="$(WixTasksPath)" /> | ||
16 | <UsingTask TaskName="HeatProject" AssemblyFile="$(WixTasksPath)" /> | ||
17 | |||
18 | <UsingTask TaskName="RefreshGeneratedFile" AssemblyFile="$(WixTasksPath)"/> | ||
19 | <UsingTask TaskName="RefreshBundleGeneratedFile" AssemblyFile="$(WixTasksPath)"/> | ||
20 | |||
21 | <!-- Default Harvester properties--> | ||
22 | <PropertyGroup> | ||
23 | <HarvestNoLogo Condition=" '$(HarvestNoLogo)' == '' ">$(NoLogo)</HarvestNoLogo> | ||
24 | <HarvestSuppressAllWarnings Condition=" '$(HarvestSuppressAllWarnings)' == '' ">$(SuppressAllWarnings)</HarvestSuppressAllWarnings> | ||
25 | <HarvestSuppressSpecificWarnings Condition=" '$(HarvestSuppressSpecificWarnings)' == '' ">$(SuppressSpecificWarnings)</HarvestSuppressSpecificWarnings> | ||
26 | <HarvestTreatWarningsAsErrors Condition=" '$(HarvestTreatWarningsAsErrors)' == '' ">$(TreatWarningsAsErrors)</HarvestTreatWarningsAsErrors> | ||
27 | <HarvestTreatSpecificWarningsAsErrors Condition=" '$(HarvestTreatSpecificWarningsAsErrors)' == '' ">$(TreatSpecificWarningsAsErrors)</HarvestTreatSpecificWarningsAsErrors> | ||
28 | <HarvestVerboseOutput Condition=" '$(HarvestVerboseOutput)' == '' ">$(VerboseOutput)</HarvestVerboseOutput> | ||
29 | <HarvestAutogenerateGuids Condition=" '$(HarvestAutogenerateGuids)' == '' ">true</HarvestAutogenerateGuids> | ||
30 | <HarvestGenerateGuidsNow Condition=" '$(HarvestGenerateGuidsNow)' == '' ">false</HarvestGenerateGuidsNow> | ||
31 | <HarvestSuppressFragments Condition=" '$(HarvestSuppressFragments)' == '' ">true</HarvestSuppressFragments> | ||
32 | <HarvestSuppressUniqueIds Condition=" '$(HarvestSuppressUniqueIds)' == '' ">false</HarvestSuppressUniqueIds> | ||
33 | </PropertyGroup> | ||
34 | |||
35 | <!-- Default HarvestProjects properties --> | ||
36 | <PropertyGroup> | ||
37 | <!-- Project harvesting is defaulted to off until it works more consistently. --> | ||
38 | <EnableProjectHarvesting Condition=" '$(EnableProjectHarvesting)'=='' ">false</EnableProjectHarvesting> | ||
39 | |||
40 | <HarvestProjectsNoLogo Condition=" '$(HarvestProjectsNoLogo)' == '' ">$(HarvestNoLogo)</HarvestProjectsNoLogo> | ||
41 | <HarvestProjectsSuppressAllWarnings Condition=" '$(HarvestProjectsSuppressAllWarnings)' == '' ">$(HarvestSuppressAllWarnings)</HarvestProjectsSuppressAllWarnings> | ||
42 | <HarvestProjectsSuppressSpecificWarnings Condition=" '$(HarvestProjectsSuppressSpecificWarnings)' == '' ">$(HarvestSuppressSpecificWarnings)</HarvestProjectsSuppressSpecificWarnings> | ||
43 | <HarvestProjectsTreatWarningsAsErrors Condition=" '$(HarvestProjectsTreatWarningsAsErrors)' == '' ">$(HarvestTreatWarningsAsErrors)</HarvestProjectsTreatWarningsAsErrors> | ||
44 | <HarvestProjectsTreatSpecificWarningsAsErrors Condition=" '$(HarvestProjectsTreatSpecificWarningsAsErrors)' == '' ">$(HarvestTreatSpecificWarningsAsErrors)</HarvestProjectsTreatSpecificWarningsAsErrors> | ||
45 | <HarvestProjectsVerboseOutput Condition=" '$(HarvestProjectsVerboseOutput)' == '' ">$(HarvestVerboseOutput)</HarvestProjectsVerboseOutput> | ||
46 | <HarvestProjectsAutogenerateGuids Condition=" '$(HarvestProjectsAutogenerateGuids)' == '' ">$(HarvestAutogenerateGuids)</HarvestProjectsAutogenerateGuids> | ||
47 | <HarvestProjectsGenerateGuidsNow Condition=" '$(HarvestProjectsGenerateGuidsNow)' == '' ">$(HarvestGenerateGuidsNow)</HarvestProjectsGenerateGuidsNow> | ||
48 | <HarvestProjectsSuppressFragments Condition=" '$(HarvestProjectsSuppressFragments)' == '' ">$(HarvestSuppressFragments)</HarvestProjectsSuppressFragments> | ||
49 | <HarvestProjectsSuppressUniqueIds Condition=" '$(HarvestProjectsSuppressUniqueIds)' == '' ">$(HarvestSuppressUniqueIds)</HarvestProjectsSuppressUniqueIds> | ||
50 | <HarvestProjectsTransforms Condition=" '$(HarvestProjectsTransforms)' == '' ">$(HarvestTransforms)</HarvestProjectsTransforms> | ||
51 | <HarvestProjectsGeneratedFile Condition=" '$(HarvestProjectsGeneratedFile)' == '' and '$(OutputType)' != 'Bundle' ">$(IntermediateOutputPath)Product.Generated.wxs</HarvestProjectsGeneratedFile> | ||
52 | <HarvestProjectsGeneratedFile Condition=" '$(HarvestProjectsGeneratedFile)' == '' and '$(OutputType)' == 'Bundle' ">$(IntermediateOutputPath)Bundle.Generated.wxs</HarvestProjectsGeneratedFile> | ||
53 | </PropertyGroup> | ||
54 | |||
55 | <!-- Default HarvestDirectory properties --> | ||
56 | <PropertyGroup> | ||
57 | <HarvestDirectoryNoLogo Condition=" '$(HarvestDirectoryNoLogo)' == '' ">$(HarvestNoLogo)</HarvestDirectoryNoLogo> | ||
58 | <HarvestDirectorySuppressAllWarnings Condition=" '$(HarvestDirectorySuppressAllWarnings)' == '' ">$(HarvestSuppressAllWarnings)</HarvestDirectorySuppressAllWarnings> | ||
59 | <HarvestDirectorySuppressSpecificWarnings Condition=" '$(HarvestDirectorySuppressSpecificWarnings)' == '' ">$(HarvestSuppressSpecificWarnings)</HarvestDirectorySuppressSpecificWarnings> | ||
60 | <HarvestDirectoryTreatWarningsAsErrors Condition=" '$(HarvestDirectoryTreatWarningsAsErrors)' == '' ">$(HarvestTreatWarningsAsErrors)</HarvestDirectoryTreatWarningsAsErrors> | ||
61 | <HarvestDirectoryTreatSpecificWarningsAsErrors Condition=" '$(HarvestDirectoryTreatSpecificWarningsAsErrors)' == '' ">$(HarvestTreatSpecificWarningsAsErrors)</HarvestDirectoryTreatSpecificWarningsAsErrors> | ||
62 | <HarvestDirectoryVerboseOutput Condition=" '$(HarvestDirectoryVerboseOutput)' == '' ">$(HarvestVerboseOutput)</HarvestDirectoryVerboseOutput> | ||
63 | <HarvestDirectoryAutogenerateGuids Condition=" '$(HarvestDirectoryAutogenerateGuids)' == '' ">$(HarvestAutogenerateGuids)</HarvestDirectoryAutogenerateGuids> | ||
64 | <HarvestDirectoryGenerateGuidsNow Condition=" '$(HarvestDirectoryGenerateGuidsNow)' == '' ">$(HarvestGenerateGuidsNow)</HarvestDirectoryGenerateGuidsNow> | ||
65 | <HarvestDirectorySuppressFragments Condition=" '$(HarvestDirectorySuppressFragments)' == '' ">$(HarvestSuppressFragments)</HarvestDirectorySuppressFragments> | ||
66 | <HarvestDirectorySuppressUniqueIds Condition=" '$(HarvestDirectorySuppressUniqueIds)' == '' ">$(HarvestSuppressUniqueIds)</HarvestDirectorySuppressUniqueIds> | ||
67 | <HarvestDirectoryTransforms Condition=" '$(HarvestDirectoryTransforms)' == '' ">$(HarvestTransforms)</HarvestDirectoryTransforms> | ||
68 | </PropertyGroup> | ||
69 | |||
70 | <!-- Default HarvestFile properties --> | ||
71 | <PropertyGroup> | ||
72 | <HarvestFileNoLogo Condition=" '$(HarvestFileNoLogo)' == '' ">$(HarvestNoLogo)</HarvestFileNoLogo> | ||
73 | <HarvestFileSuppressAllWarnings Condition=" '$(HarvestFileSuppressAllWarnings)' == '' ">$(HarvestSuppressAllWarnings)</HarvestFileSuppressAllWarnings> | ||
74 | <HarvestFileSuppressSpecificWarnings Condition=" '$(HarvestFileSuppressSpecificWarnings)' == '' ">$(HarvestSuppressSpecificWarnings)</HarvestFileSuppressSpecificWarnings> | ||
75 | <HarvestFileTreatWarningsAsErrors Condition=" '$(HarvestFileTreatWarningsAsErrors)' == '' ">$(HarvestTreatWarningsAsErrors)</HarvestFileTreatWarningsAsErrors> | ||
76 | <HarvestFileTreatSpecificWarningsAsErrors Condition=" '$(HarvestFileTreatSpecificWarningsAsErrors)' == '' ">$(HarvestTreatSpecificWarningsAsErrors)</HarvestFileTreatSpecificWarningsAsErrors> | ||
77 | <HarvestFileVerboseOutput Condition=" '$(HarvestFileVerboseOutput)' == '' ">$(HarvestVerboseOutput)</HarvestFileVerboseOutput> | ||
78 | <HarvestFileAutogenerateGuids Condition=" '$(HarvestFileAutogenerateGuids)' == '' ">$(HarvestAutogenerateGuids)</HarvestFileAutogenerateGuids> | ||
79 | <HarvestFileGenerateGuidsNow Condition=" '$(HarvestFileGenerateGuidsNow)' == '' ">$(HarvestGenerateGuidsNow)</HarvestFileGenerateGuidsNow> | ||
80 | <HarvestFileSuppressFragments Condition=" '$(HarvestFileSuppressFragments)' == '' ">$(HarvestSuppressFragments)</HarvestFileSuppressFragments> | ||
81 | <HarvestFileSuppressUniqueIds Condition=" '$(HarvestFileSuppressUniqueIds)' == '' ">$(HarvestSuppressUniqueIds)</HarvestFileSuppressUniqueIds> | ||
82 | <HarvestFileTransforms Condition=" '$(HarvestFileTransforms)' == '' ">$(HarvestTransforms)</HarvestFileTransforms> | ||
83 | </PropertyGroup> | ||
84 | |||
85 | <!-- | ||
86 | ================================================================================================== | ||
87 | Harvest | ||
88 | ================================================================================================== | ||
89 | --> | ||
90 | <PropertyGroup> | ||
91 | <HarvestDependsOn> | ||
92 | ConvertReferences; | ||
93 | ConvertBundleReferences; | ||
94 | HarvestProjects; | ||
95 | HarvestDirectory; | ||
96 | HarvestFile; | ||
97 | GenerateCode; | ||
98 | </HarvestDependsOn> | ||
99 | </PropertyGroup> | ||
100 | <Target | ||
101 | Name="Harvest" | ||
102 | DependsOnTargets="$(HarvestDependsOn)" /> | ||
103 | |||
104 | <!-- | ||
105 | ================================================================================================== | ||
106 | GenerateCode | ||
107 | ================================================================================================== | ||
108 | --> | ||
109 | <PropertyGroup> | ||
110 | <GenerateCodeDependsOn> | ||
111 | RefreshGeneratedFile; | ||
112 | RefreshBundleGeneratedFile | ||
113 | </GenerateCodeDependsOn> | ||
114 | </PropertyGroup> | ||
115 | <Target | ||
116 | Name="GenerateCode" | ||
117 | DependsOnTargets="$(GenerateCodeDependsOn)" /> | ||
118 | |||
119 | <!-- | ||
120 | ================================================================================================ | ||
121 | ConvertReferences | ||
122 | |||
123 | Converts project references to HeatProject items to auto generate authoring. | ||
124 | ================================================================================================ | ||
125 | --> | ||
126 | <Target | ||
127 | Name="ConvertReferences" | ||
128 | Condition=" $(EnableProjectHarvesting) and ('$(OutputType)' == 'Package' or '$(OutputType)' == 'PatchCreation' or '$(OutputType)' == 'Module') "> | ||
129 | |||
130 | <ItemGroup> | ||
131 | <_HeatProjectReference Include="@(_MSBuildProjectReferenceExistent)" Condition=" '%(_MSBuildProjectReferenceExistent.DoNotHarvest)' == '' "> | ||
132 | <DirectoryIds>%(_MSBuildProjectReferenceExistent.RefTargetDir)</DirectoryIds> | ||
133 | <ProjectOutputGroups>Binaries;Symbols;Sources;Content;Satellites;Documents</ProjectOutputGroups> | ||
134 | <ProjectName>%(_MSBuildProjectReferenceExistent.Name)</ProjectName> | ||
135 | <HeatOutput>$(IntermediateOutputPath)_%(_MSBuildProjectReferenceExistent.Filename).wxs</HeatOutput> | ||
136 | </_HeatProjectReference> | ||
137 | <HeatProject Include="@(_HeatProjectReference)" /> | ||
138 | </ItemGroup> | ||
139 | |||
140 | <Error | ||
141 | Text="The following files are deprecated and should be removed from your project(s): @(Compile->'%(Identity)', ', ')" | ||
142 | Condition=" '%(Compile.GenerateComponentGroups)' != '' " /> | ||
143 | |||
144 | <ItemGroup> | ||
145 | <!-- Unconditionally generate Compile items so they are always linked in. --> | ||
146 | <Compile Include="$(HarvestProjectsGeneratedFile)" /> | ||
147 | <_GeneratedFiles Include="$(HarvestProjectsGeneratedFile)" /> | ||
148 | </ItemGroup> | ||
149 | |||
150 | </Target> | ||
151 | |||
152 | <!-- | ||
153 | ================================================================================================ | ||
154 | ConvertBundleReferences | ||
155 | |||
156 | Converts project references in Bundle projects to HeatProject items to auto generate authoring. | ||
157 | ================================================================================================ | ||
158 | --> | ||
159 | <Target | ||
160 | Name="ConvertBundleReferences" | ||
161 | Condition=" $(EnableProjectHarvesting) and ('$(OutputType)' == 'Bundle') "> | ||
162 | |||
163 | <ItemGroup> | ||
164 | <_HeatProjectReference Include="@(_MSBuildProjectReferenceExistent)" Condition=" '%(_MSBuildProjectReferenceExistent.DoNotHarvest)' == '' "> | ||
165 | <ProjectOutputGroups>Binaries;Symbols;Sources;Content;Satellites;Documents</ProjectOutputGroups> | ||
166 | <GenerateType>payloadgroup</GenerateType> | ||
167 | <HeatOutput>$(IntermediateOutputPath)_%(_MSBuildProjectReferenceExistent.Filename).wxs</HeatOutput> | ||
168 | </_HeatProjectReference> | ||
169 | <HeatProject Include="@(_HeatProjectReference)" /> | ||
170 | </ItemGroup> | ||
171 | |||
172 | <Error | ||
173 | Text="The following files are deprecated and should be removed from your project(s): @(Compile->'%(Identity)', ', ')" | ||
174 | Condition=" '%(Compile.GenerateComponentGroups)' != '' " /> | ||
175 | |||
176 | <ItemGroup> | ||
177 | <!-- Unconditionally generate Compile items so they are always linked in. --> | ||
178 | <Compile Include="$(HarvestProjectsGeneratedFile)" /> | ||
179 | <_GeneratedFiles Include="$(HarvestProjectsGeneratedFile)" /> | ||
180 | </ItemGroup> | ||
181 | |||
182 | </Target> | ||
183 | |||
184 | <!-- | ||
185 | ================================================================================================ | ||
186 | CombineHarvestProjects | ||
187 | |||
188 | Combines HeatProject and HarvestProject items together and ensures each has HeatOutput metadata. | ||
189 | ================================================================================================ | ||
190 | --> | ||
191 | <Target | ||
192 | Name="CombineHarvestProjects" | ||
193 | Condition=" '@(HeatProject)' != '' or '@(HarvestProject)' != '' "> | ||
194 | |||
195 | <!-- Add default HeatOutputs for those without one specified --> | ||
196 | <CreateItem Include="@(HeatProject)" Condition= " '%(HeatProject.HeatOutput)' == '' " | ||
197 | AdditionalMetadata="HeatOutput=$(IntermediateOutputPath)_%(HeatProject.Filename).wxs"> | ||
198 | <Output TaskParameter="Include" ItemName="_AllHeatProjects" /> | ||
199 | </CreateItem> | ||
200 | <CreateItem Include="@(HarvestProject)" Condition= " '%(HarvestProject.HeatOutput)' == '' " | ||
201 | AdditionalMetadata="HeatOutput=$(IntermediateOutputPath)_%(HarvestProject.Filename).wxs"> | ||
202 | <Output TaskParameter="Include" ItemName="_AllHeatProjects" /> | ||
203 | </CreateItem> | ||
204 | |||
205 | |||
206 | <CreateItem Include="@(HeatProject)" Condition= " '%(HeatProject.HeatOutput)' != '' "> | ||
207 | <Output TaskParameter="Include" ItemName="_AllHeatProjects" /> | ||
208 | </CreateItem> | ||
209 | <CreateItem Include="@(HarvestProject)" Condition= " '%(HarvestProject.HeatOutput)' != '' "> | ||
210 | <Output TaskParameter="Include" ItemName="_AllHeatProjects" /> | ||
211 | </CreateItem> | ||
212 | |||
213 | </Target> | ||
214 | |||
215 | <!-- | ||
216 | ================================================================================================ | ||
217 | HarvestProjects | ||
218 | |||
219 | Harvests outputs of other MSBuild projects files using the VS project extension to heat.exe. | ||
220 | |||
221 | [IN] | ||
222 | @(HarvestProject) | ||
223 | @(HeatProject) | ||
224 | - The list of projects to harvest. HeatProject is provided for backward compatibility. | ||
225 | You should use HarvestProject instead. | ||
226 | |||
227 | %(HarvestProject.Transforms) | ||
228 | %(HeatProject.Transforms) | ||
229 | - XSL transforms to apply to the harvested WiX. | ||
230 | |||
231 | %(HarvestProject.ProjectOutputGroups) | ||
232 | %(HeatProjects.ProjectOutputGroups) | ||
233 | - The project output groups to harvest | ||
234 | |||
235 | [OUT] | ||
236 | %(HeatOutput) | ||
237 | - The generated .wxs files which are added to the @(Compile) item list. | ||
238 | ================================================================================================ | ||
239 | --> | ||
240 | <ItemDefinitionGroup> | ||
241 | <HeatProject> | ||
242 | <Transforms>$(HarvestProjectsTransforms)</Transforms> | ||
243 | <ProjectOutputGroups>$(HarvestProjectsProjectOutputGroups)</ProjectOutputGroups> | ||
244 | <DirectoryIds>$(HarvestProjectsDirectoryIds)</DirectoryIds> | ||
245 | </HeatProject> | ||
246 | <HarvestProject> | ||
247 | <Transforms>$(HarvestProjectsTransforms)</Transforms> | ||
248 | <ProjectOutputGroups>$(HarvestProjectsProjectOutputGroups)</ProjectOutputGroups> | ||
249 | <DirectoryIds>$(HarvestProjectsDirectoryIds)</DirectoryIds> | ||
250 | </HarvestProject> | ||
251 | </ItemDefinitionGroup> | ||
252 | |||
253 | <PropertyGroup> | ||
254 | <HarvestProjectsDependsOn>CombineHarvestProjects</HarvestProjectsDependsOn> | ||
255 | </PropertyGroup> | ||
256 | <Target Name="HarvestProjects" | ||
257 | DependsOnTargets="$(HarvestProjectsDependsOn)" | ||
258 | Inputs="@(_AllHeatProjects);%(_AllHeatProjects.Transforms);$(MSBuildAllProjects);$(ProjectPath)" | ||
259 | Outputs="@(_AllHeatProjects -> '%(HeatOutput)')" | ||
260 | Condition=" $(EnableProjectHarvesting) and ('@(HeatProject)' != '' or '@(HarvestProject)' != '') "> | ||
261 | |||
262 | <HeatProject | ||
263 | NoLogo="$(HarvestProjectsNoLogo)" | ||
264 | SuppressAllWarnings="$(HarvestProjectsSuppressAllWarnings)" | ||
265 | SuppressSpecificWarnings="$(HarvestProjectsSuppressSpecificWarnings)" | ||
266 | ToolPath="$(WixToolPath)" | ||
267 | TreatWarningsAsErrors="$(HarvestProjectsTreatWarningsAsErrors)" | ||
268 | TreatSpecificWarningsAsErrors="$(HarvestProjectsTreatSpecificWarningsAsErrors)" | ||
269 | VerboseOutput="$(HarvestProjectsVerboseOutput)" | ||
270 | AutogenerateGuids="$(HarvestProjectsAutogenerateGuids)" | ||
271 | GenerateGuidsNow="$(HarvestProjectsGenerateGuidsNow)" | ||
272 | OutputFile="%(_AllHeatProjects.HeatOutput)" | ||
273 | SuppressFragments="$(HarvestProjectsSuppressFragments)" | ||
274 | SuppressUniqueIds="$(HarvestProjectsSuppressUniqueIds)" | ||
275 | Transforms="%(_AllHeatProjects.Transforms)" | ||
276 | Project="%(_AllHeatProjects.FullPath)" | ||
277 | ProjectOutputGroups="%(_AllHeatProjects.ProjectOutputGroups)" | ||
278 | GenerateType="%(_AllHeatProjects.GenerateType)" | ||
279 | DirectoryIds="%(_AllHeatProjects.DirectoryIds)" | ||
280 | ProjectName="%(_AllHeatProjects.ProjectName)" | ||
281 | Configuration="%(_AllHeatProjects.Configuration)" | ||
282 | Platform="%(_AllHeatProjects.Platform)" | ||
283 | RunAsSeparateProcess="$(RunWixToolsOutOfProc)" | ||
284 | GenerateWixVariables="$(HarvestProjectsGenerateWixVariables)" | ||
285 | AdditionalOptions="$(HarvestProjectsAdditionalOptions)"> | ||
286 | |||
287 | <Output TaskParameter="OutputFile" ItemName="Compile" /> | ||
288 | <Output TaskParameter="OutputFile" ItemName="FileWrites" /> | ||
289 | |||
290 | </HeatProject> | ||
291 | |||
292 | </Target> | ||
293 | |||
294 | <!-- | ||
295 | ================================================================================================ | ||
296 | HarvestDirectory | ||
297 | |||
298 | Harvests directories using heat.exe. | ||
299 | |||
300 | [IN] | ||
301 | @(HarvestDirectory) - The list of directories to harvest. | ||
302 | %(HarvestDirectory.Transforms) - XSL transforms to apply to the harvested WiX. | ||
303 | %(HarvestDirectory.ComponentGroupName) - The name of the ComponentGroup to create. | ||
304 | %(HarvestDirectory.DirectoryRefId) - The ID of the directory to reference instead of TARGETDIR. | ||
305 | %(HarvestDirectory.KeepEmptyDirectories) - Whether to create Directory entries for empty directories. | ||
306 | %(HarvestDirectory.PreprocessorVariable) - Substitute SourceDir for another variable name (ex: var.Dir). | ||
307 | %(HarvestDirectory.SuppressCom) - Suppress COM elements. | ||
308 | %(HarvestDirectory.SuppressRootDirectory) - Suppress a Directory element for the root directory. | ||
309 | $(HarvestDirectory.SuppressRegistry) - Suppress registry harvesting. | ||
310 | |||
311 | [OUT] | ||
312 | $(IntermediateOutputPath)_%(HarvestDirectory.ComponentGroupName)_dir.wxs | ||
313 | - The generated .wxs files which are added to the @(Compile) item list. | ||
314 | ================================================================================================ | ||
315 | --> | ||
316 | |||
317 | <ItemDefinitionGroup> | ||
318 | <HarvestDirectory> | ||
319 | <Transforms>$(HarvestDirectoryTransforms)</Transforms> | ||
320 | <ComponentGroupName>$(HarvestDirectoryComponentGroupName)</ComponentGroupName> | ||
321 | <DirectoryRefId>$(HarvestDirectoryDirectoryRefId)</DirectoryRefId> | ||
322 | <KeepEmptyDirectories>$(HarvestDirectoryKeepEmptyDirectories)</KeepEmptyDirectories> | ||
323 | <PreprocessorVariable>$(HarvestDirectoryPreprocessorVariable)</PreprocessorVariable> | ||
324 | <SuppressCom>$(HarvestDirectorySuppressCom)</SuppressCom> | ||
325 | <SuppressRootDirectory>$(HarvestDirectorySuppressRootDirectory)</SuppressRootDirectory> | ||
326 | <SuppressRegistry>$(HarvestDirectorySuppressRegistry)</SuppressRegistry> | ||
327 | </HarvestDirectory> | ||
328 | </ItemDefinitionGroup> | ||
329 | |||
330 | <PropertyGroup> | ||
331 | <HarvestDirectoryDependsOn> | ||
332 | GetHarvestDirectoryContent | ||
333 | </HarvestDirectoryDependsOn> | ||
334 | </PropertyGroup> | ||
335 | |||
336 | <!-- Creates items to include content since wildcards will not work in Target/@Inputs. --> | ||
337 | <Target Name="GetHarvestDirectoryContent"> | ||
338 | <CreateItem Include="@(HarvestDirectory->'%(FullPath)\**\*')"> | ||
339 | <Output TaskParameter="Include" ItemName="_HarvestDirectoryContent" /> | ||
340 | </CreateItem> | ||
341 | </Target> | ||
342 | |||
343 | <Target Name="HarvestDirectory" | ||
344 | DependsOnTargets="$(HarvestDirectoryDependsOn)" | ||
345 | Inputs="@(_HarvestDirectoryContent);%(HarvestDirectory.Transforms)" | ||
346 | Outputs="$(IntermediateOutputPath)_%(HarvestDirectory.ComponentGroupName)_dir.wxs" | ||
347 | Condition=" '@(HarvestDirectory)' != '' "> | ||
348 | |||
349 | <HeatDirectory | ||
350 | NoLogo="$(HarvestDirectoryNoLogo)" | ||
351 | SuppressAllWarnings="$(HarvestDirectorySuppressAllWarnings)" | ||
352 | SuppressSpecificWarnings="$(HarvestDirectorySuppressSpecificWarnings)" | ||
353 | ToolPath="$(WixToolPath)" | ||
354 | TreatWarningsAsErrors="$(HarvestDirectoryTreatWarningsAsErrors)" | ||
355 | TreatSpecificWarningsAsErrors="$(HarvestDirectoryTreatSpecificWarningsAsErrors)" | ||
356 | VerboseOutput="$(HarvestDirectoryVerboseOutput)" | ||
357 | AutogenerateGuids="$(HarvestDirectoryAutogenerateGuids)" | ||
358 | GenerateGuidsNow="$(HarvestDirectoryGenerateGuidsNow)" | ||
359 | OutputFile="$(IntermediateOutputPath)_%(HarvestDirectory.ComponentGroupName)_dir.wxs" | ||
360 | SuppressFragments="$(HarvestDirectorySuppressFragments)" | ||
361 | SuppressUniqueIds="$(HarvestDirectorySuppressUniqueIds)" | ||
362 | Transforms="%(HarvestDirectory.Transforms)" | ||
363 | Directory="@(HarvestDirectory)" | ||
364 | ComponentGroupName="%(HarvestDirectory.ComponentGroupName)" | ||
365 | DirectoryRefId="%(HarvestDirectory.DirectoryRefId)" | ||
366 | KeepEmptyDirectories="%(HarvestDirectory.KeepEmptyDirectories)" | ||
367 | PreprocessorVariable="%(HarvestDirectory.PreprocessorVariable)" | ||
368 | RunAsSeparateProcess="$(RunWixToolsOutOfProc)" | ||
369 | SuppressCom="%(HarvestDirectory.SuppressCom)" | ||
370 | SuppressRootDirectory="%(HarvestDirectory.SuppressRootDirectory)" | ||
371 | SuppressRegistry="%(HarvestDirectory.SuppressRegistry)" | ||
372 | AdditionalOptions="$(HarvestDirectoryAdditionalOptions)"> | ||
373 | |||
374 | <Output TaskParameter="OutputFile" ItemName="Compile" /> | ||
375 | <Output TaskParameter="OutputFile" ItemName="FileWrites" /> | ||
376 | |||
377 | </HeatDirectory> | ||
378 | |||
379 | </Target> | ||
380 | |||
381 | <!-- | ||
382 | ================================================================================================ | ||
383 | HarvestFile | ||
384 | |||
385 | Harvests files of different types using heat.exe. This can harvest registry from | ||
386 | self-registering files, files with typelibs, and more. | ||
387 | |||
388 | [IN] | ||
389 | @(HarvestFile) - The list of files to harvest. | ||
390 | %(HarvestFile.Transforms) - XSL transforms to apply to the harvested WiX. | ||
391 | %(HarvestFile.ComponentGroupName) - The name of the ComponentGroup to create. | ||
392 | %(HarvestFile.DirectoryRefId) - The ID of the directory to reference instead of TARGETDIR. | ||
393 | %(HarvestFile.PreprocessorVariable) - Substitute SourceDir for another variable name (ex: var.Dir). | ||
394 | %(HarvestFile.SuppressCom) - Suppress COM elements. | ||
395 | %(HarvestFile.SuppressRootDirectory) - Suppress a Directory element for the root directory. | ||
396 | $(HarvestFile.SuppressRegistry) - Suppress registry harvesting. | ||
397 | |||
398 | [OUT] | ||
399 | $(IntermediateOutputPath)_%(HarvestFile.Filename)_file.wxs | ||
400 | - The generated .wxs files which are added to the @(Compile) item list. | ||
401 | ================================================================================================ | ||
402 | --> | ||
403 | |||
404 | <ItemDefinitionGroup> | ||
405 | <HarvestFile> | ||
406 | <Transforms>$(HarvestFileTransforms)</Transforms> | ||
407 | <ComponentGroupName>$(HarvestFileComponentGroupName)</ComponentGroupName> | ||
408 | <DirectoryRefId>$(HarvestFileDirectoryRefId)</DirectoryRefId> | ||
409 | <PreprocessorVariable>$(HarvestFilePreprocessorVariable)</PreprocessorVariable> | ||
410 | <SuppressCom>$(HarvestFileSuppressCom)</SuppressCom> | ||
411 | <SuppressRegistry>$(HarvestFileSuppressRegistry)</SuppressRegistry> | ||
412 | <SuppressRootDirectory>$(HarvestFileSuppressRootDirectory)</SuppressRootDirectory> | ||
413 | </HarvestFile> | ||
414 | </ItemDefinitionGroup> | ||
415 | |||
416 | <PropertyGroup> | ||
417 | <HarvestFileDependsOn></HarvestFileDependsOn> | ||
418 | </PropertyGroup> | ||
419 | <Target Name="HarvestFile" | ||
420 | DependsOnTargets="$(HarvestFileDependsOn)" | ||
421 | Inputs="@(HarvestFile);%(HarvestFile.Transforms)" | ||
422 | Outputs="$(IntermediateOutputPath)_%(HarvestFile.Filename)_file.wxs" | ||
423 | Condition=" '@(HarvestFile)' != '' "> | ||
424 | |||
425 | <HeatFile | ||
426 | NoLogo="$(HarvestFileNoLogo)" | ||
427 | SuppressAllWarnings="$(HarvestFileSuppressAllWarnings)" | ||
428 | SuppressSpecificWarnings="$(HarvestFileSuppressSpecificWarnings)" | ||
429 | ToolPath="$(WixToolPath)" | ||
430 | TreatWarningsAsErrors="$(HarvestFileTreatWarningsAsErrors)" | ||
431 | TreatSpecificWarningsAsErrors="$(HarvestFileTreatSpecificWarningsAsErrors)" | ||
432 | VerboseOutput="$(HarvestFileVerboseOutput)" | ||
433 | AutogenerateGuids="$(HarvestFileAutogenerateGuids)" | ||
434 | GenerateGuidsNow="$(HarvestFileGenerateGuidsNow)" | ||
435 | OutputFile="$(IntermediateOutputPath)_%(HarvestFile.Filename)_file.wxs" | ||
436 | SuppressFragments="$(HarvestFileSuppressFragments)" | ||
437 | SuppressUniqueIds="$(HarvestFileSuppressUniqueIds)" | ||
438 | Transforms="%(HarvestFile.Transforms)" | ||
439 | File="@(HarvestFile)" | ||
440 | ComponentGroupName="%(HarvestFile.ComponentGroupName)" | ||
441 | DirectoryRefId="%(HarvestFile.DirectoryRefId)" | ||
442 | PreprocessorVariable="%(HarvestFile.PreprocessorVariable)" | ||
443 | RunAsSeparateProcess="$(RunWixToolsOutOfProc)" | ||
444 | SuppressCom="%(HarvestFile.SuppressCom)" | ||
445 | SuppressRegistry="%(HarvestFile.SuppressRegistry)" | ||
446 | SuppressRootDirectory="%(HarvestFile.SuppressRootDirectory)" | ||
447 | AdditionalOptions="$(HarvestFileAdditionalOptions)"> | ||
448 | |||
449 | <Output TaskParameter="OutputFile" ItemName="Compile" /> | ||
450 | <Output TaskParameter="OutputFile" ItemName="FileWrites" /> | ||
451 | |||
452 | </HeatFile> | ||
453 | |||
454 | </Target> | ||
455 | |||
456 | <!-- | ||
457 | ================================================================================================ | ||
458 | RefreshGeneratedFile | ||
459 | |||
460 | Generates code based on metadata defined in project references. | ||
461 | |||
462 | [IN] | ||
463 | @(_MSBuildResolvedProjectReferencePaths) - The list of MSBuildable project references. | ||
464 | |||
465 | [OUT] | ||
466 | @(_GeneratedFiles) - The generated source file. | ||
467 | ================================================================================================ | ||
468 | --> | ||
469 | <PropertyGroup> | ||
470 | <RefreshGeneratedFileDependsOn></RefreshGeneratedFileDependsOn> | ||
471 | </PropertyGroup> | ||
472 | <Target Name="RefreshGeneratedFile" | ||
473 | DependsOnTargets="$(RefreshGeneratedFileDependsOn)" | ||
474 | Inputs="@(_MSBuildResolvedProjectReferencePaths);@(Compile);$(ProjectPath)" | ||
475 | Outputs="@(_GeneratedFiles)" | ||
476 | Condition=" $(EnableProjectHarvesting) and ('$(OutputType)' == 'Package' or '$(OutputType)' == 'PatchCreation' or '$(OutputType)' == 'Module') and '@(_GeneratedFiles)' != '' "> | ||
477 | |||
478 | <RefreshGeneratedFile | ||
479 | GeneratedFiles="@(_GeneratedFiles)" | ||
480 | ProjectReferencePaths="@(_MSBuildResolvedProjectReferencePaths)" /> | ||
481 | |||
482 | </Target> | ||
483 | |||
484 | <!-- | ||
485 | ================================================================================================ | ||
486 | RefreshBundleGeneratedFile | ||
487 | |||
488 | Generates code for bundle projects based on metadata defined in project references. | ||
489 | |||
490 | [IN] | ||
491 | @(_MSBuildResolvedProjectReferencePaths) - The list of MSBuildable project references. | ||
492 | |||
493 | [OUT] | ||
494 | @(_GeneratedFiles) - The generated source file. | ||
495 | ================================================================================================ | ||
496 | --> | ||
497 | <PropertyGroup> | ||
498 | <RefreshBundleGeneratedFileDependsOn></RefreshBundleGeneratedFileDependsOn> | ||
499 | </PropertyGroup> | ||
500 | <Target Name="RefreshBundleGeneratedFile" | ||
501 | DependsOnTargets="$(RefreshBundleGeneratedFileDependsOn)" | ||
502 | Inputs="@(_MSBuildResolvedProjectReferencePaths);@(Compile);$(ProjectPath)" | ||
503 | Outputs="@(_GeneratedFiles)" | ||
504 | Condition=" $(EnableProjectHarvesting) and ('$(OutputType)' == 'Bundle' and '@(_GeneratedFiles)' != '') "> | ||
505 | |||
506 | <RefreshBundleGeneratedFile | ||
507 | GeneratedFiles="@(_GeneratedFiles)" | ||
508 | ProjectReferencePaths="@(_MSBuildResolvedProjectReferencePaths)" /> | ||
509 | </Target> | ||
510 | |||
511 | </Project> | ||
diff --git a/src/WixToolset.BuildTasks/wix.signing.targets b/src/WixToolset.BuildTasks/wix.signing.targets new file mode 100644 index 00000000..6351cc8b --- /dev/null +++ b/src/WixToolset.BuildTasks/wix.signing.targets | |||
@@ -0,0 +1,378 @@ | |||
1 | <?xml version="1.0" encoding="utf-8"?> | ||
2 | <!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. --> | ||
3 | |||
4 | |||
5 | <Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> | ||
6 | |||
7 | <!-- These properties can be overridden to support non-default installations. --> | ||
8 | <PropertyGroup> | ||
9 | <WixTargetsPath Condition=" '$(WixTargetsPath)' == '' ">$(MSBuildThisFileFullPath)</WixTargetsPath> | ||
10 | <WixTasksPath Condition=" '$(WixTasksPath)' == '' ">$(WixTargetsPath)WixTasks.dll</WixTasksPath> | ||
11 | |||
12 | <SignedFile Condition=" '$(SignedFile)' == '' ">$(MSBuildProjectFile).Signed.txt</SignedFile> | ||
13 | </PropertyGroup> | ||
14 | |||
15 | <UsingTask TaskName="Insignia" AssemblyFile="$(WixTasksPath)" /> | ||
16 | |||
17 | <!-- Default Inscribe properties. --> | ||
18 | <PropertyGroup> | ||
19 | <InscribeNoLogo Condition=" '$(InscribeNoLogo)' == '' ">$(NoLogo)</InscribeNoLogo> | ||
20 | <InscribeSuppressAllWarnings Condition=" '$(InscribeSuppressAllWarnings)' == '' ">$(SuppressAllWarnings)</InscribeSuppressAllWarnings> | ||
21 | <InscribeSuppressSpecificWarnings Condition=" '$(InscribeSuppressSpecificWarnings)' == '' ">$(SuppressSpecificWarnings)</InscribeSuppressSpecificWarnings> | ||
22 | <InscribeTreatWarningsAsErrors Condition=" '$(InscribeTreatWarningsAsErrors)' == '' ">$(TreatWarningsAsErrors)</InscribeTreatWarningsAsErrors> | ||
23 | <InscribeTreatSpecificWarningsAsErrors Condition=" '$(InscribeTreatSpecificWarningsAsErrors)' == '' ">$(TreatSpecificWarningsAsErrors)</InscribeTreatSpecificWarningsAsErrors> | ||
24 | <InscribeVerboseOutput Condition=" '$(InscribeVerboseOutput)' == '' ">$(VerboseOutput)</InscribeVerboseOutput> | ||
25 | </PropertyGroup> | ||
26 | |||
27 | <!-- | ||
28 | ================================================================================================== | ||
29 | Signing | ||
30 | ================================================================================================== | ||
31 | --> | ||
32 | <PropertyGroup> | ||
33 | <InternalSignDependsOn Condition=" '$(OutputType)' == 'Module' "> | ||
34 | GetMsmsToSign; | ||
35 | InternalSignMsm; | ||
36 | </InternalSignDependsOn> | ||
37 | <InternalSignDependsOn Condition=" '$(OutputType)' == 'Package' "> | ||
38 | GetCabsToSign; | ||
39 | GetMsiToSign; | ||
40 | InternalSignCabs; | ||
41 | InscribeMsi; | ||
42 | InternalSignMsi; | ||
43 | </InternalSignDependsOn> | ||
44 | <InternalSignDependsOn Condition=" '$(OutputType)' == 'Bundle' "> | ||
45 | GetContainersToSign; | ||
46 | InternalSignContainers; | ||
47 | InscribeBundleEngine; | ||
48 | InternalSignBundleEngine; | ||
49 | InscribeBundle; | ||
50 | InternalSignBundle; | ||
51 | </InternalSignDependsOn> | ||
52 | |||
53 | <SigningDependsOn> | ||
54 | CompileAndLink; | ||
55 | BeforeSigning; | ||
56 | $(InternalSignDependsOn); | ||
57 | AfterSigning | ||
58 | </SigningDependsOn> | ||
59 | </PropertyGroup> | ||
60 | <Target | ||
61 | Name="Signing" | ||
62 | DependsOnTargets="$(SigningDependsOn)" | ||
63 | Inputs="@(SignTargetPath)" | ||
64 | Outputs="$(IntermediateOutputPath)$(SignedFile)" | ||
65 | Condition=" '@(SignTargetPath)' != '' "> | ||
66 | |||
67 | <CreateItem Include="$(IntermediateOutputPath)$(SignedFile)"> | ||
68 | <Output TaskParameter="Include" ItemName="FileWrites" /> | ||
69 | </CreateItem> | ||
70 | |||
71 | <WriteLinesToFile | ||
72 | File="$(IntermediateOutputPath)$(SignedFile)" | ||
73 | Lines="^$(MSBuildProjectFullPath);@(SignMsm);@(SignCabs);@(SignMsi);@(SignContainers);@(SignBundleEngine);@(SignBundle)" | ||
74 | Overwrite="true" /> | ||
75 | </Target> | ||
76 | |||
77 | <!-- Internal targets so correct signing targets are called. --> | ||
78 | <Target | ||
79 | Name="GetMsmsToSign" | ||
80 | Inputs="@(SignTargetPath)" | ||
81 | Outputs="$(IntermediateOutputPath)$(SignedFile)"> | ||
82 | <CreateItem Include="@(SignTargetPath)"> | ||
83 | <Output TaskParameter="Include" ItemName="SignMsm" /> | ||
84 | <Output TaskParameter="Include" ItemName="FileWrites" /> | ||
85 | </CreateItem> | ||
86 | </Target> | ||
87 | |||
88 | <Target | ||
89 | Name="InternalSignMsm" | ||
90 | DependsOnTargets="SignMsm" | ||
91 | Condition=" '@(SignMsm)' != '' " /> | ||
92 | |||
93 | <Target | ||
94 | Name="GetCabsToSign" | ||
95 | Inputs="@(SignTargetPath)" | ||
96 | Outputs="$(IntermediateOutputPath)$(SignedFile)"> | ||
97 | <GetCabList Database="%(SignTargetPath.FullPath)"> | ||
98 | <Output TaskParameter="CabList" ItemName="SignCabs" /> | ||
99 | <Output TaskParameter="CabList" ItemName="FileWrites" /> | ||
100 | </GetCabList> | ||
101 | </Target> | ||
102 | |||
103 | <Target | ||
104 | Name="InternalSignCabs" | ||
105 | DependsOnTargets="SignCabs" | ||
106 | Condition=" '@(SignCabs)' != '' " /> | ||
107 | |||
108 | <Target | ||
109 | Name="GetMsiToSign" | ||
110 | Inputs="@(SignTargetPath)" | ||
111 | Outputs="$(IntermediateOutputPath)$(SignedFile)"> | ||
112 | <CreateItemAvoidingInference InputProperties="@(SignTargetPath)"> | ||
113 | <Output TaskParameter="OuputItems" ItemName="SignMsi" /> | ||
114 | <Output TaskParameter="OuputItems" ItemName="FileWrites" /> | ||
115 | </CreateItemAvoidingInference> | ||
116 | </Target> | ||
117 | |||
118 | <Target | ||
119 | Name="InternalSignMsi" | ||
120 | DependsOnTargets="SignMsi" | ||
121 | Inputs="@(SignTargetPath)" | ||
122 | Outputs="$(IntermediateOutputPath)$(SignedFile)" | ||
123 | Condition=" '@(SignMsi)' != '' " /> | ||
124 | |||
125 | <Target | ||
126 | Name="GetContainersToSign" | ||
127 | Inputs="@(SignTargetPath)" | ||
128 | Outputs="$(IntermediateOutputPath)$(SignedFile)"> | ||
129 | <!-- TODO: implement signing detached containers --> | ||
130 | </Target> | ||
131 | |||
132 | <Target | ||
133 | Name="InternalSignContainers" | ||
134 | DependsOnTargets="SignContainers" | ||
135 | Condition=" '@(SignContainers)' != '' " /> | ||
136 | |||
137 | <Target | ||
138 | Name="InternalSignBundleEngine" | ||
139 | DependsOnTargets="SignBundleEngine" | ||
140 | Condition=" '@(SignBundleEngine)' != '' " /> | ||
141 | |||
142 | <Target | ||
143 | Name="InternalSignBundle" | ||
144 | DependsOnTargets="SignBundle" | ||
145 | Condition=" '@(SignBundle)' != '' " /> | ||
146 | |||
147 | <!-- | ||
148 | ================================================================================================ | ||
149 | InscribeMsi | ||
150 | |||
151 | To be called after signing an MSI's cabs - inscribes an MSI with the digital signature of its | ||
152 | external cabs. | ||
153 | |||
154 | [IN/OUT] | ||
155 | @(SignTargetPath) - The database file to inscribe - database file will be modified in-place. | ||
156 | |||
157 | [OUT] | ||
158 | @(SignMsi) - The database file to sign. | ||
159 | ================================================================================================ | ||
160 | --> | ||
161 | <PropertyGroup> | ||
162 | <InscribeMsiDependsOn> | ||
163 | PrepareForBuild; | ||
164 | ResolveWixExtensionReferences; | ||
165 | CompileAndLink; | ||
166 | InternalSignCabs | ||
167 | </InscribeMsiDependsOn> | ||
168 | </PropertyGroup> | ||
169 | <Target | ||
170 | Name="InscribeMsi" | ||
171 | DependsOnTargets="$(InscribeMsiDependsOn)" | ||
172 | Inputs="@(SignTargetPath)" | ||
173 | Outputs="$(IntermediateOutputPath)$(SignedFile)" | ||
174 | Condition=" '@(SignCabs)' != '' "> | ||
175 | |||
176 | <Insignia | ||
177 | DatabaseFile="%(SignTargetPath.FullPath)" | ||
178 | OutputFile="%(SignTargetPath.FullPath)" | ||
179 | ToolPath="$(WixToolPath)" | ||
180 | NoLogo="$(InscribeNoLogo)" | ||
181 | RunAsSeparateProcess="$(RunWixToolsOutOfProc)" | ||
182 | SuppressAllWarnings="$(InscribeSuppressAllWarnings)" | ||
183 | SuppressSpecificWarnings="$(InscribeSuppressSpecificWarnings)" | ||
184 | TreatWarningsAsErrors="$(InscribeTreatWarningsAsErrors)" | ||
185 | TreatSpecificWarningsAsErrors="$(InscribeTreatSpecificWarningsAsErrors)" | ||
186 | VerboseOutput="$(InscribeVerboseOutput)" | ||
187 | AdditionalOptions="$(InscribeAdditionalOptions)" /> | ||
188 | </Target> | ||
189 | |||
190 | <!-- | ||
191 | ================================================================================================ | ||
192 | InscribeBundleEngine | ||
193 | |||
194 | To be called after signing a bundle's detached containers. Also removes attached container | ||
195 | so engine can be signed without attached container. | ||
196 | |||
197 | [IN] | ||
198 | @(SignTargetPath) - The bundle to inscribe. | ||
199 | |||
200 | [OUT] | ||
201 | @(SignBundleEngine) - The bundle engine file to be signed. | ||
202 | ================================================================================================ | ||
203 | --> | ||
204 | <PropertyGroup> | ||
205 | <InscribeBundleEngineDependsOn> | ||
206 | PrepareForBuild; | ||
207 | ResolveWixExtensionReferences; | ||
208 | CompileAndLink; | ||
209 | InternalSignContainers | ||
210 | </InscribeBundleEngineDependsOn> | ||
211 | </PropertyGroup> | ||
212 | <Target | ||
213 | Name="InscribeBundleEngine" | ||
214 | DependsOnTargets="$(InscribeBundleEngineDependsOn)" | ||
215 | Inputs="@(SignTargetPath)" | ||
216 | Outputs="$(IntermediateOutputPath)$(SignedFile)"> | ||
217 | |||
218 | <Insignia | ||
219 | BundleFile="@(SignTargetPath)" | ||
220 | OutputFile="$(IntermediateOutputPath)%(SignTargetPath.Filename)%(SignTargetPath.Extension)" | ||
221 | ToolPath="$(WixToolPath)" | ||
222 | NoLogo="$(InscribeNoLogo)" | ||
223 | RunAsSeparateProcess="$(RunWixToolsOutOfProc)" | ||
224 | SuppressAllWarnings="$(InscribeSuppressAllWarnings)" | ||
225 | SuppressSpecificWarnings="$(InscribeSuppressSpecificWarnings)" | ||
226 | TreatWarningsAsErrors="$(InscribeTreatWarningsAsErrors)" | ||
227 | TreatSpecificWarningsAsErrors="$(InscribeTreatSpecificWarningsAsErrors)" | ||
228 | VerboseOutput="$(InscribeVerboseOutput)" | ||
229 | AdditionalOptions="$(InscribeAdditionalOptions)"> | ||
230 | <Output TaskParameter="Output" ItemName="SignBundleEngine" /> | ||
231 | </Insignia> | ||
232 | |||
233 | <!-- Explicitly add output to FileWrites to ensure even when the target is up to date. --> | ||
234 | <CreateItem Include="$(IntermediateOutputPath)%(SignTargetPath.Filename)%(SignTargetPath.Extension)"> | ||
235 | <Output TaskParameter="Include" ItemName="FileWrites" /> | ||
236 | </CreateItem> | ||
237 | |||
238 | </Target> | ||
239 | |||
240 | <!-- | ||
241 | ================================================================================================ | ||
242 | InscribeBundle | ||
243 | |||
244 | To be called after signing the bundle engine to reattach the attached container. | ||
245 | |||
246 | [IN] | ||
247 | @(Inscribe) - The bundle to inscribe. | ||
248 | |||
249 | [OUT] | ||
250 | @(SignBundle) - The bundle engine file to be signed. | ||
251 | ================================================================================================ | ||
252 | --> | ||
253 | <PropertyGroup> | ||
254 | <InscribeBundleDependsOn> | ||
255 | PrepareForBuild; | ||
256 | ResolveWixExtensionReferences; | ||
257 | CompileAndLink; | ||
258 | InternalSignBundleEngine | ||
259 | </InscribeBundleDependsOn> | ||
260 | </PropertyGroup> | ||
261 | <Target | ||
262 | Name="InscribeBundle" | ||
263 | DependsOnTargets="$(InscribeBundleDependsOn)" | ||
264 | Inputs="@(SignTargetPath)" | ||
265 | Outputs="$(IntermediateOutputPath)$(SignedFile)"> | ||
266 | |||
267 | <Insignia | ||
268 | BundleFile="@(SignBundleEngine)" | ||
269 | OriginalBundleFile="@(SignTargetPath)" | ||
270 | OutputFile="@(SignTargetPath)" | ||
271 | ToolPath="$(WixToolPath)" | ||
272 | NoLogo="$(InscribeNoLogo)" | ||
273 | RunAsSeparateProcess="$(RunWixToolsOutOfProc)" | ||
274 | SuppressAllWarnings="$(InscribeSuppressAllWarnings)" | ||
275 | SuppressSpecificWarnings="$(InscribeSuppressSpecificWarnings)" | ||
276 | TreatWarningsAsErrors="$(InscribeTreatWarningsAsErrors)" | ||
277 | TreatSpecificWarningsAsErrors="$(InscribeTreatSpecificWarningsAsErrors)" | ||
278 | VerboseOutput="$(InscribeVerboseOutput)" | ||
279 | AdditionalOptions="$(InscribeAdditionalOptions)"> | ||
280 | <Output TaskParameter="Output" ItemName="SignBundle" /> | ||
281 | <Output TaskParameter="Output" ItemName="FileWrites" /> | ||
282 | </Insignia> | ||
283 | </Target> | ||
284 | |||
285 | <!-- | ||
286 | ================================================================================================== | ||
287 | BeforeSigning | ||
288 | |||
289 | Redefine this target in your project in order to run tasks just before all signing tasks. | ||
290 | ================================================================================================== | ||
291 | --> | ||
292 | <Target Name="BeforeSigning" /> | ||
293 | |||
294 | <!-- | ||
295 | ================================================================================================== | ||
296 | SignMsm | ||
297 | |||
298 | Redefine this target in your project in order to sign merge modules. | ||
299 | |||
300 | [IN] | ||
301 | @(SignMsm) - merge module files to sign. | ||
302 | ================================================================================================== | ||
303 | --> | ||
304 | <Target Name="SignMsm" /> | ||
305 | |||
306 | <!-- | ||
307 | ================================================================================================== | ||
308 | SignCabs | ||
309 | |||
310 | Redefine this target in your project in order to sign the cabs of your database. | ||
311 | |||
312 | [IN] | ||
313 | @(SignCabs) - cabinet files to sign. | ||
314 | ================================================================================================== | ||
315 | --> | ||
316 | <Target Name="SignCabs" /> | ||
317 | |||
318 | <!-- | ||
319 | ================================================================================================== | ||
320 | SignMsi | ||
321 | |||
322 | Redefine this target in your project in order to sign your database, after it has been inscribed | ||
323 | with the signatures of your signed cabs. | ||
324 | |||
325 | [IN] | ||
326 | @(SignMsi) - database files to sign. | ||
327 | ================================================================================================== | ||
328 | --> | ||
329 | <Target Name="SignMsi" /> | ||
330 | |||
331 | <!-- | ||
332 | ================================================================================================== | ||
333 | SignContainers | ||
334 | |||
335 | Redefine this target in your project in order to sign your bundle's detached containers. | ||
336 | |||
337 | [IN] | ||
338 | @(SignContainers) - detached container files to sign. | ||
339 | ================================================================================================== | ||
340 | --> | ||
341 | <Target Name="SignContainers" /> | ||
342 | |||
343 | <!-- | ||
344 | ================================================================================================== | ||
345 | SignBundleEngine | ||
346 | |||
347 | Redefine this target in your project in order to sign your bundle, after it has been inscribed | ||
348 | with the signatures of your signed containers. | ||
349 | |||
350 | [IN] | ||
351 | @(SignBundleEngine) - bundle engine file to sign. | ||
352 | ================================================================================================== | ||
353 | --> | ||
354 | <Target Name="SignBundleEngine" /> | ||
355 | |||
356 | <!-- | ||
357 | ================================================================================================== | ||
358 | SignBundle | ||
359 | |||
360 | Redefine this target in your project in order to sign your bundle, after the attached container | ||
361 | is reattached. | ||
362 | |||
363 | [IN] | ||
364 | @(SignBundle) - bundle file to sign. | ||
365 | ================================================================================================== | ||
366 | --> | ||
367 | <Target Name="SignBundle" /> | ||
368 | |||
369 | <!-- | ||
370 | ================================================================================================== | ||
371 | AfterSigning | ||
372 | |||
373 | Redefine this target in your project in order to run tasks just after all signing tasks. | ||
374 | ================================================================================================== | ||
375 | --> | ||
376 | <Target Name="AfterSigning" /> | ||
377 | |||
378 | </Project> | ||
diff --git a/src/WixToolset.BuildTasks/wix.targets b/src/WixToolset.BuildTasks/wix.targets new file mode 100644 index 00000000..eadd33ec --- /dev/null +++ b/src/WixToolset.BuildTasks/wix.targets | |||
@@ -0,0 +1,1353 @@ | |||
1 | <?xml version="1.0" encoding="utf-8"?> | ||
2 | <!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. --> | ||
3 | |||
4 | |||
5 | <Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003" InitialTargets="_CheckRequiredProperties" DefaultTargets="Build"> | ||
6 | <PropertyGroup> | ||
7 | <WixTargetsImported>true</WixTargetsImported> | ||
8 | </PropertyGroup> | ||
9 | |||
10 | <!-- | ||
11 | ////////////////////////////////////////////////////////////////////////////////////////////////// | ||
12 | ////////////////////////////////////////////////////////////////////////////////////////////////// | ||
13 | Extension Points | ||
14 | ////////////////////////////////////////////////////////////////////////////////////////////////// | ||
15 | ////////////////////////////////////////////////////////////////////////////////////////////////// | ||
16 | --> | ||
17 | |||
18 | <!-- Allow a user-customized targets files to be used as part of the build. --> | ||
19 | <Import Project="$(CustomBeforeWixTargets)" Condition=" '$(CustomBeforeWixTargets)' != '' and Exists('$(CustomBeforeWixTargets)')" /> | ||
20 | |||
21 | <!-- These properties can be overridden to support non-default installations. --> | ||
22 | <PropertyGroup> | ||
23 | <WixBinDir Condition=" '$(WixBinDir)' == ''">$(MSBuildThisFileDirectory)</WixBinDir> | ||
24 | <WixTasksPath Condition=" '$(WixTasksPath)' == '' ">$(WixBinDir)WixToolset.BuildTasks.dll</WixTasksPath> | ||
25 | <WixHarvestTargetsPath Condition=" '$(WixHarvestTargetsPath)' == '' ">$(WixBinDir)wix.harvest.targets</WixHarvestTargetsPath> | ||
26 | <WixSigningTargetsPath Condition=" '$(WixSigningTargetsPath)' == '' ">$(WixBinDir)wix.signing.targets</WixSigningTargetsPath> | ||
27 | <LuxTargetsPath Condition=" '$(LuxTargetsPath)' == '' ">$(WixBinDir)lux.targets</LuxTargetsPath> | ||
28 | <LuxTasksPath Condition=" '$(LuxTasksPath)' == '' ">$(WixBinDir)LuxTasks.dll</LuxTasksPath> | ||
29 | </PropertyGroup> | ||
30 | |||
31 | <!-- This makes the project files a dependency of all targets so that things rebuild if they change --> | ||
32 | <PropertyGroup> | ||
33 | <MSBuildAllProjects>$(MSBuildAllProjects);$(MSBuildThisFileFullPath)</MSBuildAllProjects> | ||
34 | <MSBuildAllProjects Condition="Exists('$(WixHarvestTargetsPath)')">$(MSBuildAllProjects);$(WixHarvestTargetsPath)</MSBuildAllProjects> | ||
35 | <MSBuildAllProjects Condition="Exists('$(WixSigningTargetsPath)')">$(MSBuildAllProjects);$(WixSigningTargetsPath)</MSBuildAllProjects> | ||
36 | <MSBuildAllProjects Condition="Exists('$(LuxTargetsPath)')">$(MSBuildAllProjects);$(LuxTargetsPath)</MSBuildAllProjects> | ||
37 | <MSBuildAllProjects Condition="Exists('$(CustomBeforeWixTargets)')">$(MSBuildAllProjects);$(CustomBeforeWixTargets)</MSBuildAllProjects> | ||
38 | <MSBuildAllProjects Condition="Exists('$(CustomAfterWixTargets)')">$(MSBuildAllProjects);$(CustomAfterWixTargets)</MSBuildAllProjects> | ||
39 | </PropertyGroup> | ||
40 | |||
41 | <!-- | ||
42 | ////////////////////////////////////////////////////////////////////////////////////////////////// | ||
43 | ////////////////////////////////////////////////////////////////////////////////////////////////// | ||
44 | Declarations for Microsoft.Common.targets | ||
45 | ////////////////////////////////////////////////////////////////////////////////////////////////// | ||
46 | ////////////////////////////////////////////////////////////////////////////////////////////////// | ||
47 | --> | ||
48 | |||
49 | <PropertyGroup> | ||
50 | <DefaultLanguageSourceExtension>.wxs</DefaultLanguageSourceExtension> | ||
51 | <Language>wix</Language> | ||
52 | <TargetRuntime>Managed</TargetRuntime> | ||
53 | |||
54 | <!-- Use OutputName to set the AssemblyName for Microsoft.Common.targets --> | ||
55 | <OutputName Condition=" '$(OutputName)'=='' ">$(MSBuildProjectName)</OutputName> | ||
56 | <AssemblyName>$(OutputName)</AssemblyName> | ||
57 | |||
58 | <!-- Default the OutputType to a known WiX toolset TYPE. --> | ||
59 | <_OriginalOutputType>$(OutputType)</_OriginalOutputType> | ||
60 | <OutputType Condition=" '$(OutputType)' == '' ">Package</OutputType> | ||
61 | </PropertyGroup> | ||
62 | |||
63 | <!-- | ||
64 | IDE Macros available from both integrated builds and from command line builds. | ||
65 | The following properties are 'macros' that are available via IDE for pre and post build steps. | ||
66 | All of them should be added to WixBuildMacroCollection to ensure that they are shown in the UI. | ||
67 | --> | ||
68 | <PropertyGroup> | ||
69 | <TargetExt Condition=" '$(OutputType)' == 'Package' ">.msi</TargetExt> | ||
70 | <TargetExt Condition=" '$(OutputType)' == 'Module' ">.msm</TargetExt> | ||
71 | <TargetExt Condition=" '$(OutputType)' == 'PatchCreation' ">.pcp</TargetExt> | ||
72 | <TargetExt Condition=" '$(OutputType)' == 'Library' ">.wixlib</TargetExt> | ||
73 | <TargetExt Condition=" '$(OutputType)' == 'Bundle' ">.exe</TargetExt> | ||
74 | </PropertyGroup> | ||
75 | |||
76 | <!-- Provide the correct output name for the .wixpdb --> | ||
77 | <ItemGroup Condition="'$(_DebugSymbolsProduced)' == 'true'"> | ||
78 | <_DebugSymbolsIntermediatePath Include="$(PdbOutputDir)$(TargetPdbName)" Condition=" '@(_DebugSymbolsIntermediatePath)' == '' " /> | ||
79 | </ItemGroup> | ||
80 | |||
81 | <Import Project="$(MSBuildToolsPath)\Microsoft.Common.targets" /> | ||
82 | |||
83 | <PropertyGroup> | ||
84 | <!-- Default pdb output path to the intermediate output directory --> | ||
85 | <PdbOutputDir Condition=" '$(PdbOutputDir)'=='' ">$(IntermediateOutputPath)</PdbOutputDir> | ||
86 | <PdbOutputDir Condition=" '$(PdbOutputDir)' != '' and !HasTrailingSlash('$(PdbOutputDir)') ">$(PdbOutputDir)\</PdbOutputDir> | ||
87 | |||
88 | <!-- Example, C:\MyProjects\MyProject\bin\debug\ --> | ||
89 | <TargetPdbDir Condition=" '$(PdbOutputDir)'!='' ">$([System.IO.Path]::GetFullPath(`$([System.IO.Path]::Combine(`$(MSBuildProjectDirectory)`, `$(PdbOutputDir)`))`))</TargetPdbDir> | ||
90 | |||
91 | <!-- Example, MySetup.wixpdb" --> | ||
92 | <TargetPdbName Condition=" '$(TargetPdbName)' == '' ">$(TargetName).wixpdb</TargetPdbName> | ||
93 | |||
94 | <!-- Example, C:\MyProjects\MyProject\bin\debug\MyPackage.wixpdb --> | ||
95 | <TargetPdbPath Condition=" '$(TargetPdbPath)' == '' ">$(TargetPdbDir)$(TargetPdbName)</TargetPdbPath> | ||
96 | </PropertyGroup> | ||
97 | |||
98 | <!-- | ||
99 | ////////////////////////////////////////////////////////////////////////////////////////////////// | ||
100 | ////////////////////////////////////////////////////////////////////////////////////////////////// | ||
101 | Property Declarations | ||
102 | ////////////////////////////////////////////////////////////////////////////////////////////////// | ||
103 | ////////////////////////////////////////////////////////////////////////////////////////////////// | ||
104 | --> | ||
105 | |||
106 | <!-- These tasks can be used as general-purpose build tasks. --> | ||
107 | <UsingTask TaskName="Candle" AssemblyFile="$(WixTasksPath)" /> | ||
108 | <UsingTask TaskName="DoIt" AssemblyFile="$(WixTasksPath)" /> | ||
109 | <UsingTask TaskName="Lit" AssemblyFile="$(WixTasksPath)" /> | ||
110 | <UsingTask TaskName="Light" AssemblyFile="$(WixTasksPath)" /> | ||
111 | <UsingTask TaskName="Torch" AssemblyFile="$(WixTasksPath)" /> | ||
112 | |||
113 | <!-- These tasks are specific to the build process defined in this file, and are not considered general-purpose build tasks. --> | ||
114 | <UsingTask TaskName="CreateItemAvoidingInference" AssemblyFile="$(WixTasksPath)" /> | ||
115 | <UsingTask TaskName="CreateProjectReferenceDefineConstants" AssemblyFile="$(WixTasksPath)" /> | ||
116 | <UsingTask TaskName="WixAssignCulture" AssemblyFile="$(WixTasksPath)" /> | ||
117 | <UsingTask TaskName="ResolveWixReferences" AssemblyFile="$(WixTasksPath)"/> | ||
118 | <UsingTask TaskName="ReplaceString" AssemblyFile="$(WixTasksPath)"/> | ||
119 | <UsingTask TaskName="GetCabList" AssemblyFile="$(WixTasksPath)" /> | ||
120 | <UsingTask TaskName="GetLooseFileList" AssemblyFile="$(WixTasksPath)" /> | ||
121 | <UsingTask TaskName="GenerateCompileWithObjectPath" AssemblyFile="$(WixTasksPath)"/> | ||
122 | |||
123 | <!-- WiX tools are 32bit EXEs, so run them out-of-proc when MSBuild is not 32bit. --> | ||
124 | <PropertyGroup> | ||
125 | <RunWixToolsOutOfProc Condition=" '$(PROCESSOR_ARCHITECTURE)'!='x86' ">true</RunWixToolsOutOfProc> | ||
126 | </PropertyGroup> | ||
127 | |||
128 | <PropertyGroup> | ||
129 | <BindContentsFile Condition=" '$(BindContentsFile)' == '' ">$(MSBuildProjectFile).BindContentsFileList.txt</BindContentsFile> | ||
130 | <BindOutputsFile Condition=" '$(BindOutputsFile)' == '' ">$(MSBuildProjectFile).BindOutputsFileList.txt</BindOutputsFile> | ||
131 | <BindBuiltOutputsFile Condition=" '$(BindBuiltOutputsFile)' == '' ">$(MSBuildProjectFile).BindBuiltOutputsFileList.txt</BindBuiltOutputsFile> | ||
132 | </PropertyGroup> | ||
133 | |||
134 | <PropertyGroup> | ||
135 | <CabinetCachePath Condition=" '$(CabinetCachePath)'=='' and '$(ReuseCabinetCache)'=='true' ">$(IntermediateOutputPath)cabcache\</CabinetCachePath> | ||
136 | </PropertyGroup> | ||
137 | |||
138 | <PropertyGroup> | ||
139 | <WixToolDir Condition=" '$(WixToolDir)' == ''">$(WixBinDir)</WixToolDir> | ||
140 | <WixExtDir Condition=" '$(WixExtDir)' == ''">$(WixToolDir)</WixExtDir> | ||
141 | </PropertyGroup> | ||
142 | |||
143 | <!-- | ||
144 | Set the SignTargetPath item directly when output is a Bundle. The AssignCultures target | ||
145 | sets SignTargetPath item for other output types based on the cultures provided. | ||
146 | --> | ||
147 | <ItemGroup> | ||
148 | <SignTargetPath Include="$(TargetPath)" Condition=" '$(OutputType)' == 'Bundle' AND '$(SignOutput)' == 'true' AND '$(SuppressLayout)' != 'true' " /> | ||
149 | </ItemGroup> | ||
150 | |||
151 | <!-- | ||
152 | ////////////////////////////////////////////////////////////////////////////////////////////////// | ||
153 | ////////////////////////////////////////////////////////////////////////////////////////////////// | ||
154 | Default Compiler, Linker, and Librarian Property Declarations | ||
155 | ////////////////////////////////////////////////////////////////////////////////////////////////// | ||
156 | ////////////////////////////////////////////////////////////////////////////////////////////////// | ||
157 | --> | ||
158 | |||
159 | <!-- If WixExtension was passed in via the command line, then convert it to an ItemGroup --> | ||
160 | <ItemGroup> | ||
161 | <WixExtension Include="$(WixExtension)" Condition=" '$(WixExtension)' != '' " /> | ||
162 | </ItemGroup> | ||
163 | |||
164 | <!-- Defaut Compiler properties. --> | ||
165 | <PropertyGroup> | ||
166 | <CompilerNoLogo Condition=" '$(CompilerNoLogo)' == '' ">$(NoLogo)</CompilerNoLogo> | ||
167 | <CompilerSuppressAllWarnings Condition=" '$(CompilerSuppressAllWarnings)' == '' ">$(SuppressAllWarnings)</CompilerSuppressAllWarnings> | ||
168 | <CompilerSuppressSpecificWarnings Condition=" '$(CompilerSuppressSpecificWarnings)' == '' ">$(SuppressSpecificWarnings)</CompilerSuppressSpecificWarnings> | ||
169 | <CompilerTreatWarningsAsErrors Condition=" '$(CompilerTreatWarningsAsErrors)' == '' ">$(TreatWarningsAsErrors)</CompilerTreatWarningsAsErrors> | ||
170 | <CompilerTreatSpecificWarningsAsErrors Condition=" '$(CompilerTreatSpecificWarningsAsErrors)' == '' ">$(TreatSpecificWarningsAsErrors)</CompilerTreatSpecificWarningsAsErrors> | ||
171 | <CompilerVerboseOutput Condition=" '$(CompilerVerboseOutput)' == '' ">$(VerboseOutput)</CompilerVerboseOutput> | ||
172 | <!-- TODO: This probably doesn't work any longer since Platform won't be defined until Microsoft.Common.targets is included --> | ||
173 | <InstallerPlatform Condition=" '$(InstallerPlatform)' == '' and '$(Platform)' != 'AnyCPU' and '$(Platform)' != 'Any CPU' ">$(Platform)</InstallerPlatform> | ||
174 | </PropertyGroup> | ||
175 | |||
176 | <!-- Default Lib properties. --> | ||
177 | <PropertyGroup> | ||
178 | <LibNoLogo Condition=" '$(LibNoLogo)' == '' ">$(NoLogo)</LibNoLogo> | ||
179 | <LibBindFiles Condition=" '$(LibBindFiles)' == '' ">$(BindFiles)</LibBindFiles> | ||
180 | <LibPedantic Condition=" '$(LibPedantic)' == '' ">$(Pedantic)</LibPedantic> | ||
181 | <LibSuppressAllWarnings Condition=" '$(LibSuppressAllWarnings)' == '' ">$(SuppressAllWarnings)</LibSuppressAllWarnings> | ||
182 | <LibSuppressSpecificWarnings Condition=" '$(LibSuppressSpecificWarnings)' == '' ">$(SuppressSpecificWarnings)</LibSuppressSpecificWarnings> | ||
183 | <LibSuppressSchemaValidation Condition=" '$(LibSuppressSchemaValidation)' == '' ">$(SuppressSchemaValidation)</LibSuppressSchemaValidation> | ||
184 | <LibSuppressIntermediateFileVersionMatching Condition=" '$(LibSuppressIntermediateFileVersionMatching)' == '' ">$(SuppressIntermediateFileVersionMatching)</LibSuppressIntermediateFileVersionMatching> | ||
185 | <LibTreatWarningsAsErrors Condition=" '$(LibTreatWarningsAsErrors)' == '' ">$(TreatWarningsAsErrors)</LibTreatWarningsAsErrors> | ||
186 | <LibTreatSpecificWarningsAsErrors Condition=" '$(LibTreatSpecificWarningsAsErrors)' == '' ">$(TreatSpecificWarningsAsErrors)</LibTreatSpecificWarningsAsErrors> | ||
187 | <LibVerboseOutput Condition=" '$(LibVerboseOutput)' == '' ">$(VerboseOutput)</LibVerboseOutput> | ||
188 | </PropertyGroup> | ||
189 | |||
190 | <!-- Default Linker properties. --> | ||
191 | <PropertyGroup> | ||
192 | <LinkerNoLogo Condition=" '$(LinkerNoLogo)' == '' ">$(NoLogo)</LinkerNoLogo> | ||
193 | <LinkerBindFiles Condition=" '$(LinkerBindFiles)' == '' ">$(BindFiles)</LinkerBindFiles> | ||
194 | <LinkerPedantic Condition=" '$(LinkerPedantic)' == '' ">$(Pedantic)</LinkerPedantic> | ||
195 | <LinkerSuppressAllWarnings Condition=" '$(LinkerSuppressAllWarnings)' == '' ">$(SuppressAllWarnings)</LinkerSuppressAllWarnings> | ||
196 | <LinkerSuppressSpecificWarnings Condition=" '$(LinkerSuppressSpecificWarnings)' == '' ">$(SuppressSpecificWarnings)</LinkerSuppressSpecificWarnings> | ||
197 | <LinkerSuppressSchemaValidation Condition=" '$(LinkerSuppressSchemaValidation)' == '' ">$(SuppressSchemaValidation)</LinkerSuppressSchemaValidation> | ||
198 | <LinkerSuppressIntermediateFileVersionMatching Condition=" '$(LinkerSuppressIntermediateFileVersionMatching)' == '' ">$(SuppressIntermediateFileVersionMatching)</LinkerSuppressIntermediateFileVersionMatching> | ||
199 | <LinkerTreatWarningsAsErrors Condition=" '$(LinkerTreatWarningsAsErrors)' == '' ">$(TreatWarningsAsErrors)</LinkerTreatWarningsAsErrors> | ||
200 | <LinkerTreatSpecificWarningsAsErrors Condition=" '$(LinkerTreatSpecificWarningsAsErrors)' == '' ">$(TreatSpecificWarningsAsErrors)</LinkerTreatSpecificWarningsAsErrors> | ||
201 | <LinkerVerboseOutput Condition=" '$(LinkerVerboseOutput)' == '' ">$(VerboseOutput)</LinkerVerboseOutput> | ||
202 | </PropertyGroup> | ||
203 | |||
204 | <!-- If BindInputPaths (or LinkerBindInputPaths) was passed in via the command line, then convert it to an ItemGroup --> | ||
205 | <ItemGroup> | ||
206 | <BindInputPaths Include="$(BindInputPaths)" Condition=" '$(BindInputPaths)' != '' " /> | ||
207 | <LinkerBindInputPaths Include="$(LinkerBindInputPaths)" Condition=" '$(LinkerBindInputPaths)' != '' " /> | ||
208 | </ItemGroup> | ||
209 | |||
210 | <!-- Default Lit and Light "properties" --> | ||
211 | <ItemGroup> | ||
212 | <LinkerBindInputPaths Condition=" '@(LinkerBindInputPaths)' == '' " Include="@(BindInputPaths)" /> | ||
213 | </ItemGroup> | ||
214 | |||
215 | <!-- | ||
216 | ////////////////////////////////////////////////////////////////////////////////////////////////// | ||
217 | ////////////////////////////////////////////////////////////////////////////////////////////////// | ||
218 | Initial Targets | ||
219 | ////////////////////////////////////////////////////////////////////////////////////////////////// | ||
220 | ////////////////////////////////////////////////////////////////////////////////////////////////// | ||
221 | --> | ||
222 | |||
223 | <!-- | ||
224 | ================================================================================================== | ||
225 | _CheckRequiredProperties | ||
226 | |||
227 | Checks properties that must be set in the main project file or on the command line before | ||
228 | using this .TARGETS file. | ||
229 | |||
230 | [IN] | ||
231 | $(OutputName) - The name of the MSI/MSM/wixlib to build (without the extension) | ||
232 | $(OutputType) - Possible values are 'package', 'PatchCreation', 'module', 'library', 'bundle' | ||
233 | ================================================================================================== | ||
234 | --> | ||
235 | <PropertyGroup> | ||
236 | <_PleaseSetThisInProjectFile>Please set this in the project file before the <Import> 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 "$(MSBuildProjectFile)". 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 "$(MSBuildProjectFile)". 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 "$(MSBuildProjectFile)". $(_OutputTypeDescription)" /> | ||
255 | |||
256 | <Error | ||
257 | Code="WIXTARGETS103" | ||
258 | Condition=" '$(MSBuildToolsVersion)' == '' OR '$(MSBuildToolsVersion)' < '4.0' " | ||
259 | Text="MSBuild v$(MSBuildToolsVersion) is not supported by the project "$(MSBuildProjectFile)". 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) -> $(TargetPath)" Condition="'$(CopyBuildOutputToOutputDirectory)' == 'true' and '$(SkipCopyBuildProduct)'!='true'" /> | ||
1326 | <!--<Message Importance="High" Text="$(MSBuildProjectName) -> @(MainAssembly->'%(FullPath)')" Condition="'$(CopyBuildOutputToOutputDirectory)' == 'true' and '$(SkipCopyBuildProduct)'!='true'" />--> | ||
1327 | |||
1328 | <!-- Copy the debug information file (.pdb), if any | ||
1329 | <Copy | ||
1330 | SourceFiles="@(_DebugSymbolsIntermediatePath)" | ||
1331 | DestinationFiles="@(_DebugSymbolsOutputPath)" | ||
1332 | SkipUnchangedFiles="$(SkipCopyUnchangedFiles)" | ||
1333 | OverwriteReadOnlyFiles="$(OverwriteReadOnlyFiles)" | ||
1334 | Retries="$(CopyRetryCount)" | ||
1335 | RetryDelayMilliseconds="$(CopyRetryDelayMilliseconds)" | ||
1336 | UseHardlinksIfPossible="$(CreateHardLinksForCopyFilesToOutputDirectoryIfPossible)" | ||
1337 | Condition="'$(_DebugSymbolsProduced)'=='true' and '$(SkipCopyingSymbolsToOutputDirectory)' != 'true' and '$(CopyOutputSymbolsToOutputDirectory)'=='true'"> | ||
1338 | |||
1339 | <Output TaskParameter="DestinationFiles" ItemName="FileWrites"/> | ||
1340 | |||
1341 | </Copy> | ||
1342 | --> | ||
1343 | </Target> | ||
1344 | |||
1345 | |||
1346 | <Import Project="$(WixHarvestTargetsPath)" Condition=" '$(WixHarvestTargetsPath)' != '' and Exists('$(WixHarvestTargetsPath)')" /> | ||
1347 | <Import Project="$(WixSigningTargetsPath)" Condition=" '$(WixSigningTargetsPath)' != '' and Exists('$(WixSigningTargetsPath)')" /> | ||
1348 | <Import Project="$(LuxTargetsPath)" Condition=" '$(LuxTargetsPath)' != '' and Exists('$(LuxTargetsPath)')" /> | ||
1349 | |||
1350 | <!-- Extension point: Define CustomAfterWixTargets to a .targets file that you want to include after this file. --> | ||
1351 | <Import Project="$(CustomAfterWixTargets)" Condition=" '$(CustomAfterWixTargets)' != '' and Exists('$(CustomAfterWixTargets)')" /> | ||
1352 | |||
1353 | </Project> | ||
diff --git a/src/WixToolset.Core/AppCommon.cs b/src/WixToolset.Core/AppCommon.cs new file mode 100644 index 00000000..30ec7771 --- /dev/null +++ b/src/WixToolset.Core/AppCommon.cs | |||
@@ -0,0 +1,145 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
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 | } | ||
diff --git a/src/WixToolset.Core/AssemblyInfo.cs b/src/WixToolset.Core/AssemblyInfo.cs new file mode 100644 index 00000000..b3740b2a --- /dev/null +++ b/src/WixToolset.Core/AssemblyInfo.cs | |||
@@ -0,0 +1,9 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
3 | using System; | ||
4 | using System.Reflection; | ||
5 | using System.Runtime.InteropServices; | ||
6 | |||
7 | [assembly: AssemblyCulture("")] | ||
8 | [assembly: CLSCompliant(true)] | ||
9 | [assembly: ComVisible(false)] | ||
diff --git a/src/WixToolset.Core/Bind/BindBundleCommand.cs b/src/WixToolset.Core/Bind/BindBundleCommand.cs new file mode 100644 index 00000000..7ea0c830 --- /dev/null +++ b/src/WixToolset.Core/Bind/BindBundleCommand.cs | |||
@@ -0,0 +1,905 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
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 | } | ||
diff --git a/src/WixToolset.Core/Bind/BindDatabaseCommand.cs b/src/WixToolset.Core/Bind/BindDatabaseCommand.cs new file mode 100644 index 00000000..93af2e9a --- /dev/null +++ b/src/WixToolset.Core/Bind/BindDatabaseCommand.cs | |||
@@ -0,0 +1,1311 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
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 | } | ||
diff --git a/src/WixToolset.Core/Bind/BindTransformCommand.cs b/src/WixToolset.Core/Bind/BindTransformCommand.cs new file mode 100644 index 00000000..e909f191 --- /dev/null +++ b/src/WixToolset.Core/Bind/BindTransformCommand.cs | |||
@@ -0,0 +1,473 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
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 | } | ||
diff --git a/src/WixToolset.Core/Bind/Bundles/AutomaticallySlipstreamPatchesCommand.cs b/src/WixToolset.Core/Bind/Bundles/AutomaticallySlipstreamPatchesCommand.cs new file mode 100644 index 00000000..eb02a983 --- /dev/null +++ b/src/WixToolset.Core/Bind/Bundles/AutomaticallySlipstreamPatchesCommand.cs | |||
@@ -0,0 +1,112 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
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 | } | ||
diff --git a/src/WixToolset.Core/Bind/Bundles/BurnCommon.cs b/src/WixToolset.Core/Bind/Bundles/BurnCommon.cs new file mode 100644 index 00000000..8cb07791 --- /dev/null +++ b/src/WixToolset.Core/Bind/Bundles/BurnCommon.cs | |||
@@ -0,0 +1,378 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
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 | } | ||
diff --git a/src/WixToolset.Core/Bind/Bundles/BurnReader.cs b/src/WixToolset.Core/Bind/Bundles/BurnReader.cs new file mode 100644 index 00000000..f6d7a197 --- /dev/null +++ b/src/WixToolset.Core/Bind/Bundles/BurnReader.cs | |||
@@ -0,0 +1,210 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
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 | } | ||
diff --git a/src/WixToolset.Core/Bind/Bundles/BurnWriter.cs b/src/WixToolset.Core/Bind/Bundles/BurnWriter.cs new file mode 100644 index 00000000..bc0baf46 --- /dev/null +++ b/src/WixToolset.Core/Bind/Bundles/BurnWriter.cs | |||
@@ -0,0 +1,239 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
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 | } | ||
diff --git a/src/WixToolset.Core/Bind/Bundles/CreateBootstrapperApplicationManifestCommand.cs b/src/WixToolset.Core/Bind/Bundles/CreateBootstrapperApplicationManifestCommand.cs new file mode 100644 index 00000000..1040b394 --- /dev/null +++ b/src/WixToolset.Core/Bind/Bundles/CreateBootstrapperApplicationManifestCommand.cs | |||
@@ -0,0 +1,241 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
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 | } | ||
diff --git a/src/WixToolset.Core/Bind/Bundles/CreateBurnManifestCommand.cs b/src/WixToolset.Core/Bind/Bundles/CreateBurnManifestCommand.cs new file mode 100644 index 00000000..7bc708a3 --- /dev/null +++ b/src/WixToolset.Core/Bind/Bundles/CreateBurnManifestCommand.cs | |||
@@ -0,0 +1,686 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
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 | } | ||
diff --git a/src/WixToolset.Core/Bind/Bundles/CreateContainerCommand.cs b/src/WixToolset.Core/Bind/Bundles/CreateContainerCommand.cs new file mode 100644 index 00000000..1bf987e3 --- /dev/null +++ b/src/WixToolset.Core/Bind/Bundles/CreateContainerCommand.cs | |||
@@ -0,0 +1,68 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
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 | } | ||
diff --git a/src/WixToolset.Core/Bind/Bundles/GetPackageFacadesCommand.cs b/src/WixToolset.Core/Bind/Bundles/GetPackageFacadesCommand.cs new file mode 100644 index 00000000..dc19e380 --- /dev/null +++ b/src/WixToolset.Core/Bind/Bundles/GetPackageFacadesCommand.cs | |||
@@ -0,0 +1,62 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
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 | } | ||
diff --git a/src/WixToolset.Core/Bind/Bundles/OrderPackagesAndRollbackBoundariesCommand.cs b/src/WixToolset.Core/Bind/Bundles/OrderPackagesAndRollbackBoundariesCommand.cs new file mode 100644 index 00000000..ac3a301d --- /dev/null +++ b/src/WixToolset.Core/Bind/Bundles/OrderPackagesAndRollbackBoundariesCommand.cs | |||
@@ -0,0 +1,145 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
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 | } | ||
diff --git a/src/WixToolset.Core/Bind/Bundles/PackageFacade.cs b/src/WixToolset.Core/Bind/Bundles/PackageFacade.cs new file mode 100644 index 00000000..f7e6410f --- /dev/null +++ b/src/WixToolset.Core/Bind/Bundles/PackageFacade.cs | |||
@@ -0,0 +1,58 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
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 | } | ||
diff --git a/src/WixToolset.Core/Bind/Bundles/ProcessExePackageCommand.cs b/src/WixToolset.Core/Bind/Bundles/ProcessExePackageCommand.cs new file mode 100644 index 00000000..a1e7c271 --- /dev/null +++ b/src/WixToolset.Core/Bind/Bundles/ProcessExePackageCommand.cs | |||
@@ -0,0 +1,33 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
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 | } | ||
diff --git a/src/WixToolset.Core/Bind/Bundles/ProcessMsiPackageCommand.cs b/src/WixToolset.Core/Bind/Bundles/ProcessMsiPackageCommand.cs new file mode 100644 index 00000000..f73776c0 --- /dev/null +++ b/src/WixToolset.Core/Bind/Bundles/ProcessMsiPackageCommand.cs | |||
@@ -0,0 +1,560 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
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 | } | ||
diff --git a/src/WixToolset.Core/Bind/Bundles/ProcessMspPackageCommand.cs b/src/WixToolset.Core/Bind/Bundles/ProcessMspPackageCommand.cs new file mode 100644 index 00000000..24063221 --- /dev/null +++ b/src/WixToolset.Core/Bind/Bundles/ProcessMspPackageCommand.cs | |||
@@ -0,0 +1,189 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
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 | } | ||
diff --git a/src/WixToolset.Core/Bind/Bundles/ProcessMsuPackageCommand.cs b/src/WixToolset.Core/Bind/Bundles/ProcessMsuPackageCommand.cs new file mode 100644 index 00000000..ba59f5f5 --- /dev/null +++ b/src/WixToolset.Core/Bind/Bundles/ProcessMsuPackageCommand.cs | |||
@@ -0,0 +1,30 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
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 | } | ||
diff --git a/src/WixToolset.Core/Bind/Bundles/ProcessPayloadsCommand.cs b/src/WixToolset.Core/Bind/Bundles/ProcessPayloadsCommand.cs new file mode 100644 index 00000000..a83a7a4a --- /dev/null +++ b/src/WixToolset.Core/Bind/Bundles/ProcessPayloadsCommand.cs | |||
@@ -0,0 +1,159 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
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 | } | ||
diff --git a/src/WixToolset.Core/Bind/Bundles/VerifyPayloadsWithCatalogCommand.cs b/src/WixToolset.Core/Bind/Bundles/VerifyPayloadsWithCatalogCommand.cs new file mode 100644 index 00000000..9c614c26 --- /dev/null +++ b/src/WixToolset.Core/Bind/Bundles/VerifyPayloadsWithCatalogCommand.cs | |||
@@ -0,0 +1,148 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
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 | } | ||
diff --git a/src/WixToolset.Core/Bind/Databases/AssignMediaCommand.cs b/src/WixToolset.Core/Bind/Databases/AssignMediaCommand.cs new file mode 100644 index 00000000..5e2650e9 --- /dev/null +++ b/src/WixToolset.Core/Bind/Databases/AssignMediaCommand.cs | |||
@@ -0,0 +1,314 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
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 | } | ||
diff --git a/src/WixToolset.Core/Bind/Databases/BindSummaryInfoCommand.cs b/src/WixToolset.Core/Bind/Databases/BindSummaryInfoCommand.cs new file mode 100644 index 00000000..95bd4cf0 --- /dev/null +++ b/src/WixToolset.Core/Bind/Databases/BindSummaryInfoCommand.cs | |||
@@ -0,0 +1,135 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
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 | } | ||
diff --git a/src/WixToolset.Core/Bind/Databases/CabinetBuilder.cs b/src/WixToolset.Core/Bind/Databases/CabinetBuilder.cs new file mode 100644 index 00000000..2de6ec25 --- /dev/null +++ b/src/WixToolset.Core/Bind/Databases/CabinetBuilder.cs | |||
@@ -0,0 +1,176 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
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 | |||
diff --git a/src/WixToolset.Core/Bind/Databases/CabinetWorkItem.cs b/src/WixToolset.Core/Bind/Databases/CabinetWorkItem.cs new file mode 100644 index 00000000..20241bc9 --- /dev/null +++ b/src/WixToolset.Core/Bind/Databases/CabinetWorkItem.cs | |||
@@ -0,0 +1,78 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
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 | } | ||
diff --git a/src/WixToolset.Core/Bind/Databases/ConfigurationCallback.cs b/src/WixToolset.Core/Bind/Databases/ConfigurationCallback.cs new file mode 100644 index 00000000..7cb18e0f --- /dev/null +++ b/src/WixToolset.Core/Bind/Databases/ConfigurationCallback.cs | |||
@@ -0,0 +1,91 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
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 | } | ||
diff --git a/src/WixToolset.Core/Bind/Databases/CopyTransformDataCommand.cs b/src/WixToolset.Core/Bind/Databases/CopyTransformDataCommand.cs new file mode 100644 index 00000000..af1ab3b0 --- /dev/null +++ b/src/WixToolset.Core/Bind/Databases/CopyTransformDataCommand.cs | |||
@@ -0,0 +1,606 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
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 | } | ||
diff --git a/src/WixToolset.Core/Bind/Databases/CreateCabinetsCommand.cs b/src/WixToolset.Core/Bind/Databases/CreateCabinetsCommand.cs new file mode 100644 index 00000000..35c8abb4 --- /dev/null +++ b/src/WixToolset.Core/Bind/Databases/CreateCabinetsCommand.cs | |||
@@ -0,0 +1,489 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
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 | } | ||
diff --git a/src/WixToolset.Core/Bind/Databases/CreateDeltaPatchesCommand.cs b/src/WixToolset.Core/Bind/Databases/CreateDeltaPatchesCommand.cs new file mode 100644 index 00000000..933a1ea8 --- /dev/null +++ b/src/WixToolset.Core/Bind/Databases/CreateDeltaPatchesCommand.cs | |||
@@ -0,0 +1,86 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
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 | } | ||
diff --git a/src/WixToolset.Core/Bind/Databases/CreateSpecialPropertiesCommand.cs b/src/WixToolset.Core/Bind/Databases/CreateSpecialPropertiesCommand.cs new file mode 100644 index 00000000..5db2768b --- /dev/null +++ b/src/WixToolset.Core/Bind/Databases/CreateSpecialPropertiesCommand.cs | |||
@@ -0,0 +1,68 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
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 | } | ||
diff --git a/src/WixToolset.Core/Bind/Databases/ExtractMergeModuleFilesCommand.cs b/src/WixToolset.Core/Bind/Databases/ExtractMergeModuleFilesCommand.cs new file mode 100644 index 00000000..bee1488b --- /dev/null +++ b/src/WixToolset.Core/Bind/Databases/ExtractMergeModuleFilesCommand.cs | |||
@@ -0,0 +1,225 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
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 | } | ||
diff --git a/src/WixToolset.Core/Bind/Databases/FileFacade.cs b/src/WixToolset.Core/Bind/Databases/FileFacade.cs new file mode 100644 index 00000000..37115c97 --- /dev/null +++ b/src/WixToolset.Core/Bind/Databases/FileFacade.cs | |||
@@ -0,0 +1,44 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
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 | } | ||
diff --git a/src/WixToolset.Core/Bind/Databases/GetFileFacadesCommand.cs b/src/WixToolset.Core/Bind/Databases/GetFileFacadesCommand.cs new file mode 100644 index 00000000..b6bcd3af --- /dev/null +++ b/src/WixToolset.Core/Bind/Databases/GetFileFacadesCommand.cs | |||
@@ -0,0 +1,148 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
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 | } | ||
diff --git a/src/WixToolset.Core/Bind/Databases/MergeModulesCommand.cs b/src/WixToolset.Core/Bind/Databases/MergeModulesCommand.cs new file mode 100644 index 00000000..035ef059 --- /dev/null +++ b/src/WixToolset.Core/Bind/Databases/MergeModulesCommand.cs | |||
@@ -0,0 +1,350 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
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 | } | ||
diff --git a/src/WixToolset.Core/Bind/Databases/ProcessUncompressedFilesCommand.cs b/src/WixToolset.Core/Bind/Databases/ProcessUncompressedFilesCommand.cs new file mode 100644 index 00000000..dd7b85b7 --- /dev/null +++ b/src/WixToolset.Core/Bind/Databases/ProcessUncompressedFilesCommand.cs | |||
@@ -0,0 +1,115 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
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 | } | ||
diff --git a/src/WixToolset.Core/Bind/Databases/UpdateControlTextCommand.cs b/src/WixToolset.Core/Bind/Databases/UpdateControlTextCommand.cs new file mode 100644 index 00000000..9e17ee02 --- /dev/null +++ b/src/WixToolset.Core/Bind/Databases/UpdateControlTextCommand.cs | |||
@@ -0,0 +1,80 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
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 | } | ||
diff --git a/src/WixToolset.Core/Bind/Databases/UpdateFileFacadesCommand.cs b/src/WixToolset.Core/Bind/Databases/UpdateFileFacadesCommand.cs new file mode 100644 index 00000000..36818afa --- /dev/null +++ b/src/WixToolset.Core/Bind/Databases/UpdateFileFacadesCommand.cs | |||
@@ -0,0 +1,532 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
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 | } | ||
diff --git a/src/WixToolset.Core/Bind/DelayedField.cs b/src/WixToolset.Core/Bind/DelayedField.cs new file mode 100644 index 00000000..181ac3e3 --- /dev/null +++ b/src/WixToolset.Core/Bind/DelayedField.cs | |||
@@ -0,0 +1,38 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
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 | } | ||
diff --git a/src/WixToolset.Core/Bind/ExtractEmbeddedFiles.cs b/src/WixToolset.Core/Bind/ExtractEmbeddedFiles.cs new file mode 100644 index 00000000..0ecd0096 --- /dev/null +++ b/src/WixToolset.Core/Bind/ExtractEmbeddedFiles.cs | |||
@@ -0,0 +1,83 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
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 | } | ||
diff --git a/src/WixToolset.Core/Bind/ExtractEmbeddedFilesCommand.cs b/src/WixToolset.Core/Bind/ExtractEmbeddedFilesCommand.cs new file mode 100644 index 00000000..68bfd8d7 --- /dev/null +++ b/src/WixToolset.Core/Bind/ExtractEmbeddedFilesCommand.cs | |||
@@ -0,0 +1,53 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
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 | } | ||
diff --git a/src/WixToolset.Core/Bind/FileTransfer.cs b/src/WixToolset.Core/Bind/FileTransfer.cs new file mode 100644 index 00000000..64bbc5f1 --- /dev/null +++ b/src/WixToolset.Core/Bind/FileTransfer.cs | |||
@@ -0,0 +1,113 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
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 | } | ||
diff --git a/src/WixToolset.Core/Bind/GenerateDatabaseCommand.cs b/src/WixToolset.Core/Bind/GenerateDatabaseCommand.cs new file mode 100644 index 00000000..fdf1ab32 --- /dev/null +++ b/src/WixToolset.Core/Bind/GenerateDatabaseCommand.cs | |||
@@ -0,0 +1,335 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
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 | } | ||
diff --git a/src/WixToolset.Core/Bind/ResolveDelayedFieldsCommand.cs b/src/WixToolset.Core/Bind/ResolveDelayedFieldsCommand.cs new file mode 100644 index 00000000..4ffe9e82 --- /dev/null +++ b/src/WixToolset.Core/Bind/ResolveDelayedFieldsCommand.cs | |||
@@ -0,0 +1,121 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
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 | } | ||
diff --git a/src/WixToolset.Core/Bind/ResolveFieldsCommand.cs b/src/WixToolset.Core/Bind/ResolveFieldsCommand.cs new file mode 100644 index 00000000..4caec9b4 --- /dev/null +++ b/src/WixToolset.Core/Bind/ResolveFieldsCommand.cs | |||
@@ -0,0 +1,215 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
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 | } | ||
diff --git a/src/WixToolset.Core/Bind/ResolvedDirectory.cs b/src/WixToolset.Core/Bind/ResolvedDirectory.cs new file mode 100644 index 00000000..6985f95d --- /dev/null +++ b/src/WixToolset.Core/Bind/ResolvedDirectory.cs | |||
@@ -0,0 +1,31 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
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 | } | ||
diff --git a/src/WixToolset.Core/Bind/TransferFilesCommand.cs b/src/WixToolset.Core/Bind/TransferFilesCommand.cs new file mode 100644 index 00000000..719b8b20 --- /dev/null +++ b/src/WixToolset.Core/Bind/TransferFilesCommand.cs | |||
@@ -0,0 +1,214 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
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 | } | ||
diff --git a/src/WixToolset.Core/Binder.cs b/src/WixToolset.Core/Binder.cs new file mode 100644 index 00000000..18ad2d62 --- /dev/null +++ b/src/WixToolset.Core/Binder.cs | |||
@@ -0,0 +1,686 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
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 | } | ||
diff --git a/src/WixToolset.Core/BinderCore.cs b/src/WixToolset.Core/BinderCore.cs new file mode 100644 index 00000000..0feae0b2 --- /dev/null +++ b/src/WixToolset.Core/BinderCore.cs | |||
@@ -0,0 +1,58 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
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 | } | ||
diff --git a/src/WixToolset.Core/BinderFileManager.cs b/src/WixToolset.Core/BinderFileManager.cs new file mode 100644 index 00000000..0da54002 --- /dev/null +++ b/src/WixToolset.Core/BinderFileManager.cs | |||
@@ -0,0 +1,370 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
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 | } | ||
diff --git a/src/WixToolset.Core/BinderFileManagerCore.cs b/src/WixToolset.Core/BinderFileManagerCore.cs new file mode 100644 index 00000000..6a5e1d5e --- /dev/null +++ b/src/WixToolset.Core/BinderFileManagerCore.cs | |||
@@ -0,0 +1,108 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
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 | } | ||
diff --git a/src/WixToolset.Core/CLR/Interop/CLRInterop.cs b/src/WixToolset.Core/CLR/Interop/CLRInterop.cs new file mode 100644 index 00000000..4157f23a --- /dev/null +++ b/src/WixToolset.Core/CLR/Interop/CLRInterop.cs | |||
@@ -0,0 +1,147 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
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 | } | ||
diff --git a/src/WixToolset.Core/Cab/CabinetFileInfo.cs b/src/WixToolset.Core/Cab/CabinetFileInfo.cs new file mode 100644 index 00000000..849bb3bb --- /dev/null +++ b/src/WixToolset.Core/Cab/CabinetFileInfo.cs | |||
@@ -0,0 +1,64 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
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 | } | ||
diff --git a/src/WixToolset.Core/Cab/Interop/CabInterop.cs b/src/WixToolset.Core/Cab/Interop/CabInterop.cs new file mode 100644 index 00000000..6c1ae2c1 --- /dev/null +++ b/src/WixToolset.Core/Cab/Interop/CabInterop.cs | |||
@@ -0,0 +1,316 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
3 | #if false | ||
4 | |||
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 | ||
diff --git a/src/WixToolset.Core/Cab/WixCreateCab.cs b/src/WixToolset.Core/Cab/WixCreateCab.cs new file mode 100644 index 00000000..8f985a43 --- /dev/null +++ b/src/WixToolset.Core/Cab/WixCreateCab.cs | |||
@@ -0,0 +1,249 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
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 | } | ||
diff --git a/src/WixToolset.Core/Cab/WixEnumerateCab.cs b/src/WixToolset.Core/Cab/WixEnumerateCab.cs new file mode 100644 index 00000000..017eeffb --- /dev/null +++ b/src/WixToolset.Core/Cab/WixEnumerateCab.cs | |||
@@ -0,0 +1,89 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
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 | } | ||
diff --git a/src/WixToolset.Core/Cab/WixExtractCab.cs b/src/WixToolset.Core/Cab/WixExtractCab.cs new file mode 100644 index 00000000..debdaf15 --- /dev/null +++ b/src/WixToolset.Core/Cab/WixExtractCab.cs | |||
@@ -0,0 +1,76 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
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 | } | ||
diff --git a/src/WixToolset.Core/CommandLine/BuildCommand.cs b/src/WixToolset.Core/CommandLine/BuildCommand.cs new file mode 100644 index 00000000..ffb48305 --- /dev/null +++ b/src/WixToolset.Core/CommandLine/BuildCommand.cs | |||
@@ -0,0 +1,100 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
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 | } | ||
diff --git a/src/WixToolset.Core/CommandLine/CommandLine.cs b/src/WixToolset.Core/CommandLine/CommandLine.cs new file mode 100644 index 00000000..440ae9ef --- /dev/null +++ b/src/WixToolset.Core/CommandLine/CommandLine.cs | |||
@@ -0,0 +1,592 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
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 | } | ||
diff --git a/src/WixToolset.Core/CommandLine/CommandLineHelper.cs b/src/WixToolset.Core/CommandLine/CommandLineHelper.cs new file mode 100644 index 00000000..86724603 --- /dev/null +++ b/src/WixToolset.Core/CommandLine/CommandLineHelper.cs | |||
@@ -0,0 +1,255 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
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 | } | ||
diff --git a/src/WixToolset.Core/CommandLine/CommandLineOption.cs b/src/WixToolset.Core/CommandLine/CommandLineOption.cs new file mode 100644 index 00000000..85a654bf --- /dev/null +++ b/src/WixToolset.Core/CommandLine/CommandLineOption.cs | |||
@@ -0,0 +1,27 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
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 | } | ||
diff --git a/src/WixToolset.Core/CommandLine/CommandLineResponseFile.cs b/src/WixToolset.Core/CommandLine/CommandLineResponseFile.cs new file mode 100644 index 00000000..f27296b7 --- /dev/null +++ b/src/WixToolset.Core/CommandLine/CommandLineResponseFile.cs | |||
@@ -0,0 +1,137 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
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 | } | ||
diff --git a/src/WixToolset.Core/CommandLine/CompileCommand.cs b/src/WixToolset.Core/CommandLine/CompileCommand.cs new file mode 100644 index 00000000..17847b57 --- /dev/null +++ b/src/WixToolset.Core/CommandLine/CompileCommand.cs | |||
@@ -0,0 +1,39 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
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 | } | ||
diff --git a/src/WixToolset.Core/CommandLine/HelpCommand.cs b/src/WixToolset.Core/CommandLine/HelpCommand.cs new file mode 100644 index 00000000..1c101781 --- /dev/null +++ b/src/WixToolset.Core/CommandLine/HelpCommand.cs | |||
@@ -0,0 +1,30 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
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 | } | ||
diff --git a/src/WixToolset.Core/CommandLine/ICommand.cs b/src/WixToolset.Core/CommandLine/ICommand.cs new file mode 100644 index 00000000..41abbbdc --- /dev/null +++ b/src/WixToolset.Core/CommandLine/ICommand.cs | |||
@@ -0,0 +1,9 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
3 | namespace WixToolset.Core | ||
4 | { | ||
5 | public interface ICommand | ||
6 | { | ||
7 | int Execute(); | ||
8 | } | ||
9 | } | ||
diff --git a/src/WixToolset.Core/CommandLine/VersionCommand.cs b/src/WixToolset.Core/CommandLine/VersionCommand.cs new file mode 100644 index 00000000..a1980a2b --- /dev/null +++ b/src/WixToolset.Core/CommandLine/VersionCommand.cs | |||
@@ -0,0 +1,17 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
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 | } | ||
diff --git a/src/WixToolset.Core/Common.cs b/src/WixToolset.Core/Common.cs new file mode 100644 index 00000000..0404de5e --- /dev/null +++ b/src/WixToolset.Core/Common.cs | |||
@@ -0,0 +1,665 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
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 | } | ||
diff --git a/src/WixToolset.Core/Compiler.cs b/src/WixToolset.Core/Compiler.cs new file mode 100644 index 00000000..00618152 --- /dev/null +++ b/src/WixToolset.Core/Compiler.cs | |||
@@ -0,0 +1,20667 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
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; | ||
5000 | case "FeatureRef": | ||
5001 | this.ParseFeatureRefElement(child, ComplexReferenceParentType.Feature, id); | ||
5002 | break; | ||
5003 | case "MergeRef": | ||
5004 | this.ParseMergeRefElement(child, ComplexReferenceParentType.Feature, id); | ||
5005 | break; | ||
5006 | default: | ||
5007 | this.core.UnexpectedElement(node, child); | ||
5008 | break; | ||
5009 | } | ||
5010 | } | ||
5011 | else | ||
5012 | { | ||
5013 | this.core.ParseExtensionElement(node, child); | ||
5014 | } | ||
5015 | } | ||
5016 | |||
5017 | if (!this.core.EncounteredError) | ||
5018 | { | ||
5019 | if (ComplexReferenceParentType.Unknown != parentType && YesNoType.Yes != ignoreParent) | ||
5020 | { | ||
5021 | this.core.CreateComplexReference(sourceLineNumbers, parentType, parentId, null, ComplexReferenceChildType.Feature, id, false); | ||
5022 | } | ||
5023 | } | ||
5024 | } | ||
5025 | |||
5026 | /// <summary> | ||
5027 | /// Parses a feature group element. | ||
5028 | /// </summary> | ||
5029 | /// <param name="node">Element to parse.</param> | ||
5030 | [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1800:DoNotCastUnnecessarily")] | ||
5031 | private void ParseFeatureGroupElement(XElement node, ComplexReferenceParentType parentType, string parentId) | ||
5032 | { | ||
5033 | SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
5034 | Identifier id = null; | ||
5035 | |||
5036 | foreach (XAttribute attrib in node.Attributes()) | ||
5037 | { | ||
5038 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
5039 | { | ||
5040 | switch (attrib.Name.LocalName) | ||
5041 | { | ||
5042 | case "Id": | ||
5043 | id = this.core.GetAttributeIdentifier(sourceLineNumbers, attrib); | ||
5044 | break; | ||
5045 | default: | ||
5046 | this.core.UnexpectedAttribute(node, attrib); | ||
5047 | break; | ||
5048 | } | ||
5049 | } | ||
5050 | else | ||
5051 | { | ||
5052 | this.core.ParseExtensionAttribute(node, attrib); | ||
5053 | } | ||
5054 | } | ||
5055 | |||
5056 | if (null == id) | ||
5057 | { | ||
5058 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); | ||
5059 | id = Identifier.Invalid; | ||
5060 | } | ||
5061 | |||
5062 | int lastDisplay = 0; | ||
5063 | foreach (XElement child in node.Elements()) | ||
5064 | { | ||
5065 | if (CompilerCore.WixNamespace == child.Name.Namespace) | ||
5066 | { | ||
5067 | switch (child.Name.LocalName) | ||
5068 | { | ||
5069 | case "ComponentGroupRef": | ||
5070 | this.ParseComponentGroupRefElement(child, ComplexReferenceParentType.FeatureGroup, id.Id, null); | ||
5071 | break; | ||
5072 | case "ComponentRef": | ||
5073 | this.ParseComponentRefElement(child, ComplexReferenceParentType.FeatureGroup, id.Id, null); | ||
5074 | break; | ||
5075 | case "Component": | ||
5076 | this.ParseComponentElement(child, ComplexReferenceParentType.FeatureGroup, id.Id, null, CompilerConstants.IntegerNotSet, null, null); | ||
5077 | break; | ||
5078 | case "Feature": | ||
5079 | this.ParseFeatureElement(child, ComplexReferenceParentType.FeatureGroup, id.Id, ref lastDisplay); | ||
5080 | break; | ||
5081 | case "FeatureGroupRef": | ||
5082 | this.ParseFeatureGroupRefElement(child, ComplexReferenceParentType.FeatureGroup, id.Id); | ||
5083 | break; | ||
5084 | case "FeatureRef": | ||
5085 | this.ParseFeatureRefElement(child, ComplexReferenceParentType.FeatureGroup, id.Id); | ||
5086 | break; | ||
5087 | case "MergeRef": | ||
5088 | this.ParseMergeRefElement(child, ComplexReferenceParentType.FeatureGroup, id.Id); | ||
5089 | break; | ||
5090 | default: | ||
5091 | this.core.UnexpectedElement(node, child); | ||
5092 | break; | ||
5093 | } | ||
5094 | } | ||
5095 | else | ||
5096 | { | ||
5097 | this.core.ParseExtensionElement(node, child); | ||
5098 | } | ||
5099 | } | ||
5100 | |||
5101 | if (!this.core.EncounteredError) | ||
5102 | { | ||
5103 | Row row = this.core.CreateRow(sourceLineNumbers, "WixFeatureGroup", id); | ||
5104 | |||
5105 | //Add this FeatureGroup and its parent in WixGroup. | ||
5106 | this.core.CreateWixGroupRow(sourceLineNumbers, parentType, parentId, ComplexReferenceChildType.FeatureGroup, id.Id); | ||
5107 | } | ||
5108 | } | ||
5109 | |||
5110 | /// <summary> | ||
5111 | /// Parses a feature group reference element. | ||
5112 | /// </summary> | ||
5113 | /// <param name="node">Element to parse.</param> | ||
5114 | /// <param name="parentType">The type of parent.</param> | ||
5115 | /// <param name="parentId">Identifier of parent element.</param> | ||
5116 | private void ParseFeatureGroupRefElement(XElement node, ComplexReferenceParentType parentType, string parentId) | ||
5117 | { | ||
5118 | Debug.Assert(ComplexReferenceParentType.Feature == parentType || ComplexReferenceParentType.FeatureGroup == parentType || ComplexReferenceParentType.ComponentGroup == parentType || ComplexReferenceParentType.Product == parentType); | ||
5119 | |||
5120 | SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
5121 | string id = null; | ||
5122 | YesNoType ignoreParent = YesNoType.NotSet; | ||
5123 | YesNoType primary = YesNoType.NotSet; | ||
5124 | |||
5125 | foreach (XAttribute attrib in node.Attributes()) | ||
5126 | { | ||
5127 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
5128 | { | ||
5129 | switch (attrib.Name.LocalName) | ||
5130 | { | ||
5131 | case "Id": | ||
5132 | id = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
5133 | this.core.CreateSimpleReference(sourceLineNumbers, "WixFeatureGroup", id); | ||
5134 | break; | ||
5135 | case "IgnoreParent": | ||
5136 | ignoreParent = this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
5137 | break; | ||
5138 | case "Primary": | ||
5139 | primary = this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
5140 | break; | ||
5141 | default: | ||
5142 | this.core.UnexpectedAttribute(node, attrib); | ||
5143 | break; | ||
5144 | } | ||
5145 | } | ||
5146 | else | ||
5147 | { | ||
5148 | this.core.ParseExtensionAttribute(node, attrib); | ||
5149 | } | ||
5150 | } | ||
5151 | |||
5152 | if (null == id) | ||
5153 | { | ||
5154 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); | ||
5155 | } | ||
5156 | |||
5157 | this.core.ParseForExtensionElements(node); | ||
5158 | |||
5159 | if (!this.core.EncounteredError) | ||
5160 | { | ||
5161 | if (YesNoType.Yes != ignoreParent) | ||
5162 | { | ||
5163 | this.core.CreateComplexReference(sourceLineNumbers, parentType, parentId, null, ComplexReferenceChildType.FeatureGroup, id, (YesNoType.Yes == primary)); | ||
5164 | } | ||
5165 | } | ||
5166 | } | ||
5167 | |||
5168 | /// <summary> | ||
5169 | /// Parses an environment element. | ||
5170 | /// </summary> | ||
5171 | /// <param name="node">Element to parse.</param> | ||
5172 | /// <param name="componentId">Identifier of parent component.</param> | ||
5173 | private void ParseEnvironmentElement(XElement node, string componentId) | ||
5174 | { | ||
5175 | SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
5176 | Identifier id = null; | ||
5177 | string action = null; | ||
5178 | string name = null; | ||
5179 | Wix.Environment.PartType partType = Wix.Environment.PartType.NotSet; | ||
5180 | string part = null; | ||
5181 | bool permanent = false; | ||
5182 | string separator = ";"; // default to ';' | ||
5183 | bool system = false; | ||
5184 | string text = null; | ||
5185 | string uninstall = "-"; // default to remove at uninstall | ||
5186 | |||
5187 | foreach (XAttribute attrib in node.Attributes()) | ||
5188 | { | ||
5189 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
5190 | { | ||
5191 | switch (attrib.Name.LocalName) | ||
5192 | { | ||
5193 | case "Id": | ||
5194 | id = this.core.GetAttributeIdentifier(sourceLineNumbers, attrib); | ||
5195 | break; | ||
5196 | case "Action": | ||
5197 | string value = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
5198 | if (0 < value.Length) | ||
5199 | { | ||
5200 | Wix.Environment.ActionType actionType = Wix.Environment.ParseActionType(value); | ||
5201 | switch (actionType) | ||
5202 | { | ||
5203 | case Wix.Environment.ActionType.create: | ||
5204 | action = "+"; | ||
5205 | break; | ||
5206 | case Wix.Environment.ActionType.set: | ||
5207 | action = "="; | ||
5208 | break; | ||
5209 | case Wix.Environment.ActionType.remove: | ||
5210 | action = "!"; | ||
5211 | break; | ||
5212 | default: | ||
5213 | this.core.OnMessage(WixErrors.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, value, "create", "set", "remove")); | ||
5214 | break; | ||
5215 | } | ||
5216 | } | ||
5217 | break; | ||
5218 | case "Name": | ||
5219 | name = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
5220 | break; | ||
5221 | case "Part": | ||
5222 | part = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
5223 | if (!Wix.Environment.TryParsePartType(part, out partType)) | ||
5224 | { | ||
5225 | this.core.OnMessage(WixErrors.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, "Part", part, "all", "first", "last")); | ||
5226 | } | ||
5227 | break; | ||
5228 | case "Permanent": | ||
5229 | permanent = YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
5230 | break; | ||
5231 | case "Separator": | ||
5232 | separator = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
5233 | break; | ||
5234 | case "System": | ||
5235 | system = YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
5236 | break; | ||
5237 | case "Value": | ||
5238 | text = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
5239 | break; | ||
5240 | default: | ||
5241 | this.core.UnexpectedAttribute(node, attrib); | ||
5242 | break; | ||
5243 | } | ||
5244 | } | ||
5245 | else | ||
5246 | { | ||
5247 | this.core.ParseExtensionAttribute(node, attrib); | ||
5248 | } | ||
5249 | } | ||
5250 | |||
5251 | if (null == id) | ||
5252 | { | ||
5253 | id = this.core.CreateIdentifier("env", action, name, part, system.ToString()); | ||
5254 | } | ||
5255 | |||
5256 | if (null == name) | ||
5257 | { | ||
5258 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name")); | ||
5259 | } | ||
5260 | |||
5261 | if (Wix.Environment.PartType.NotSet != partType) | ||
5262 | { | ||
5263 | if ("+" == action) | ||
5264 | { | ||
5265 | this.core.OnMessage(WixErrors.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Part", "Action", "create")); | ||
5266 | } | ||
5267 | |||
5268 | switch (partType) | ||
5269 | { | ||
5270 | case Wix.Environment.PartType.all: | ||
5271 | break; | ||
5272 | case Wix.Environment.PartType.first: | ||
5273 | text = String.Concat(text, separator, "[~]"); | ||
5274 | break; | ||
5275 | case Wix.Environment.PartType.last: | ||
5276 | text = String.Concat("[~]", separator, text); | ||
5277 | break; | ||
5278 | } | ||
5279 | } | ||
5280 | |||
5281 | if (permanent) | ||
5282 | { | ||
5283 | uninstall = null; | ||
5284 | } | ||
5285 | |||
5286 | this.core.ParseForExtensionElements(node); | ||
5287 | |||
5288 | if (!this.core.EncounteredError) | ||
5289 | { | ||
5290 | Row row = this.core.CreateRow(sourceLineNumbers, "Environment", id); | ||
5291 | row[1] = String.Concat(action, uninstall, system ? "*" : String.Empty, name); | ||
5292 | row[2] = text; | ||
5293 | row[3] = componentId; | ||
5294 | } | ||
5295 | } | ||
5296 | |||
5297 | /// <summary> | ||
5298 | /// Parses an error element. | ||
5299 | /// </summary> | ||
5300 | /// <param name="node">Element to parse.</param> | ||
5301 | private void ParseErrorElement(XElement node) | ||
5302 | { | ||
5303 | SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
5304 | int id = CompilerConstants.IntegerNotSet; | ||
5305 | |||
5306 | foreach (XAttribute attrib in node.Attributes()) | ||
5307 | { | ||
5308 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
5309 | { | ||
5310 | switch (attrib.Name.LocalName) | ||
5311 | { | ||
5312 | case "Id": | ||
5313 | id = this.core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, short.MaxValue); | ||
5314 | break; | ||
5315 | default: | ||
5316 | this.core.UnexpectedAttribute(node, attrib); | ||
5317 | break; | ||
5318 | } | ||
5319 | } | ||
5320 | else | ||
5321 | { | ||
5322 | this.core.ParseExtensionAttribute(node, attrib); | ||
5323 | } | ||
5324 | } | ||
5325 | |||
5326 | if (CompilerConstants.IntegerNotSet == id) | ||
5327 | { | ||
5328 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); | ||
5329 | id = CompilerConstants.IllegalInteger; | ||
5330 | } | ||
5331 | |||
5332 | this.core.ParseForExtensionElements(node); | ||
5333 | |||
5334 | if (!this.core.EncounteredError) | ||
5335 | { | ||
5336 | Row row = this.core.CreateRow(sourceLineNumbers, "Error"); | ||
5337 | row[0] = id; | ||
5338 | row[1] = Common.GetInnerText(node); // TODO: * | ||
5339 | } | ||
5340 | } | ||
5341 | |||
5342 | /// <summary> | ||
5343 | /// Parses an extension element. | ||
5344 | /// </summary> | ||
5345 | /// <param name="node">Element to parse.</param> | ||
5346 | /// <param name="componentId">Identifier of parent component.</param> | ||
5347 | /// <param name="advertise">Flag if this extension is advertised.</param> | ||
5348 | /// <param name="progId">ProgId for extension.</param> | ||
5349 | private void ParseExtensionElement(XElement node, string componentId, YesNoType advertise, string progId) | ||
5350 | { | ||
5351 | SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
5352 | string extension = null; | ||
5353 | string mime = null; | ||
5354 | |||
5355 | foreach (XAttribute attrib in node.Attributes()) | ||
5356 | { | ||
5357 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
5358 | { | ||
5359 | switch (attrib.Name.LocalName) | ||
5360 | { | ||
5361 | case "Id": | ||
5362 | extension = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
5363 | break; | ||
5364 | case "Advertise": | ||
5365 | YesNoType extensionAdvertise = this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
5366 | if ((YesNoType.No == advertise && YesNoType.Yes == extensionAdvertise) || (YesNoType.Yes == advertise && YesNoType.No == extensionAdvertise)) | ||
5367 | { | ||
5368 | this.core.OnMessage(WixErrors.AdvertiseStateMustMatch(sourceLineNumbers, extensionAdvertise.ToString(), advertise.ToString())); | ||
5369 | } | ||
5370 | advertise = extensionAdvertise; | ||
5371 | break; | ||
5372 | case "ContentType": | ||
5373 | mime = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
5374 | break; | ||
5375 | default: | ||
5376 | this.core.UnexpectedAttribute(node, attrib); | ||
5377 | break; | ||
5378 | } | ||
5379 | } | ||
5380 | else | ||
5381 | { | ||
5382 | Dictionary<string, string> context = new Dictionary<string, string>() { { "ProgId", progId }, { "ComponentId", componentId } }; | ||
5383 | this.core.ParseExtensionAttribute(node, attrib, context); | ||
5384 | } | ||
5385 | } | ||
5386 | |||
5387 | if (YesNoType.NotSet == advertise) | ||
5388 | { | ||
5389 | advertise = YesNoType.No; | ||
5390 | } | ||
5391 | |||
5392 | foreach (XElement child in node.Elements()) | ||
5393 | { | ||
5394 | if (CompilerCore.WixNamespace == child.Name.Namespace) | ||
5395 | { | ||
5396 | switch (child.Name.LocalName) | ||
5397 | { | ||
5398 | case "Verb": | ||
5399 | this.ParseVerbElement(child, extension, progId, componentId, advertise); | ||
5400 | break; | ||
5401 | case "MIME": | ||
5402 | string newMime = this.ParseMIMEElement(child, extension, componentId, advertise); | ||
5403 | if (null != newMime && null == mime) | ||
5404 | { | ||
5405 | mime = newMime; | ||
5406 | } | ||
5407 | break; | ||
5408 | default: | ||
5409 | this.core.UnexpectedElement(node, child); | ||
5410 | break; | ||
5411 | } | ||
5412 | } | ||
5413 | else | ||
5414 | { | ||
5415 | this.core.ParseExtensionElement(node, child); | ||
5416 | } | ||
5417 | } | ||
5418 | |||
5419 | |||
5420 | if (YesNoType.Yes == advertise) | ||
5421 | { | ||
5422 | if (!this.core.EncounteredError) | ||
5423 | { | ||
5424 | Row row = this.core.CreateRow(sourceLineNumbers, "Extension"); | ||
5425 | row[0] = extension; | ||
5426 | row[1] = componentId; | ||
5427 | row[2] = progId; | ||
5428 | row[3] = mime; | ||
5429 | row[4] = Guid.Empty.ToString("B"); | ||
5430 | |||
5431 | this.core.EnsureTable(sourceLineNumbers, "Verb"); | ||
5432 | } | ||
5433 | } | ||
5434 | else if (YesNoType.No == advertise) | ||
5435 | { | ||
5436 | this.core.CreateRegistryRow(sourceLineNumbers, MsiInterop.MsidbRegistryRootClassesRoot, String.Concat(".", extension), String.Empty, progId, componentId); // Extension | ||
5437 | if (null != mime) | ||
5438 | { | ||
5439 | this.core.CreateRegistryRow(sourceLineNumbers, MsiInterop.MsidbRegistryRootClassesRoot, String.Concat(".", extension), "Content Type", mime, componentId); // Extension's MIME ContentType | ||
5440 | } | ||
5441 | } | ||
5442 | } | ||
5443 | |||
5444 | |||
5445 | /// <summary> | ||
5446 | /// Parses a file element. | ||
5447 | /// </summary> | ||
5448 | /// <param name="node">File element to parse.</param> | ||
5449 | /// <param name="componentId">Parent's component id.</param> | ||
5450 | /// <param name="directoryId">Ancestor's directory id.</param> | ||
5451 | /// <param name="diskId">Disk id inherited from parent component.</param> | ||
5452 | /// <param name="sourcePath">Default source path of parent directory.</param> | ||
5453 | /// <param name="possibleKeyPath">This will be set with the possible keyPath for the parent component.</param> | ||
5454 | /// <param name="win64Component">true if the component is 64-bit.</param> | ||
5455 | /// <returns>Yes if this element was marked as the parent component's key path, No if explicitly marked as not being a key path, or NotSet otherwise.</returns> | ||
5456 | [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1800:DoNotCastUnnecessarily")] | ||
5457 | private YesNoType ParseFileElement(XElement node, string componentId, string directoryId, int diskId, string sourcePath, out string possibleKeyPath, bool win64Component, string componentGuid) | ||
5458 | { | ||
5459 | SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
5460 | Identifier id = null; | ||
5461 | FileAssemblyType assemblyType = FileAssemblyType.NotAnAssembly; | ||
5462 | string assemblyApplication = null; | ||
5463 | string assemblyManifest = null; | ||
5464 | string bindPath = null; | ||
5465 | int bits = MsiInterop.MsidbFileAttributesVital; // assume all files are vital. | ||
5466 | string companionFile = null; | ||
5467 | string defaultLanguage = null; | ||
5468 | int defaultSize = 0; | ||
5469 | string defaultVersion = null; | ||
5470 | string fontTitle = null; | ||
5471 | bool generatedShortFileName = false; | ||
5472 | YesNoType keyPath = YesNoType.NotSet; | ||
5473 | string name = null; | ||
5474 | int patchGroup = CompilerConstants.IntegerNotSet; | ||
5475 | bool patchIgnore = false; | ||
5476 | bool patchIncludeWholeFile = false; | ||
5477 | bool patchAllowIgnoreOnError = false; | ||
5478 | |||
5479 | string ignoreLengths = null; | ||
5480 | string ignoreOffsets = null; | ||
5481 | string protectLengths = null; | ||
5482 | string protectOffsets = null; | ||
5483 | string symbols = null; | ||
5484 | |||
5485 | string procArch = null; | ||
5486 | int selfRegCost = CompilerConstants.IntegerNotSet; | ||
5487 | string shortName = null; | ||
5488 | string source = sourcePath; // assume we'll use the parents as the source for this file | ||
5489 | bool sourceSet = false; | ||
5490 | |||
5491 | foreach (XAttribute attrib in node.Attributes()) | ||
5492 | { | ||
5493 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
5494 | { | ||
5495 | switch (attrib.Name.LocalName) | ||
5496 | { | ||
5497 | case "Id": | ||
5498 | id = this.core.GetAttributeIdentifier(sourceLineNumbers, attrib); | ||
5499 | break; | ||
5500 | case "Assembly": | ||
5501 | string assemblyValue = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
5502 | if (0 < assemblyValue.Length) | ||
5503 | { | ||
5504 | Wix.File.AssemblyType parsedAssemblyType = Wix.File.ParseAssemblyType(assemblyValue); | ||
5505 | switch (parsedAssemblyType) | ||
5506 | { | ||
5507 | case Wix.File.AssemblyType.net: | ||
5508 | assemblyType = FileAssemblyType.DotNetAssembly; | ||
5509 | break; | ||
5510 | case Wix.File.AssemblyType.no: | ||
5511 | assemblyType = FileAssemblyType.NotAnAssembly; | ||
5512 | break; | ||
5513 | case Wix.File.AssemblyType.win32: | ||
5514 | assemblyType = FileAssemblyType.Win32Assembly; | ||
5515 | break; | ||
5516 | default: | ||
5517 | this.core.OnMessage(WixErrors.IllegalAttributeValue(sourceLineNumbers, "File", "Assembly", assemblyValue, "no", "win32", ".net")); | ||
5518 | break; | ||
5519 | } | ||
5520 | } | ||
5521 | break; | ||
5522 | case "AssemblyApplication": | ||
5523 | assemblyApplication = this.core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); | ||
5524 | this.core.CreateSimpleReference(sourceLineNumbers, "File", assemblyApplication); | ||
5525 | break; | ||
5526 | case "AssemblyManifest": | ||
5527 | assemblyManifest = this.core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); | ||
5528 | this.core.CreateSimpleReference(sourceLineNumbers, "File", assemblyManifest); | ||
5529 | break; | ||
5530 | case "BindPath": | ||
5531 | bindPath = this.core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty); | ||
5532 | break; | ||
5533 | case "Checksum": | ||
5534 | if (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) | ||
5535 | { | ||
5536 | bits |= MsiInterop.MsidbFileAttributesChecksum; | ||
5537 | } | ||
5538 | break; | ||
5539 | case "CompanionFile": | ||
5540 | companionFile = this.core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); | ||
5541 | this.core.CreateSimpleReference(sourceLineNumbers, "File", companionFile); | ||
5542 | break; | ||
5543 | case "Compressed": | ||
5544 | YesNoDefaultType compressed = this.core.GetAttributeYesNoDefaultValue(sourceLineNumbers, attrib); | ||
5545 | if (YesNoDefaultType.Yes == compressed) | ||
5546 | { | ||
5547 | bits |= MsiInterop.MsidbFileAttributesCompressed; | ||
5548 | } | ||
5549 | else if (YesNoDefaultType.No == compressed) | ||
5550 | { | ||
5551 | bits |= MsiInterop.MsidbFileAttributesNoncompressed; | ||
5552 | } | ||
5553 | break; | ||
5554 | case "DefaultLanguage": | ||
5555 | defaultLanguage = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
5556 | break; | ||
5557 | case "DefaultSize": | ||
5558 | defaultSize = this.core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, int.MaxValue); | ||
5559 | break; | ||
5560 | case "DefaultVersion": | ||
5561 | defaultVersion = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
5562 | break; | ||
5563 | case "DiskId": | ||
5564 | diskId = this.core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 1, short.MaxValue); | ||
5565 | break; | ||
5566 | case "FontTitle": | ||
5567 | fontTitle = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
5568 | break; | ||
5569 | case "Hidden": | ||
5570 | if (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) | ||
5571 | { | ||
5572 | bits |= MsiInterop.MsidbFileAttributesHidden; | ||
5573 | } | ||
5574 | break; | ||
5575 | case "KeyPath": | ||
5576 | keyPath = this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
5577 | break; | ||
5578 | case "Name": | ||
5579 | name = this.core.GetAttributeLongFilename(sourceLineNumbers, attrib, false); | ||
5580 | break; | ||
5581 | case "PatchGroup": | ||
5582 | patchGroup = this.core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 1, int.MaxValue); | ||
5583 | break; | ||
5584 | case "PatchIgnore": | ||
5585 | patchIgnore = YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
5586 | break; | ||
5587 | case "PatchWholeFile": | ||
5588 | patchIncludeWholeFile = YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
5589 | break; | ||
5590 | case "PatchAllowIgnoreOnError": | ||
5591 | patchAllowIgnoreOnError = YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
5592 | break; | ||
5593 | case "ProcessorArchitecture": | ||
5594 | string procArchValue = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
5595 | if (0 < procArchValue.Length) | ||
5596 | { | ||
5597 | Wix.File.ProcessorArchitectureType procArchType = Wix.File.ParseProcessorArchitectureType(procArchValue); | ||
5598 | switch (procArchType) | ||
5599 | { | ||
5600 | case Wix.File.ProcessorArchitectureType.msil: | ||
5601 | procArch = "MSIL"; | ||
5602 | break; | ||
5603 | case Wix.File.ProcessorArchitectureType.x86: | ||
5604 | procArch = "x86"; | ||
5605 | break; | ||
5606 | case Wix.File.ProcessorArchitectureType.x64: | ||
5607 | procArch = "amd64"; | ||
5608 | break; | ||
5609 | case Wix.File.ProcessorArchitectureType.ia64: | ||
5610 | procArch = "ia64"; | ||
5611 | break; | ||
5612 | default: | ||
5613 | this.core.OnMessage(WixErrors.IllegalAttributeValue(sourceLineNumbers, "File", "ProcessorArchitecture", procArchValue, "msil", "x86", "x64", "ia64")); | ||
5614 | break; | ||
5615 | } | ||
5616 | } | ||
5617 | break; | ||
5618 | case "ReadOnly": | ||
5619 | if (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) | ||
5620 | { | ||
5621 | bits |= MsiInterop.MsidbFileAttributesReadOnly; | ||
5622 | } | ||
5623 | break; | ||
5624 | case "SelfRegCost": | ||
5625 | selfRegCost = this.core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, short.MaxValue); | ||
5626 | break; | ||
5627 | case "ShortName": | ||
5628 | shortName = this.core.GetAttributeShortFilename(sourceLineNumbers, attrib, false); | ||
5629 | break; | ||
5630 | case "Source": | ||
5631 | source = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
5632 | sourceSet = true; | ||
5633 | break; | ||
5634 | case "System": | ||
5635 | if (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) | ||
5636 | { | ||
5637 | bits |= MsiInterop.MsidbFileAttributesSystem; | ||
5638 | } | ||
5639 | break; | ||
5640 | case "TrueType": | ||
5641 | if (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) | ||
5642 | { | ||
5643 | fontTitle = String.Empty; | ||
5644 | } | ||
5645 | break; | ||
5646 | case "Vital": | ||
5647 | YesNoType isVital = this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
5648 | if (YesNoType.Yes == isVital) | ||
5649 | { | ||
5650 | bits |= MsiInterop.MsidbFileAttributesVital; | ||
5651 | } | ||
5652 | else if (YesNoType.No == isVital) | ||
5653 | { | ||
5654 | bits &= ~MsiInterop.MsidbFileAttributesVital; | ||
5655 | } | ||
5656 | break; | ||
5657 | default: | ||
5658 | this.core.UnexpectedAttribute(node, attrib); | ||
5659 | break; | ||
5660 | } | ||
5661 | } | ||
5662 | else | ||
5663 | { | ||
5664 | this.core.ParseExtensionAttribute(node, attrib); | ||
5665 | } | ||
5666 | } | ||
5667 | |||
5668 | if (null != companionFile) | ||
5669 | { | ||
5670 | // the companion file cannot be the key path of a component | ||
5671 | if (YesNoType.Yes == keyPath) | ||
5672 | { | ||
5673 | this.core.OnMessage(WixErrors.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "CompanionFile", "KeyPath", "yes")); | ||
5674 | } | ||
5675 | } | ||
5676 | |||
5677 | if (sourceSet && !source.EndsWith(Path.DirectorySeparatorChar.ToString(), StringComparison.Ordinal) && null == name) | ||
5678 | { | ||
5679 | name = Path.GetFileName(source); | ||
5680 | if (!this.core.IsValidLongFilename(name, false)) | ||
5681 | { | ||
5682 | this.core.OnMessage(WixErrors.IllegalLongFilename(sourceLineNumbers, node.Name.LocalName, "Source", name)); | ||
5683 | } | ||
5684 | } | ||
5685 | |||
5686 | // generate a short file name | ||
5687 | if (null == shortName && (null != name && !this.core.IsValidShortFilename(name, false))) | ||
5688 | { | ||
5689 | shortName = this.core.CreateShortName(name, true, false, node.Name.LocalName, directoryId); | ||
5690 | generatedShortFileName = true; | ||
5691 | } | ||
5692 | |||
5693 | if (null == id) | ||
5694 | { | ||
5695 | id = this.core.CreateIdentifier("fil", directoryId, name ?? shortName); | ||
5696 | } | ||
5697 | |||
5698 | if (!this.compilingModule && CompilerConstants.IntegerNotSet == diskId) | ||
5699 | { | ||
5700 | diskId = 1; // default to first Media | ||
5701 | } | ||
5702 | |||
5703 | if (null != defaultVersion && null != companionFile) | ||
5704 | { | ||
5705 | this.core.OnMessage(WixErrors.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "DefaultVersion", "CompanionFile", companionFile)); | ||
5706 | } | ||
5707 | |||
5708 | if (FileAssemblyType.NotAnAssembly == assemblyType) | ||
5709 | { | ||
5710 | if (null != assemblyManifest) | ||
5711 | { | ||
5712 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Assembly", "AssemblyManifest")); | ||
5713 | } | ||
5714 | |||
5715 | if (null != assemblyApplication) | ||
5716 | { | ||
5717 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Assembly", "AssemblyApplication")); | ||
5718 | } | ||
5719 | } | ||
5720 | else | ||
5721 | { | ||
5722 | if (FileAssemblyType.Win32Assembly == assemblyType && null == assemblyManifest) | ||
5723 | { | ||
5724 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "AssemblyManifest", "Assembly", "win32")); | ||
5725 | } | ||
5726 | |||
5727 | // allow "*" guid components to omit explicit KeyPath as they can have only one file and therefore this file is the keypath | ||
5728 | if (YesNoType.Yes != keyPath && "*" != componentGuid) | ||
5729 | { | ||
5730 | this.core.OnMessage(WixErrors.IllegalAttributeValueWithoutOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Assembly", (FileAssemblyType.DotNetAssembly == assemblyType ? ".net" : "win32"), "KeyPath", "yes")); | ||
5731 | } | ||
5732 | } | ||
5733 | |||
5734 | foreach (XElement child in node.Elements()) | ||
5735 | { | ||
5736 | if (CompilerCore.WixNamespace == child.Name.Namespace) | ||
5737 | { | ||
5738 | switch (child.Name.LocalName) | ||
5739 | { | ||
5740 | case "AppId": | ||
5741 | this.ParseAppIdElement(child, componentId, YesNoType.NotSet, id.Id, null, null); | ||
5742 | break; | ||
5743 | case "AssemblyName": | ||
5744 | this.ParseAssemblyName(child, componentId); | ||
5745 | break; | ||
5746 | case "Class": | ||
5747 | this.ParseClassElement(child, componentId, YesNoType.NotSet, id.Id, null, null, null); | ||
5748 | break; | ||
5749 | case "CopyFile": | ||
5750 | this.ParseCopyFileElement(child, componentId, id.Id); | ||
5751 | break; | ||
5752 | case "IgnoreRange": | ||
5753 | this.ParseRangeElement(child, ref ignoreOffsets, ref ignoreLengths); | ||
5754 | break; | ||
5755 | case "ODBCDriver": | ||
5756 | this.ParseODBCDriverOrTranslator(child, componentId, id.Id, this.tableDefinitions["ODBCDriver"]); | ||
5757 | break; | ||
5758 | case "ODBCTranslator": | ||
5759 | this.ParseODBCDriverOrTranslator(child, componentId, id.Id, this.tableDefinitions["ODBCTranslator"]); | ||
5760 | break; | ||
5761 | case "Permission": | ||
5762 | this.ParsePermissionElement(child, id.Id, "File"); | ||
5763 | break; | ||
5764 | case "PermissionEx": | ||
5765 | this.ParsePermissionExElement(child, id.Id, "File"); | ||
5766 | break; | ||
5767 | case "ProtectRange": | ||
5768 | this.ParseRangeElement(child, ref protectOffsets, ref protectLengths); | ||
5769 | break; | ||
5770 | case "Shortcut": | ||
5771 | this.ParseShortcutElement(child, componentId, node.Name.LocalName, id.Id, keyPath); | ||
5772 | break; | ||
5773 | case "SymbolPath": | ||
5774 | if (null != symbols) | ||
5775 | { | ||
5776 | symbols += ";" + this.ParseSymbolPathElement(child); | ||
5777 | } | ||
5778 | else | ||
5779 | { | ||
5780 | symbols = this.ParseSymbolPathElement(child); | ||
5781 | } | ||
5782 | break; | ||
5783 | case "TypeLib": | ||
5784 | this.ParseTypeLibElement(child, componentId, id.Id, win64Component); | ||
5785 | break; | ||
5786 | default: | ||
5787 | this.core.UnexpectedElement(node, child); | ||
5788 | break; | ||
5789 | } | ||
5790 | } | ||
5791 | else | ||
5792 | { | ||
5793 | Dictionary<string, string> context = new Dictionary<string, string>() { { "FileId", id.Id }, { "ComponentId", componentId }, { "Win64", win64Component.ToString() } }; | ||
5794 | this.core.ParseExtensionElement(node, child, context); | ||
5795 | } | ||
5796 | } | ||
5797 | |||
5798 | |||
5799 | if (!this.core.EncounteredError) | ||
5800 | { | ||
5801 | PatchAttributeType patchAttributes = PatchAttributeType.None; | ||
5802 | if (patchIgnore) | ||
5803 | { | ||
5804 | patchAttributes |= PatchAttributeType.Ignore; | ||
5805 | } | ||
5806 | if (patchIncludeWholeFile) | ||
5807 | { | ||
5808 | patchAttributes |= PatchAttributeType.IncludeWholeFile; | ||
5809 | } | ||
5810 | if (patchAllowIgnoreOnError) | ||
5811 | { | ||
5812 | patchAttributes |= PatchAttributeType.AllowIgnoreOnError; | ||
5813 | } | ||
5814 | |||
5815 | if (String.IsNullOrEmpty(source)) | ||
5816 | { | ||
5817 | if (!this.useShortFileNames && null != name) | ||
5818 | { | ||
5819 | source = name; | ||
5820 | } | ||
5821 | else | ||
5822 | { | ||
5823 | source = shortName; | ||
5824 | } | ||
5825 | } | ||
5826 | else if (source.EndsWith(Path.DirectorySeparatorChar.ToString(), StringComparison.Ordinal)) // if source relies on parent directories, append the file name | ||
5827 | { | ||
5828 | if (!this.useShortFileNames && null != name) | ||
5829 | { | ||
5830 | source = Path.Combine(source, name); | ||
5831 | } | ||
5832 | else | ||
5833 | { | ||
5834 | source = Path.Combine(source, shortName); | ||
5835 | } | ||
5836 | } | ||
5837 | |||
5838 | FileRow fileRow = (FileRow)this.core.CreateRow(sourceLineNumbers, "File", id); | ||
5839 | fileRow[1] = componentId; | ||
5840 | fileRow[2] = GetMsiFilenameValue(shortName, name); | ||
5841 | fileRow[3] = defaultSize; | ||
5842 | if (null != companionFile) | ||
5843 | { | ||
5844 | fileRow[4] = companionFile; | ||
5845 | } | ||
5846 | else if (null != defaultVersion) | ||
5847 | { | ||
5848 | fileRow[4] = defaultVersion; | ||
5849 | } | ||
5850 | fileRow[5] = defaultLanguage; | ||
5851 | fileRow[6] = bits; | ||
5852 | |||
5853 | // the Sequence row is set in the binder | ||
5854 | |||
5855 | WixFileRow wixFileRow = (WixFileRow)this.core.CreateRow(sourceLineNumbers, "WixFile", id); | ||
5856 | wixFileRow.AssemblyType = assemblyType; | ||
5857 | wixFileRow.AssemblyManifest = assemblyManifest; | ||
5858 | wixFileRow.AssemblyApplication = assemblyApplication; | ||
5859 | wixFileRow.Directory = directoryId; | ||
5860 | wixFileRow.DiskId = (CompilerConstants.IntegerNotSet == diskId) ? 0 : diskId; | ||
5861 | wixFileRow.Source = source; | ||
5862 | wixFileRow.ProcessorArchitecture = procArch; | ||
5863 | wixFileRow.PatchGroup = (CompilerConstants.IntegerNotSet != patchGroup ? patchGroup : -1); | ||
5864 | wixFileRow.Attributes = (generatedShortFileName ? 0x1 : 0x0); | ||
5865 | wixFileRow.PatchAttributes = patchAttributes; | ||
5866 | |||
5867 | // Always create a delta patch row for this file since other elements (like Component and Media) may | ||
5868 | // want to add symbol paths to it. | ||
5869 | WixDeltaPatchFileRow deltaPatchFileRow = (WixDeltaPatchFileRow)this.core.CreateRow(sourceLineNumbers, "WixDeltaPatchFile", id); | ||
5870 | deltaPatchFileRow.RetainLengths = protectLengths; | ||
5871 | deltaPatchFileRow.IgnoreOffsets = ignoreOffsets; | ||
5872 | deltaPatchFileRow.IgnoreLengths = ignoreLengths; | ||
5873 | deltaPatchFileRow.RetainOffsets = protectOffsets; | ||
5874 | |||
5875 | if (null != symbols) | ||
5876 | { | ||
5877 | WixDeltaPatchSymbolPathsRow symbolRow = (WixDeltaPatchSymbolPathsRow)this.core.CreateRow(sourceLineNumbers, "WixDeltaPatchSymbolPaths", id); | ||
5878 | symbolRow.Type = SymbolPathType.File; | ||
5879 | symbolRow.SymbolPaths = symbols; | ||
5880 | } | ||
5881 | |||
5882 | if (FileAssemblyType.NotAnAssembly != assemblyType) | ||
5883 | { | ||
5884 | Row row = this.core.CreateRow(sourceLineNumbers, "MsiAssembly"); | ||
5885 | row[0] = componentId; | ||
5886 | row[1] = Guid.Empty.ToString("B"); | ||
5887 | row[2] = assemblyManifest; | ||
5888 | row[3] = assemblyApplication; | ||
5889 | row[4] = (FileAssemblyType.DotNetAssembly == assemblyType) ? 0 : 1; | ||
5890 | } | ||
5891 | |||
5892 | if (null != bindPath) | ||
5893 | { | ||
5894 | Row row = this.core.CreateRow(sourceLineNumbers, "BindImage"); | ||
5895 | row[0] = id.Id; | ||
5896 | row[1] = bindPath; | ||
5897 | |||
5898 | // TODO: technically speaking each of the properties in the "bindPath" should be added as references, but how much do we really care about BindImage? | ||
5899 | } | ||
5900 | |||
5901 | if (CompilerConstants.IntegerNotSet != selfRegCost) | ||
5902 | { | ||
5903 | Row row = this.core.CreateRow(sourceLineNumbers, "SelfReg"); | ||
5904 | row[0] = id.Id; | ||
5905 | row[1] = selfRegCost; | ||
5906 | } | ||
5907 | |||
5908 | if (null != fontTitle) | ||
5909 | { | ||
5910 | Row row = this.core.CreateRow(sourceLineNumbers, "Font"); | ||
5911 | row[0] = id.Id; | ||
5912 | row[1] = fontTitle; | ||
5913 | } | ||
5914 | } | ||
5915 | |||
5916 | this.core.CreateSimpleReference(sourceLineNumbers, "Media", diskId.ToString(CultureInfo.InvariantCulture.NumberFormat)); | ||
5917 | |||
5918 | // If this component does not have a companion file this file is a possible keypath. | ||
5919 | possibleKeyPath = null; | ||
5920 | if (null == companionFile) | ||
5921 | { | ||
5922 | possibleKeyPath = id.Id; | ||
5923 | } | ||
5924 | |||
5925 | return keyPath; | ||
5926 | } | ||
5927 | |||
5928 | /// <summary> | ||
5929 | /// Parses a file search element. | ||
5930 | /// </summary> | ||
5931 | /// <param name="node">Element to parse.</param> | ||
5932 | /// <param name="parentSignature">Signature of parent search element.</param> | ||
5933 | /// <param name="parentDirectorySearch">Whether this search element is used to search for the parent directory.</param> | ||
5934 | /// <param name="parentDepth">The depth specified by the parent search element.</param> | ||
5935 | /// <returns>Signature of search element.</returns> | ||
5936 | private string ParseFileSearchElement(XElement node, string parentSignature, bool parentDirectorySearch, int parentDepth) | ||
5937 | { | ||
5938 | SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
5939 | Identifier id = null; | ||
5940 | string languages = null; | ||
5941 | int minDate = CompilerConstants.IntegerNotSet; | ||
5942 | int maxDate = CompilerConstants.IntegerNotSet; | ||
5943 | int maxSize = CompilerConstants.IntegerNotSet; | ||
5944 | int minSize = CompilerConstants.IntegerNotSet; | ||
5945 | string maxVersion = null; | ||
5946 | string minVersion = null; | ||
5947 | string name = null; | ||
5948 | string shortName = null; | ||
5949 | |||
5950 | foreach (XAttribute attrib in node.Attributes()) | ||
5951 | { | ||
5952 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
5953 | { | ||
5954 | switch (attrib.Name.LocalName) | ||
5955 | { | ||
5956 | case "Id": | ||
5957 | id = this.core.GetAttributeIdentifier(sourceLineNumbers, attrib); | ||
5958 | break; | ||
5959 | case "Name": | ||
5960 | name = this.core.GetAttributeLongFilename(sourceLineNumbers, attrib, false); | ||
5961 | break; | ||
5962 | case "MinVersion": | ||
5963 | minVersion = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
5964 | break; | ||
5965 | case "MaxVersion": | ||
5966 | maxVersion = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
5967 | break; | ||
5968 | case "MinSize": | ||
5969 | minSize = this.core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, int.MaxValue); | ||
5970 | break; | ||
5971 | case "MaxSize": | ||
5972 | maxSize = this.core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, int.MaxValue); | ||
5973 | break; | ||
5974 | case "MinDate": | ||
5975 | minDate = this.core.GetAttributeDateTimeValue(sourceLineNumbers, attrib); | ||
5976 | break; | ||
5977 | case "MaxDate": | ||
5978 | maxDate = this.core.GetAttributeDateTimeValue(sourceLineNumbers, attrib); | ||
5979 | break; | ||
5980 | case "Languages": | ||
5981 | languages = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
5982 | break; | ||
5983 | case "ShortName": | ||
5984 | shortName = this.core.GetAttributeShortFilename(sourceLineNumbers, attrib, false); | ||
5985 | break; | ||
5986 | default: | ||
5987 | this.core.UnexpectedAttribute(node, attrib); | ||
5988 | break; | ||
5989 | } | ||
5990 | } | ||
5991 | else | ||
5992 | { | ||
5993 | this.core.ParseExtensionAttribute(node, attrib); | ||
5994 | } | ||
5995 | } | ||
5996 | |||
5997 | // Using both ShortName and Name will not always work due to a Windows Installer bug. | ||
5998 | if (null != shortName && null != name) | ||
5999 | { | ||
6000 | this.core.OnMessage(WixWarnings.FileSearchFileNameIssue(sourceLineNumbers, node.Name.LocalName, "ShortName", "Name")); | ||
6001 | } | ||
6002 | else if (null == shortName && null == name) // at least one name must be specified. | ||
6003 | { | ||
6004 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name")); | ||
6005 | } | ||
6006 | |||
6007 | if (this.core.IsValidShortFilename(name, false)) | ||
6008 | { | ||
6009 | if (null == shortName) | ||
6010 | { | ||
6011 | shortName = name; | ||
6012 | name = null; | ||
6013 | } | ||
6014 | else | ||
6015 | { | ||
6016 | this.core.OnMessage(WixErrors.IllegalAttributeValueWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Name", name, "ShortName")); | ||
6017 | } | ||
6018 | } | ||
6019 | |||
6020 | if (null == id) | ||
6021 | { | ||
6022 | if (String.IsNullOrEmpty(parentSignature)) | ||
6023 | { | ||
6024 | id = this.core.CreateIdentifier("fs", name ?? shortName); | ||
6025 | } | ||
6026 | else // reuse parent signature in the Signature table | ||
6027 | { | ||
6028 | id = new Identifier(parentSignature, AccessModifier.Private); | ||
6029 | } | ||
6030 | } | ||
6031 | |||
6032 | bool isSameId = String.Equals(id.Id, parentSignature, StringComparison.Ordinal); | ||
6033 | if (parentDirectorySearch) | ||
6034 | { | ||
6035 | // If searching for the parent directory, the Id attribute | ||
6036 | // value must be specified and unique. | ||
6037 | if (isSameId) | ||
6038 | { | ||
6039 | this.core.OnMessage(WixErrors.UniqueFileSearchIdRequired(sourceLineNumbers, parentSignature, node.Name.LocalName)); | ||
6040 | } | ||
6041 | } | ||
6042 | else if (parentDepth > 1) | ||
6043 | { | ||
6044 | // Otherwise, if the depth > 1 the Id must be absent or the same | ||
6045 | // as the parent DirectorySearch if AssignToProperty is not set. | ||
6046 | if (!isSameId) | ||
6047 | { | ||
6048 | this.core.OnMessage(WixErrors.IllegalSearchIdForParentDepth(sourceLineNumbers, id.Id, parentSignature)); | ||
6049 | } | ||
6050 | } | ||
6051 | |||
6052 | this.core.ParseForExtensionElements(node); | ||
6053 | |||
6054 | if (!this.core.EncounteredError) | ||
6055 | { | ||
6056 | Row row = this.core.CreateRow(sourceLineNumbers, "Signature", id); | ||
6057 | row[1] = name ?? shortName; | ||
6058 | row[2] = minVersion; | ||
6059 | row[3] = maxVersion; | ||
6060 | |||
6061 | if (CompilerConstants.IntegerNotSet != minSize) | ||
6062 | { | ||
6063 | row[4] = minSize; | ||
6064 | } | ||
6065 | if (CompilerConstants.IntegerNotSet != maxSize) | ||
6066 | { | ||
6067 | row[5] = maxSize; | ||
6068 | } | ||
6069 | if (CompilerConstants.IntegerNotSet != minDate) | ||
6070 | { | ||
6071 | row[6] = minDate; | ||
6072 | } | ||
6073 | if (CompilerConstants.IntegerNotSet != maxDate) | ||
6074 | { | ||
6075 | row[7] = maxDate; | ||
6076 | } | ||
6077 | row[8] = languages; | ||
6078 | |||
6079 | // Create a DrLocator row to associate the file with a directory | ||
6080 | // when a different identifier is specified for the FileSearch. | ||
6081 | if (!isSameId) | ||
6082 | { | ||
6083 | if (parentDirectorySearch) | ||
6084 | { | ||
6085 | // Creates the DrLocator row for the directory search while | ||
6086 | // the parent DirectorySearch creates the file locator row. | ||
6087 | row = this.core.CreateRow(sourceLineNumbers, "DrLocator"); | ||
6088 | row[0] = parentSignature; | ||
6089 | row[1] = id; | ||
6090 | } | ||
6091 | else | ||
6092 | { | ||
6093 | row = this.core.CreateRow(sourceLineNumbers, "DrLocator", id); | ||
6094 | row[1] = parentSignature; | ||
6095 | } | ||
6096 | } | ||
6097 | } | ||
6098 | |||
6099 | return id.Id; // the id of the FileSearch element is its signature | ||
6100 | } | ||
6101 | |||
6102 | |||
6103 | /// <summary> | ||
6104 | /// Parses a fragment element. | ||
6105 | /// </summary> | ||
6106 | /// <param name="node">Element to parse.</param> | ||
6107 | private void ParseFragmentElement(XElement node) | ||
6108 | { | ||
6109 | SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
6110 | string id = null; | ||
6111 | |||
6112 | this.activeName = null; | ||
6113 | this.activeLanguage = null; | ||
6114 | |||
6115 | foreach (XAttribute attrib in node.Attributes()) | ||
6116 | { | ||
6117 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
6118 | { | ||
6119 | switch (attrib.Name.LocalName) | ||
6120 | { | ||
6121 | case "Id": | ||
6122 | id = this.core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); | ||
6123 | break; | ||
6124 | default: | ||
6125 | this.core.UnexpectedAttribute(node, attrib); | ||
6126 | break; | ||
6127 | } | ||
6128 | } | ||
6129 | else | ||
6130 | { | ||
6131 | this.core.ParseExtensionAttribute(node, attrib); | ||
6132 | } | ||
6133 | } | ||
6134 | |||
6135 | // NOTE: Id is not required for Fragments, this is a departure from the normal run of the mill processing. | ||
6136 | |||
6137 | this.core.CreateActiveSection(id, SectionType.Fragment, 0); | ||
6138 | |||
6139 | int featureDisplay = 0; | ||
6140 | foreach (XElement child in node.Elements()) | ||
6141 | { | ||
6142 | if (CompilerCore.WixNamespace == child.Name.Namespace) | ||
6143 | { | ||
6144 | switch (child.Name.LocalName) | ||
6145 | { | ||
6146 | case "_locDefinition": | ||
6147 | break; | ||
6148 | case "AdminExecuteSequence": | ||
6149 | this.ParseSequenceElement(child, child.Name.LocalName); | ||
6150 | break; | ||
6151 | case "AdminUISequence": | ||
6152 | this.ParseSequenceElement(child, child.Name.LocalName); | ||
6153 | break; | ||
6154 | case "AdvertiseExecuteSequence": | ||
6155 | this.ParseSequenceElement(child, child.Name.LocalName); | ||
6156 | break; | ||
6157 | case "InstallExecuteSequence": | ||
6158 | this.ParseSequenceElement(child, child.Name.LocalName); | ||
6159 | break; | ||
6160 | case "InstallUISequence": | ||
6161 | this.ParseSequenceElement(child, child.Name.LocalName); | ||
6162 | break; | ||
6163 | case "AppId": | ||
6164 | this.ParseAppIdElement(child, null, YesNoType.Yes, null, null, null); | ||
6165 | break; | ||
6166 | case "Binary": | ||
6167 | this.ParseBinaryElement(child); | ||
6168 | break; | ||
6169 | case "BootstrapperApplication": | ||
6170 | this.ParseBootstrapperApplicationElement(child); | ||
6171 | break; | ||
6172 | case "BootstrapperApplicationRef": | ||
6173 | this.ParseBootstrapperApplicationRefElement(child); | ||
6174 | break; | ||
6175 | case "ComplianceCheck": | ||
6176 | this.ParseComplianceCheckElement(child); | ||
6177 | break; | ||
6178 | case "Component": | ||
6179 | this.ParseComponentElement(child, ComplexReferenceParentType.Unknown, null, null, CompilerConstants.IntegerNotSet, null, null); | ||
6180 | break; | ||
6181 | case "ComponentGroup": | ||
6182 | this.ParseComponentGroupElement(child, ComplexReferenceParentType.Unknown, id); | ||
6183 | break; | ||
6184 | case "Condition": | ||
6185 | this.ParseConditionElement(child, node.Name.LocalName, null, null); | ||
6186 | break; | ||
6187 | case "Container": | ||
6188 | this.ParseContainerElement(child); | ||
6189 | break; | ||
6190 | case "CustomAction": | ||
6191 | this.ParseCustomActionElement(child); | ||
6192 | break; | ||
6193 | case "CustomActionRef": | ||
6194 | this.ParseSimpleRefElement(child, "CustomAction"); | ||
6195 | break; | ||
6196 | case "CustomTable": | ||
6197 | this.ParseCustomTableElement(child); | ||
6198 | break; | ||
6199 | case "Directory": | ||
6200 | this.ParseDirectoryElement(child, null, CompilerConstants.IntegerNotSet, String.Empty); | ||
6201 | break; | ||
6202 | case "DirectoryRef": | ||
6203 | this.ParseDirectoryRefElement(child); | ||
6204 | break; | ||
6205 | case "EmbeddedChainer": | ||
6206 | this.ParseEmbeddedChainerElement(child); | ||
6207 | break; | ||
6208 | case "EmbeddedChainerRef": | ||
6209 | this.ParseSimpleRefElement(child, "MsiEmbeddedChainer"); | ||
6210 | break; | ||
6211 | case "EnsureTable": | ||
6212 | this.ParseEnsureTableElement(child); | ||
6213 | break; | ||
6214 | case "Feature": | ||
6215 | this.ParseFeatureElement(child, ComplexReferenceParentType.Unknown, null, ref featureDisplay); | ||
6216 | break; | ||
6217 | case "FeatureGroup": | ||
6218 | this.ParseFeatureGroupElement(child, ComplexReferenceParentType.Unknown, id); | ||
6219 | break; | ||
6220 | case "FeatureRef": | ||
6221 | this.ParseFeatureRefElement(child, ComplexReferenceParentType.Unknown, null); | ||
6222 | break; | ||
6223 | case "Icon": | ||
6224 | this.ParseIconElement(child); | ||
6225 | break; | ||
6226 | case "IgnoreModularization": | ||
6227 | this.ParseIgnoreModularizationElement(child); | ||
6228 | break; | ||
6229 | case "Media": | ||
6230 | this.ParseMediaElement(child, null); | ||
6231 | break; | ||
6232 | case "MediaTemplate": | ||
6233 | this.ParseMediaTemplateElement(child, null); | ||
6234 | break; | ||
6235 | case "PackageGroup": | ||
6236 | this.ParsePackageGroupElement(child); | ||
6237 | break; | ||
6238 | case "PackageCertificates": | ||
6239 | case "PatchCertificates": | ||
6240 | this.ParseCertificatesElement(child); | ||
6241 | break; | ||
6242 | case "PatchFamily": | ||
6243 | this.ParsePatchFamilyElement(child, ComplexReferenceParentType.Unknown, id); | ||
6244 | break; | ||
6245 | case "PatchFamilyGroup": | ||
6246 | this.ParsePatchFamilyGroupElement(child, ComplexReferenceParentType.Unknown, id); | ||
6247 | break; | ||
6248 | case "PatchFamilyGroupRef": | ||
6249 | this.ParsePatchFamilyGroupRefElement(child, ComplexReferenceParentType.Unknown, id); | ||
6250 | break; | ||
6251 | case "PayloadGroup": | ||
6252 | this.ParsePayloadGroupElement(child, ComplexReferenceParentType.Unknown, null); | ||
6253 | break; | ||
6254 | case "Property": | ||
6255 | this.ParsePropertyElement(child); | ||
6256 | break; | ||
6257 | case "PropertyRef": | ||
6258 | this.ParseSimpleRefElement(child, "Property"); | ||
6259 | break; | ||
6260 | case "RelatedBundle": | ||
6261 | this.ParseRelatedBundleElement(child); | ||
6262 | break; | ||
6263 | case "SetDirectory": | ||
6264 | this.ParseSetDirectoryElement(child); | ||
6265 | break; | ||
6266 | case "SetProperty": | ||
6267 | this.ParseSetPropertyElement(child); | ||
6268 | break; | ||
6269 | case "SFPCatalog": | ||
6270 | string parentName = null; | ||
6271 | this.ParseSFPCatalogElement(child, ref parentName); | ||
6272 | break; | ||
6273 | case "UI": | ||
6274 | this.ParseUIElement(child); | ||
6275 | break; | ||
6276 | case "UIRef": | ||
6277 | this.ParseSimpleRefElement(child, "WixUI"); | ||
6278 | break; | ||
6279 | case "Upgrade": | ||
6280 | this.ParseUpgradeElement(child); | ||
6281 | break; | ||
6282 | case "Variable": | ||
6283 | this.ParseVariableElement(child); | ||
6284 | break; | ||
6285 | case "WixVariable": | ||
6286 | this.ParseWixVariableElement(child); | ||
6287 | break; | ||
6288 | default: | ||
6289 | this.core.UnexpectedElement(node, child); | ||
6290 | break; | ||
6291 | } | ||
6292 | } | ||
6293 | else | ||
6294 | { | ||
6295 | this.core.ParseExtensionElement(node, child); | ||
6296 | } | ||
6297 | } | ||
6298 | |||
6299 | if (!this.core.EncounteredError && null != id) | ||
6300 | { | ||
6301 | Row row = this.core.CreateRow(sourceLineNumbers, "WixFragment"); | ||
6302 | row[0] = id; | ||
6303 | } | ||
6304 | } | ||
6305 | |||
6306 | |||
6307 | /// <summary> | ||
6308 | /// Parses a condition element. | ||
6309 | /// </summary> | ||
6310 | /// <param name="node">Element to parse.</param> | ||
6311 | /// <param name="parentElementLocalName">LocalName of the parent element.</param> | ||
6312 | /// <param name="id">Id of the parent element.</param> | ||
6313 | /// <param name="dialog">Dialog of the parent element if its a Control.</param> | ||
6314 | /// <returns>The condition if one was found.</returns> | ||
6315 | private string ParseConditionElement(XElement node, string parentElementLocalName, string id, string dialog) | ||
6316 | { | ||
6317 | SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
6318 | string action = null; | ||
6319 | string condition = null; | ||
6320 | int level = CompilerConstants.IntegerNotSet; | ||
6321 | string message = null; | ||
6322 | |||
6323 | foreach (XAttribute attrib in node.Attributes()) | ||
6324 | { | ||
6325 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
6326 | { | ||
6327 | switch (attrib.Name.LocalName) | ||
6328 | { | ||
6329 | case "Action": | ||
6330 | if ("Control" == parentElementLocalName) | ||
6331 | { | ||
6332 | action = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
6333 | if (0 < action.Length) | ||
6334 | { | ||
6335 | Wix.Condition.ActionType actionType; | ||
6336 | if (Wix.Condition.TryParseActionType(action, out actionType)) | ||
6337 | { | ||
6338 | action = Compiler.UppercaseFirstChar(action); | ||
6339 | } | ||
6340 | else | ||
6341 | { | ||
6342 | this.core.OnMessage(WixErrors.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, action, "default", "disable", "enable", "hide", "show")); | ||
6343 | } | ||
6344 | } | ||
6345 | } | ||
6346 | else | ||
6347 | { | ||
6348 | this.core.UnexpectedAttribute(node, attrib); | ||
6349 | } | ||
6350 | break; | ||
6351 | case "Level": | ||
6352 | if ("Feature" == parentElementLocalName) | ||
6353 | { | ||
6354 | level = this.core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, short.MaxValue); | ||
6355 | } | ||
6356 | else | ||
6357 | { | ||
6358 | this.core.UnexpectedAttribute(node, attrib); | ||
6359 | } | ||
6360 | break; | ||
6361 | case "Message": | ||
6362 | if ("Fragment" == parentElementLocalName || "Product" == parentElementLocalName) | ||
6363 | { | ||
6364 | message = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
6365 | } | ||
6366 | else | ||
6367 | { | ||
6368 | this.core.UnexpectedAttribute(node, attrib); | ||
6369 | } | ||
6370 | break; | ||
6371 | default: | ||
6372 | this.core.UnexpectedAttribute(node, attrib); | ||
6373 | break; | ||
6374 | } | ||
6375 | } | ||
6376 | else | ||
6377 | { | ||
6378 | this.core.ParseExtensionAttribute(node, attrib); | ||
6379 | } | ||
6380 | } | ||
6381 | |||
6382 | // get the condition from the inner text of the element | ||
6383 | condition = this.core.GetConditionInnerText(node); | ||
6384 | |||
6385 | this.core.ParseForExtensionElements(node); | ||
6386 | |||
6387 | // the condition should not be empty | ||
6388 | if (null == condition || 0 == condition.Length) | ||
6389 | { | ||
6390 | condition = null; | ||
6391 | this.core.OnMessage(WixErrors.ConditionExpected(sourceLineNumbers, node.Name.LocalName)); | ||
6392 | } | ||
6393 | |||
6394 | switch (parentElementLocalName) | ||
6395 | { | ||
6396 | case "Control": | ||
6397 | if (null == action) | ||
6398 | { | ||
6399 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Action")); | ||
6400 | } | ||
6401 | |||
6402 | if (!this.core.EncounteredError) | ||
6403 | { | ||
6404 | Row row = this.core.CreateRow(sourceLineNumbers, "ControlCondition"); | ||
6405 | row[0] = dialog; | ||
6406 | row[1] = id; | ||
6407 | row[2] = action; | ||
6408 | row[3] = condition; | ||
6409 | } | ||
6410 | break; | ||
6411 | case "Feature": | ||
6412 | if (CompilerConstants.IntegerNotSet == level) | ||
6413 | { | ||
6414 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Level")); | ||
6415 | level = CompilerConstants.IllegalInteger; | ||
6416 | } | ||
6417 | |||
6418 | if (!this.core.EncounteredError) | ||
6419 | { | ||
6420 | Row row = this.core.CreateRow(sourceLineNumbers, "Condition"); | ||
6421 | row[0] = id; | ||
6422 | row[1] = level; | ||
6423 | row[2] = condition; | ||
6424 | } | ||
6425 | break; | ||
6426 | case "Fragment": | ||
6427 | case "Product": | ||
6428 | if (null == message) | ||
6429 | { | ||
6430 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Message")); | ||
6431 | } | ||
6432 | |||
6433 | if (!this.core.EncounteredError) | ||
6434 | { | ||
6435 | Row row = this.core.CreateRow(sourceLineNumbers, "LaunchCondition"); | ||
6436 | row[0] = condition; | ||
6437 | row[1] = message; | ||
6438 | } | ||
6439 | break; | ||
6440 | } | ||
6441 | |||
6442 | return condition; | ||
6443 | } | ||
6444 | |||
6445 | /// <summary> | ||
6446 | /// Parses a IniFile element. | ||
6447 | /// </summary> | ||
6448 | /// <param name="node">Element to parse.</param> | ||
6449 | /// <param name="componentId">Identifier of the parent component.</param> | ||
6450 | private void ParseIniFileElement(XElement node, string componentId) | ||
6451 | { | ||
6452 | SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
6453 | Identifier id = null; | ||
6454 | int action = CompilerConstants.IntegerNotSet; | ||
6455 | string directory = null; | ||
6456 | string key = null; | ||
6457 | string name = null; | ||
6458 | string section = null; | ||
6459 | string shortName = null; | ||
6460 | string tableName = null; | ||
6461 | string value = null; | ||
6462 | |||
6463 | foreach (XAttribute attrib in node.Attributes()) | ||
6464 | { | ||
6465 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
6466 | { | ||
6467 | switch (attrib.Name.LocalName) | ||
6468 | { | ||
6469 | case "Id": | ||
6470 | id = this.core.GetAttributeIdentifier(sourceLineNumbers, attrib); | ||
6471 | break; | ||
6472 | case "Action": | ||
6473 | string actionValue = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
6474 | if (0 < actionValue.Length) | ||
6475 | { | ||
6476 | Wix.IniFile.ActionType actionType = Wix.IniFile.ParseActionType(actionValue); | ||
6477 | switch (actionType) | ||
6478 | { | ||
6479 | case Wix.IniFile.ActionType.addLine: | ||
6480 | action = MsiInterop.MsidbIniFileActionAddLine; | ||
6481 | break; | ||
6482 | case Wix.IniFile.ActionType.addTag: | ||
6483 | action = MsiInterop.MsidbIniFileActionAddTag; | ||
6484 | break; | ||
6485 | case Wix.IniFile.ActionType.createLine: | ||
6486 | action = MsiInterop.MsidbIniFileActionCreateLine; | ||
6487 | break; | ||
6488 | case Wix.IniFile.ActionType.removeLine: | ||
6489 | action = MsiInterop.MsidbIniFileActionRemoveLine; | ||
6490 | break; | ||
6491 | case Wix.IniFile.ActionType.removeTag: | ||
6492 | action = MsiInterop.MsidbIniFileActionRemoveTag; | ||
6493 | break; | ||
6494 | default: | ||
6495 | this.core.OnMessage(WixErrors.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, "Action", actionValue, "addLine", "addTag", "createLine", "removeLine", "removeTag")); | ||
6496 | break; | ||
6497 | } | ||
6498 | } | ||
6499 | break; | ||
6500 | case "Directory": | ||
6501 | directory = this.core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); | ||
6502 | break; | ||
6503 | case "Key": | ||
6504 | key = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
6505 | break; | ||
6506 | case "Name": | ||
6507 | name = this.core.GetAttributeLongFilename(sourceLineNumbers, attrib, false); | ||
6508 | break; | ||
6509 | case "Section": | ||
6510 | section = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
6511 | break; | ||
6512 | case "ShortName": | ||
6513 | shortName = this.core.GetAttributeShortFilename(sourceLineNumbers, attrib, false); | ||
6514 | break; | ||
6515 | case "Value": | ||
6516 | value = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
6517 | break; | ||
6518 | default: | ||
6519 | this.core.UnexpectedAttribute(node, attrib); | ||
6520 | break; | ||
6521 | } | ||
6522 | } | ||
6523 | else | ||
6524 | { | ||
6525 | this.core.ParseExtensionAttribute(node, attrib); | ||
6526 | } | ||
6527 | } | ||
6528 | |||
6529 | if (CompilerConstants.IntegerNotSet == action) | ||
6530 | { | ||
6531 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Action")); | ||
6532 | action = CompilerConstants.IllegalInteger; | ||
6533 | } | ||
6534 | |||
6535 | if (null == key) | ||
6536 | { | ||
6537 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Key")); | ||
6538 | } | ||
6539 | |||
6540 | if (null == name) | ||
6541 | { | ||
6542 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name")); | ||
6543 | } | ||
6544 | else if (0 < name.Length) | ||
6545 | { | ||
6546 | if (this.core.IsValidShortFilename(name, false)) | ||
6547 | { | ||
6548 | if (null == shortName) | ||
6549 | { | ||
6550 | shortName = name; | ||
6551 | name = null; | ||
6552 | } | ||
6553 | else | ||
6554 | { | ||
6555 | this.core.OnMessage(WixErrors.IllegalAttributeValueWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Name", name, "ShortName")); | ||
6556 | } | ||
6557 | } | ||
6558 | else // generate a short file name. | ||
6559 | { | ||
6560 | if (null == shortName) | ||
6561 | { | ||
6562 | shortName = this.core.CreateShortName(name, true, false, node.Name.LocalName, componentId); | ||
6563 | } | ||
6564 | } | ||
6565 | } | ||
6566 | |||
6567 | if (null == section) | ||
6568 | { | ||
6569 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Section")); | ||
6570 | } | ||
6571 | |||
6572 | if (null == id) | ||
6573 | { | ||
6574 | id = this.core.CreateIdentifier("ini", directory, name ?? shortName, section, key, name); | ||
6575 | } | ||
6576 | |||
6577 | this.core.ParseForExtensionElements(node); | ||
6578 | |||
6579 | if (MsiInterop.MsidbIniFileActionRemoveLine == action || MsiInterop.MsidbIniFileActionRemoveTag == action) | ||
6580 | { | ||
6581 | tableName = "RemoveIniFile"; | ||
6582 | } | ||
6583 | else | ||
6584 | { | ||
6585 | if (null == value) | ||
6586 | { | ||
6587 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Value")); | ||
6588 | } | ||
6589 | |||
6590 | tableName = "IniFile"; | ||
6591 | } | ||
6592 | |||
6593 | if (!this.core.EncounteredError) | ||
6594 | { | ||
6595 | Row row = this.core.CreateRow(sourceLineNumbers, tableName, id); | ||
6596 | row[1] = GetMsiFilenameValue(shortName, name); | ||
6597 | row[2] = directory; | ||
6598 | row[3] = section; | ||
6599 | row[4] = key; | ||
6600 | row[5] = value; | ||
6601 | row[6] = action; | ||
6602 | row[7] = componentId; | ||
6603 | } | ||
6604 | } | ||
6605 | |||
6606 | /// <summary> | ||
6607 | /// Parses an IniFile search element. | ||
6608 | /// </summary> | ||
6609 | /// <param name="node">Element to parse.</param> | ||
6610 | /// <returns>Signature for search element.</returns> | ||
6611 | private string ParseIniFileSearchElement(XElement node) | ||
6612 | { | ||
6613 | SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
6614 | Identifier id = null; | ||
6615 | int field = CompilerConstants.IntegerNotSet; | ||
6616 | string key = null; | ||
6617 | string name = null; | ||
6618 | string section = null; | ||
6619 | string shortName = null; | ||
6620 | string signature = null; | ||
6621 | int type = 1; // default is file | ||
6622 | |||
6623 | foreach (XAttribute attrib in node.Attributes()) | ||
6624 | { | ||
6625 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
6626 | { | ||
6627 | switch (attrib.Name.LocalName) | ||
6628 | { | ||
6629 | case "Id": | ||
6630 | id = this.core.GetAttributeIdentifier(sourceLineNumbers, attrib); | ||
6631 | break; | ||
6632 | case "Field": | ||
6633 | field = this.core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, short.MaxValue); | ||
6634 | break; | ||
6635 | case "Key": | ||
6636 | key = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
6637 | break; | ||
6638 | case "Name": | ||
6639 | name = this.core.GetAttributeLongFilename(sourceLineNumbers, attrib, false); | ||
6640 | break; | ||
6641 | case "Section": | ||
6642 | section = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
6643 | break; | ||
6644 | case "ShortName": | ||
6645 | shortName = this.core.GetAttributeShortFilename(sourceLineNumbers, attrib, false); | ||
6646 | break; | ||
6647 | case "Type": | ||
6648 | string typeValue = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
6649 | if (0 < typeValue.Length) | ||
6650 | { | ||
6651 | Wix.IniFileSearch.TypeType typeType = Wix.IniFileSearch.ParseTypeType(typeValue); | ||
6652 | switch (typeType) | ||
6653 | { | ||
6654 | case Wix.IniFileSearch.TypeType.directory: | ||
6655 | type = 0; | ||
6656 | break; | ||
6657 | case Wix.IniFileSearch.TypeType.file: | ||
6658 | type = 1; | ||
6659 | break; | ||
6660 | case Wix.IniFileSearch.TypeType.raw: | ||
6661 | type = 2; | ||
6662 | break; | ||
6663 | default: | ||
6664 | this.core.OnMessage(WixErrors.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, "Type", typeValue, "directory", "file", "registry")); | ||
6665 | break; | ||
6666 | } | ||
6667 | } | ||
6668 | break; | ||
6669 | default: | ||
6670 | this.core.UnexpectedAttribute(node, attrib); | ||
6671 | break; | ||
6672 | } | ||
6673 | } | ||
6674 | else | ||
6675 | { | ||
6676 | this.core.ParseExtensionAttribute(node, attrib); | ||
6677 | } | ||
6678 | } | ||
6679 | |||
6680 | if (null == key) | ||
6681 | { | ||
6682 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Key")); | ||
6683 | } | ||
6684 | |||
6685 | if (null == name) | ||
6686 | { | ||
6687 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name")); | ||
6688 | } | ||
6689 | else if (0 < name.Length) | ||
6690 | { | ||
6691 | if (this.core.IsValidShortFilename(name, false)) | ||
6692 | { | ||
6693 | if (null == shortName) | ||
6694 | { | ||
6695 | shortName = name; | ||
6696 | name = null; | ||
6697 | } | ||
6698 | else | ||
6699 | { | ||
6700 | this.core.OnMessage(WixErrors.IllegalAttributeValueWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Name", name, "ShortName")); | ||
6701 | } | ||
6702 | } | ||
6703 | else if (null == shortName) // generate a short file name. | ||
6704 | { | ||
6705 | shortName = this.core.CreateShortName(name, true, false, node.Name.LocalName); | ||
6706 | } | ||
6707 | } | ||
6708 | |||
6709 | if (null == section) | ||
6710 | { | ||
6711 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Section")); | ||
6712 | } | ||
6713 | |||
6714 | if (null == id) | ||
6715 | { | ||
6716 | id = this.core.CreateIdentifier("ini", name, section, key, field.ToString(), type.ToString()); | ||
6717 | } | ||
6718 | |||
6719 | signature = id.Id; | ||
6720 | |||
6721 | bool oneChild = false; | ||
6722 | foreach (XElement child in node.Elements()) | ||
6723 | { | ||
6724 | if (CompilerCore.WixNamespace == child.Name.Namespace) | ||
6725 | { | ||
6726 | SourceLineNumber childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child); | ||
6727 | switch (child.Name.LocalName) | ||
6728 | { | ||
6729 | case "DirectorySearch": | ||
6730 | if (oneChild) | ||
6731 | { | ||
6732 | this.core.OnMessage(WixErrors.TooManySearchElements(childSourceLineNumbers, node.Name.LocalName)); | ||
6733 | } | ||
6734 | oneChild = true; | ||
6735 | |||
6736 | // directorysearch parentage should work like directory element, not the rest of the signature type because of the DrLocator.Parent column | ||
6737 | signature = this.ParseDirectorySearchElement(child, id.Id); | ||
6738 | break; | ||
6739 | case "DirectorySearchRef": | ||
6740 | if (oneChild) | ||
6741 | { | ||
6742 | this.core.OnMessage(WixErrors.TooManySearchElements(childSourceLineNumbers, node.Name.LocalName)); | ||
6743 | } | ||
6744 | oneChild = true; | ||
6745 | signature = this.ParseDirectorySearchRefElement(child, id.Id); | ||
6746 | break; | ||
6747 | case "FileSearch": | ||
6748 | if (oneChild) | ||
6749 | { | ||
6750 | this.core.OnMessage(WixErrors.TooManySearchElements(sourceLineNumbers, node.Name.LocalName)); | ||
6751 | } | ||
6752 | oneChild = true; | ||
6753 | signature = this.ParseFileSearchElement(child, id.Id, false, CompilerConstants.IntegerNotSet); | ||
6754 | id = new Identifier(signature, AccessModifier.Private); // FileSearch signatures override parent signatures | ||
6755 | break; | ||
6756 | case "FileSearchRef": | ||
6757 | if (oneChild) | ||
6758 | { | ||
6759 | this.core.OnMessage(WixErrors.TooManySearchElements(sourceLineNumbers, node.Name.LocalName)); | ||
6760 | } | ||
6761 | oneChild = true; | ||
6762 | string newId = this.ParseSimpleRefElement(child, "Signature"); // FileSearch signatures override parent signatures | ||
6763 | id = new Identifier(newId, AccessModifier.Private); | ||
6764 | signature = null; | ||
6765 | break; | ||
6766 | default: | ||
6767 | this.core.UnexpectedElement(node, child); | ||
6768 | break; | ||
6769 | } | ||
6770 | } | ||
6771 | else | ||
6772 | { | ||
6773 | this.core.ParseExtensionElement(node, child); | ||
6774 | } | ||
6775 | } | ||
6776 | |||
6777 | if (!this.core.EncounteredError) | ||
6778 | { | ||
6779 | Row row = this.core.CreateRow(sourceLineNumbers, "IniLocator", id); | ||
6780 | row[1] = GetMsiFilenameValue(shortName, name); | ||
6781 | row[2] = section; | ||
6782 | row[3] = key; | ||
6783 | if (CompilerConstants.IntegerNotSet != field) | ||
6784 | { | ||
6785 | row[4] = field; | ||
6786 | } | ||
6787 | row[5] = type; | ||
6788 | } | ||
6789 | |||
6790 | return signature; | ||
6791 | } | ||
6792 | |||
6793 | /// <summary> | ||
6794 | /// Parses an isolated component element. | ||
6795 | /// </summary> | ||
6796 | /// <param name="node">Element to parse.</param> | ||
6797 | /// <param name="componentId">Identifier of parent component.</param> | ||
6798 | private void ParseIsolateComponentElement(XElement node, string componentId) | ||
6799 | { | ||
6800 | SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
6801 | string shared = null; | ||
6802 | |||
6803 | foreach (XAttribute attrib in node.Attributes()) | ||
6804 | { | ||
6805 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
6806 | { | ||
6807 | switch (attrib.Name.LocalName) | ||
6808 | { | ||
6809 | case "Shared": | ||
6810 | shared = this.core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); | ||
6811 | this.core.CreateSimpleReference(sourceLineNumbers, "Component", shared); | ||
6812 | break; | ||
6813 | default: | ||
6814 | this.core.UnexpectedAttribute(node, attrib); | ||
6815 | break; | ||
6816 | } | ||
6817 | } | ||
6818 | else | ||
6819 | { | ||
6820 | this.core.ParseExtensionAttribute(node, attrib); | ||
6821 | } | ||
6822 | } | ||
6823 | |||
6824 | if (null == shared) | ||
6825 | { | ||
6826 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Shared")); | ||
6827 | } | ||
6828 | |||
6829 | this.core.ParseForExtensionElements(node); | ||
6830 | |||
6831 | if (!this.core.EncounteredError) | ||
6832 | { | ||
6833 | Row row = this.core.CreateRow(sourceLineNumbers, "IsolatedComponent"); | ||
6834 | row[0] = shared; | ||
6835 | row[1] = componentId; | ||
6836 | } | ||
6837 | } | ||
6838 | |||
6839 | /// <summary> | ||
6840 | /// Parses a PatchCertificates or PackageCertificates element. | ||
6841 | /// </summary> | ||
6842 | /// <param name="node">The element to parse.</param> | ||
6843 | private void ParseCertificatesElement(XElement node) | ||
6844 | { | ||
6845 | SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
6846 | |||
6847 | // no attributes are supported for this element | ||
6848 | foreach (XAttribute attrib in node.Attributes()) | ||
6849 | { | ||
6850 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
6851 | { | ||
6852 | this.core.UnexpectedAttribute(node, attrib); | ||
6853 | } | ||
6854 | else | ||
6855 | { | ||
6856 | this.core.ParseExtensionAttribute(node, attrib); | ||
6857 | } | ||
6858 | } | ||
6859 | |||
6860 | foreach (XElement child in node.Elements()) | ||
6861 | { | ||
6862 | if (CompilerCore.WixNamespace == child.Name.Namespace) | ||
6863 | { | ||
6864 | switch (child.Name.LocalName) | ||
6865 | { | ||
6866 | case "DigitalCertificate": | ||
6867 | string name = this.ParseDigitalCertificateElement(child); | ||
6868 | |||
6869 | if (!this.core.EncounteredError) | ||
6870 | { | ||
6871 | Row row = this.core.CreateRow(sourceLineNumbers, "PatchCertificates" == node.Name.LocalName ? "MsiPatchCertificate" : "MsiPackageCertificate"); | ||
6872 | row[0] = name; | ||
6873 | row[1] = name; | ||
6874 | } | ||
6875 | break; | ||
6876 | default: | ||
6877 | this.core.UnexpectedElement(node, child); | ||
6878 | break; | ||
6879 | } | ||
6880 | } | ||
6881 | else | ||
6882 | { | ||
6883 | this.core.ParseExtensionElement(node, child); | ||
6884 | } | ||
6885 | } | ||
6886 | } | ||
6887 | |||
6888 | /// <summary> | ||
6889 | /// Parses an digital certificate element. | ||
6890 | /// </summary> | ||
6891 | /// <param name="node">Element to parse.</param> | ||
6892 | /// <returns>The identifier of the certificate.</returns> | ||
6893 | private string ParseDigitalCertificateElement(XElement node) | ||
6894 | { | ||
6895 | SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
6896 | Identifier id = null; | ||
6897 | string sourceFile = null; | ||
6898 | |||
6899 | foreach (XAttribute attrib in node.Attributes()) | ||
6900 | { | ||
6901 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
6902 | { | ||
6903 | switch (attrib.Name.LocalName) | ||
6904 | { | ||
6905 | case "Id": | ||
6906 | id = this.core.GetAttributeIdentifier(sourceLineNumbers, attrib); | ||
6907 | break; | ||
6908 | case "SourceFile": | ||
6909 | sourceFile = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
6910 | break; | ||
6911 | default: | ||
6912 | this.core.UnexpectedAttribute(node, attrib); | ||
6913 | break; | ||
6914 | } | ||
6915 | } | ||
6916 | else | ||
6917 | { | ||
6918 | this.core.ParseExtensionAttribute(node, attrib); | ||
6919 | } | ||
6920 | } | ||
6921 | |||
6922 | if (null == id) | ||
6923 | { | ||
6924 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); | ||
6925 | id = Identifier.Invalid; | ||
6926 | } | ||
6927 | else if (40 < id.Id.Length) | ||
6928 | { | ||
6929 | this.core.OnMessage(WixErrors.StreamNameTooLong(sourceLineNumbers, node.Name.LocalName, "Id", id.Id, id.Id.Length, 40)); | ||
6930 | |||
6931 | // No need to check for modularization problems since DigitalSignature and thus DigitalCertificate | ||
6932 | // currently have no usage in merge modules. | ||
6933 | } | ||
6934 | |||
6935 | if (null == sourceFile) | ||
6936 | { | ||
6937 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "SourceFile")); | ||
6938 | } | ||
6939 | |||
6940 | this.core.ParseForExtensionElements(node); | ||
6941 | |||
6942 | if (!this.core.EncounteredError) | ||
6943 | { | ||
6944 | Row row = this.core.CreateRow(sourceLineNumbers, "MsiDigitalCertificate", id); | ||
6945 | row[1] = sourceFile; | ||
6946 | } | ||
6947 | |||
6948 | return id.Id; | ||
6949 | } | ||
6950 | |||
6951 | /// <summary> | ||
6952 | /// Parses an digital signature element. | ||
6953 | /// </summary> | ||
6954 | /// <param name="node">Element to parse.</param> | ||
6955 | /// <param name="diskId">Disk id inherited from parent media.</param> | ||
6956 | private void ParseDigitalSignatureElement(XElement node, string diskId) | ||
6957 | { | ||
6958 | SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
6959 | string certificateId = null; | ||
6960 | string sourceFile = null; | ||
6961 | |||
6962 | foreach (XAttribute attrib in node.Attributes()) | ||
6963 | { | ||
6964 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
6965 | { | ||
6966 | switch (attrib.Name.LocalName) | ||
6967 | { | ||
6968 | case "SourceFile": | ||
6969 | sourceFile = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
6970 | break; | ||
6971 | default: | ||
6972 | this.core.UnexpectedAttribute(node, attrib); | ||
6973 | break; | ||
6974 | } | ||
6975 | } | ||
6976 | else | ||
6977 | { | ||
6978 | this.core.ParseExtensionAttribute(node, attrib); | ||
6979 | } | ||
6980 | } | ||
6981 | |||
6982 | // sanity check for debug to ensure the stream name will not be a problem | ||
6983 | if (null != sourceFile) | ||
6984 | { | ||
6985 | Debug.Assert(62 >= "MsiDigitalSignature.Media.".Length + diskId.Length); | ||
6986 | } | ||
6987 | |||
6988 | foreach (XElement child in node.Elements()) | ||
6989 | { | ||
6990 | if (CompilerCore.WixNamespace == child.Name.Namespace) | ||
6991 | { | ||
6992 | switch (child.Name.LocalName) | ||
6993 | { | ||
6994 | case "DigitalCertificate": | ||
6995 | certificateId = this.ParseDigitalCertificateElement(child); | ||
6996 | break; | ||
6997 | default: | ||
6998 | this.core.UnexpectedElement(node, child); | ||
6999 | break; | ||
7000 | } | ||
7001 | } | ||
7002 | else | ||
7003 | { | ||
7004 | this.core.ParseExtensionElement(node, child); | ||
7005 | } | ||
7006 | } | ||
7007 | |||
7008 | if (null == certificateId) | ||
7009 | { | ||
7010 | this.core.OnMessage(WixErrors.ExpectedElement(sourceLineNumbers, node.Name.LocalName, "DigitalCertificate")); | ||
7011 | } | ||
7012 | |||
7013 | if (!this.core.EncounteredError) | ||
7014 | { | ||
7015 | Row row = this.core.CreateRow(sourceLineNumbers, "MsiDigitalSignature"); | ||
7016 | row[0] = "Media"; | ||
7017 | row[1] = diskId; | ||
7018 | row[2] = certificateId; | ||
7019 | row[3] = sourceFile; | ||
7020 | } | ||
7021 | } | ||
7022 | |||
7023 | /// <summary> | ||
7024 | /// Parses a MajorUpgrade element. | ||
7025 | /// </summary> | ||
7026 | /// <param name="node">The element to parse.</param> | ||
7027 | /// <param name="parentElement">The parent element.</param> | ||
7028 | private void ParseMajorUpgradeElement(XElement node, IDictionary<string, string> contextValues) | ||
7029 | { | ||
7030 | SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
7031 | int options = MsiInterop.MsidbUpgradeAttributesMigrateFeatures; | ||
7032 | bool allowDowngrades = false; | ||
7033 | bool allowSameVersionUpgrades = false; | ||
7034 | bool blockUpgrades = false; | ||
7035 | string downgradeErrorMessage = null; | ||
7036 | string disallowUpgradeErrorMessage = null; | ||
7037 | string removeFeatures = null; | ||
7038 | string schedule = null; | ||
7039 | |||
7040 | string upgradeCode = contextValues["UpgradeCode"]; | ||
7041 | if (String.IsNullOrEmpty(upgradeCode)) | ||
7042 | { | ||
7043 | this.core.OnMessage(WixErrors.ParentElementAttributeRequired(sourceLineNumbers, "Product", "UpgradeCode", node.Name.LocalName)); | ||
7044 | } | ||
7045 | |||
7046 | string productVersion = contextValues["ProductVersion"]; | ||
7047 | if (String.IsNullOrEmpty(productVersion)) | ||
7048 | { | ||
7049 | this.core.OnMessage(WixErrors.ParentElementAttributeRequired(sourceLineNumbers, "Product", "Version", node.Name.LocalName)); | ||
7050 | } | ||
7051 | |||
7052 | string productLanguage = contextValues["ProductLanguage"]; | ||
7053 | |||
7054 | foreach (XAttribute attrib in node.Attributes()) | ||
7055 | { | ||
7056 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
7057 | { | ||
7058 | switch (attrib.Name.LocalName) | ||
7059 | { | ||
7060 | case "AllowDowngrades": | ||
7061 | allowDowngrades = YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
7062 | break; | ||
7063 | case "AllowSameVersionUpgrades": | ||
7064 | allowSameVersionUpgrades = YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
7065 | break; | ||
7066 | case "Disallow": | ||
7067 | blockUpgrades = YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
7068 | break; | ||
7069 | case "DowngradeErrorMessage": | ||
7070 | downgradeErrorMessage = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
7071 | break; | ||
7072 | case "DisallowUpgradeErrorMessage": | ||
7073 | disallowUpgradeErrorMessage = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
7074 | break; | ||
7075 | case "MigrateFeatures": | ||
7076 | if (YesNoType.No == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) | ||
7077 | { | ||
7078 | options &= ~MsiInterop.MsidbUpgradeAttributesMigrateFeatures; | ||
7079 | } | ||
7080 | break; | ||
7081 | case "IgnoreLanguage": | ||
7082 | if (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) | ||
7083 | { | ||
7084 | productLanguage = null; | ||
7085 | } | ||
7086 | break; | ||
7087 | case "IgnoreRemoveFailure": | ||
7088 | if (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) | ||
7089 | { | ||
7090 | options |= MsiInterop.MsidbUpgradeAttributesIgnoreRemoveFailure; | ||
7091 | } | ||
7092 | break; | ||
7093 | case "RemoveFeatures": | ||
7094 | removeFeatures = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
7095 | break; | ||
7096 | case "Schedule": | ||
7097 | schedule = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
7098 | break; | ||
7099 | default: | ||
7100 | this.core.UnexpectedAttribute(node, attrib); | ||
7101 | break; | ||
7102 | } | ||
7103 | } | ||
7104 | else | ||
7105 | { | ||
7106 | this.core.ParseExtensionAttribute(node, attrib); | ||
7107 | } | ||
7108 | } | ||
7109 | |||
7110 | this.core.ParseForExtensionElements(node); | ||
7111 | |||
7112 | if (!allowDowngrades && String.IsNullOrEmpty(downgradeErrorMessage)) | ||
7113 | { | ||
7114 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "DowngradeErrorMessage", "AllowDowngrades", "yes", true)); | ||
7115 | } | ||
7116 | |||
7117 | if (allowDowngrades && !String.IsNullOrEmpty(downgradeErrorMessage)) | ||
7118 | { | ||
7119 | this.core.OnMessage(WixErrors.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "DowngradeErrorMessage", "AllowDowngrades", "yes")); | ||
7120 | } | ||
7121 | |||
7122 | if (allowDowngrades && allowSameVersionUpgrades) | ||
7123 | { | ||
7124 | this.core.OnMessage(WixErrors.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "AllowSameVersionUpgrades", "AllowDowngrades", "yes")); | ||
7125 | } | ||
7126 | |||
7127 | if (blockUpgrades && String.IsNullOrEmpty(disallowUpgradeErrorMessage)) | ||
7128 | { | ||
7129 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "DisallowUpgradeErrorMessage", "Disallow", "yes", true)); | ||
7130 | } | ||
7131 | |||
7132 | if (!blockUpgrades && !String.IsNullOrEmpty(disallowUpgradeErrorMessage)) | ||
7133 | { | ||
7134 | this.core.OnMessage(WixErrors.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "DisallowUpgradeErrorMessage", "Disallow", "yes")); | ||
7135 | } | ||
7136 | |||
7137 | if (!this.core.EncounteredError) | ||
7138 | { | ||
7139 | // create the row that performs the upgrade (or downgrade) | ||
7140 | Row row = this.core.CreateRow(sourceLineNumbers, "Upgrade"); | ||
7141 | row[0] = upgradeCode; | ||
7142 | if (allowDowngrades) | ||
7143 | { | ||
7144 | row[1] = "0"; // let any version satisfy | ||
7145 | // row[2] = maximum version; omit so we don't have to fake a version like "255.255.65535"; | ||
7146 | row[3] = productLanguage; | ||
7147 | row[4] = options | MsiInterop.MsidbUpgradeAttributesVersionMinInclusive; | ||
7148 | } | ||
7149 | else | ||
7150 | { | ||
7151 | // row[1] = minimum version; skip it so we detect all prior versions. | ||
7152 | row[2] = productVersion; | ||
7153 | row[3] = productLanguage; | ||
7154 | row[4] = allowSameVersionUpgrades ? (options | MsiInterop.MsidbUpgradeAttributesVersionMaxInclusive) : options; | ||
7155 | } | ||
7156 | |||
7157 | row[5] = removeFeatures; | ||
7158 | row[6] = Compiler.UpgradeDetectedProperty; | ||
7159 | |||
7160 | // Ensure the action property is secure. | ||
7161 | this.AddWixPropertyRow(sourceLineNumbers, new Identifier(Compiler.UpgradeDetectedProperty, AccessModifier.Public), false, true, false); | ||
7162 | |||
7163 | // Add launch condition that blocks upgrades | ||
7164 | if (blockUpgrades) | ||
7165 | { | ||
7166 | row = this.core.CreateRow(sourceLineNumbers, "LaunchCondition"); | ||
7167 | row[0] = Compiler.UpgradePreventedCondition; | ||
7168 | row[1] = disallowUpgradeErrorMessage; | ||
7169 | } | ||
7170 | |||
7171 | // now create the Upgrade row and launch conditions to prevent downgrades (unless explicitly permitted) | ||
7172 | if (!allowDowngrades) | ||
7173 | { | ||
7174 | row = this.core.CreateRow(sourceLineNumbers, "Upgrade"); | ||
7175 | row[0] = upgradeCode; | ||
7176 | row[1] = productVersion; | ||
7177 | // row[2] = maximum version; skip it so we detect all future versions. | ||
7178 | row[3] = productLanguage; | ||
7179 | row[4] = MsiInterop.MsidbUpgradeAttributesOnlyDetect; | ||
7180 | // row[5] = removeFeatures; | ||
7181 | row[6] = Compiler.DowngradeDetectedProperty; | ||
7182 | |||
7183 | // Ensure the action property is secure. | ||
7184 | this.AddWixPropertyRow(sourceLineNumbers, new Identifier(Compiler.DowngradeDetectedProperty, AccessModifier.Public), false, true, false); | ||
7185 | |||
7186 | row = this.core.CreateRow(sourceLineNumbers, "LaunchCondition"); | ||
7187 | row[0] = Compiler.DowngradePreventedCondition; | ||
7188 | row[1] = downgradeErrorMessage; | ||
7189 | } | ||
7190 | |||
7191 | // finally, schedule RemoveExistingProducts | ||
7192 | row = this.core.CreateRow(sourceLineNumbers, "WixAction"); | ||
7193 | row[0] = "InstallExecuteSequence"; | ||
7194 | row[1] = "RemoveExistingProducts"; | ||
7195 | // row[2] = condition; | ||
7196 | // row[3] = sequence; | ||
7197 | row[6] = 0; // overridable | ||
7198 | |||
7199 | switch (schedule) | ||
7200 | { | ||
7201 | case null: | ||
7202 | case "afterInstallValidate": | ||
7203 | // row[4] = beforeAction; | ||
7204 | row[5] = "InstallValidate"; | ||
7205 | break; | ||
7206 | case "afterInstallInitialize": | ||
7207 | // row[4] = beforeAction; | ||
7208 | row[5] = "InstallInitialize"; | ||
7209 | break; | ||
7210 | case "afterInstallExecute": | ||
7211 | // row[4] = beforeAction; | ||
7212 | row[5] = "InstallExecute"; | ||
7213 | break; | ||
7214 | case "afterInstallExecuteAgain": | ||
7215 | // row[4] = beforeAction; | ||
7216 | row[5] = "InstallExecuteAgain"; | ||
7217 | break; | ||
7218 | case "afterInstallFinalize": | ||
7219 | // row[4] = beforeAction; | ||
7220 | row[5] = "InstallFinalize"; | ||
7221 | break; | ||
7222 | } | ||
7223 | } | ||
7224 | } | ||
7225 | |||
7226 | /// <summary> | ||
7227 | /// Parses a media element. | ||
7228 | /// </summary> | ||
7229 | /// <param name="node">Element to parse.</param> | ||
7230 | /// <param name="patchId">Set to the PatchId if parsing Patch/Media element otherwise null.</param> | ||
7231 | private void ParseMediaElement(XElement node, string patchId) | ||
7232 | { | ||
7233 | SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
7234 | int id = CompilerConstants.IntegerNotSet; | ||
7235 | string cabinet = null; | ||
7236 | CompressionLevel? compressionLevel = null; | ||
7237 | string diskPrompt = null; | ||
7238 | string layout = null; | ||
7239 | bool patch = null != patchId; | ||
7240 | string volumeLabel = null; | ||
7241 | string source = null; | ||
7242 | string symbols = null; | ||
7243 | |||
7244 | YesNoType embedCab = patch ? YesNoType.Yes : YesNoType.NotSet; | ||
7245 | |||
7246 | foreach (XAttribute attrib in node.Attributes()) | ||
7247 | { | ||
7248 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
7249 | { | ||
7250 | switch (attrib.Name.LocalName) | ||
7251 | { | ||
7252 | case "Id": | ||
7253 | id = this.core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 1, short.MaxValue); | ||
7254 | break; | ||
7255 | case "Cabinet": | ||
7256 | cabinet = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
7257 | break; | ||
7258 | case "CompressionLevel": | ||
7259 | string compressionLevelString = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
7260 | if (0 < compressionLevelString.Length) | ||
7261 | { | ||
7262 | Wix.CompressionLevelType compressionLevelType; | ||
7263 | if (!Wix.Enums.TryParseCompressionLevelType(compressionLevelString, out compressionLevelType)) | ||
7264 | { | ||
7265 | this.core.OnMessage(WixErrors.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, compressionLevelString, "high", "low", "medium", "mszip", "none")); | ||
7266 | } | ||
7267 | else | ||
7268 | { | ||
7269 | compressionLevel = (CompressionLevel)Enum.Parse(typeof(CompressionLevel), compressionLevelString, true); | ||
7270 | } | ||
7271 | } | ||
7272 | break; | ||
7273 | case "DiskPrompt": | ||
7274 | diskPrompt = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
7275 | this.core.CreateSimpleReference(sourceLineNumbers, "Property", "DiskPrompt"); // ensure the output has a DiskPrompt Property defined | ||
7276 | break; | ||
7277 | case "EmbedCab": | ||
7278 | embedCab = this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
7279 | break; | ||
7280 | case "Layout": | ||
7281 | case "src": | ||
7282 | if (null != layout) | ||
7283 | { | ||
7284 | this.core.OnMessage(WixErrors.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Layout", "src")); | ||
7285 | } | ||
7286 | |||
7287 | if ("src" == attrib.Name.LocalName) | ||
7288 | { | ||
7289 | this.core.OnMessage(WixWarnings.DeprecatedAttribute(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "Layout")); | ||
7290 | } | ||
7291 | layout = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
7292 | break; | ||
7293 | case "VolumeLabel": | ||
7294 | volumeLabel = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
7295 | break; | ||
7296 | case "Source": | ||
7297 | source = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
7298 | break; | ||
7299 | default: | ||
7300 | this.core.UnexpectedAttribute(node, attrib); | ||
7301 | break; | ||
7302 | } | ||
7303 | } | ||
7304 | else | ||
7305 | { | ||
7306 | this.core.ParseExtensionAttribute(node, attrib); | ||
7307 | } | ||
7308 | } | ||
7309 | |||
7310 | if (CompilerConstants.IntegerNotSet == id) | ||
7311 | { | ||
7312 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); | ||
7313 | id = CompilerConstants.IllegalInteger; | ||
7314 | } | ||
7315 | |||
7316 | if (YesNoType.IllegalValue != embedCab) | ||
7317 | { | ||
7318 | if (YesNoType.Yes == embedCab) | ||
7319 | { | ||
7320 | if (null == cabinet) | ||
7321 | { | ||
7322 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Cabinet", "EmbedCab", "yes")); | ||
7323 | } | ||
7324 | else | ||
7325 | { | ||
7326 | if (62 < cabinet.Length) | ||
7327 | { | ||
7328 | this.core.OnMessage(WixErrors.MediaEmbeddedCabinetNameTooLong(sourceLineNumbers, node.Name.LocalName, "Cabinet", cabinet, cabinet.Length)); | ||
7329 | } | ||
7330 | |||
7331 | cabinet = String.Concat("#", cabinet); | ||
7332 | } | ||
7333 | } | ||
7334 | else // external cabinet file | ||
7335 | { | ||
7336 | // external cabinet files must use 8.3 filenames | ||
7337 | if (!String.IsNullOrEmpty(cabinet) && !this.core.IsValidShortFilename(cabinet, false)) | ||
7338 | { | ||
7339 | // WiX variables in the name will trip the "not a valid 8.3 name" switch, so let them through | ||
7340 | if (!Common.WixVariableRegex.Match(cabinet).Success) | ||
7341 | { | ||
7342 | this.core.OnMessage(WixWarnings.MediaExternalCabinetFilenameIllegal(sourceLineNumbers, node.Name.LocalName, "Cabinet", cabinet)); | ||
7343 | } | ||
7344 | } | ||
7345 | } | ||
7346 | } | ||
7347 | |||
7348 | if (null != compressionLevel && null == cabinet) | ||
7349 | { | ||
7350 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Cabinet", "CompressionLevel")); | ||
7351 | } | ||
7352 | |||
7353 | if (patch) | ||
7354 | { | ||
7355 | // Default Source to a form of the Patch Id if none is specified. | ||
7356 | if (null == source) | ||
7357 | { | ||
7358 | source = String.Concat("_", new Guid(patchId).ToString("N", CultureInfo.InvariantCulture).ToUpper(CultureInfo.InvariantCulture)); | ||
7359 | } | ||
7360 | } | ||
7361 | |||
7362 | foreach (XElement child in node.Elements()) | ||
7363 | { | ||
7364 | SourceLineNumber childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child); | ||
7365 | if (CompilerCore.WixNamespace == child.Name.Namespace) | ||
7366 | { | ||
7367 | switch (child.Name.LocalName) | ||
7368 | { | ||
7369 | case "DigitalSignature": | ||
7370 | if (YesNoType.Yes == embedCab) | ||
7371 | { | ||
7372 | this.core.OnMessage(WixErrors.SignedEmbeddedCabinet(childSourceLineNumbers)); | ||
7373 | } | ||
7374 | else if (null == cabinet) | ||
7375 | { | ||
7376 | this.core.OnMessage(WixErrors.ExpectedSignedCabinetName(childSourceLineNumbers)); | ||
7377 | } | ||
7378 | else | ||
7379 | { | ||
7380 | this.ParseDigitalSignatureElement(child, id.ToString(CultureInfo.InvariantCulture.NumberFormat)); | ||
7381 | } | ||
7382 | break; | ||
7383 | case "PatchBaseline": | ||
7384 | if (patch) | ||
7385 | { | ||
7386 | this.ParsePatchBaselineElement(child, id); | ||
7387 | } | ||
7388 | else | ||
7389 | { | ||
7390 | this.core.UnexpectedElement(node, child); | ||
7391 | } | ||
7392 | break; | ||
7393 | case "SymbolPath": | ||
7394 | if (null != symbols) | ||
7395 | { | ||
7396 | symbols += "" + this.ParseSymbolPathElement(child); | ||
7397 | } | ||
7398 | else | ||
7399 | { | ||
7400 | symbols = this.ParseSymbolPathElement(child); | ||
7401 | } | ||
7402 | break; | ||
7403 | default: | ||
7404 | this.core.UnexpectedElement(node, child); | ||
7405 | break; | ||
7406 | } | ||
7407 | } | ||
7408 | else | ||
7409 | { | ||
7410 | this.core.ParseExtensionElement(node, child); | ||
7411 | } | ||
7412 | } | ||
7413 | |||
7414 | |||
7415 | |||
7416 | // add the row to the section | ||
7417 | if (!this.core.EncounteredError) | ||
7418 | { | ||
7419 | MediaRow mediaRow = (MediaRow)this.core.CreateRow(sourceLineNumbers, "Media"); | ||
7420 | mediaRow.DiskId = id; | ||
7421 | mediaRow.LastSequence = 0; // this is set in the binder | ||
7422 | mediaRow.DiskPrompt = diskPrompt; | ||
7423 | mediaRow.Cabinet = cabinet; | ||
7424 | mediaRow.VolumeLabel = volumeLabel; | ||
7425 | mediaRow.Source = source; | ||
7426 | |||
7427 | // the Source column is only set when creating a patch | ||
7428 | |||
7429 | if (null != compressionLevel || null != layout) | ||
7430 | { | ||
7431 | WixMediaRow row = (WixMediaRow)this.core.CreateRow(sourceLineNumbers, "WixMedia"); | ||
7432 | row.DiskId = id; | ||
7433 | row.CompressionLevel = compressionLevel; | ||
7434 | row.Layout = layout; | ||
7435 | } | ||
7436 | |||
7437 | if (null != symbols) | ||
7438 | { | ||
7439 | WixDeltaPatchSymbolPathsRow symbolRow = (WixDeltaPatchSymbolPathsRow)this.core.CreateRow(sourceLineNumbers, "WixDeltaPatchSymbolPaths"); | ||
7440 | symbolRow.Id = id.ToString(CultureInfo.InvariantCulture); | ||
7441 | symbolRow.Type = SymbolPathType.Media; | ||
7442 | symbolRow.SymbolPaths = symbols; | ||
7443 | } | ||
7444 | } | ||
7445 | } | ||
7446 | |||
7447 | /// <summary> | ||
7448 | /// Parses a media template element. | ||
7449 | /// </summary> | ||
7450 | /// <param name="node">Element to parse.</param> | ||
7451 | /// <param name="patchId">Set to the PatchId if parsing Patch/Media element otherwise null.</param> | ||
7452 | private void ParseMediaTemplateElement(XElement node, string patchId) | ||
7453 | { | ||
7454 | SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
7455 | string cabinetTemplate = "cab{0}.cab"; | ||
7456 | string compressionLevel = null; // this defaults to mszip in Binder | ||
7457 | string diskPrompt = null; | ||
7458 | bool patch = null != patchId; | ||
7459 | string volumeLabel = null; | ||
7460 | int maximumUncompressedMediaSize = CompilerConstants.IntegerNotSet; | ||
7461 | int maximumCabinetSizeForLargeFileSplitting = CompilerConstants.IntegerNotSet; | ||
7462 | Wix.CompressionLevelType compressionLevelType = Wix.CompressionLevelType.NotSet; | ||
7463 | |||
7464 | YesNoType embedCab = patch ? YesNoType.Yes : YesNoType.NotSet; | ||
7465 | |||
7466 | foreach (XAttribute attrib in node.Attributes()) | ||
7467 | { | ||
7468 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
7469 | { | ||
7470 | switch (attrib.Name.LocalName) | ||
7471 | { | ||
7472 | case "CabinetTemplate": | ||
7473 | string authoredCabinetTemplateValue = this.core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty); | ||
7474 | if (!String.IsNullOrEmpty(authoredCabinetTemplateValue)) | ||
7475 | { | ||
7476 | cabinetTemplate = authoredCabinetTemplateValue; | ||
7477 | } | ||
7478 | |||
7479 | // Create an example cabinet name using the maximum number of cabinets supported, 999. | ||
7480 | string exampleCabinetName = String.Format(cabinetTemplate, "###"); | ||
7481 | if (!this.core.IsValidLocIdentifier(exampleCabinetName)) | ||
7482 | { | ||
7483 | // The example name should not match the authored template since that would nullify the | ||
7484 | // reason for having multiple cabients. External cabinet files must also be valid file names. | ||
7485 | if (exampleCabinetName.Equals(authoredCabinetTemplateValue) || !this.core.IsValidLongFilename(exampleCabinetName, false)) | ||
7486 | { | ||
7487 | this.core.OnMessage(WixErrors.InvalidCabinetTemplate(sourceLineNumbers, cabinetTemplate)); | ||
7488 | } | ||
7489 | else if (!this.core.IsValidShortFilename(exampleCabinetName, false) && !Common.WixVariableRegex.Match(exampleCabinetName).Success) // ignore short names with wix variables because it rarely works out. | ||
7490 | { | ||
7491 | this.core.OnMessage(WixWarnings.MediaExternalCabinetFilenameIllegal(sourceLineNumbers, node.Name.LocalName, "CabinetTemplate", cabinetTemplate)); | ||
7492 | } | ||
7493 | } | ||
7494 | break; | ||
7495 | case "CompressionLevel": | ||
7496 | compressionLevel = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
7497 | if (0 < compressionLevel.Length) | ||
7498 | { | ||
7499 | if (!Wix.Enums.TryParseCompressionLevelType(compressionLevel, out compressionLevelType)) | ||
7500 | { | ||
7501 | this.core.OnMessage(WixErrors.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, compressionLevel, "high", "low", "medium", "mszip", "none")); | ||
7502 | } | ||
7503 | } | ||
7504 | break; | ||
7505 | case "DiskPrompt": | ||
7506 | diskPrompt = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
7507 | this.core.CreateSimpleReference(sourceLineNumbers, "Property", "DiskPrompt"); // ensure the output has a DiskPrompt Property defined | ||
7508 | this.core.OnMessage(WixWarnings.ReservedAttribute(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName)); | ||
7509 | break; | ||
7510 | case "EmbedCab": | ||
7511 | embedCab = this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
7512 | break; | ||
7513 | case "VolumeLabel": | ||
7514 | volumeLabel = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
7515 | this.core.OnMessage(WixWarnings.ReservedAttribute(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName)); | ||
7516 | break; | ||
7517 | case "MaximumUncompressedMediaSize": | ||
7518 | maximumUncompressedMediaSize = this.core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 1, int.MaxValue); | ||
7519 | break; | ||
7520 | case "MaximumCabinetSizeForLargeFileSplitting": | ||
7521 | maximumCabinetSizeForLargeFileSplitting = this.core.GetAttributeIntegerValue(sourceLineNumbers, attrib, CompilerCore.MinValueOfMaxCabSizeForLargeFileSplitting, CompilerCore.MaxValueOfMaxCabSizeForLargeFileSplitting); | ||
7522 | break; | ||
7523 | default: | ||
7524 | this.core.UnexpectedAttribute(node, attrib); | ||
7525 | break; | ||
7526 | } | ||
7527 | } | ||
7528 | else | ||
7529 | { | ||
7530 | this.core.ParseExtensionAttribute(node, attrib); | ||
7531 | } | ||
7532 | } | ||
7533 | |||
7534 | if (YesNoType.IllegalValue != embedCab) | ||
7535 | { | ||
7536 | if (YesNoType.Yes == embedCab) | ||
7537 | { | ||
7538 | cabinetTemplate = String.Concat("#", cabinetTemplate); | ||
7539 | } | ||
7540 | } | ||
7541 | |||
7542 | if (!this.core.EncounteredError) | ||
7543 | { | ||
7544 | MediaRow temporaryMediaRow = (MediaRow)this.core.CreateRow(sourceLineNumbers, "Media"); | ||
7545 | temporaryMediaRow.DiskId = 1; | ||
7546 | WixMediaTemplateRow mediaTemplateRow = (WixMediaTemplateRow)this.core.CreateRow(sourceLineNumbers, "WixMediaTemplate"); | ||
7547 | mediaTemplateRow.CabinetTemplate = cabinetTemplate; | ||
7548 | mediaTemplateRow.VolumeLabel = volumeLabel; | ||
7549 | mediaTemplateRow.DiskPrompt = diskPrompt; | ||
7550 | mediaTemplateRow.VolumeLabel = volumeLabel; | ||
7551 | |||
7552 | if (maximumUncompressedMediaSize != CompilerConstants.IntegerNotSet) | ||
7553 | { | ||
7554 | mediaTemplateRow.MaximumUncompressedMediaSize = maximumUncompressedMediaSize; | ||
7555 | } | ||
7556 | else | ||
7557 | { | ||
7558 | mediaTemplateRow.MaximumUncompressedMediaSize = CompilerCore.DefaultMaximumUncompressedMediaSize; | ||
7559 | } | ||
7560 | |||
7561 | if (maximumCabinetSizeForLargeFileSplitting != CompilerConstants.IntegerNotSet) | ||
7562 | { | ||
7563 | mediaTemplateRow.MaximumCabinetSizeForLargeFileSplitting = maximumCabinetSizeForLargeFileSplitting; | ||
7564 | } | ||
7565 | else | ||
7566 | { | ||
7567 | mediaTemplateRow.MaximumCabinetSizeForLargeFileSplitting = 0; // Default value of 0 corresponds to max size of 2048 MB (i.e. 2 GB) | ||
7568 | } | ||
7569 | |||
7570 | switch (compressionLevelType) | ||
7571 | { | ||
7572 | case Wix.CompressionLevelType.high: | ||
7573 | mediaTemplateRow.CompressionLevel = CompressionLevel.High; | ||
7574 | break; | ||
7575 | case Wix.CompressionLevelType.low: | ||
7576 | mediaTemplateRow.CompressionLevel = CompressionLevel.Low; | ||
7577 | break; | ||
7578 | case Wix.CompressionLevelType.medium: | ||
7579 | mediaTemplateRow.CompressionLevel = CompressionLevel.Medium; | ||
7580 | break; | ||
7581 | case Wix.CompressionLevelType.none: | ||
7582 | mediaTemplateRow.CompressionLevel = CompressionLevel.None; | ||
7583 | break; | ||
7584 | case Wix.CompressionLevelType.mszip: | ||
7585 | mediaTemplateRow.CompressionLevel = CompressionLevel.Mszip; | ||
7586 | break; | ||
7587 | } | ||
7588 | } | ||
7589 | } | ||
7590 | |||
7591 | /// <summary> | ||
7592 | /// Parses a merge element. | ||
7593 | /// </summary> | ||
7594 | /// <param name="node">Element to parse.</param> | ||
7595 | /// <param name="directoryId">Identifier for parent directory.</param> | ||
7596 | /// <param name="diskId">Disk id inherited from parent directory.</param> | ||
7597 | private void ParseMergeElement(XElement node, string directoryId, int diskId) | ||
7598 | { | ||
7599 | SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
7600 | Identifier id = null; | ||
7601 | string configData = String.Empty; | ||
7602 | YesNoType fileCompression = YesNoType.NotSet; | ||
7603 | string language = null; | ||
7604 | string sourceFile = null; | ||
7605 | |||
7606 | foreach (XAttribute attrib in node.Attributes()) | ||
7607 | { | ||
7608 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
7609 | { | ||
7610 | switch (attrib.Name.LocalName) | ||
7611 | { | ||
7612 | case "Id": | ||
7613 | id = this.core.GetAttributeIdentifier(sourceLineNumbers, attrib); | ||
7614 | break; | ||
7615 | case "DiskId": | ||
7616 | diskId = this.core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 1, short.MaxValue); | ||
7617 | this.core.CreateSimpleReference(sourceLineNumbers, "Media", diskId.ToString(CultureInfo.InvariantCulture.NumberFormat)); | ||
7618 | break; | ||
7619 | case "FileCompression": | ||
7620 | fileCompression = this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
7621 | break; | ||
7622 | case "Language": | ||
7623 | language = this.core.GetAttributeLocalizableIntegerValue(sourceLineNumbers, attrib, 0, short.MaxValue); | ||
7624 | break; | ||
7625 | case "SourceFile": | ||
7626 | sourceFile = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
7627 | break; | ||
7628 | default: | ||
7629 | this.core.UnexpectedAttribute(node, attrib); | ||
7630 | break; | ||
7631 | } | ||
7632 | } | ||
7633 | else | ||
7634 | { | ||
7635 | this.core.ParseExtensionAttribute(node, attrib); | ||
7636 | } | ||
7637 | } | ||
7638 | |||
7639 | if (null == id) | ||
7640 | { | ||
7641 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); | ||
7642 | } | ||
7643 | |||
7644 | if (null == language) | ||
7645 | { | ||
7646 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Language")); | ||
7647 | } | ||
7648 | |||
7649 | if (null == sourceFile) | ||
7650 | { | ||
7651 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "SourceFile")); | ||
7652 | } | ||
7653 | |||
7654 | if (CompilerConstants.IntegerNotSet == diskId) | ||
7655 | { | ||
7656 | this.core.OnMessage(WixErrors.ExpectedAttributeInElementOrParent(sourceLineNumbers, node.Name.LocalName, "DiskId", "Directory")); | ||
7657 | diskId = CompilerConstants.IllegalInteger; | ||
7658 | } | ||
7659 | |||
7660 | foreach (XElement child in node.Elements()) | ||
7661 | { | ||
7662 | if (CompilerCore.WixNamespace == child.Name.Namespace) | ||
7663 | { | ||
7664 | switch (child.Name.LocalName) | ||
7665 | { | ||
7666 | case "ConfigurationData": | ||
7667 | if (0 == configData.Length) | ||
7668 | { | ||
7669 | configData = this.ParseConfigurationDataElement(child); | ||
7670 | } | ||
7671 | else | ||
7672 | { | ||
7673 | configData = String.Concat(configData, ",", this.ParseConfigurationDataElement(child)); | ||
7674 | } | ||
7675 | break; | ||
7676 | default: | ||
7677 | this.core.UnexpectedElement(node, child); | ||
7678 | break; | ||
7679 | } | ||
7680 | } | ||
7681 | else | ||
7682 | { | ||
7683 | this.core.ParseExtensionElement(node, child); | ||
7684 | } | ||
7685 | } | ||
7686 | |||
7687 | if (!this.core.EncounteredError) | ||
7688 | { | ||
7689 | Row row = this.core.CreateRow(sourceLineNumbers, "WixMerge", id); | ||
7690 | row[1] = language; | ||
7691 | row[2] = directoryId; | ||
7692 | row[3] = sourceFile; | ||
7693 | row[4] = diskId; | ||
7694 | if (YesNoType.Yes == fileCompression) | ||
7695 | { | ||
7696 | row[5] = 1; | ||
7697 | } | ||
7698 | else if (YesNoType.No == fileCompression) | ||
7699 | { | ||
7700 | row[5] = 0; | ||
7701 | } | ||
7702 | else // YesNoType.NotSet == fileCompression | ||
7703 | { | ||
7704 | // and we leave the column null | ||
7705 | } | ||
7706 | row[6] = configData; | ||
7707 | row[7] = Guid.Empty.ToString("B"); | ||
7708 | } | ||
7709 | } | ||
7710 | |||
7711 | /// <summary> | ||
7712 | /// Parses a configuration data element. | ||
7713 | /// </summary> | ||
7714 | /// <param name="node">Element to parse.</param> | ||
7715 | /// <returns>String in format "name=value" with '%', ',' and '=' hex encoded.</returns> | ||
7716 | private string ParseConfigurationDataElement(XElement node) | ||
7717 | { | ||
7718 | SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
7719 | string name = null; | ||
7720 | string value = null; | ||
7721 | |||
7722 | foreach (XAttribute attrib in node.Attributes()) | ||
7723 | { | ||
7724 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
7725 | { | ||
7726 | switch (attrib.Name.LocalName) | ||
7727 | { | ||
7728 | case "Name": | ||
7729 | name = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
7730 | break; | ||
7731 | case "Value": | ||
7732 | value = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
7733 | break; | ||
7734 | default: | ||
7735 | this.core.UnexpectedAttribute(node, attrib); | ||
7736 | break; | ||
7737 | } | ||
7738 | } | ||
7739 | else | ||
7740 | { | ||
7741 | this.core.ParseExtensionAttribute(node, attrib); | ||
7742 | } | ||
7743 | } | ||
7744 | |||
7745 | if (null == name) | ||
7746 | { | ||
7747 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name")); | ||
7748 | } | ||
7749 | else // need to hex encode these characters | ||
7750 | { | ||
7751 | name = name.Replace("%", "%25"); | ||
7752 | name = name.Replace("=", "%3D"); | ||
7753 | name = name.Replace(",", "%2C"); | ||
7754 | } | ||
7755 | |||
7756 | if (null == value) | ||
7757 | { | ||
7758 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Value")); | ||
7759 | } | ||
7760 | else // need to hex encode these characters | ||
7761 | { | ||
7762 | value = value.Replace("%", "%25"); | ||
7763 | value = value.Replace("=", "%3D"); | ||
7764 | value = value.Replace(",", "%2C"); | ||
7765 | } | ||
7766 | |||
7767 | this.core.ParseForExtensionElements(node); | ||
7768 | |||
7769 | return String.Concat(name, "=", value); | ||
7770 | } | ||
7771 | |||
7772 | /// <summary> | ||
7773 | /// Parses a merge reference element. | ||
7774 | /// </summary> | ||
7775 | /// <param name="node">Element to parse.</param> | ||
7776 | /// <param name="parentType">Parents complex reference type.</param> | ||
7777 | /// <param name="parentId">Identifier for parent feature or feature group.</param> | ||
7778 | private void ParseMergeRefElement(XElement node, ComplexReferenceParentType parentType, string parentId) | ||
7779 | { | ||
7780 | SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
7781 | string id = null; | ||
7782 | YesNoType primary = YesNoType.NotSet; | ||
7783 | |||
7784 | foreach (XAttribute attrib in node.Attributes()) | ||
7785 | { | ||
7786 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
7787 | { | ||
7788 | switch (attrib.Name.LocalName) | ||
7789 | { | ||
7790 | case "Id": | ||
7791 | id = this.core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); | ||
7792 | this.core.CreateSimpleReference(sourceLineNumbers, "WixMerge", id); | ||
7793 | break; | ||
7794 | case "Primary": | ||
7795 | primary = this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
7796 | break; | ||
7797 | default: | ||
7798 | this.core.UnexpectedAttribute(node, attrib); | ||
7799 | break; | ||
7800 | } | ||
7801 | } | ||
7802 | else | ||
7803 | { | ||
7804 | this.core.ParseExtensionAttribute(node, attrib); | ||
7805 | } | ||
7806 | } | ||
7807 | |||
7808 | if (null == id) | ||
7809 | { | ||
7810 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); | ||
7811 | } | ||
7812 | |||
7813 | this.core.ParseForExtensionElements(node); | ||
7814 | |||
7815 | this.core.CreateComplexReference(sourceLineNumbers, parentType, parentId, null, ComplexReferenceChildType.Module, id, (YesNoType.Yes == primary)); | ||
7816 | } | ||
7817 | |||
7818 | /// <summary> | ||
7819 | /// Parses a mime element. | ||
7820 | /// </summary> | ||
7821 | /// <param name="node">Element to parse.</param> | ||
7822 | /// <param name="extension">Identifier for parent extension.</param> | ||
7823 | /// <param name="componentId">Identifier for parent component.</param> | ||
7824 | /// <param name="parentAdvertised">Flag if the parent element is advertised.</param> | ||
7825 | /// <returns>Content type if this is the default for the MIME type.</returns> | ||
7826 | private string ParseMIMEElement(XElement node, string extension, string componentId, YesNoType parentAdvertised) | ||
7827 | { | ||
7828 | SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
7829 | string classId = null; | ||
7830 | string contentType = null; | ||
7831 | YesNoType advertise = parentAdvertised; | ||
7832 | YesNoType returnContentType = YesNoType.NotSet; | ||
7833 | |||
7834 | foreach (XAttribute attrib in node.Attributes()) | ||
7835 | { | ||
7836 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
7837 | { | ||
7838 | switch (attrib.Name.LocalName) | ||
7839 | { | ||
7840 | case "Advertise": | ||
7841 | advertise = this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
7842 | break; | ||
7843 | case "Class": | ||
7844 | classId = this.core.GetAttributeGuidValue(sourceLineNumbers, attrib, false); | ||
7845 | break; | ||
7846 | case "ContentType": | ||
7847 | contentType = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
7848 | break; | ||
7849 | case "Default": | ||
7850 | returnContentType = this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
7851 | break; | ||
7852 | default: | ||
7853 | this.core.UnexpectedAttribute(node, attrib); | ||
7854 | break; | ||
7855 | } | ||
7856 | } | ||
7857 | else | ||
7858 | { | ||
7859 | this.core.ParseExtensionAttribute(node, attrib); | ||
7860 | } | ||
7861 | } | ||
7862 | |||
7863 | if (null == contentType) | ||
7864 | { | ||
7865 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "ContentType")); | ||
7866 | } | ||
7867 | |||
7868 | // if the advertise state has not been set, default to non-advertised | ||
7869 | if (YesNoType.NotSet == advertise) | ||
7870 | { | ||
7871 | advertise = YesNoType.No; | ||
7872 | } | ||
7873 | |||
7874 | this.core.ParseForExtensionElements(node); | ||
7875 | |||
7876 | if (YesNoType.Yes == advertise) | ||
7877 | { | ||
7878 | if (YesNoType.Yes != parentAdvertised) | ||
7879 | { | ||
7880 | this.core.OnMessage(WixErrors.AdvertiseStateMustMatch(sourceLineNumbers, advertise.ToString(), parentAdvertised.ToString())); | ||
7881 | } | ||
7882 | |||
7883 | if (!this.core.EncounteredError) | ||
7884 | { | ||
7885 | Row row = this.core.CreateRow(sourceLineNumbers, "MIME"); | ||
7886 | row[0] = contentType; | ||
7887 | row[1] = extension; | ||
7888 | row[2] = classId; | ||
7889 | } | ||
7890 | } | ||
7891 | else if (YesNoType.No == advertise) | ||
7892 | { | ||
7893 | if (YesNoType.Yes == returnContentType && YesNoType.Yes == parentAdvertised) | ||
7894 | { | ||
7895 | this.core.OnMessage(WixErrors.CannotDefaultMismatchedAdvertiseStates(sourceLineNumbers)); | ||
7896 | } | ||
7897 | |||
7898 | this.core.CreateRegistryRow(sourceLineNumbers, MsiInterop.MsidbRegistryRootClassesRoot, String.Concat("MIME\\Database\\Content Type\\", contentType), "Extension", String.Concat(".", extension), componentId); | ||
7899 | if (null != classId) | ||
7900 | { | ||
7901 | this.core.CreateRegistryRow(sourceLineNumbers, MsiInterop.MsidbRegistryRootClassesRoot, String.Concat("MIME\\Database\\Content Type\\", contentType), "CLSID", classId, componentId); | ||
7902 | } | ||
7903 | } | ||
7904 | |||
7905 | return YesNoType.Yes == returnContentType ? contentType : null; | ||
7906 | } | ||
7907 | |||
7908 | /// <summary> | ||
7909 | /// Parses a module element. | ||
7910 | /// </summary> | ||
7911 | /// <param name="node">Element to parse.</param> | ||
7912 | private void ParseModuleElement(XElement node) | ||
7913 | { | ||
7914 | SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
7915 | int codepage = 0; | ||
7916 | string moduleId = null; | ||
7917 | string version = null; | ||
7918 | |||
7919 | this.activeName = null; | ||
7920 | this.activeLanguage = null; | ||
7921 | |||
7922 | foreach (XAttribute attrib in node.Attributes()) | ||
7923 | { | ||
7924 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
7925 | { | ||
7926 | switch (attrib.Name.LocalName) | ||
7927 | { | ||
7928 | case "Id": | ||
7929 | this.activeName = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
7930 | if ("PUT-MODULE-NAME-HERE" == this.activeName) | ||
7931 | { | ||
7932 | this.core.OnMessage(WixWarnings.PlaceholderValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, this.activeName)); | ||
7933 | } | ||
7934 | else | ||
7935 | { | ||
7936 | this.activeName = this.core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); | ||
7937 | } | ||
7938 | break; | ||
7939 | case "Codepage": | ||
7940 | codepage = this.core.GetAttributeCodePageValue(sourceLineNumbers, attrib); | ||
7941 | break; | ||
7942 | case "Guid": | ||
7943 | moduleId = this.core.GetAttributeGuidValue(sourceLineNumbers, attrib, false); | ||
7944 | this.core.OnMessage(WixWarnings.DeprecatedModuleGuidAttribute(sourceLineNumbers)); | ||
7945 | break; | ||
7946 | case "Language": | ||
7947 | this.activeLanguage = this.core.GetAttributeLocalizableIntegerValue(sourceLineNumbers, attrib, 0, short.MaxValue); | ||
7948 | break; | ||
7949 | case "Version": | ||
7950 | version = this.core.GetAttributeVersionValue(sourceLineNumbers, attrib); | ||
7951 | break; | ||
7952 | default: | ||
7953 | this.core.UnexpectedAttribute(node, attrib); | ||
7954 | break; | ||
7955 | } | ||
7956 | } | ||
7957 | else | ||
7958 | { | ||
7959 | this.core.ParseExtensionAttribute(node, attrib); | ||
7960 | } | ||
7961 | } | ||
7962 | |||
7963 | if (null == this.activeName) | ||
7964 | { | ||
7965 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); | ||
7966 | } | ||
7967 | |||
7968 | if (null == this.activeLanguage) | ||
7969 | { | ||
7970 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Language")); | ||
7971 | } | ||
7972 | |||
7973 | if (null == version) | ||
7974 | { | ||
7975 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Version")); | ||
7976 | } | ||
7977 | else if (!CompilerCore.IsValidModuleOrBundleVersion(version)) | ||
7978 | { | ||
7979 | this.core.OnMessage(WixWarnings.InvalidModuleOrBundleVersion(sourceLineNumbers, "Module", version)); | ||
7980 | } | ||
7981 | |||
7982 | try | ||
7983 | { | ||
7984 | this.compilingModule = true; // notice that we are actually building a Merge Module here | ||
7985 | this.core.CreateActiveSection(this.activeName, SectionType.Module, codepage); | ||
7986 | |||
7987 | foreach (XElement child in node.Elements()) | ||
7988 | { | ||
7989 | if (CompilerCore.WixNamespace == child.Name.Namespace) | ||
7990 | { | ||
7991 | switch (child.Name.LocalName) | ||
7992 | { | ||
7993 | case "AdminExecuteSequence": | ||
7994 | case "AdminUISequence": | ||
7995 | case "AdvertiseExecuteSequence": | ||
7996 | case "InstallExecuteSequence": | ||
7997 | case "InstallUISequence": | ||
7998 | this.ParseSequenceElement(child, child.Name.LocalName); | ||
7999 | break; | ||
8000 | case "AppId": | ||
8001 | this.ParseAppIdElement(child, null, YesNoType.Yes, null, null, null); | ||
8002 | break; | ||
8003 | case "Binary": | ||
8004 | this.ParseBinaryElement(child); | ||
8005 | break; | ||
8006 | case "Component": | ||
8007 | this.ParseComponentElement(child, ComplexReferenceParentType.Module, this.activeName, this.activeLanguage, CompilerConstants.IntegerNotSet, null, null); | ||
8008 | break; | ||
8009 | case "ComponentGroupRef": | ||
8010 | this.ParseComponentGroupRefElement(child, ComplexReferenceParentType.Module, this.activeName, this.activeLanguage); | ||
8011 | break; | ||
8012 | case "ComponentRef": | ||
8013 | this.ParseComponentRefElement(child, ComplexReferenceParentType.Module, this.activeName, this.activeLanguage); | ||
8014 | break; | ||
8015 | case "Configuration": | ||
8016 | this.ParseConfigurationElement(child); | ||
8017 | break; | ||
8018 | case "CustomAction": | ||
8019 | this.ParseCustomActionElement(child); | ||
8020 | break; | ||
8021 | case "CustomActionRef": | ||
8022 | this.ParseSimpleRefElement(child, "CustomAction"); | ||
8023 | break; | ||
8024 | case "CustomTable": | ||
8025 | this.ParseCustomTableElement(child); | ||
8026 | break; | ||
8027 | case "Dependency": | ||
8028 | this.ParseDependencyElement(child); | ||
8029 | break; | ||
8030 | case "Directory": | ||
8031 | this.ParseDirectoryElement(child, null, CompilerConstants.IntegerNotSet, String.Empty); | ||
8032 | break; | ||
8033 | case "DirectoryRef": | ||
8034 | this.ParseDirectoryRefElement(child); | ||
8035 | break; | ||
8036 | case "EmbeddedChainer": | ||
8037 | this.ParseEmbeddedChainerElement(child); | ||
8038 | break; | ||
8039 | case "EmbeddedChainerRef": | ||
8040 | this.ParseSimpleRefElement(child, "MsiEmbeddedChainer"); | ||
8041 | break; | ||
8042 | case "EnsureTable": | ||
8043 | this.ParseEnsureTableElement(child); | ||
8044 | break; | ||
8045 | case "Exclusion": | ||
8046 | this.ParseExclusionElement(child); | ||
8047 | break; | ||
8048 | case "Icon": | ||
8049 | this.ParseIconElement(child); | ||
8050 | break; | ||
8051 | case "IgnoreModularization": | ||
8052 | this.ParseIgnoreModularizationElement(child); | ||
8053 | break; | ||
8054 | case "IgnoreTable": | ||
8055 | this.ParseIgnoreTableElement(child); | ||
8056 | break; | ||
8057 | case "Package": | ||
8058 | this.ParsePackageElement(child, null, moduleId); | ||
8059 | break; | ||
8060 | case "Property": | ||
8061 | this.ParsePropertyElement(child); | ||
8062 | break; | ||
8063 | case "PropertyRef": | ||
8064 | this.ParseSimpleRefElement(child, "Property"); | ||
8065 | break; | ||
8066 | case "SetDirectory": | ||
8067 | this.ParseSetDirectoryElement(child); | ||
8068 | break; | ||
8069 | case "SetProperty": | ||
8070 | this.ParseSetPropertyElement(child); | ||
8071 | break; | ||
8072 | case "SFPCatalog": | ||
8073 | string parentName = null; | ||
8074 | this.ParseSFPCatalogElement(child, ref parentName); | ||
8075 | break; | ||
8076 | case "Substitution": | ||
8077 | this.ParseSubstitutionElement(child); | ||
8078 | break; | ||
8079 | case "UI": | ||
8080 | this.ParseUIElement(child); | ||
8081 | break; | ||
8082 | case "UIRef": | ||
8083 | this.ParseSimpleRefElement(child, "WixUI"); | ||
8084 | break; | ||
8085 | case "WixVariable": | ||
8086 | this.ParseWixVariableElement(child); | ||
8087 | break; | ||
8088 | default: | ||
8089 | this.core.UnexpectedElement(node, child); | ||
8090 | break; | ||
8091 | } | ||
8092 | } | ||
8093 | else | ||
8094 | { | ||
8095 | this.core.ParseExtensionElement(node, child); | ||
8096 | } | ||
8097 | } | ||
8098 | |||
8099 | |||
8100 | if (!this.core.EncounteredError) | ||
8101 | { | ||
8102 | Row row = this.core.CreateRow(sourceLineNumbers, "ModuleSignature"); | ||
8103 | row[0] = this.activeName; | ||
8104 | row[1] = this.activeLanguage; | ||
8105 | row[2] = version; | ||
8106 | } | ||
8107 | } | ||
8108 | finally | ||
8109 | { | ||
8110 | this.compilingModule = false; // notice that we are no longer building a Merge Module here | ||
8111 | } | ||
8112 | } | ||
8113 | |||
8114 | /// <summary> | ||
8115 | /// Parses a patch creation element. | ||
8116 | /// </summary> | ||
8117 | /// <param name="node">The element to parse.</param> | ||
8118 | private void ParsePatchCreationElement(XElement node) | ||
8119 | { | ||
8120 | SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
8121 | bool clean = true; // Default is to clean | ||
8122 | int codepage = 0; | ||
8123 | string outputPath = null; | ||
8124 | bool productMismatches = false; | ||
8125 | string replaceGuids = String.Empty; | ||
8126 | string sourceList = null; | ||
8127 | string symbolFlags = null; | ||
8128 | string targetProducts = String.Empty; | ||
8129 | bool versionMismatches = false; | ||
8130 | bool wholeFiles = false; | ||
8131 | |||
8132 | foreach (XAttribute attrib in node.Attributes()) | ||
8133 | { | ||
8134 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
8135 | { | ||
8136 | switch (attrib.Name.LocalName) | ||
8137 | { | ||
8138 | case "Id": | ||
8139 | this.activeName = this.core.GetAttributeGuidValue(sourceLineNumbers, attrib, false); | ||
8140 | break; | ||
8141 | case "AllowMajorVersionMismatches": | ||
8142 | versionMismatches = YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
8143 | break; | ||
8144 | case "AllowProductCodeMismatches": | ||
8145 | productMismatches = YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
8146 | break; | ||
8147 | case "CleanWorkingFolder": | ||
8148 | clean = YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
8149 | break; | ||
8150 | case "Codepage": | ||
8151 | codepage = this.core.GetAttributeCodePageValue(sourceLineNumbers, attrib); | ||
8152 | break; | ||
8153 | case "OutputPath": | ||
8154 | outputPath = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
8155 | break; | ||
8156 | case "SourceList": | ||
8157 | sourceList = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
8158 | break; | ||
8159 | case "SymbolFlags": | ||
8160 | symbolFlags = String.Format(CultureInfo.InvariantCulture, "0x{0:x8}", this.core.GetAttributeLongValue(sourceLineNumbers, attrib, 0, uint.MaxValue)); | ||
8161 | break; | ||
8162 | case "WholeFilesOnly": | ||
8163 | wholeFiles = YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
8164 | break; | ||
8165 | default: | ||
8166 | this.core.UnexpectedAttribute(node, attrib); | ||
8167 | break; | ||
8168 | } | ||
8169 | } | ||
8170 | else | ||
8171 | { | ||
8172 | this.core.ParseExtensionAttribute(node, attrib); | ||
8173 | } | ||
8174 | } | ||
8175 | |||
8176 | if (null == this.activeName) | ||
8177 | { | ||
8178 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); | ||
8179 | } | ||
8180 | |||
8181 | this.core.CreateActiveSection(this.activeName, SectionType.PatchCreation, codepage); | ||
8182 | |||
8183 | foreach (XElement child in node.Elements()) | ||
8184 | { | ||
8185 | if (CompilerCore.WixNamespace == child.Name.Namespace) | ||
8186 | { | ||
8187 | switch (child.Name.LocalName) | ||
8188 | { | ||
8189 | case "Family": | ||
8190 | this.ParseFamilyElement(child); | ||
8191 | break; | ||
8192 | case "PatchInformation": | ||
8193 | this.ParsePatchInformationElement(child); | ||
8194 | break; | ||
8195 | case "PatchMetadata": | ||
8196 | this.ParsePatchMetadataElement(child); | ||
8197 | break; | ||
8198 | case "PatchProperty": | ||
8199 | this.ParsePatchPropertyElement(child, false); | ||
8200 | break; | ||
8201 | case "PatchSequence": | ||
8202 | this.ParsePatchSequenceElement(child); | ||
8203 | break; | ||
8204 | case "ReplacePatch": | ||
8205 | replaceGuids = String.Concat(replaceGuids, this.ParseReplacePatchElement(child)); | ||
8206 | break; | ||
8207 | case "TargetProductCode": | ||
8208 | string targetProduct = this.ParseTargetProductCodeElement(child); | ||
8209 | if (0 < targetProducts.Length) | ||
8210 | { | ||
8211 | targetProducts = String.Concat(targetProducts, ";"); | ||
8212 | } | ||
8213 | targetProducts = String.Concat(targetProducts, targetProduct); | ||
8214 | break; | ||
8215 | default: | ||
8216 | this.core.UnexpectedElement(node, child); | ||
8217 | break; | ||
8218 | } | ||
8219 | } | ||
8220 | else | ||
8221 | { | ||
8222 | this.core.ParseExtensionElement(node, child); | ||
8223 | } | ||
8224 | } | ||
8225 | |||
8226 | this.ProcessProperties(sourceLineNumbers, "PatchGUID", this.activeName); | ||
8227 | this.ProcessProperties(sourceLineNumbers, "AllowProductCodeMismatches", productMismatches ? "1" : "0"); | ||
8228 | this.ProcessProperties(sourceLineNumbers, "AllowProductVersionMajorMismatches", versionMismatches ? "1" : "0"); | ||
8229 | this.ProcessProperties(sourceLineNumbers, "DontRemoveTempFolderWhenFinished", clean ? "0" : "1"); | ||
8230 | this.ProcessProperties(sourceLineNumbers, "IncludeWholeFilesOnly", wholeFiles ? "1" : "0"); | ||
8231 | |||
8232 | if (null != symbolFlags) | ||
8233 | { | ||
8234 | this.ProcessProperties(sourceLineNumbers, "ApiPatchingSymbolFlags", symbolFlags); | ||
8235 | } | ||
8236 | |||
8237 | if (0 < replaceGuids.Length) | ||
8238 | { | ||
8239 | this.ProcessProperties(sourceLineNumbers, "ListOfPatchGUIDsToReplace", replaceGuids); | ||
8240 | } | ||
8241 | |||
8242 | if (0 < targetProducts.Length) | ||
8243 | { | ||
8244 | this.ProcessProperties(sourceLineNumbers, "ListOfTargetProductCodes", targetProducts); | ||
8245 | } | ||
8246 | |||
8247 | if (null != outputPath) | ||
8248 | { | ||
8249 | this.ProcessProperties(sourceLineNumbers, "PatchOutputPath", outputPath); | ||
8250 | } | ||
8251 | |||
8252 | if (null != sourceList) | ||
8253 | { | ||
8254 | this.ProcessProperties(sourceLineNumbers, "PatchSourceList", sourceList); | ||
8255 | } | ||
8256 | } | ||
8257 | |||
8258 | /// <summary> | ||
8259 | /// Parses a family element. | ||
8260 | /// </summary> | ||
8261 | /// <param name="node">The element to parse.</param> | ||
8262 | private void ParseFamilyElement(XElement node) | ||
8263 | { | ||
8264 | SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
8265 | int diskId = CompilerConstants.IntegerNotSet; | ||
8266 | string diskPrompt = null; | ||
8267 | string mediaSrcProp = null; | ||
8268 | string name = null; | ||
8269 | int sequenceStart = CompilerConstants.IntegerNotSet; | ||
8270 | string volumeLabel = null; | ||
8271 | |||
8272 | foreach (XAttribute attrib in node.Attributes()) | ||
8273 | { | ||
8274 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
8275 | { | ||
8276 | switch (attrib.Name.LocalName) | ||
8277 | { | ||
8278 | case "DiskId": | ||
8279 | diskId = this.core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 1, short.MaxValue); | ||
8280 | break; | ||
8281 | case "DiskPrompt": | ||
8282 | diskPrompt = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
8283 | break; | ||
8284 | case "MediaSrcProp": | ||
8285 | mediaSrcProp = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
8286 | break; | ||
8287 | case "Name": | ||
8288 | name = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
8289 | break; | ||
8290 | case "SequenceStart": | ||
8291 | sequenceStart = this.core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 1, int.MaxValue); | ||
8292 | break; | ||
8293 | case "VolumeLabel": | ||
8294 | volumeLabel = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
8295 | break; | ||
8296 | default: | ||
8297 | this.core.UnexpectedAttribute(node, attrib); | ||
8298 | break; | ||
8299 | } | ||
8300 | } | ||
8301 | else | ||
8302 | { | ||
8303 | this.core.ParseExtensionAttribute(node, attrib); | ||
8304 | } | ||
8305 | } | ||
8306 | |||
8307 | if (null == name) | ||
8308 | { | ||
8309 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name")); | ||
8310 | } | ||
8311 | else if (0 < name.Length) | ||
8312 | { | ||
8313 | if (8 < name.Length) // check the length | ||
8314 | { | ||
8315 | this.core.OnMessage(WixErrors.FamilyNameTooLong(sourceLineNumbers, node.Name.LocalName, "Name", name, name.Length)); | ||
8316 | } | ||
8317 | else // check for illegal characters | ||
8318 | { | ||
8319 | foreach (char character in name) | ||
8320 | { | ||
8321 | if (!Char.IsLetterOrDigit(character) && '_' != character) | ||
8322 | { | ||
8323 | this.core.OnMessage(WixErrors.IllegalFamilyName(sourceLineNumbers, node.Name.LocalName, "Name", name)); | ||
8324 | } | ||
8325 | } | ||
8326 | } | ||
8327 | } | ||
8328 | |||
8329 | foreach (XElement child in node.Elements()) | ||
8330 | { | ||
8331 | if (CompilerCore.WixNamespace == child.Name.Namespace) | ||
8332 | { | ||
8333 | switch (child.Name.LocalName) | ||
8334 | { | ||
8335 | case "UpgradeImage": | ||
8336 | this.ParseUpgradeImageElement(child, name); | ||
8337 | break; | ||
8338 | case "ExternalFile": | ||
8339 | this.ParseExternalFileElement(child, name); | ||
8340 | break; | ||
8341 | case "ProtectFile": | ||
8342 | this.ParseProtectFileElement(child, name); | ||
8343 | break; | ||
8344 | default: | ||
8345 | this.core.UnexpectedElement(node, child); | ||
8346 | break; | ||
8347 | } | ||
8348 | } | ||
8349 | else | ||
8350 | { | ||
8351 | this.core.ParseExtensionElement(node, child); | ||
8352 | } | ||
8353 | } | ||
8354 | |||
8355 | if (!this.core.EncounteredError) | ||
8356 | { | ||
8357 | Row row = this.core.CreateRow(sourceLineNumbers, "ImageFamilies"); | ||
8358 | row[0] = name; | ||
8359 | row[1] = mediaSrcProp; | ||
8360 | if (CompilerConstants.IntegerNotSet != diskId) | ||
8361 | { | ||
8362 | row[2] = diskId; | ||
8363 | } | ||
8364 | |||
8365 | if (CompilerConstants.IntegerNotSet != sequenceStart) | ||
8366 | { | ||
8367 | row[3] = sequenceStart; | ||
8368 | } | ||
8369 | row[4] = diskPrompt; | ||
8370 | row[5] = volumeLabel; | ||
8371 | } | ||
8372 | } | ||
8373 | |||
8374 | /// <summary> | ||
8375 | /// Parses an upgrade image element. | ||
8376 | /// </summary> | ||
8377 | /// <param name="node">The element to parse.</param> | ||
8378 | /// <param name="family">The family for this element.</param> | ||
8379 | private void ParseUpgradeImageElement(XElement node, string family) | ||
8380 | { | ||
8381 | SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
8382 | string sourceFile = null; | ||
8383 | string sourcePatch = null; | ||
8384 | List<string> symbols = new List<string>(); | ||
8385 | string upgrade = null; | ||
8386 | |||
8387 | foreach (XAttribute attrib in node.Attributes()) | ||
8388 | { | ||
8389 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
8390 | { | ||
8391 | switch (attrib.Name.LocalName) | ||
8392 | { | ||
8393 | case "Id": | ||
8394 | upgrade = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
8395 | if (13 < upgrade.Length) | ||
8396 | { | ||
8397 | this.core.OnMessage(WixErrors.IdentifierTooLongError(sourceLineNumbers, node.Name.LocalName, "Id", upgrade, 13)); | ||
8398 | } | ||
8399 | break; | ||
8400 | case "SourceFile": | ||
8401 | case "src": | ||
8402 | if (null != sourceFile) | ||
8403 | { | ||
8404 | this.core.OnMessage(WixErrors.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "src", "SourceFile")); | ||
8405 | } | ||
8406 | |||
8407 | if ("src" == attrib.Name.LocalName) | ||
8408 | { | ||
8409 | this.core.OnMessage(WixWarnings.DeprecatedAttribute(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "SourceFile")); | ||
8410 | } | ||
8411 | sourceFile = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
8412 | break; | ||
8413 | case "SourcePatch": | ||
8414 | case "srcPatch": | ||
8415 | if (null != sourcePatch) | ||
8416 | { | ||
8417 | this.core.OnMessage(WixErrors.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "srcPatch", "SourcePatch")); | ||
8418 | } | ||
8419 | |||
8420 | if ("srcPatch" == attrib.Name.LocalName) | ||
8421 | { | ||
8422 | this.core.OnMessage(WixWarnings.DeprecatedAttribute(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "SourcePatch")); | ||
8423 | } | ||
8424 | sourcePatch = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
8425 | break; | ||
8426 | default: | ||
8427 | this.core.UnexpectedAttribute(node, attrib); | ||
8428 | break; | ||
8429 | } | ||
8430 | } | ||
8431 | else | ||
8432 | { | ||
8433 | this.core.ParseExtensionAttribute(node, attrib); | ||
8434 | } | ||
8435 | } | ||
8436 | |||
8437 | if (null == upgrade) | ||
8438 | { | ||
8439 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); | ||
8440 | } | ||
8441 | |||
8442 | if (null == sourceFile) | ||
8443 | { | ||
8444 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "SourceFile")); | ||
8445 | } | ||
8446 | |||
8447 | foreach (XElement child in node.Elements()) | ||
8448 | { | ||
8449 | if (CompilerCore.WixNamespace == child.Name.Namespace) | ||
8450 | { | ||
8451 | switch (child.Name.LocalName) | ||
8452 | { | ||
8453 | case "SymbolPath": | ||
8454 | symbols.Add(this.ParseSymbolPathElement(child)); | ||
8455 | break; | ||
8456 | case "TargetImage": | ||
8457 | this.ParseTargetImageElement(child, upgrade, family); | ||
8458 | break; | ||
8459 | case "UpgradeFile": | ||
8460 | this.ParseUpgradeFileElement(child, upgrade); | ||
8461 | break; | ||
8462 | default: | ||
8463 | this.core.UnexpectedElement(node, child); | ||
8464 | break; | ||
8465 | } | ||
8466 | } | ||
8467 | else | ||
8468 | { | ||
8469 | this.core.ParseExtensionElement(node, child); | ||
8470 | } | ||
8471 | } | ||
8472 | |||
8473 | if (!this.core.EncounteredError) | ||
8474 | { | ||
8475 | Row row = this.core.CreateRow(sourceLineNumbers, "UpgradedImages"); | ||
8476 | row[0] = upgrade; | ||
8477 | row[1] = sourceFile; | ||
8478 | row[2] = sourcePatch; | ||
8479 | row[3] = String.Join(";", symbols); | ||
8480 | row[4] = family; | ||
8481 | } | ||
8482 | } | ||
8483 | |||
8484 | /// <summary> | ||
8485 | /// Parses an upgrade file element. | ||
8486 | /// </summary> | ||
8487 | /// <param name="node">The element to parse.</param> | ||
8488 | /// <param name="upgrade">The upgrade key for this element.</param> | ||
8489 | private void ParseUpgradeFileElement(XElement node, string upgrade) | ||
8490 | { | ||
8491 | SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
8492 | bool allowIgnoreOnError = false; | ||
8493 | string file = null; | ||
8494 | bool ignore = false; | ||
8495 | List<string> symbols = new List<string>(); | ||
8496 | bool wholeFile = false; | ||
8497 | |||
8498 | foreach (XAttribute attrib in node.Attributes()) | ||
8499 | { | ||
8500 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
8501 | { | ||
8502 | switch (attrib.Name.LocalName) | ||
8503 | { | ||
8504 | case "AllowIgnoreOnError": | ||
8505 | allowIgnoreOnError = YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
8506 | break; | ||
8507 | case "File": | ||
8508 | file = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
8509 | break; | ||
8510 | case "Ignore": | ||
8511 | ignore = YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
8512 | break; | ||
8513 | case "WholeFile": | ||
8514 | wholeFile = YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
8515 | break; | ||
8516 | default: | ||
8517 | this.core.UnexpectedAttribute(node, attrib); | ||
8518 | break; | ||
8519 | } | ||
8520 | } | ||
8521 | else | ||
8522 | { | ||
8523 | this.core.ParseExtensionAttribute(node, attrib); | ||
8524 | } | ||
8525 | } | ||
8526 | |||
8527 | if (null == file) | ||
8528 | { | ||
8529 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "File")); | ||
8530 | } | ||
8531 | |||
8532 | foreach (XElement child in node.Elements()) | ||
8533 | { | ||
8534 | if (CompilerCore.WixNamespace == child.Name.Namespace) | ||
8535 | { | ||
8536 | switch (child.Name.LocalName) | ||
8537 | { | ||
8538 | case "SymbolPath": | ||
8539 | symbols.Add(this.ParseSymbolPathElement(child)); | ||
8540 | break; | ||
8541 | default: | ||
8542 | this.core.UnexpectedElement(node, child); | ||
8543 | break; | ||
8544 | } | ||
8545 | } | ||
8546 | else | ||
8547 | { | ||
8548 | this.core.ParseExtensionElement(node, child); | ||
8549 | } | ||
8550 | } | ||
8551 | |||
8552 | if (!this.core.EncounteredError) | ||
8553 | { | ||
8554 | if (ignore) | ||
8555 | { | ||
8556 | Row row = this.core.CreateRow(sourceLineNumbers, "UpgradedFilesToIgnore"); | ||
8557 | row[0] = upgrade; | ||
8558 | row[1] = file; | ||
8559 | } | ||
8560 | else | ||
8561 | { | ||
8562 | Row row = this.core.CreateRow(sourceLineNumbers, "UpgradedFiles_OptionalData"); | ||
8563 | row[0] = upgrade; | ||
8564 | row[1] = file; | ||
8565 | row[2] = String.Join(";", symbols); | ||
8566 | row[3] = allowIgnoreOnError ? 1 : 0; | ||
8567 | row[4] = wholeFile ? 1 : 0; | ||
8568 | } | ||
8569 | } | ||
8570 | } | ||
8571 | |||
8572 | /// <summary> | ||
8573 | /// Parses a target image element. | ||
8574 | /// </summary> | ||
8575 | /// <param name="node">The element to parse.</param> | ||
8576 | /// <param name="upgrade">The upgrade key for this element.</param> | ||
8577 | /// <param name="family">The family key for this element.</param> | ||
8578 | private void ParseTargetImageElement(XElement node, string upgrade, string family) | ||
8579 | { | ||
8580 | SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
8581 | bool ignore = false; | ||
8582 | int order = CompilerConstants.IntegerNotSet; | ||
8583 | string sourceFile = null; | ||
8584 | string symbols = null; | ||
8585 | string target = null; | ||
8586 | string validation = null; | ||
8587 | |||
8588 | foreach (XAttribute attrib in node.Attributes()) | ||
8589 | { | ||
8590 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
8591 | { | ||
8592 | switch (attrib.Name.LocalName) | ||
8593 | { | ||
8594 | case "Id": | ||
8595 | target = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
8596 | if (target.Length > 13) | ||
8597 | { | ||
8598 | this.core.OnMessage(WixErrors.IdentifierTooLongError(sourceLineNumbers, node.Name.LocalName, "Id", target, 13)); | ||
8599 | } | ||
8600 | break; | ||
8601 | case "IgnoreMissingFiles": | ||
8602 | ignore = YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
8603 | break; | ||
8604 | case "Order": | ||
8605 | order = this.core.GetAttributeIntegerValue(sourceLineNumbers, attrib, int.MinValue + 2, int.MaxValue); | ||
8606 | break; | ||
8607 | case "SourceFile": | ||
8608 | case "src": | ||
8609 | if (null != sourceFile) | ||
8610 | { | ||
8611 | this.core.OnMessage(WixErrors.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "src", "SourceFile")); | ||
8612 | } | ||
8613 | |||
8614 | if ("src" == attrib.Name.LocalName) | ||
8615 | { | ||
8616 | this.core.OnMessage(WixWarnings.DeprecatedAttribute(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "SourceFile")); | ||
8617 | } | ||
8618 | sourceFile = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
8619 | break; | ||
8620 | case "Validation": | ||
8621 | validation = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
8622 | break; | ||
8623 | default: | ||
8624 | this.core.UnexpectedAttribute(node, attrib); | ||
8625 | break; | ||
8626 | } | ||
8627 | } | ||
8628 | else | ||
8629 | { | ||
8630 | this.core.ParseExtensionAttribute(node, attrib); | ||
8631 | } | ||
8632 | } | ||
8633 | |||
8634 | if (null == target) | ||
8635 | { | ||
8636 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); | ||
8637 | } | ||
8638 | |||
8639 | if (null == sourceFile) | ||
8640 | { | ||
8641 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "SourceFile")); | ||
8642 | } | ||
8643 | |||
8644 | if (CompilerConstants.IntegerNotSet == order) | ||
8645 | { | ||
8646 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Order")); | ||
8647 | } | ||
8648 | |||
8649 | foreach (XElement child in node.Elements()) | ||
8650 | { | ||
8651 | if (CompilerCore.WixNamespace == child.Name.Namespace) | ||
8652 | { | ||
8653 | switch (child.Name.LocalName) | ||
8654 | { | ||
8655 | case "SymbolPath": | ||
8656 | if (null != symbols) | ||
8657 | { | ||
8658 | symbols = String.Concat(symbols, ";", this.ParseSymbolPathElement(child)); | ||
8659 | } | ||
8660 | else | ||
8661 | { | ||
8662 | symbols = this.ParseSymbolPathElement(child); | ||
8663 | } | ||
8664 | break; | ||
8665 | case "TargetFile": | ||
8666 | this.ParseTargetFileElement(child, target, family); | ||
8667 | break; | ||
8668 | default: | ||
8669 | this.core.UnexpectedElement(node, child); | ||
8670 | break; | ||
8671 | } | ||
8672 | } | ||
8673 | else | ||
8674 | { | ||
8675 | this.core.ParseExtensionElement(node, child); | ||
8676 | } | ||
8677 | } | ||
8678 | |||
8679 | if (!this.core.EncounteredError) | ||
8680 | { | ||
8681 | Row row = this.core.CreateRow(sourceLineNumbers, "TargetImages"); | ||
8682 | row[0] = target; | ||
8683 | row[1] = sourceFile; | ||
8684 | row[2] = symbols; | ||
8685 | row[3] = upgrade; | ||
8686 | row[4] = order; | ||
8687 | row[5] = validation; | ||
8688 | row[6] = ignore ? 1 : 0; | ||
8689 | } | ||
8690 | } | ||
8691 | |||
8692 | /// <summary> | ||
8693 | /// Parses an upgrade file element. | ||
8694 | /// </summary> | ||
8695 | /// <param name="node">The element to parse.</param> | ||
8696 | /// <param name="target">The upgrade key for this element.</param> | ||
8697 | /// <param name="family">The family key for this element.</param> | ||
8698 | private void ParseTargetFileElement(XElement node, string target, string family) | ||
8699 | { | ||
8700 | SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
8701 | string file = null; | ||
8702 | string ignoreLengths = null; | ||
8703 | string ignoreOffsets = null; | ||
8704 | string protectLengths = null; | ||
8705 | string protectOffsets = null; | ||
8706 | string symbols = null; | ||
8707 | |||
8708 | foreach (XAttribute attrib in node.Attributes()) | ||
8709 | { | ||
8710 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
8711 | { | ||
8712 | switch (attrib.Name.LocalName) | ||
8713 | { | ||
8714 | case "Id": | ||
8715 | file = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
8716 | break; | ||
8717 | default: | ||
8718 | this.core.UnexpectedAttribute(node, attrib); | ||
8719 | break; | ||
8720 | } | ||
8721 | } | ||
8722 | else | ||
8723 | { | ||
8724 | this.core.ParseExtensionAttribute(node, attrib); | ||
8725 | } | ||
8726 | } | ||
8727 | |||
8728 | if (null == file) | ||
8729 | { | ||
8730 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); | ||
8731 | } | ||
8732 | |||
8733 | foreach (XElement child in node.Elements()) | ||
8734 | { | ||
8735 | if (CompilerCore.WixNamespace == child.Name.Namespace) | ||
8736 | { | ||
8737 | switch (child.Name.LocalName) | ||
8738 | { | ||
8739 | case "IgnoreRange": | ||
8740 | this.ParseRangeElement(child, ref ignoreOffsets, ref ignoreLengths); | ||
8741 | break; | ||
8742 | case "ProtectRange": | ||
8743 | this.ParseRangeElement(child, ref protectOffsets, ref protectLengths); | ||
8744 | break; | ||
8745 | case "SymbolPath": | ||
8746 | symbols = this.ParseSymbolPathElement(child); | ||
8747 | break; | ||
8748 | default: | ||
8749 | this.core.UnexpectedElement(node, child); | ||
8750 | break; | ||
8751 | } | ||
8752 | } | ||
8753 | else | ||
8754 | { | ||
8755 | this.core.ParseExtensionElement(node, child); | ||
8756 | } | ||
8757 | } | ||
8758 | |||
8759 | if (!this.core.EncounteredError) | ||
8760 | { | ||
8761 | Row row = this.core.CreateRow(sourceLineNumbers, "TargetFiles_OptionalData"); | ||
8762 | row[0] = target; | ||
8763 | row[1] = file; | ||
8764 | row[2] = symbols; | ||
8765 | row[3] = ignoreOffsets; | ||
8766 | row[4] = ignoreLengths; | ||
8767 | |||
8768 | if (null != protectOffsets) | ||
8769 | { | ||
8770 | row[5] = protectOffsets; | ||
8771 | |||
8772 | Row row2 = this.core.CreateRow(sourceLineNumbers, "FamilyFileRanges"); | ||
8773 | row2[0] = family; | ||
8774 | row2[1] = file; | ||
8775 | row2[2] = protectOffsets; | ||
8776 | row2[3] = protectLengths; | ||
8777 | } | ||
8778 | } | ||
8779 | } | ||
8780 | |||
8781 | /// <summary> | ||
8782 | /// Parses an external file element. | ||
8783 | /// </summary> | ||
8784 | /// <param name="node">The element to parse.</param> | ||
8785 | /// <param name="family">The family for this element.</param> | ||
8786 | private void ParseExternalFileElement(XElement node, string family) | ||
8787 | { | ||
8788 | SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
8789 | string file = null; | ||
8790 | string ignoreLengths = null; | ||
8791 | string ignoreOffsets = null; | ||
8792 | int order = CompilerConstants.IntegerNotSet; | ||
8793 | string protectLengths = null; | ||
8794 | string protectOffsets = null; | ||
8795 | string source = null; | ||
8796 | string symbols = null; | ||
8797 | |||
8798 | foreach (XAttribute attrib in node.Attributes()) | ||
8799 | { | ||
8800 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
8801 | { | ||
8802 | switch (attrib.Name.LocalName) | ||
8803 | { | ||
8804 | case "File": | ||
8805 | file = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
8806 | break; | ||
8807 | case "Order": | ||
8808 | order = this.core.GetAttributeIntegerValue(sourceLineNumbers, attrib, int.MinValue + 2, int.MaxValue); | ||
8809 | break; | ||
8810 | case "Source": | ||
8811 | case "src": | ||
8812 | if (null != source) | ||
8813 | { | ||
8814 | this.core.OnMessage(WixErrors.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "src", "Source")); | ||
8815 | } | ||
8816 | |||
8817 | if ("src" == attrib.Name.LocalName) | ||
8818 | { | ||
8819 | this.core.OnMessage(WixWarnings.DeprecatedAttribute(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "Source")); | ||
8820 | } | ||
8821 | source = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
8822 | break; | ||
8823 | default: | ||
8824 | this.core.UnexpectedAttribute(node, attrib); | ||
8825 | break; | ||
8826 | } | ||
8827 | } | ||
8828 | else | ||
8829 | { | ||
8830 | this.core.ParseExtensionAttribute(node, attrib); | ||
8831 | } | ||
8832 | } | ||
8833 | |||
8834 | if (null == file) | ||
8835 | { | ||
8836 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "File")); | ||
8837 | } | ||
8838 | |||
8839 | if (null == source) | ||
8840 | { | ||
8841 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Source")); | ||
8842 | } | ||
8843 | |||
8844 | if (CompilerConstants.IntegerNotSet == order) | ||
8845 | { | ||
8846 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Order")); | ||
8847 | } | ||
8848 | |||
8849 | foreach (XElement child in node.Elements()) | ||
8850 | { | ||
8851 | if (CompilerCore.WixNamespace == child.Name.Namespace) | ||
8852 | { | ||
8853 | switch (child.Name.LocalName) | ||
8854 | { | ||
8855 | case "IgnoreRange": | ||
8856 | this.ParseRangeElement(child, ref ignoreOffsets, ref ignoreLengths); | ||
8857 | break; | ||
8858 | case "ProtectRange": | ||
8859 | this.ParseRangeElement(child, ref protectOffsets, ref protectLengths); | ||
8860 | break; | ||
8861 | case "SymbolPath": | ||
8862 | symbols = this.ParseSymbolPathElement(child); | ||
8863 | break; | ||
8864 | default: | ||
8865 | this.core.UnexpectedElement(node, child); | ||
8866 | break; | ||
8867 | } | ||
8868 | } | ||
8869 | else | ||
8870 | { | ||
8871 | this.core.ParseExtensionElement(node, child); | ||
8872 | } | ||
8873 | } | ||
8874 | |||
8875 | if (!this.core.EncounteredError) | ||
8876 | { | ||
8877 | Row row = this.core.CreateRow(sourceLineNumbers, "ExternalFiles"); | ||
8878 | row[0] = family; | ||
8879 | row[1] = file; | ||
8880 | row[2] = source; | ||
8881 | row[3] = symbols; | ||
8882 | row[4] = ignoreOffsets; | ||
8883 | row[5] = ignoreLengths; | ||
8884 | if (null != protectOffsets) | ||
8885 | { | ||
8886 | row[6] = protectOffsets; | ||
8887 | } | ||
8888 | |||
8889 | if (CompilerConstants.IntegerNotSet != order) | ||
8890 | { | ||
8891 | row[7] = order; | ||
8892 | } | ||
8893 | |||
8894 | if (null != protectOffsets) | ||
8895 | { | ||
8896 | Row row2 = this.core.CreateRow(sourceLineNumbers, "FamilyFileRanges"); | ||
8897 | row2[0] = family; | ||
8898 | row2[1] = file; | ||
8899 | row2[2] = protectOffsets; | ||
8900 | row2[3] = protectLengths; | ||
8901 | } | ||
8902 | } | ||
8903 | } | ||
8904 | |||
8905 | /// <summary> | ||
8906 | /// Parses a protect file element. | ||
8907 | /// </summary> | ||
8908 | /// <param name="node">The element to parse.</param> | ||
8909 | /// <param name="family">The family for this element.</param> | ||
8910 | private void ParseProtectFileElement(XElement node, string family) | ||
8911 | { | ||
8912 | SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
8913 | string file = null; | ||
8914 | string protectLengths = null; | ||
8915 | string protectOffsets = null; | ||
8916 | |||
8917 | foreach (XAttribute attrib in node.Attributes()) | ||
8918 | { | ||
8919 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
8920 | { | ||
8921 | switch (attrib.Name.LocalName) | ||
8922 | { | ||
8923 | case "File": | ||
8924 | file = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
8925 | break; | ||
8926 | default: | ||
8927 | this.core.UnexpectedAttribute(node, attrib); | ||
8928 | break; | ||
8929 | } | ||
8930 | } | ||
8931 | else | ||
8932 | { | ||
8933 | this.core.ParseExtensionAttribute(node, attrib); | ||
8934 | } | ||
8935 | } | ||
8936 | |||
8937 | if (null == file) | ||
8938 | { | ||
8939 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "File")); | ||
8940 | } | ||
8941 | |||
8942 | foreach (XElement child in node.Elements()) | ||
8943 | { | ||
8944 | if (CompilerCore.WixNamespace == child.Name.Namespace) | ||
8945 | { | ||
8946 | switch (child.Name.LocalName) | ||
8947 | { | ||
8948 | case "ProtectRange": | ||
8949 | this.ParseRangeElement(child, ref protectOffsets, ref protectLengths); | ||
8950 | break; | ||
8951 | default: | ||
8952 | this.core.UnexpectedElement(node, child); | ||
8953 | break; | ||
8954 | } | ||
8955 | } | ||
8956 | else | ||
8957 | { | ||
8958 | this.core.ParseExtensionElement(node, child); | ||
8959 | } | ||
8960 | } | ||
8961 | |||
8962 | if (null == protectOffsets || null == protectLengths) | ||
8963 | { | ||
8964 | this.core.OnMessage(WixErrors.ExpectedElement(sourceLineNumbers, node.Name.LocalName, "ProtectRange")); | ||
8965 | } | ||
8966 | |||
8967 | if (!this.core.EncounteredError) | ||
8968 | { | ||
8969 | Row row = this.core.CreateRow(sourceLineNumbers, "FamilyFileRanges"); | ||
8970 | row[0] = family; | ||
8971 | row[1] = file; | ||
8972 | row[2] = protectOffsets; | ||
8973 | row[3] = protectLengths; | ||
8974 | } | ||
8975 | } | ||
8976 | |||
8977 | /// <summary> | ||
8978 | /// Parses a range element (ProtectRange, IgnoreRange, etc). | ||
8979 | /// </summary> | ||
8980 | /// <param name="node">The element to parse.</param> | ||
8981 | /// <param name="offsets">Reference to the offsets string.</param> | ||
8982 | /// <param name="lengths">Reference to the lengths string.</param> | ||
8983 | private void ParseRangeElement(XElement node, ref string offsets, ref string lengths) | ||
8984 | { | ||
8985 | SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
8986 | string length = null; | ||
8987 | string offset = null; | ||
8988 | |||
8989 | foreach (XAttribute attrib in node.Attributes()) | ||
8990 | { | ||
8991 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
8992 | { | ||
8993 | switch (attrib.Name.LocalName) | ||
8994 | { | ||
8995 | case "Length": | ||
8996 | length = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
8997 | break; | ||
8998 | case "Offset": | ||
8999 | offset = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
9000 | break; | ||
9001 | default: | ||
9002 | this.core.UnexpectedAttribute(node, attrib); | ||
9003 | break; | ||
9004 | } | ||
9005 | } | ||
9006 | else | ||
9007 | { | ||
9008 | this.core.ParseExtensionAttribute(node, attrib); | ||
9009 | } | ||
9010 | } | ||
9011 | |||
9012 | if (null == length) | ||
9013 | { | ||
9014 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Length")); | ||
9015 | } | ||
9016 | |||
9017 | if (null == offset) | ||
9018 | { | ||
9019 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Offset")); | ||
9020 | } | ||
9021 | |||
9022 | this.core.ParseForExtensionElements(node); | ||
9023 | |||
9024 | if (null != lengths) | ||
9025 | { | ||
9026 | lengths = String.Concat(lengths, ",", length); | ||
9027 | } | ||
9028 | else | ||
9029 | { | ||
9030 | lengths = length; | ||
9031 | } | ||
9032 | |||
9033 | if (null != offsets) | ||
9034 | { | ||
9035 | offsets = String.Concat(offsets, ",", offset); | ||
9036 | } | ||
9037 | else | ||
9038 | { | ||
9039 | offsets = offset; | ||
9040 | } | ||
9041 | } | ||
9042 | |||
9043 | /// <summary> | ||
9044 | /// Parses a patch property element. | ||
9045 | /// </summary> | ||
9046 | /// <param name="node">The element to parse.</param> | ||
9047 | /// <param name="patch">True if parsing an patch element.</param> | ||
9048 | private void ParsePatchPropertyElement(XElement node, bool patch) | ||
9049 | { | ||
9050 | SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
9051 | string name = null; | ||
9052 | string company = null; | ||
9053 | string value = null; | ||
9054 | |||
9055 | foreach (XAttribute attrib in node.Attributes()) | ||
9056 | { | ||
9057 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
9058 | { | ||
9059 | switch (attrib.Name.LocalName) | ||
9060 | { | ||
9061 | case "Id": | ||
9062 | case "Name": | ||
9063 | name = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
9064 | break; | ||
9065 | case "Company": | ||
9066 | company = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
9067 | break; | ||
9068 | case "Value": | ||
9069 | value = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
9070 | break; | ||
9071 | default: | ||
9072 | this.core.UnexpectedAttribute(node, attrib); | ||
9073 | break; | ||
9074 | } | ||
9075 | } | ||
9076 | else | ||
9077 | { | ||
9078 | this.core.ParseExtensionAttribute(node, attrib); | ||
9079 | } | ||
9080 | } | ||
9081 | |||
9082 | if (null == name) | ||
9083 | { | ||
9084 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name")); | ||
9085 | } | ||
9086 | |||
9087 | if (null == value) | ||
9088 | { | ||
9089 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Value")); | ||
9090 | } | ||
9091 | |||
9092 | this.core.ParseForExtensionElements(node); | ||
9093 | |||
9094 | if (patch) | ||
9095 | { | ||
9096 | // /Patch/PatchProperty goes directly into MsiPatchMetadata table | ||
9097 | Row row = this.core.CreateRow(sourceLineNumbers, "MsiPatchMetadata"); | ||
9098 | row[0] = company; | ||
9099 | row[1] = name; | ||
9100 | row[2] = value; | ||
9101 | } | ||
9102 | else | ||
9103 | { | ||
9104 | if (null != company) | ||
9105 | { | ||
9106 | this.core.OnMessage(WixErrors.UnexpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Company")); | ||
9107 | } | ||
9108 | this.ProcessProperties(sourceLineNumbers, name, value); | ||
9109 | } | ||
9110 | } | ||
9111 | |||
9112 | /// <summary> | ||
9113 | /// Parses a patch sequence element. | ||
9114 | /// </summary> | ||
9115 | /// <param name="node">The element to parse.</param> | ||
9116 | private void ParsePatchSequenceElement(XElement node) | ||
9117 | { | ||
9118 | SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
9119 | string family = null; | ||
9120 | string target = null; | ||
9121 | string sequence = null; | ||
9122 | int attributes = 0; | ||
9123 | |||
9124 | foreach (XAttribute attrib in node.Attributes()) | ||
9125 | { | ||
9126 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
9127 | { | ||
9128 | switch (attrib.Name.LocalName) | ||
9129 | { | ||
9130 | case "PatchFamily": | ||
9131 | family = this.core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); | ||
9132 | break; | ||
9133 | case "ProductCode": | ||
9134 | if (null != target) | ||
9135 | { | ||
9136 | this.core.OnMessage(WixErrors.IllegalAttributeWithOtherAttributes(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "Target", "TargetImage")); | ||
9137 | } | ||
9138 | target = this.core.GetAttributeGuidValue(sourceLineNumbers, attrib, false); | ||
9139 | break; | ||
9140 | case "Target": | ||
9141 | if (null != target) | ||
9142 | { | ||
9143 | this.core.OnMessage(WixErrors.IllegalAttributeWithOtherAttributes(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "TargetImage", "ProductCode")); | ||
9144 | } | ||
9145 | this.core.OnMessage(WixWarnings.DeprecatedPatchSequenceTargetAttribute(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName)); | ||
9146 | target = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
9147 | break; | ||
9148 | case "TargetImage": | ||
9149 | if (null != target) | ||
9150 | { | ||
9151 | this.core.OnMessage(WixErrors.IllegalAttributeWithOtherAttributes(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "Target", "ProductCode")); | ||
9152 | } | ||
9153 | target = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
9154 | this.core.CreateSimpleReference(sourceLineNumbers, "TargetImages", target); | ||
9155 | break; | ||
9156 | case "Sequence": | ||
9157 | sequence = this.core.GetAttributeVersionValue(sourceLineNumbers, attrib); | ||
9158 | break; | ||
9159 | case "Supersede": | ||
9160 | if (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) | ||
9161 | { | ||
9162 | attributes |= 0x1; | ||
9163 | } | ||
9164 | break; | ||
9165 | default: | ||
9166 | this.core.UnexpectedAttribute(node, attrib); | ||
9167 | break; | ||
9168 | } | ||
9169 | } | ||
9170 | else | ||
9171 | { | ||
9172 | this.core.ParseExtensionAttribute(node, attrib); | ||
9173 | } | ||
9174 | } | ||
9175 | |||
9176 | if (null == family) | ||
9177 | { | ||
9178 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "PatchFamily")); | ||
9179 | } | ||
9180 | |||
9181 | this.core.ParseForExtensionElements(node); | ||
9182 | |||
9183 | if (!this.core.EncounteredError) | ||
9184 | { | ||
9185 | Row row = this.core.CreateRow(sourceLineNumbers, "PatchSequence"); | ||
9186 | row[0] = family; | ||
9187 | row[1] = target; | ||
9188 | if (!String.IsNullOrEmpty(sequence)) | ||
9189 | { | ||
9190 | row[2] = sequence; | ||
9191 | } | ||
9192 | row[3] = attributes; | ||
9193 | } | ||
9194 | } | ||
9195 | |||
9196 | /// <summary> | ||
9197 | /// Parses a TargetProductCode element. | ||
9198 | /// </summary> | ||
9199 | /// <param name="node">The element to parse.</param> | ||
9200 | /// <returns>The id from the node.</returns> | ||
9201 | private string ParseTargetProductCodeElement(XElement node) | ||
9202 | { | ||
9203 | SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
9204 | string id = null; | ||
9205 | |||
9206 | foreach (XAttribute attrib in node.Attributes()) | ||
9207 | { | ||
9208 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
9209 | { | ||
9210 | switch (attrib.Name.LocalName) | ||
9211 | { | ||
9212 | case "Id": | ||
9213 | id = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
9214 | if (id.Length > 0 && "*" != id) | ||
9215 | { | ||
9216 | id = this.core.GetAttributeGuidValue(sourceLineNumbers, attrib, false); | ||
9217 | } | ||
9218 | break; | ||
9219 | default: | ||
9220 | this.core.UnexpectedAttribute(node, attrib); | ||
9221 | break; | ||
9222 | } | ||
9223 | } | ||
9224 | else | ||
9225 | { | ||
9226 | this.core.ParseExtensionAttribute(node, attrib); | ||
9227 | } | ||
9228 | } | ||
9229 | |||
9230 | if (null == id) | ||
9231 | { | ||
9232 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); | ||
9233 | } | ||
9234 | |||
9235 | this.core.ParseForExtensionElements(node); | ||
9236 | |||
9237 | return id; | ||
9238 | } | ||
9239 | |||
9240 | /// <summary> | ||
9241 | /// Parses a TargetProductCodes element. | ||
9242 | /// </summary> | ||
9243 | /// <param name="node">The element to parse.</param> | ||
9244 | private void ParseTargetProductCodesElement(XElement node) | ||
9245 | { | ||
9246 | SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
9247 | bool replace = false; | ||
9248 | List<string> targetProductCodes = new List<string>(); | ||
9249 | |||
9250 | foreach (XAttribute attrib in node.Attributes()) | ||
9251 | { | ||
9252 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
9253 | { | ||
9254 | switch (attrib.Name.LocalName) | ||
9255 | { | ||
9256 | case "Replace": | ||
9257 | replace = YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
9258 | break; | ||
9259 | default: | ||
9260 | this.core.UnexpectedAttribute(node, attrib); | ||
9261 | break; | ||
9262 | } | ||
9263 | } | ||
9264 | else | ||
9265 | { | ||
9266 | this.core.ParseExtensionAttribute(node, attrib); | ||
9267 | } | ||
9268 | } | ||
9269 | |||
9270 | foreach (XElement child in node.Elements()) | ||
9271 | { | ||
9272 | if (CompilerCore.WixNamespace == child.Name.Namespace) | ||
9273 | { | ||
9274 | switch (child.Name.LocalName) | ||
9275 | { | ||
9276 | case "TargetProductCode": | ||
9277 | string id = this.ParseTargetProductCodeElement(child); | ||
9278 | if (0 == String.CompareOrdinal("*", id)) | ||
9279 | { | ||
9280 | this.core.OnMessage(WixErrors.IllegalAttributeValueWhenNested(sourceLineNumbers, child.Name.LocalName, "Id", id, node.Name.LocalName)); | ||
9281 | } | ||
9282 | else | ||
9283 | { | ||
9284 | targetProductCodes.Add(id); | ||
9285 | } | ||
9286 | break; | ||
9287 | default: | ||
9288 | this.core.UnexpectedElement(node, child); | ||
9289 | break; | ||
9290 | } | ||
9291 | } | ||
9292 | else | ||
9293 | { | ||
9294 | this.core.ParseExtensionElement(node, child); | ||
9295 | } | ||
9296 | } | ||
9297 | |||
9298 | if (!this.core.EncounteredError) | ||
9299 | { | ||
9300 | // By default, target ProductCodes should be added. | ||
9301 | if (!replace) | ||
9302 | { | ||
9303 | Row row = this.core.CreateRow(sourceLineNumbers, "WixPatchTarget"); | ||
9304 | row[0] = "*"; | ||
9305 | } | ||
9306 | |||
9307 | foreach (string targetProductCode in targetProductCodes) | ||
9308 | { | ||
9309 | Row row = this.core.CreateRow(sourceLineNumbers, "WixPatchTarget"); | ||
9310 | row[0] = targetProductCode; | ||
9311 | } | ||
9312 | } | ||
9313 | } | ||
9314 | |||
9315 | /// <summary> | ||
9316 | /// Parses a ReplacePatch element. | ||
9317 | /// </summary> | ||
9318 | /// <param name="node">The element to parse.</param> | ||
9319 | /// <returns>The id from the node.</returns> | ||
9320 | private string ParseReplacePatchElement(XElement node) | ||
9321 | { | ||
9322 | SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
9323 | string id = null; | ||
9324 | |||
9325 | foreach (XAttribute attrib in node.Attributes()) | ||
9326 | { | ||
9327 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
9328 | { | ||
9329 | switch (attrib.Name.LocalName) | ||
9330 | { | ||
9331 | case "Id": | ||
9332 | id = this.core.GetAttributeGuidValue(sourceLineNumbers, attrib, false); | ||
9333 | break; | ||
9334 | default: | ||
9335 | this.core.UnexpectedAttribute(node, attrib); | ||
9336 | break; | ||
9337 | } | ||
9338 | } | ||
9339 | else | ||
9340 | { | ||
9341 | this.core.ParseExtensionAttribute(node, attrib); | ||
9342 | } | ||
9343 | } | ||
9344 | |||
9345 | if (null == id) | ||
9346 | { | ||
9347 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); | ||
9348 | } | ||
9349 | |||
9350 | this.core.ParseForExtensionElements(node); | ||
9351 | |||
9352 | return id; | ||
9353 | } | ||
9354 | |||
9355 | /// <summary> | ||
9356 | /// Parses a symbol path element. | ||
9357 | /// </summary> | ||
9358 | /// <param name="node">The element to parse.</param> | ||
9359 | /// <returns>The path from the node.</returns> | ||
9360 | private string ParseSymbolPathElement(XElement node) | ||
9361 | { | ||
9362 | SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
9363 | string path = null; | ||
9364 | |||
9365 | foreach (XAttribute attrib in node.Attributes()) | ||
9366 | { | ||
9367 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
9368 | { | ||
9369 | switch (attrib.Name.LocalName) | ||
9370 | { | ||
9371 | case "Path": | ||
9372 | path = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
9373 | break; | ||
9374 | default: | ||
9375 | this.core.UnexpectedAttribute(node, attrib); | ||
9376 | break; | ||
9377 | } | ||
9378 | } | ||
9379 | else | ||
9380 | { | ||
9381 | this.core.ParseExtensionAttribute(node, attrib); | ||
9382 | } | ||
9383 | } | ||
9384 | |||
9385 | if (null == path) | ||
9386 | { | ||
9387 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Path")); | ||
9388 | } | ||
9389 | |||
9390 | this.core.ParseForExtensionElements(node); | ||
9391 | |||
9392 | return path; | ||
9393 | } | ||
9394 | |||
9395 | /// <summary> | ||
9396 | /// Parses an patch element. | ||
9397 | /// </summary> | ||
9398 | /// <param name="node">The element to parse.</param> | ||
9399 | private void ParsePatchElement(XElement node) | ||
9400 | { | ||
9401 | SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
9402 | string patchId = null; | ||
9403 | int codepage = 0; | ||
9404 | ////bool versionMismatches = false; | ||
9405 | ////bool productMismatches = false; | ||
9406 | bool allowRemoval = false; | ||
9407 | string classification = null; | ||
9408 | string clientPatchId = null; | ||
9409 | string description = null; | ||
9410 | string displayName = null; | ||
9411 | string comments = null; | ||
9412 | string manufacturer = null; | ||
9413 | YesNoType minorUpdateTargetRTM = YesNoType.NotSet; | ||
9414 | string moreInfoUrl = null; | ||
9415 | int optimizeCA = CompilerConstants.IntegerNotSet; | ||
9416 | YesNoType optimizedInstallMode = YesNoType.NotSet; | ||
9417 | string targetProductName = null; | ||
9418 | // string replaceGuids = String.Empty; | ||
9419 | int apiPatchingSymbolFlags = 0; | ||
9420 | bool optimizePatchSizeForLargeFiles = false; | ||
9421 | |||
9422 | foreach (XAttribute attrib in node.Attributes()) | ||
9423 | { | ||
9424 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
9425 | { | ||
9426 | switch (attrib.Name.LocalName) | ||
9427 | { | ||
9428 | case "Id": | ||
9429 | patchId = this.core.GetAttributeGuidValue(sourceLineNumbers, attrib, true); | ||
9430 | break; | ||
9431 | case "Codepage": | ||
9432 | codepage = this.core.GetAttributeCodePageValue(sourceLineNumbers, attrib); | ||
9433 | break; | ||
9434 | case "AllowMajorVersionMismatches": | ||
9435 | ////versionMismatches = (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib)); | ||
9436 | break; | ||
9437 | case "AllowProductCodeMismatches": | ||
9438 | ////productMismatches = (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib)); | ||
9439 | break; | ||
9440 | case "AllowRemoval": | ||
9441 | allowRemoval = (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib)); | ||
9442 | break; | ||
9443 | case "Classification": | ||
9444 | classification = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
9445 | break; | ||
9446 | case "ClientPatchId": | ||
9447 | clientPatchId = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
9448 | break; | ||
9449 | case "Description": | ||
9450 | description = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
9451 | break; | ||
9452 | case "DisplayName": | ||
9453 | displayName = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
9454 | break; | ||
9455 | case "Comments": | ||
9456 | comments = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
9457 | break; | ||
9458 | case "Manufacturer": | ||
9459 | manufacturer = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
9460 | break; | ||
9461 | case "MinorUpdateTargetRTM": | ||
9462 | minorUpdateTargetRTM = this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
9463 | break; | ||
9464 | case "MoreInfoURL": | ||
9465 | moreInfoUrl = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
9466 | break; | ||
9467 | case "OptimizedInstallMode": | ||
9468 | optimizedInstallMode = this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
9469 | break; | ||
9470 | case "TargetProductName": | ||
9471 | targetProductName = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
9472 | break; | ||
9473 | case "ApiPatchingSymbolNoImagehlpFlag": | ||
9474 | apiPatchingSymbolFlags |= (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) ? (int)PatchAPI.PatchInterop.PatchSymbolFlagsType.PATCH_SYMBOL_NO_IMAGEHLP : 0; | ||
9475 | break; | ||
9476 | case "ApiPatchingSymbolNoFailuresFlag": | ||
9477 | apiPatchingSymbolFlags |= (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) ? (int)PatchAPI.PatchInterop.PatchSymbolFlagsType.PATCH_SYMBOL_NO_FAILURES : 0; | ||
9478 | break; | ||
9479 | case "ApiPatchingSymbolUndecoratedTooFlag": | ||
9480 | apiPatchingSymbolFlags |= (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) ? (int)PatchAPI.PatchInterop.PatchSymbolFlagsType.PATCH_SYMBOL_UNDECORATED_TOO : 0; | ||
9481 | break; | ||
9482 | case "OptimizePatchSizeForLargeFiles": | ||
9483 | optimizePatchSizeForLargeFiles = (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib)); | ||
9484 | break; | ||
9485 | default: | ||
9486 | this.core.UnexpectedAttribute(node, attrib); | ||
9487 | break; | ||
9488 | } | ||
9489 | } | ||
9490 | else | ||
9491 | { | ||
9492 | this.core.ParseExtensionAttribute(node, attrib); | ||
9493 | } | ||
9494 | } | ||
9495 | |||
9496 | if (patchId == null || patchId == "*") | ||
9497 | { | ||
9498 | // auto-generate at compile time, since this value gets dispersed to several locations | ||
9499 | patchId = Common.GenerateGuid(); | ||
9500 | } | ||
9501 | this.activeName = patchId; | ||
9502 | |||
9503 | if (null == this.activeName) | ||
9504 | { | ||
9505 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); | ||
9506 | } | ||
9507 | if (null == classification) | ||
9508 | { | ||
9509 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Classification")); | ||
9510 | } | ||
9511 | if (null == clientPatchId) | ||
9512 | { | ||
9513 | clientPatchId = String.Concat("_", new Guid(patchId).ToString("N", CultureInfo.InvariantCulture).ToUpper(CultureInfo.InvariantCulture)); | ||
9514 | } | ||
9515 | if (null == description) | ||
9516 | { | ||
9517 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Description")); | ||
9518 | } | ||
9519 | if (null == displayName) | ||
9520 | { | ||
9521 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "DisplayName")); | ||
9522 | } | ||
9523 | if (null == manufacturer) | ||
9524 | { | ||
9525 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Manufacturer")); | ||
9526 | } | ||
9527 | |||
9528 | this.core.CreateActiveSection(this.activeName, SectionType.Patch, codepage); | ||
9529 | |||
9530 | foreach (XElement child in node.Elements()) | ||
9531 | { | ||
9532 | if (CompilerCore.WixNamespace == child.Name.Namespace) | ||
9533 | { | ||
9534 | switch (child.Name.LocalName) | ||
9535 | { | ||
9536 | case "PatchInformation": | ||
9537 | this.ParsePatchInformationElement(child); | ||
9538 | break; | ||
9539 | case "Media": | ||
9540 | this.ParseMediaElement(child, patchId); | ||
9541 | break; | ||
9542 | case "OptimizeCustomActions": | ||
9543 | optimizeCA = this.ParseOptimizeCustomActionsElement(child); | ||
9544 | break; | ||
9545 | case "PatchFamily": | ||
9546 | this.ParsePatchFamilyElement(child, ComplexReferenceParentType.Patch, patchId); | ||
9547 | break; | ||
9548 | case "PatchFamilyRef": | ||
9549 | this.ParsePatchFamilyRefElement(child, ComplexReferenceParentType.Patch, patchId); | ||
9550 | break; | ||
9551 | case "PatchFamilyGroup": | ||
9552 | this.ParsePatchFamilyGroupElement(child, ComplexReferenceParentType.Patch, patchId); | ||
9553 | break; | ||
9554 | case "PatchFamilyGroupRef": | ||
9555 | this.ParsePatchFamilyGroupRefElement(child, ComplexReferenceParentType.Patch, patchId); | ||
9556 | break; | ||
9557 | case "PatchProperty": | ||
9558 | this.ParsePatchPropertyElement(child, true); | ||
9559 | break; | ||
9560 | case "TargetProductCodes": | ||
9561 | this.ParseTargetProductCodesElement(child); | ||
9562 | break; | ||
9563 | default: | ||
9564 | this.core.UnexpectedElement(node, child); | ||
9565 | break; | ||
9566 | } | ||
9567 | } | ||
9568 | else | ||
9569 | { | ||
9570 | this.core.ParseExtensionElement(node, child); | ||
9571 | } | ||
9572 | } | ||
9573 | |||
9574 | |||
9575 | if (!this.core.EncounteredError) | ||
9576 | { | ||
9577 | Row patchIdRow = this.core.CreateRow(sourceLineNumbers, "WixPatchId"); | ||
9578 | patchIdRow[0] = patchId; | ||
9579 | patchIdRow[1] = clientPatchId; | ||
9580 | patchIdRow[2] = optimizePatchSizeForLargeFiles ? 1 : 0; | ||
9581 | patchIdRow[3] = apiPatchingSymbolFlags; | ||
9582 | |||
9583 | if (allowRemoval) | ||
9584 | { | ||
9585 | Row row = this.core.CreateRow(sourceLineNumbers, "MsiPatchMetadata"); | ||
9586 | row[0] = null; | ||
9587 | row[1] = "AllowRemoval"; | ||
9588 | row[2] = allowRemoval ? "1" : "0"; | ||
9589 | } | ||
9590 | |||
9591 | if (null != classification) | ||
9592 | { | ||
9593 | Row row = this.core.CreateRow(sourceLineNumbers, "MsiPatchMetadata"); | ||
9594 | row[0] = null; | ||
9595 | row[1] = "Classification"; | ||
9596 | row[2] = classification; | ||
9597 | } | ||
9598 | |||
9599 | // always generate the CreationTimeUTC | ||
9600 | { | ||
9601 | Row row = this.core.CreateRow(sourceLineNumbers, "MsiPatchMetadata"); | ||
9602 | row[0] = null; | ||
9603 | row[1] = "CreationTimeUTC"; | ||
9604 | row[2] = DateTime.UtcNow.ToString("MM-dd-yy HH:mm", CultureInfo.InvariantCulture); | ||
9605 | } | ||
9606 | |||
9607 | if (null != description) | ||
9608 | { | ||
9609 | Row row = this.core.CreateRow(sourceLineNumbers, "MsiPatchMetadata"); | ||
9610 | row[0] = null; | ||
9611 | row[1] = "Description"; | ||
9612 | row[2] = description; | ||
9613 | } | ||
9614 | |||
9615 | if (null != displayName) | ||
9616 | { | ||
9617 | Row row = this.core.CreateRow(sourceLineNumbers, "MsiPatchMetadata"); | ||
9618 | row[0] = null; | ||
9619 | row[1] = "DisplayName"; | ||
9620 | row[2] = displayName; | ||
9621 | } | ||
9622 | |||
9623 | if (null != manufacturer) | ||
9624 | { | ||
9625 | Row row = this.core.CreateRow(sourceLineNumbers, "MsiPatchMetadata"); | ||
9626 | row[0] = null; | ||
9627 | row[1] = "ManufacturerName"; | ||
9628 | row[2] = manufacturer; | ||
9629 | } | ||
9630 | |||
9631 | if (YesNoType.NotSet != minorUpdateTargetRTM) | ||
9632 | { | ||
9633 | Row row = this.core.CreateRow(sourceLineNumbers, "MsiPatchMetadata"); | ||
9634 | row[0] = null; | ||
9635 | row[1] = "MinorUpdateTargetRTM"; | ||
9636 | row[2] = YesNoType.Yes == minorUpdateTargetRTM ? "1" : "0"; | ||
9637 | } | ||
9638 | |||
9639 | if (null != moreInfoUrl) | ||
9640 | { | ||
9641 | Row row = this.core.CreateRow(sourceLineNumbers, "MsiPatchMetadata"); | ||
9642 | row[0] = null; | ||
9643 | row[1] = "MoreInfoURL"; | ||
9644 | row[2] = moreInfoUrl; | ||
9645 | } | ||
9646 | |||
9647 | if (CompilerConstants.IntegerNotSet != optimizeCA) | ||
9648 | { | ||
9649 | Row row = this.core.CreateRow(sourceLineNumbers, "MsiPatchMetadata"); | ||
9650 | row[0] = null; | ||
9651 | row[1] = "OptimizeCA"; | ||
9652 | row[2] = optimizeCA.ToString(CultureInfo.InvariantCulture); | ||
9653 | } | ||
9654 | |||
9655 | if (YesNoType.NotSet != optimizedInstallMode) | ||
9656 | { | ||
9657 | Row row = this.core.CreateRow(sourceLineNumbers, "MsiPatchMetadata"); | ||
9658 | row[0] = null; | ||
9659 | row[1] = "OptimizedInstallMode"; | ||
9660 | row[2] = YesNoType.Yes == optimizedInstallMode ? "1" : "0"; | ||
9661 | } | ||
9662 | |||
9663 | if (null != targetProductName) | ||
9664 | { | ||
9665 | Row row = this.core.CreateRow(sourceLineNumbers, "MsiPatchMetadata"); | ||
9666 | row[0] = null; | ||
9667 | row[1] = "TargetProductName"; | ||
9668 | row[2] = targetProductName; | ||
9669 | } | ||
9670 | |||
9671 | if (null != comments) | ||
9672 | { | ||
9673 | Row row = this.core.CreateRow(sourceLineNumbers, "WixPatchMetadata"); | ||
9674 | row[0] = "Comments"; | ||
9675 | row[1] = comments; | ||
9676 | } | ||
9677 | } | ||
9678 | // TODO: do something with versionMismatches and productMismatches | ||
9679 | } | ||
9680 | |||
9681 | /// <summary> | ||
9682 | /// Parses a PatchFamily element. | ||
9683 | /// </summary> | ||
9684 | /// <param name="node">The element to parse.</param> | ||
9685 | private void ParsePatchFamilyElement(XElement node, ComplexReferenceParentType parentType, string parentId) | ||
9686 | { | ||
9687 | SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
9688 | Identifier id = null; | ||
9689 | string productCode = null; | ||
9690 | string version = null; | ||
9691 | int attributes = 0; | ||
9692 | |||
9693 | foreach (XAttribute attrib in node.Attributes()) | ||
9694 | { | ||
9695 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
9696 | { | ||
9697 | switch (attrib.Name.LocalName) | ||
9698 | { | ||
9699 | case "Id": | ||
9700 | id = this.core.GetAttributeIdentifier(sourceLineNumbers, attrib); | ||
9701 | break; | ||
9702 | case "ProductCode": | ||
9703 | productCode = this.core.GetAttributeGuidValue(sourceLineNumbers, attrib, false); | ||
9704 | break; | ||
9705 | case "Version": | ||
9706 | version = this.core.GetAttributeVersionValue(sourceLineNumbers, attrib); | ||
9707 | break; | ||
9708 | case "Supersede": | ||
9709 | if (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) | ||
9710 | { | ||
9711 | attributes |= 0x1; | ||
9712 | } | ||
9713 | break; | ||
9714 | default: | ||
9715 | this.core.UnexpectedAttribute(node, attrib); | ||
9716 | break; | ||
9717 | } | ||
9718 | } | ||
9719 | else | ||
9720 | { | ||
9721 | this.core.ParseExtensionAttribute(node, attrib); | ||
9722 | } | ||
9723 | } | ||
9724 | |||
9725 | if (null == id) | ||
9726 | { | ||
9727 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); | ||
9728 | id = Identifier.Invalid; | ||
9729 | } | ||
9730 | |||
9731 | if (String.IsNullOrEmpty(version)) | ||
9732 | { | ||
9733 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Version")); | ||
9734 | } | ||
9735 | else if (!CompilerCore.IsValidProductVersion(version)) | ||
9736 | { | ||
9737 | this.core.OnMessage(WixErrors.InvalidProductVersion(sourceLineNumbers, version)); | ||
9738 | } | ||
9739 | |||
9740 | // find unexpected child elements | ||
9741 | foreach (XElement child in node.Elements()) | ||
9742 | { | ||
9743 | if (CompilerCore.WixNamespace == child.Name.Namespace) | ||
9744 | { | ||
9745 | switch (child.Name.LocalName) | ||
9746 | { | ||
9747 | case "All": | ||
9748 | this.ParseAllElement(child); | ||
9749 | break; | ||
9750 | case "BinaryRef": | ||
9751 | this.ParsePatchChildRefElement(child, "Binary"); | ||
9752 | break; | ||
9753 | case "ComponentRef": | ||
9754 | this.ParsePatchChildRefElement(child, "Component"); | ||
9755 | break; | ||
9756 | case "CustomActionRef": | ||
9757 | this.ParsePatchChildRefElement(child, "CustomAction"); | ||
9758 | break; | ||
9759 | case "DirectoryRef": | ||
9760 | this.ParsePatchChildRefElement(child, "Directory"); | ||
9761 | break; | ||
9762 | case "DigitalCertificateRef": | ||
9763 | this.ParsePatchChildRefElement(child, "MsiDigitalCertificate"); | ||
9764 | break; | ||
9765 | case "FeatureRef": | ||
9766 | this.ParsePatchChildRefElement(child, "Feature"); | ||
9767 | break; | ||
9768 | case "IconRef": | ||
9769 | this.ParsePatchChildRefElement(child, "Icon"); | ||
9770 | break; | ||
9771 | case "PropertyRef": | ||
9772 | this.ParsePatchChildRefElement(child, "Property"); | ||
9773 | break; | ||
9774 | case "UIRef": | ||
9775 | this.ParsePatchChildRefElement(child, "WixUI"); | ||
9776 | break; | ||
9777 | default: | ||
9778 | this.core.UnexpectedElement(node, child); | ||
9779 | break; | ||
9780 | } | ||
9781 | } | ||
9782 | else | ||
9783 | { | ||
9784 | this.core.ParseExtensionElement(node, child); | ||
9785 | } | ||
9786 | } | ||
9787 | |||
9788 | |||
9789 | if (!this.core.EncounteredError) | ||
9790 | { | ||
9791 | Row row = this.core.CreateRow(sourceLineNumbers, "MsiPatchSequence", id); | ||
9792 | row[1] = productCode; | ||
9793 | row[2] = version; | ||
9794 | row[3] = attributes; | ||
9795 | |||
9796 | if (ComplexReferenceParentType.Unknown != parentType) | ||
9797 | { | ||
9798 | this.core.CreateComplexReference(sourceLineNumbers, parentType, parentId, null, ComplexReferenceChildType.PatchFamily, id.Id, ComplexReferenceParentType.Patch == parentType); | ||
9799 | } | ||
9800 | } | ||
9801 | } | ||
9802 | |||
9803 | /// <summary> | ||
9804 | /// Parses the All element under a PatchFamily. | ||
9805 | /// </summary> | ||
9806 | /// <param name="node">The element to parse.</param> | ||
9807 | private void ParseAllElement(XElement node) | ||
9808 | { | ||
9809 | SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
9810 | |||
9811 | // find unexpected attributes | ||
9812 | foreach (XAttribute attrib in node.Attributes()) | ||
9813 | { | ||
9814 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
9815 | { | ||
9816 | this.core.UnexpectedAttribute(node, attrib); | ||
9817 | } | ||
9818 | else | ||
9819 | { | ||
9820 | this.core.ParseExtensionAttribute(node, attrib); | ||
9821 | } | ||
9822 | } | ||
9823 | |||
9824 | this.core.ParseForExtensionElements(node); | ||
9825 | |||
9826 | // Always warn when using the All element. | ||
9827 | this.core.OnMessage(WixWarnings.AllChangesIncludedInPatch(sourceLineNumbers)); | ||
9828 | |||
9829 | if (!this.core.EncounteredError) | ||
9830 | { | ||
9831 | this.core.CreatePatchFamilyChildReference(sourceLineNumbers, "*", "*"); | ||
9832 | } | ||
9833 | } | ||
9834 | |||
9835 | /// <summary> | ||
9836 | /// Parses all reference elements under a PatchFamily. | ||
9837 | /// </summary> | ||
9838 | /// <param name="node">The element to parse.</param> | ||
9839 | /// <param name="tableName">Table that reference was made to.</param> | ||
9840 | private void ParsePatchChildRefElement(XElement node, string tableName) | ||
9841 | { | ||
9842 | SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
9843 | string id = null; | ||
9844 | |||
9845 | foreach (XAttribute attrib in node.Attributes()) | ||
9846 | { | ||
9847 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
9848 | { | ||
9849 | switch (attrib.Name.LocalName) | ||
9850 | { | ||
9851 | case "Id": | ||
9852 | id = this.core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); | ||
9853 | break; | ||
9854 | default: | ||
9855 | this.core.UnexpectedAttribute(node, attrib); | ||
9856 | break; | ||
9857 | } | ||
9858 | } | ||
9859 | else | ||
9860 | { | ||
9861 | this.core.ParseExtensionAttribute(node, attrib); | ||
9862 | } | ||
9863 | } | ||
9864 | |||
9865 | if (null == id) | ||
9866 | { | ||
9867 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); | ||
9868 | } | ||
9869 | |||
9870 | this.core.ParseForExtensionElements(node); | ||
9871 | |||
9872 | if (!this.core.EncounteredError) | ||
9873 | { | ||
9874 | this.core.CreatePatchFamilyChildReference(sourceLineNumbers, tableName, id); | ||
9875 | } | ||
9876 | } | ||
9877 | |||
9878 | /// <summary> | ||
9879 | /// Parses a PatchBaseline element. | ||
9880 | /// </summary> | ||
9881 | /// <param name="node">The element to parse.</param> | ||
9882 | /// <param name="diskId">Media index from parent element.</param> | ||
9883 | private void ParsePatchBaselineElement(XElement node, int diskId) | ||
9884 | { | ||
9885 | SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
9886 | Identifier id = null; | ||
9887 | bool parsedValidate = false; | ||
9888 | TransformFlags validationFlags = TransformFlags.PatchTransformDefault; | ||
9889 | |||
9890 | foreach (XAttribute attrib in node.Attributes()) | ||
9891 | { | ||
9892 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
9893 | { | ||
9894 | switch (attrib.Name.LocalName) | ||
9895 | { | ||
9896 | case "Id": | ||
9897 | id = this.core.GetAttributeIdentifier(sourceLineNumbers, attrib); | ||
9898 | break; | ||
9899 | default: | ||
9900 | this.core.UnexpectedAttribute(node, attrib); | ||
9901 | break; | ||
9902 | } | ||
9903 | } | ||
9904 | else | ||
9905 | { | ||
9906 | this.core.ParseExtensionAttribute(node, attrib); | ||
9907 | } | ||
9908 | } | ||
9909 | |||
9910 | if (null == id) | ||
9911 | { | ||
9912 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); | ||
9913 | id = Identifier.Invalid; | ||
9914 | } | ||
9915 | else if (27 < id.Id.Length) | ||
9916 | { | ||
9917 | this.core.OnMessage(WixErrors.IdentifierTooLongError(sourceLineNumbers, node.Name.LocalName, "Id", id.Id, 27)); | ||
9918 | } | ||
9919 | |||
9920 | foreach (XElement child in node.Elements()) | ||
9921 | { | ||
9922 | if (CompilerCore.WixNamespace == child.Name.Namespace) | ||
9923 | { | ||
9924 | switch (child.Name.LocalName) | ||
9925 | { | ||
9926 | case "Validate": | ||
9927 | if (parsedValidate) | ||
9928 | { | ||
9929 | SourceLineNumber childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child); | ||
9930 | this.core.OnMessage(WixErrors.TooManyChildren(childSourceLineNumbers, node.Name.LocalName, child.Name.LocalName)); | ||
9931 | } | ||
9932 | else | ||
9933 | { | ||
9934 | this.ParseValidateElement(child, ref validationFlags); | ||
9935 | parsedValidate = true; | ||
9936 | } | ||
9937 | break; | ||
9938 | default: | ||
9939 | this.core.UnexpectedElement(node, child); | ||
9940 | break; | ||
9941 | } | ||
9942 | } | ||
9943 | else | ||
9944 | { | ||
9945 | this.core.ParseExtensionElement(node, child); | ||
9946 | } | ||
9947 | } | ||
9948 | |||
9949 | if (!this.core.EncounteredError) | ||
9950 | { | ||
9951 | Row row = this.core.CreateRow(sourceLineNumbers, "WixPatchBaseline", id); | ||
9952 | row[1] = diskId; | ||
9953 | row[2] = (int)validationFlags; | ||
9954 | } | ||
9955 | } | ||
9956 | |||
9957 | /// <summary> | ||
9958 | /// Parses a Validate element. | ||
9959 | /// </summary> | ||
9960 | /// <param name="node">The element to parse.</param> | ||
9961 | /// <param name="validationFlags">TransformValidation flags to use when creating the authoring patch transform.</param> | ||
9962 | private void ParseValidateElement(XElement node, ref TransformFlags validationFlags) | ||
9963 | { | ||
9964 | SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
9965 | |||
9966 | foreach (XAttribute attrib in node.Attributes()) | ||
9967 | { | ||
9968 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
9969 | { | ||
9970 | switch (attrib.Name.LocalName) | ||
9971 | { | ||
9972 | case "ProductId": | ||
9973 | if (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) | ||
9974 | { | ||
9975 | validationFlags |= TransformFlags.ValidateProduct; | ||
9976 | } | ||
9977 | else | ||
9978 | { | ||
9979 | validationFlags &= ~TransformFlags.ValidateProduct; | ||
9980 | } | ||
9981 | break; | ||
9982 | case "ProductLanguage": | ||
9983 | if (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) | ||
9984 | { | ||
9985 | validationFlags |= TransformFlags.ValidateLanguage; | ||
9986 | } | ||
9987 | else | ||
9988 | { | ||
9989 | validationFlags &= ~TransformFlags.ValidateLanguage; | ||
9990 | } | ||
9991 | break; | ||
9992 | case "ProductVersion": | ||
9993 | string check = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
9994 | validationFlags &= ~TransformFlags.ProductVersionMask; | ||
9995 | Wix.Validate.ProductVersionType productVersionType = Wix.Validate.ParseProductVersionType(check); | ||
9996 | switch (productVersionType) | ||
9997 | { | ||
9998 | case Wix.Validate.ProductVersionType.Major: | ||
9999 | validationFlags |= TransformFlags.ValidateMajorVersion; | ||
10000 | break; | ||
10001 | case Wix.Validate.ProductVersionType.Minor: | ||
10002 | validationFlags |= TransformFlags.ValidateMinorVersion; | ||
10003 | break; | ||
10004 | case Wix.Validate.ProductVersionType.Update: | ||
10005 | validationFlags |= TransformFlags.ValidateUpdateVersion; | ||
10006 | break; | ||
10007 | default: | ||
10008 | this.core.OnMessage(WixErrors.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, "Version", check, "Major", "Minor", "Update")); | ||
10009 | break; | ||
10010 | } | ||
10011 | break; | ||
10012 | case "ProductVersionOperator": | ||
10013 | string op = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
10014 | validationFlags &= ~TransformFlags.ProductVersionOperatorMask; | ||
10015 | Wix.Validate.ProductVersionOperatorType opType = Wix.Validate.ParseProductVersionOperatorType(op); | ||
10016 | switch (opType) | ||
10017 | { | ||
10018 | case Wix.Validate.ProductVersionOperatorType.Lesser: | ||
10019 | validationFlags |= TransformFlags.ValidateNewLessBaseVersion; | ||
10020 | break; | ||
10021 | case Wix.Validate.ProductVersionOperatorType.LesserOrEqual: | ||
10022 | validationFlags |= TransformFlags.ValidateNewLessEqualBaseVersion; | ||
10023 | break; | ||
10024 | case Wix.Validate.ProductVersionOperatorType.Equal: | ||
10025 | validationFlags |= TransformFlags.ValidateNewEqualBaseVersion; | ||
10026 | break; | ||
10027 | case Wix.Validate.ProductVersionOperatorType.GreaterOrEqual: | ||
10028 | validationFlags |= TransformFlags.ValidateNewGreaterEqualBaseVersion; | ||
10029 | break; | ||
10030 | case Wix.Validate.ProductVersionOperatorType.Greater: | ||
10031 | validationFlags |= TransformFlags.ValidateNewGreaterBaseVersion; | ||
10032 | break; | ||
10033 | default: | ||
10034 | this.core.OnMessage(WixErrors.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, "Operator", op, "Lesser", "LesserOrEqual", "Equal", "GreaterOrEqual", "Greater")); | ||
10035 | break; | ||
10036 | } | ||
10037 | break; | ||
10038 | case "UpgradeCode": | ||
10039 | if (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) | ||
10040 | { | ||
10041 | validationFlags |= TransformFlags.ValidateUpgradeCode; | ||
10042 | } | ||
10043 | else | ||
10044 | { | ||
10045 | validationFlags &= ~TransformFlags.ValidateUpgradeCode; | ||
10046 | } | ||
10047 | break; | ||
10048 | case "IgnoreAddExistingRow": | ||
10049 | if (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) | ||
10050 | { | ||
10051 | validationFlags |= TransformFlags.ErrorAddExistingRow; | ||
10052 | } | ||
10053 | else | ||
10054 | { | ||
10055 | validationFlags &= ~TransformFlags.ErrorAddExistingRow; | ||
10056 | } | ||
10057 | break; | ||
10058 | case "IgnoreAddExistingTable": | ||
10059 | if (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) | ||
10060 | { | ||
10061 | validationFlags |= TransformFlags.ErrorAddExistingTable; | ||
10062 | } | ||
10063 | else | ||
10064 | { | ||
10065 | validationFlags &= ~TransformFlags.ErrorAddExistingTable; | ||
10066 | } | ||
10067 | break; | ||
10068 | case "IgnoreDeleteMissingRow": | ||
10069 | if (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) | ||
10070 | { | ||
10071 | validationFlags |= TransformFlags.ErrorDeleteMissingRow; | ||
10072 | } | ||
10073 | else | ||
10074 | { | ||
10075 | validationFlags &= ~TransformFlags.ErrorDeleteMissingRow; | ||
10076 | } | ||
10077 | break; | ||
10078 | case "IgnoreDeleteMissingTable": | ||
10079 | if (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) | ||
10080 | { | ||
10081 | validationFlags |= TransformFlags.ErrorDeleteMissingTable; | ||
10082 | } | ||
10083 | else | ||
10084 | { | ||
10085 | validationFlags &= ~TransformFlags.ErrorDeleteMissingTable; | ||
10086 | } | ||
10087 | break; | ||
10088 | case "IgnoreUpdateMissingRow": | ||
10089 | if (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) | ||
10090 | { | ||
10091 | validationFlags |= TransformFlags.ErrorUpdateMissingRow; | ||
10092 | } | ||
10093 | else | ||
10094 | { | ||
10095 | validationFlags &= ~TransformFlags.ErrorUpdateMissingRow; | ||
10096 | } | ||
10097 | break; | ||
10098 | case "IgnoreChangingCodePage": | ||
10099 | if (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) | ||
10100 | { | ||
10101 | validationFlags |= TransformFlags.ErrorChangeCodePage; | ||
10102 | } | ||
10103 | else | ||
10104 | { | ||
10105 | validationFlags &= ~TransformFlags.ErrorChangeCodePage; | ||
10106 | } | ||
10107 | break; | ||
10108 | default: | ||
10109 | this.core.UnexpectedAttribute(node, attrib); | ||
10110 | break; | ||
10111 | } | ||
10112 | } | ||
10113 | else | ||
10114 | { | ||
10115 | this.core.ParseExtensionAttribute(node, attrib); | ||
10116 | } | ||
10117 | } | ||
10118 | |||
10119 | } | ||
10120 | |||
10121 | /// <summary> | ||
10122 | /// Adds a row to the properties table. | ||
10123 | /// </summary> | ||
10124 | /// <param name="sourceLineNumbers">Source line numbers.</param> | ||
10125 | /// <param name="name">Name of the property.</param> | ||
10126 | /// <param name="value">Value of the property.</param> | ||
10127 | private void ProcessProperties(SourceLineNumber sourceLineNumbers, string name, string value) | ||
10128 | { | ||
10129 | if (!this.core.EncounteredError) | ||
10130 | { | ||
10131 | Row row = this.core.CreateRow(sourceLineNumbers, "Properties"); | ||
10132 | row[0] = name; | ||
10133 | row[1] = value; | ||
10134 | } | ||
10135 | } | ||
10136 | |||
10137 | /// <summary> | ||
10138 | /// Parses a dependency element. | ||
10139 | /// </summary> | ||
10140 | /// <param name="node">Element to parse.</param> | ||
10141 | private void ParseDependencyElement(XElement node) | ||
10142 | { | ||
10143 | SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
10144 | string requiredId = null; | ||
10145 | int requiredLanguage = CompilerConstants.IntegerNotSet; | ||
10146 | string requiredVersion = null; | ||
10147 | |||
10148 | foreach (XAttribute attrib in node.Attributes()) | ||
10149 | { | ||
10150 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
10151 | { | ||
10152 | switch (attrib.Name.LocalName) | ||
10153 | { | ||
10154 | case "RequiredId": | ||
10155 | requiredId = this.core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); | ||
10156 | break; | ||
10157 | case "RequiredLanguage": | ||
10158 | requiredLanguage = this.core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, short.MaxValue); | ||
10159 | break; | ||
10160 | case "RequiredVersion": | ||
10161 | requiredVersion = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
10162 | break; | ||
10163 | default: | ||
10164 | this.core.UnexpectedAttribute(node, attrib); | ||
10165 | break; | ||
10166 | } | ||
10167 | } | ||
10168 | else | ||
10169 | { | ||
10170 | this.core.ParseExtensionAttribute(node, attrib); | ||
10171 | } | ||
10172 | } | ||
10173 | |||
10174 | if (null == requiredId) | ||
10175 | { | ||
10176 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "RequiredId")); | ||
10177 | requiredId = String.Empty; | ||
10178 | } | ||
10179 | |||
10180 | if (CompilerConstants.IntegerNotSet == requiredLanguage) | ||
10181 | { | ||
10182 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "RequiredLanguage")); | ||
10183 | requiredLanguage = CompilerConstants.IllegalInteger; | ||
10184 | } | ||
10185 | |||
10186 | this.core.ParseForExtensionElements(node); | ||
10187 | |||
10188 | if (!this.core.EncounteredError) | ||
10189 | { | ||
10190 | Row row = this.core.CreateRow(sourceLineNumbers, "ModuleDependency"); | ||
10191 | row[0] = this.activeName; | ||
10192 | row[1] = this.activeLanguage; | ||
10193 | row[2] = requiredId; | ||
10194 | row[3] = requiredLanguage.ToString(CultureInfo.InvariantCulture); | ||
10195 | row[4] = requiredVersion; | ||
10196 | } | ||
10197 | } | ||
10198 | |||
10199 | /// <summary> | ||
10200 | /// Parses an exclusion element. | ||
10201 | /// </summary> | ||
10202 | /// <param name="node">Element to parse.</param> | ||
10203 | private void ParseExclusionElement(XElement node) | ||
10204 | { | ||
10205 | SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
10206 | string excludedId = null; | ||
10207 | int excludeExceptLanguage = CompilerConstants.IntegerNotSet; | ||
10208 | int excludeLanguage = CompilerConstants.IntegerNotSet; | ||
10209 | string excludedLanguageField = "0"; | ||
10210 | string excludedMaxVersion = null; | ||
10211 | string excludedMinVersion = null; | ||
10212 | |||
10213 | foreach (XAttribute attrib in node.Attributes()) | ||
10214 | { | ||
10215 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
10216 | { | ||
10217 | switch (attrib.Name.LocalName) | ||
10218 | { | ||
10219 | case "ExcludedId": | ||
10220 | excludedId = this.core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); | ||
10221 | break; | ||
10222 | case "ExcludeExceptLanguage": | ||
10223 | excludeExceptLanguage = this.core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, short.MaxValue); | ||
10224 | break; | ||
10225 | case "ExcludeLanguage": | ||
10226 | excludeLanguage = this.core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, short.MaxValue); | ||
10227 | break; | ||
10228 | case "ExcludedMaxVersion": | ||
10229 | excludedMaxVersion = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
10230 | break; | ||
10231 | case "ExcludedMinVersion": | ||
10232 | excludedMinVersion = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
10233 | break; | ||
10234 | default: | ||
10235 | this.core.UnexpectedAttribute(node, attrib); | ||
10236 | break; | ||
10237 | } | ||
10238 | } | ||
10239 | else | ||
10240 | { | ||
10241 | this.core.ParseExtensionAttribute(node, attrib); | ||
10242 | } | ||
10243 | } | ||
10244 | |||
10245 | if (null == excludedId) | ||
10246 | { | ||
10247 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "ExcludedId")); | ||
10248 | excludedId = String.Empty; | ||
10249 | } | ||
10250 | |||
10251 | if (CompilerConstants.IntegerNotSet != excludeExceptLanguage && CompilerConstants.IntegerNotSet != excludeLanguage) | ||
10252 | { | ||
10253 | this.core.OnMessage(WixErrors.IllegalModuleExclusionLanguageAttributes(sourceLineNumbers)); | ||
10254 | } | ||
10255 | else if (CompilerConstants.IntegerNotSet != excludeExceptLanguage) | ||
10256 | { | ||
10257 | excludedLanguageField = Convert.ToString(-excludeExceptLanguage, CultureInfo.InvariantCulture); | ||
10258 | } | ||
10259 | else if (CompilerConstants.IntegerNotSet != excludeLanguage) | ||
10260 | { | ||
10261 | excludedLanguageField = Convert.ToString(excludeLanguage, CultureInfo.InvariantCulture); | ||
10262 | } | ||
10263 | |||
10264 | this.core.ParseForExtensionElements(node); | ||
10265 | |||
10266 | if (!this.core.EncounteredError) | ||
10267 | { | ||
10268 | Row row = this.core.CreateRow(sourceLineNumbers, "ModuleExclusion"); | ||
10269 | row[0] = this.activeName; | ||
10270 | row[1] = this.activeLanguage; | ||
10271 | row[2] = excludedId; | ||
10272 | row[3] = excludedLanguageField; | ||
10273 | row[4] = excludedMinVersion; | ||
10274 | row[5] = excludedMaxVersion; | ||
10275 | } | ||
10276 | } | ||
10277 | |||
10278 | /// <summary> | ||
10279 | /// Parses a configuration element for a configurable merge module. | ||
10280 | /// </summary> | ||
10281 | /// <param name="node">Element to parse.</param> | ||
10282 | private void ParseConfigurationElement(XElement node) | ||
10283 | { | ||
10284 | SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
10285 | int attributes = 0; | ||
10286 | string contextData = null; | ||
10287 | string defaultValue = null; | ||
10288 | string description = null; | ||
10289 | string displayName = null; | ||
10290 | int format = CompilerConstants.IntegerNotSet; | ||
10291 | string helpKeyword = null; | ||
10292 | string helpLocation = null; | ||
10293 | string name = null; | ||
10294 | string type = null; | ||
10295 | |||
10296 | foreach (XAttribute attrib in node.Attributes()) | ||
10297 | { | ||
10298 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
10299 | { | ||
10300 | switch (attrib.Name.LocalName) | ||
10301 | { | ||
10302 | case "Name": | ||
10303 | name = this.core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); | ||
10304 | break; | ||
10305 | case "ContextData": | ||
10306 | contextData = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
10307 | break; | ||
10308 | case "Description": | ||
10309 | description = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
10310 | break; | ||
10311 | case "DefaultValue": | ||
10312 | defaultValue = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
10313 | break; | ||
10314 | case "DisplayName": | ||
10315 | displayName = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
10316 | break; | ||
10317 | case "Format": | ||
10318 | string formatStr = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
10319 | if (0 < formatStr.Length) | ||
10320 | { | ||
10321 | Wix.Configuration.FormatType formatType = Wix.Configuration.ParseFormatType(formatStr); | ||
10322 | switch (formatType) | ||
10323 | { | ||
10324 | case Wix.Configuration.FormatType.Text: | ||
10325 | format = 0; | ||
10326 | break; | ||
10327 | case Wix.Configuration.FormatType.Key: | ||
10328 | format = 1; | ||
10329 | break; | ||
10330 | case Wix.Configuration.FormatType.Integer: | ||
10331 | format = 2; | ||
10332 | break; | ||
10333 | case Wix.Configuration.FormatType.Bitfield: | ||
10334 | format = 3; | ||
10335 | break; | ||
10336 | default: | ||
10337 | this.core.OnMessage(WixErrors.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, "Format", formatStr, "Text", "Key", "Integer", "Bitfield")); | ||
10338 | break; | ||
10339 | } | ||
10340 | } | ||
10341 | break; | ||
10342 | case "HelpKeyword": | ||
10343 | helpKeyword = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
10344 | break; | ||
10345 | case "HelpLocation": | ||
10346 | helpLocation = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
10347 | break; | ||
10348 | case "KeyNoOrphan": | ||
10349 | if (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) | ||
10350 | { | ||
10351 | attributes |= MsiInterop.MsidbMsmConfigurableOptionKeyNoOrphan; | ||
10352 | } | ||
10353 | break; | ||
10354 | case "NonNullable": | ||
10355 | if (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) | ||
10356 | { | ||
10357 | attributes |= MsiInterop.MsidbMsmConfigurableOptionNonNullable; | ||
10358 | } | ||
10359 | break; | ||
10360 | case "Type": | ||
10361 | type = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
10362 | break; | ||
10363 | default: | ||
10364 | this.core.UnexpectedAttribute(node, attrib); | ||
10365 | break; | ||
10366 | } | ||
10367 | } | ||
10368 | else | ||
10369 | { | ||
10370 | this.core.ParseExtensionAttribute(node, attrib); | ||
10371 | } | ||
10372 | } | ||
10373 | |||
10374 | if (null == name) | ||
10375 | { | ||
10376 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name")); | ||
10377 | name = String.Empty; | ||
10378 | } | ||
10379 | |||
10380 | if (CompilerConstants.IntegerNotSet == format) | ||
10381 | { | ||
10382 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Format")); | ||
10383 | format = CompilerConstants.IllegalInteger; | ||
10384 | } | ||
10385 | |||
10386 | this.core.ParseForExtensionElements(node); | ||
10387 | |||
10388 | if (!this.core.EncounteredError) | ||
10389 | { | ||
10390 | Row row = this.core.CreateRow(sourceLineNumbers, "ModuleConfiguration"); | ||
10391 | row[0] = name; | ||
10392 | row[1] = format; | ||
10393 | row[2] = type; | ||
10394 | row[3] = contextData; | ||
10395 | row[4] = defaultValue; | ||
10396 | row[5] = attributes; | ||
10397 | row[6] = displayName; | ||
10398 | row[7] = description; | ||
10399 | row[8] = helpLocation; | ||
10400 | row[9] = helpKeyword; | ||
10401 | } | ||
10402 | } | ||
10403 | |||
10404 | /// <summary> | ||
10405 | /// Parses a substitution element for a configurable merge module. | ||
10406 | /// </summary> | ||
10407 | /// <param name="node">Element to parse.</param> | ||
10408 | private void ParseSubstitutionElement(XElement node) | ||
10409 | { | ||
10410 | SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
10411 | string column = null; | ||
10412 | string rowKeys = null; | ||
10413 | string table = null; | ||
10414 | string value = null; | ||
10415 | |||
10416 | foreach (XAttribute attrib in node.Attributes()) | ||
10417 | { | ||
10418 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
10419 | { | ||
10420 | switch (attrib.Name.LocalName) | ||
10421 | { | ||
10422 | case "Column": | ||
10423 | column = this.core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); | ||
10424 | break; | ||
10425 | case "Row": | ||
10426 | rowKeys = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
10427 | break; | ||
10428 | case "Table": | ||
10429 | table = this.core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); | ||
10430 | break; | ||
10431 | case "Value": | ||
10432 | value = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
10433 | break; | ||
10434 | default: | ||
10435 | this.core.UnexpectedAttribute(node, attrib); | ||
10436 | break; | ||
10437 | } | ||
10438 | } | ||
10439 | else | ||
10440 | { | ||
10441 | this.core.ParseExtensionAttribute(node, attrib); | ||
10442 | } | ||
10443 | } | ||
10444 | |||
10445 | if (null == column) | ||
10446 | { | ||
10447 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Column")); | ||
10448 | column = String.Empty; | ||
10449 | } | ||
10450 | |||
10451 | if (null == table) | ||
10452 | { | ||
10453 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Table")); | ||
10454 | table = String.Empty; | ||
10455 | } | ||
10456 | |||
10457 | if (null == rowKeys) | ||
10458 | { | ||
10459 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Row")); | ||
10460 | } | ||
10461 | |||
10462 | this.core.ParseForExtensionElements(node); | ||
10463 | |||
10464 | if (!this.core.EncounteredError) | ||
10465 | { | ||
10466 | Row row = this.core.CreateRow(sourceLineNumbers, "ModuleSubstitution"); | ||
10467 | row[0] = table; | ||
10468 | row[1] = rowKeys; | ||
10469 | row[2] = column; | ||
10470 | row[3] = value; | ||
10471 | } | ||
10472 | } | ||
10473 | |||
10474 | /// <summary> | ||
10475 | /// Parses an IgnoreTable element. | ||
10476 | /// </summary> | ||
10477 | /// <param name="node">Element to parse.</param> | ||
10478 | private void ParseIgnoreTableElement(XElement node) | ||
10479 | { | ||
10480 | SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
10481 | string id = null; | ||
10482 | |||
10483 | foreach (XAttribute attrib in node.Attributes()) | ||
10484 | { | ||
10485 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
10486 | { | ||
10487 | switch (attrib.Name.LocalName) | ||
10488 | { | ||
10489 | case "Id": | ||
10490 | id = this.core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); | ||
10491 | break; | ||
10492 | default: | ||
10493 | this.core.UnexpectedAttribute(node, attrib); | ||
10494 | break; | ||
10495 | } | ||
10496 | } | ||
10497 | else | ||
10498 | { | ||
10499 | this.core.ParseExtensionAttribute(node, attrib); | ||
10500 | } | ||
10501 | } | ||
10502 | |||
10503 | if (null == id) | ||
10504 | { | ||
10505 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); | ||
10506 | } | ||
10507 | |||
10508 | this.core.ParseForExtensionElements(node); | ||
10509 | |||
10510 | if (!this.core.EncounteredError) | ||
10511 | { | ||
10512 | Row row = this.core.CreateRow(sourceLineNumbers, "ModuleIgnoreTable"); | ||
10513 | row[0] = id; | ||
10514 | } | ||
10515 | } | ||
10516 | |||
10517 | /// <summary> | ||
10518 | /// Parses an odbc driver or translator element. | ||
10519 | /// </summary> | ||
10520 | /// <param name="node">Element to parse.</param> | ||
10521 | /// <param name="componentId">Identifier of parent component.</param> | ||
10522 | /// <param name="fileId">Default identifer for driver/translator file.</param> | ||
10523 | /// <param name="table">Table we're processing for.</param> | ||
10524 | private void ParseODBCDriverOrTranslator(XElement node, string componentId, string fileId, TableDefinition table) | ||
10525 | { | ||
10526 | SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
10527 | Identifier id = null; | ||
10528 | string driver = fileId; | ||
10529 | string name = null; | ||
10530 | string setup = fileId; | ||
10531 | |||
10532 | foreach (XAttribute attrib in node.Attributes()) | ||
10533 | { | ||
10534 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
10535 | { | ||
10536 | switch (attrib.Name.LocalName) | ||
10537 | { | ||
10538 | case "Id": | ||
10539 | id = this.core.GetAttributeIdentifier(sourceLineNumbers, attrib); | ||
10540 | break; | ||
10541 | case "File": | ||
10542 | driver = this.core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); | ||
10543 | this.core.CreateSimpleReference(sourceLineNumbers, "File", driver); | ||
10544 | break; | ||
10545 | case "Name": | ||
10546 | name = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
10547 | break; | ||
10548 | case "SetupFile": | ||
10549 | setup = this.core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); | ||
10550 | this.core.CreateSimpleReference(sourceLineNumbers, "File", setup); | ||
10551 | break; | ||
10552 | default: | ||
10553 | this.core.UnexpectedAttribute(node, attrib); | ||
10554 | break; | ||
10555 | } | ||
10556 | } | ||
10557 | else | ||
10558 | { | ||
10559 | this.core.ParseExtensionAttribute(node, attrib); | ||
10560 | } | ||
10561 | } | ||
10562 | |||
10563 | if (null == name) | ||
10564 | { | ||
10565 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name")); | ||
10566 | } | ||
10567 | |||
10568 | if (null == id) | ||
10569 | { | ||
10570 | id = this.core.CreateIdentifier("odb", name, fileId, setup); | ||
10571 | } | ||
10572 | |||
10573 | // drivers have a few possible children | ||
10574 | if ("ODBCDriver" == table.Name) | ||
10575 | { | ||
10576 | // process any data sources for the driver | ||
10577 | foreach (XElement child in node.Elements()) | ||
10578 | { | ||
10579 | if (CompilerCore.WixNamespace == child.Name.Namespace) | ||
10580 | { | ||
10581 | switch (child.Name.LocalName) | ||
10582 | { | ||
10583 | case "ODBCDataSource": | ||
10584 | string ignoredKeyPath = null; | ||
10585 | this.ParseODBCDataSource(child, componentId, name, out ignoredKeyPath); | ||
10586 | break; | ||
10587 | case "Property": | ||
10588 | this.ParseODBCProperty(child, id.Id, "ODBCAttribute"); | ||
10589 | break; | ||
10590 | default: | ||
10591 | this.core.UnexpectedElement(node, child); | ||
10592 | break; | ||
10593 | } | ||
10594 | } | ||
10595 | else | ||
10596 | { | ||
10597 | this.core.ParseExtensionElement(node, child); | ||
10598 | } | ||
10599 | } | ||
10600 | } | ||
10601 | else | ||
10602 | { | ||
10603 | this.core.ParseForExtensionElements(node); | ||
10604 | } | ||
10605 | |||
10606 | if (!this.core.EncounteredError) | ||
10607 | { | ||
10608 | Row row = this.core.CreateRow(sourceLineNumbers, table.Name, id); | ||
10609 | row[1] = componentId; | ||
10610 | row[2] = name; | ||
10611 | row[3] = driver; | ||
10612 | row[4] = setup; | ||
10613 | } | ||
10614 | } | ||
10615 | |||
10616 | /// <summary> | ||
10617 | /// Parses a Property element underneath an ODBC driver or translator. | ||
10618 | /// </summary> | ||
10619 | /// <param name="node">Element to parse.</param> | ||
10620 | /// <param name="parentId">Identifier of parent driver or translator.</param> | ||
10621 | /// <param name="tableName">Name of the table to create property in.</param> | ||
10622 | private void ParseODBCProperty(XElement node, string parentId, string tableName) | ||
10623 | { | ||
10624 | SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
10625 | string id = null; | ||
10626 | string propertyValue = null; | ||
10627 | |||
10628 | foreach (XAttribute attrib in node.Attributes()) | ||
10629 | { | ||
10630 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
10631 | { | ||
10632 | switch (attrib.Name.LocalName) | ||
10633 | { | ||
10634 | case "Id": | ||
10635 | id = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
10636 | break; | ||
10637 | case "Value": | ||
10638 | propertyValue = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
10639 | break; | ||
10640 | default: | ||
10641 | this.core.UnexpectedAttribute(node, attrib); | ||
10642 | break; | ||
10643 | } | ||
10644 | } | ||
10645 | else | ||
10646 | { | ||
10647 | this.core.ParseExtensionAttribute(node, attrib); | ||
10648 | } | ||
10649 | } | ||
10650 | |||
10651 | if (null == id) | ||
10652 | { | ||
10653 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); | ||
10654 | } | ||
10655 | |||
10656 | this.core.ParseForExtensionElements(node); | ||
10657 | |||
10658 | if (!this.core.EncounteredError) | ||
10659 | { | ||
10660 | Row row = this.core.CreateRow(sourceLineNumbers, tableName); | ||
10661 | row[0] = parentId; | ||
10662 | row[1] = id; | ||
10663 | row[2] = propertyValue; | ||
10664 | } | ||
10665 | } | ||
10666 | |||
10667 | /// <summary> | ||
10668 | /// Parse an odbc data source element. | ||
10669 | /// </summary> | ||
10670 | /// <param name="node">Element to parse.</param> | ||
10671 | /// <param name="componentId">Identifier of parent component.</param> | ||
10672 | /// <param name="driverName">Default name of driver.</param> | ||
10673 | /// <param name="possibleKeyPath">Identifier of this element in case it is a keypath.</param> | ||
10674 | /// <returns>Yes if this element was marked as the parent component's key path, No if explicitly marked as not being a key path, or NotSet otherwise.</returns> | ||
10675 | private YesNoType ParseODBCDataSource(XElement node, string componentId, string driverName, out string possibleKeyPath) | ||
10676 | { | ||
10677 | SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
10678 | Identifier id = null; | ||
10679 | YesNoType keyPath = YesNoType.NotSet; | ||
10680 | string name = null; | ||
10681 | int registration = CompilerConstants.IntegerNotSet; | ||
10682 | |||
10683 | foreach (XAttribute attrib in node.Attributes()) | ||
10684 | { | ||
10685 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
10686 | { | ||
10687 | switch (attrib.Name.LocalName) | ||
10688 | { | ||
10689 | case "Id": | ||
10690 | id = this.core.GetAttributeIdentifier(sourceLineNumbers, attrib); | ||
10691 | break; | ||
10692 | case "DriverName": | ||
10693 | driverName = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
10694 | break; | ||
10695 | case "KeyPath": | ||
10696 | keyPath = this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
10697 | break; | ||
10698 | case "Name": | ||
10699 | name = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
10700 | break; | ||
10701 | case "Registration": | ||
10702 | string registrationValue = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
10703 | if (0 < registrationValue.Length) | ||
10704 | { | ||
10705 | Wix.ODBCDataSource.RegistrationType registrationType = Wix.ODBCDataSource.ParseRegistrationType(registrationValue); | ||
10706 | switch (registrationType) | ||
10707 | { | ||
10708 | case Wix.ODBCDataSource.RegistrationType.machine: | ||
10709 | registration = 0; | ||
10710 | break; | ||
10711 | case Wix.ODBCDataSource.RegistrationType.user: | ||
10712 | registration = 1; | ||
10713 | break; | ||
10714 | default: | ||
10715 | this.core.OnMessage(WixErrors.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, "Registration", registrationValue, "machine", "user")); | ||
10716 | break; | ||
10717 | } | ||
10718 | } | ||
10719 | break; | ||
10720 | default: | ||
10721 | this.core.UnexpectedAttribute(node, attrib); | ||
10722 | break; | ||
10723 | } | ||
10724 | } | ||
10725 | else | ||
10726 | { | ||
10727 | this.core.ParseExtensionAttribute(node, attrib); | ||
10728 | } | ||
10729 | } | ||
10730 | |||
10731 | if (CompilerConstants.IntegerNotSet == registration) | ||
10732 | { | ||
10733 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Registration")); | ||
10734 | registration = CompilerConstants.IllegalInteger; | ||
10735 | } | ||
10736 | |||
10737 | if (null == id) | ||
10738 | { | ||
10739 | id = this.core.CreateIdentifier("odc", name, driverName, registration.ToString()); | ||
10740 | } | ||
10741 | |||
10742 | foreach (XElement child in node.Elements()) | ||
10743 | { | ||
10744 | if (CompilerCore.WixNamespace == child.Name.Namespace) | ||
10745 | { | ||
10746 | switch (child.Name.LocalName) | ||
10747 | { | ||
10748 | case "Property": | ||
10749 | this.ParseODBCProperty(child, id.Id, "ODBCSourceAttribute"); | ||
10750 | break; | ||
10751 | default: | ||
10752 | this.core.UnexpectedElement(node, child); | ||
10753 | break; | ||
10754 | } | ||
10755 | } | ||
10756 | else | ||
10757 | { | ||
10758 | this.core.ParseExtensionElement(node, child); | ||
10759 | } | ||
10760 | } | ||
10761 | |||
10762 | if (!this.core.EncounteredError) | ||
10763 | { | ||
10764 | Row row = this.core.CreateRow(sourceLineNumbers, "ODBCDataSource", id); | ||
10765 | row[1] = componentId; | ||
10766 | row[2] = name; | ||
10767 | row[3] = driverName; | ||
10768 | row[4] = registration; | ||
10769 | } | ||
10770 | |||
10771 | possibleKeyPath = id.Id; | ||
10772 | return keyPath; | ||
10773 | } | ||
10774 | |||
10775 | /// <summary> | ||
10776 | /// Parses a package element. | ||
10777 | /// </summary> | ||
10778 | /// <param name="node">Element to parse.</param> | ||
10779 | /// <param name="productAuthor">Default package author.</param> | ||
10780 | /// <param name="moduleId">The module guid - this is necessary until Module/@Guid is removed.</param> | ||
10781 | private void ParsePackageElement(XElement node, string productAuthor, string moduleId) | ||
10782 | { | ||
10783 | SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
10784 | string codepage = "65001"; | ||
10785 | string comments = String.Format(CultureInfo.InvariantCulture, "This installer database contains the logic and data required to install {0}.", this.activeName); | ||
10786 | string keywords = "Installer"; | ||
10787 | int msiVersion = 100; // lowest released version, really should be specified | ||
10788 | string packageAuthor = productAuthor; | ||
10789 | string packageCode = null; | ||
10790 | string packageLanguages = this.activeLanguage; | ||
10791 | string packageName = this.activeName; | ||
10792 | string platform = null; | ||
10793 | string platformValue = null; | ||
10794 | YesNoDefaultType security = YesNoDefaultType.Default; | ||
10795 | int sourceBits = (this.compilingModule ? 2 : 0); | ||
10796 | Row row; | ||
10797 | bool installPrivilegeSeen = false; | ||
10798 | bool installScopeSeen = false; | ||
10799 | |||
10800 | switch (this.CurrentPlatform) | ||
10801 | { | ||
10802 | case Platform.X86: | ||
10803 | platform = "Intel"; | ||
10804 | break; | ||
10805 | case Platform.X64: | ||
10806 | platform = "x64"; | ||
10807 | msiVersion = 200; | ||
10808 | break; | ||
10809 | case Platform.IA64: | ||
10810 | platform = "Intel64"; | ||
10811 | msiVersion = 200; | ||
10812 | break; | ||
10813 | case Platform.ARM: | ||
10814 | platform = "Arm"; | ||
10815 | msiVersion = 500; | ||
10816 | break; | ||
10817 | default: | ||
10818 | throw new ArgumentException(WixStrings.EXP_UnknownPlatformEnum, this.CurrentPlatform.ToString()); | ||
10819 | } | ||
10820 | |||
10821 | foreach (XAttribute attrib in node.Attributes()) | ||
10822 | { | ||
10823 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
10824 | { | ||
10825 | switch (attrib.Name.LocalName) | ||
10826 | { | ||
10827 | case "Id": | ||
10828 | packageCode = this.core.GetAttributeGuidValue(sourceLineNumbers, attrib, this.compilingProduct); | ||
10829 | break; | ||
10830 | case "AdminImage": | ||
10831 | if (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) | ||
10832 | { | ||
10833 | sourceBits = sourceBits | 4; | ||
10834 | } | ||
10835 | break; | ||
10836 | case "Comments": | ||
10837 | comments = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
10838 | break; | ||
10839 | case "Compressed": | ||
10840 | // merge modules must always be compressed, so this attribute is invalid | ||
10841 | if (this.compilingModule) | ||
10842 | { | ||
10843 | this.core.OnMessage(WixWarnings.DeprecatedPackageCompressedAttribute(sourceLineNumbers)); | ||
10844 | // this.core.OnMessage(WixErrors.IllegalAttributeWhenNested(sourceLineNumbers, node.Name.LocalName, "Compressed", "Module")); | ||
10845 | } | ||
10846 | else if (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) | ||
10847 | { | ||
10848 | sourceBits = sourceBits | 2; | ||
10849 | } | ||
10850 | break; | ||
10851 | case "Description": | ||
10852 | packageName = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
10853 | break; | ||
10854 | case "InstallPrivileges": | ||
10855 | string installPrivileges = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
10856 | if (0 < installPrivileges.Length) | ||
10857 | { | ||
10858 | installPrivilegeSeen = true; | ||
10859 | Wix.Package.InstallPrivilegesType installPrivilegesType = Wix.Package.ParseInstallPrivilegesType(installPrivileges); | ||
10860 | switch (installPrivilegesType) | ||
10861 | { | ||
10862 | case Wix.Package.InstallPrivilegesType.elevated: | ||
10863 | // this is the default setting | ||
10864 | break; | ||
10865 | case Wix.Package.InstallPrivilegesType.limited: | ||
10866 | sourceBits = sourceBits | 8; | ||
10867 | break; | ||
10868 | default: | ||
10869 | this.core.OnMessage(WixErrors.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, installPrivileges, "elevated", "limited")); | ||
10870 | break; | ||
10871 | } | ||
10872 | } | ||
10873 | break; | ||
10874 | case "InstallScope": | ||
10875 | string installScope = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
10876 | if (0 < installScope.Length) | ||
10877 | { | ||
10878 | installScopeSeen = true; | ||
10879 | Wix.Package.InstallScopeType installScopeType = Wix.Package.ParseInstallScopeType(installScope); | ||
10880 | switch (installScopeType) | ||
10881 | { | ||
10882 | case Wix.Package.InstallScopeType.perMachine: | ||
10883 | row = this.core.CreateRow(sourceLineNumbers, "Property"); | ||
10884 | row[0] = "ALLUSERS"; | ||
10885 | row[1] = "1"; | ||
10886 | break; | ||
10887 | case Wix.Package.InstallScopeType.perUser: | ||
10888 | sourceBits = sourceBits | 8; | ||
10889 | break; | ||
10890 | default: | ||
10891 | this.core.OnMessage(WixErrors.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, installScope, "perMachine", "perUser")); | ||
10892 | break; | ||
10893 | } | ||
10894 | } | ||
10895 | break; | ||
10896 | case "InstallerVersion": | ||
10897 | msiVersion = this.core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, int.MaxValue); | ||
10898 | break; | ||
10899 | case "Keywords": | ||
10900 | keywords = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
10901 | break; | ||
10902 | case "Languages": | ||
10903 | packageLanguages = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
10904 | break; | ||
10905 | case "Manufacturer": | ||
10906 | packageAuthor = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
10907 | if ("PUT-COMPANY-NAME-HERE" == packageAuthor) | ||
10908 | { | ||
10909 | this.core.OnMessage(WixWarnings.PlaceholderValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, packageAuthor)); | ||
10910 | } | ||
10911 | break; | ||
10912 | case "Platform": | ||
10913 | if (null != platformValue) | ||
10914 | { | ||
10915 | this.core.OnMessage(WixErrors.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "Platforms")); | ||
10916 | } | ||
10917 | |||
10918 | platformValue = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
10919 | Wix.Package.PlatformType platformType = Wix.Package.ParsePlatformType(platformValue); | ||
10920 | switch (platformType) | ||
10921 | { | ||
10922 | case Wix.Package.PlatformType.intel: | ||
10923 | this.core.OnMessage(WixWarnings.DeprecatedAttributeValue(sourceLineNumbers, platformValue, node.Name.LocalName, attrib.Name.LocalName, "x86")); | ||
10924 | goto case Wix.Package.PlatformType.x86; | ||
10925 | case Wix.Package.PlatformType.x86: | ||
10926 | platform = "Intel"; | ||
10927 | break; | ||
10928 | case Wix.Package.PlatformType.x64: | ||
10929 | platform = "x64"; | ||
10930 | break; | ||
10931 | case Wix.Package.PlatformType.intel64: | ||
10932 | this.core.OnMessage(WixWarnings.DeprecatedAttributeValue(sourceLineNumbers, platformValue, node.Name.LocalName, attrib.Name.LocalName, "ia64")); | ||
10933 | goto case Wix.Package.PlatformType.ia64; | ||
10934 | case Wix.Package.PlatformType.ia64: | ||
10935 | platform = "Intel64"; | ||
10936 | break; | ||
10937 | case Wix.Package.PlatformType.arm: | ||
10938 | platform = "Arm"; | ||
10939 | break; | ||
10940 | default: | ||
10941 | this.core.OnMessage(WixErrors.InvalidPlatformValue(sourceLineNumbers, platformValue)); | ||
10942 | break; | ||
10943 | } | ||
10944 | break; | ||
10945 | case "Platforms": | ||
10946 | if (null != platformValue) | ||
10947 | { | ||
10948 | this.core.OnMessage(WixErrors.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "Platform")); | ||
10949 | } | ||
10950 | |||
10951 | this.core.OnMessage(WixWarnings.DeprecatedAttribute(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "Platform")); | ||
10952 | platformValue = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
10953 | platform = platformValue; | ||
10954 | break; | ||
10955 | case "ReadOnly": | ||
10956 | security = this.core.GetAttributeYesNoDefaultValue(sourceLineNumbers, attrib); | ||
10957 | break; | ||
10958 | case "ShortNames": | ||
10959 | if (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) | ||
10960 | { | ||
10961 | sourceBits = sourceBits | 1; | ||
10962 | this.useShortFileNames = true; | ||
10963 | } | ||
10964 | break; | ||
10965 | case "SummaryCodepage": | ||
10966 | codepage = this.core.GetAttributeLocalizableCodePageValue(sourceLineNumbers, attrib, true); | ||
10967 | break; | ||
10968 | default: | ||
10969 | this.core.UnexpectedAttribute(node, attrib); | ||
10970 | break; | ||
10971 | } | ||
10972 | } | ||
10973 | else | ||
10974 | { | ||
10975 | this.core.ParseExtensionAttribute(node, attrib); | ||
10976 | } | ||
10977 | } | ||
10978 | |||
10979 | if (installPrivilegeSeen && installScopeSeen) | ||
10980 | { | ||
10981 | this.core.OnMessage(WixErrors.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "InstallPrivileges", "InstallScope")); | ||
10982 | } | ||
10983 | |||
10984 | if ((0 != String.Compare(platform, "Intel", StringComparison.OrdinalIgnoreCase)) && 200 > msiVersion) | ||
10985 | { | ||
10986 | msiVersion = 200; | ||
10987 | this.core.OnMessage(WixWarnings.RequiresMsi200for64bitPackage(sourceLineNumbers)); | ||
10988 | } | ||
10989 | |||
10990 | if ((0 == String.Compare(platform, "Arm", StringComparison.OrdinalIgnoreCase)) && 500 > msiVersion) | ||
10991 | { | ||
10992 | msiVersion = 500; | ||
10993 | this.core.OnMessage(WixWarnings.RequiresMsi500forArmPackage(sourceLineNumbers)); | ||
10994 | } | ||
10995 | |||
10996 | if (null == packageAuthor) | ||
10997 | { | ||
10998 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Manufacturer")); | ||
10999 | } | ||
11000 | |||
11001 | if (this.compilingModule) | ||
11002 | { | ||
11003 | if (null == packageCode) | ||
11004 | { | ||
11005 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); | ||
11006 | } | ||
11007 | |||
11008 | // merge modules use the modularization guid as the package code | ||
11009 | if (null != moduleId) | ||
11010 | { | ||
11011 | packageCode = moduleId; | ||
11012 | } | ||
11013 | |||
11014 | // merge modules are always compressed | ||
11015 | sourceBits = 2; | ||
11016 | } | ||
11017 | else // product | ||
11018 | { | ||
11019 | if (null == packageCode) | ||
11020 | { | ||
11021 | packageCode = "*"; | ||
11022 | } | ||
11023 | |||
11024 | if ("*" != packageCode) | ||
11025 | { | ||
11026 | this.core.OnMessage(WixWarnings.PackageCodeSet(sourceLineNumbers)); | ||
11027 | } | ||
11028 | } | ||
11029 | |||
11030 | this.core.ParseForExtensionElements(node); | ||
11031 | |||
11032 | if (!this.core.EncounteredError) | ||
11033 | { | ||
11034 | row = this.core.CreateRow(sourceLineNumbers, "_SummaryInformation"); | ||
11035 | row[0] = 1; | ||
11036 | row[1] = codepage; | ||
11037 | |||
11038 | row = this.core.CreateRow(sourceLineNumbers, "_SummaryInformation"); | ||
11039 | row[0] = 2; | ||
11040 | row[1] = "Installation Database"; | ||
11041 | |||
11042 | row = this.core.CreateRow(sourceLineNumbers, "_SummaryInformation"); | ||
11043 | row[0] = 3; | ||
11044 | row[1] = packageName; | ||
11045 | |||
11046 | row = this.core.CreateRow(sourceLineNumbers, "_SummaryInformation"); | ||
11047 | row[0] = 4; | ||
11048 | row[1] = packageAuthor; | ||
11049 | |||
11050 | row = this.core.CreateRow(sourceLineNumbers, "_SummaryInformation"); | ||
11051 | row[0] = 5; | ||
11052 | row[1] = keywords; | ||
11053 | |||
11054 | row = this.core.CreateRow(sourceLineNumbers, "_SummaryInformation"); | ||
11055 | row[0] = 6; | ||
11056 | row[1] = comments; | ||
11057 | |||
11058 | row = this.core.CreateRow(sourceLineNumbers, "_SummaryInformation"); | ||
11059 | row[0] = 7; | ||
11060 | row[1] = String.Format(CultureInfo.InvariantCulture, "{0};{1}", platform, packageLanguages); | ||
11061 | |||
11062 | row = this.core.CreateRow(sourceLineNumbers, "_SummaryInformation"); | ||
11063 | row[0] = 9; | ||
11064 | row[1] = packageCode; | ||
11065 | |||
11066 | row = this.core.CreateRow(sourceLineNumbers, "_SummaryInformation"); | ||
11067 | row[0] = 14; | ||
11068 | row[1] = msiVersion.ToString(CultureInfo.InvariantCulture); | ||
11069 | |||
11070 | row = this.core.CreateRow(sourceLineNumbers, "_SummaryInformation"); | ||
11071 | row[0] = 15; | ||
11072 | row[1] = sourceBits.ToString(CultureInfo.InvariantCulture); | ||
11073 | |||
11074 | row = this.core.CreateRow(sourceLineNumbers, "_SummaryInformation"); | ||
11075 | row[0] = 19; | ||
11076 | switch (security) | ||
11077 | { | ||
11078 | case YesNoDefaultType.No: // no restriction | ||
11079 | row[1] = "0"; | ||
11080 | break; | ||
11081 | case YesNoDefaultType.Default: // read-only recommended | ||
11082 | row[1] = "2"; | ||
11083 | break; | ||
11084 | case YesNoDefaultType.Yes: // read-only enforced | ||
11085 | row[1] = "4"; | ||
11086 | break; | ||
11087 | } | ||
11088 | } | ||
11089 | } | ||
11090 | |||
11091 | /// <summary> | ||
11092 | /// Parses a patch metadata element. | ||
11093 | /// </summary> | ||
11094 | /// <param name="node">Element to parse.</param> | ||
11095 | private void ParsePatchMetadataElement(XElement node) | ||
11096 | { | ||
11097 | SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
11098 | YesNoType allowRemoval = YesNoType.NotSet; | ||
11099 | string classification = null; | ||
11100 | string creationTimeUtc = null; | ||
11101 | string description = null; | ||
11102 | string displayName = null; | ||
11103 | string manufacturerName = null; | ||
11104 | string minorUpdateTargetRTM = null; | ||
11105 | string moreInfoUrl = null; | ||
11106 | int optimizeCA = CompilerConstants.IntegerNotSet; | ||
11107 | YesNoType optimizedInstallMode = YesNoType.NotSet; | ||
11108 | string targetProductName = null; | ||
11109 | |||
11110 | foreach (XAttribute attrib in node.Attributes()) | ||
11111 | { | ||
11112 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
11113 | { | ||
11114 | switch (attrib.Name.LocalName) | ||
11115 | { | ||
11116 | case "AllowRemoval": | ||
11117 | allowRemoval = this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
11118 | break; | ||
11119 | case "Classification": | ||
11120 | classification = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
11121 | break; | ||
11122 | case "CreationTimeUTC": | ||
11123 | creationTimeUtc = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
11124 | break; | ||
11125 | case "Description": | ||
11126 | description = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
11127 | break; | ||
11128 | case "DisplayName": | ||
11129 | displayName = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
11130 | break; | ||
11131 | case "ManufacturerName": | ||
11132 | manufacturerName = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
11133 | break; | ||
11134 | case "MinorUpdateTargetRTM": | ||
11135 | minorUpdateTargetRTM = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
11136 | break; | ||
11137 | case "MoreInfoURL": | ||
11138 | moreInfoUrl = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
11139 | break; | ||
11140 | case "OptimizedInstallMode": | ||
11141 | optimizedInstallMode = this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
11142 | break; | ||
11143 | case "TargetProductName": | ||
11144 | targetProductName = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
11145 | break; | ||
11146 | default: | ||
11147 | this.core.UnexpectedAttribute(node, attrib); | ||
11148 | break; | ||
11149 | } | ||
11150 | } | ||
11151 | else | ||
11152 | { | ||
11153 | this.core.ParseExtensionAttribute(node, attrib); | ||
11154 | } | ||
11155 | } | ||
11156 | |||
11157 | if (YesNoType.NotSet == allowRemoval) | ||
11158 | { | ||
11159 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "AllowRemoval")); | ||
11160 | } | ||
11161 | |||
11162 | if (null == classification) | ||
11163 | { | ||
11164 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Classification")); | ||
11165 | } | ||
11166 | |||
11167 | if (null == description) | ||
11168 | { | ||
11169 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Description")); | ||
11170 | } | ||
11171 | |||
11172 | if (null == displayName) | ||
11173 | { | ||
11174 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "DisplayName")); | ||
11175 | } | ||
11176 | |||
11177 | if (null == manufacturerName) | ||
11178 | { | ||
11179 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "ManufacturerName")); | ||
11180 | } | ||
11181 | |||
11182 | if (null == moreInfoUrl) | ||
11183 | { | ||
11184 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "MoreInfoURL")); | ||
11185 | } | ||
11186 | |||
11187 | if (null == targetProductName) | ||
11188 | { | ||
11189 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "TargetProductName")); | ||
11190 | } | ||
11191 | |||
11192 | foreach (XElement child in node.Elements()) | ||
11193 | { | ||
11194 | if (CompilerCore.WixNamespace == child.Name.Namespace) | ||
11195 | { | ||
11196 | switch (child.Name.LocalName) | ||
11197 | { | ||
11198 | case "CustomProperty": | ||
11199 | this.ParseCustomPropertyElement(child); | ||
11200 | break; | ||
11201 | case "OptimizeCustomActions": | ||
11202 | optimizeCA = this.ParseOptimizeCustomActionsElement(child); | ||
11203 | break; | ||
11204 | default: | ||
11205 | this.core.UnexpectedElement(node, child); | ||
11206 | break; | ||
11207 | } | ||
11208 | } | ||
11209 | else | ||
11210 | { | ||
11211 | this.core.ParseExtensionElement(node, child); | ||
11212 | } | ||
11213 | } | ||
11214 | |||
11215 | if (!this.core.EncounteredError) | ||
11216 | { | ||
11217 | if (YesNoType.NotSet != allowRemoval) | ||
11218 | { | ||
11219 | Row row = this.core.CreateRow(sourceLineNumbers, "PatchMetadata"); | ||
11220 | row[0] = null; | ||
11221 | row[1] = "AllowRemoval"; | ||
11222 | row[2] = YesNoType.Yes == allowRemoval ? "1" : "0"; | ||
11223 | } | ||
11224 | |||
11225 | if (null != classification) | ||
11226 | { | ||
11227 | Row row = this.core.CreateRow(sourceLineNumbers, "PatchMetadata"); | ||
11228 | row[0] = null; | ||
11229 | row[1] = "Classification"; | ||
11230 | row[2] = classification; | ||
11231 | } | ||
11232 | |||
11233 | if (null != creationTimeUtc) | ||
11234 | { | ||
11235 | Row row = this.core.CreateRow(sourceLineNumbers, "PatchMetadata"); | ||
11236 | row[0] = null; | ||
11237 | row[1] = "CreationTimeUTC"; | ||
11238 | row[2] = creationTimeUtc; | ||
11239 | } | ||
11240 | |||
11241 | if (null != description) | ||
11242 | { | ||
11243 | Row row = this.core.CreateRow(sourceLineNumbers, "PatchMetadata"); | ||
11244 | row[0] = null; | ||
11245 | row[1] = "Description"; | ||
11246 | row[2] = description; | ||
11247 | } | ||
11248 | |||
11249 | if (null != displayName) | ||
11250 | { | ||
11251 | Row row = this.core.CreateRow(sourceLineNumbers, "PatchMetadata"); | ||
11252 | row[0] = null; | ||
11253 | row[1] = "DisplayName"; | ||
11254 | row[2] = displayName; | ||
11255 | } | ||
11256 | |||
11257 | if (null != manufacturerName) | ||
11258 | { | ||
11259 | Row row = this.core.CreateRow(sourceLineNumbers, "PatchMetadata"); | ||
11260 | row[0] = null; | ||
11261 | row[1] = "ManufacturerName"; | ||
11262 | row[2] = manufacturerName; | ||
11263 | } | ||
11264 | |||
11265 | if (null != minorUpdateTargetRTM) | ||
11266 | { | ||
11267 | Row row = this.core.CreateRow(sourceLineNumbers, "PatchMetadata"); | ||
11268 | row[0] = null; | ||
11269 | row[1] = "MinorUpdateTargetRTM"; | ||
11270 | row[2] = minorUpdateTargetRTM; | ||
11271 | } | ||
11272 | |||
11273 | if (null != moreInfoUrl) | ||
11274 | { | ||
11275 | Row row = this.core.CreateRow(sourceLineNumbers, "PatchMetadata"); | ||
11276 | row[0] = null; | ||
11277 | row[1] = "MoreInfoURL"; | ||
11278 | row[2] = moreInfoUrl; | ||
11279 | } | ||
11280 | |||
11281 | if (CompilerConstants.IntegerNotSet != optimizeCA) | ||
11282 | { | ||
11283 | Row row = this.core.CreateRow(sourceLineNumbers, "PatchMetadata"); | ||
11284 | row[0] = null; | ||
11285 | row[1] = "OptimizeCA"; | ||
11286 | row[2] = optimizeCA.ToString(CultureInfo.InvariantCulture); | ||
11287 | } | ||
11288 | |||
11289 | if (YesNoType.NotSet != optimizedInstallMode) | ||
11290 | { | ||
11291 | Row row = this.core.CreateRow(sourceLineNumbers, "PatchMetadata"); | ||
11292 | row[0] = null; | ||
11293 | row[1] = "OptimizedInstallMode"; | ||
11294 | row[2] = YesNoType.Yes == optimizedInstallMode ? "1" : "0"; | ||
11295 | } | ||
11296 | |||
11297 | if (null != targetProductName) | ||
11298 | { | ||
11299 | Row row = this.core.CreateRow(sourceLineNumbers, "PatchMetadata"); | ||
11300 | row[0] = null; | ||
11301 | row[1] = "TargetProductName"; | ||
11302 | row[2] = targetProductName; | ||
11303 | } | ||
11304 | } | ||
11305 | } | ||
11306 | |||
11307 | /// <summary> | ||
11308 | /// Parses a custom property element for the PatchMetadata table. | ||
11309 | /// </summary> | ||
11310 | /// <param name="node">Element to parse.</param> | ||
11311 | private void ParseCustomPropertyElement(XElement node) | ||
11312 | { | ||
11313 | SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
11314 | string company = null; | ||
11315 | string property = null; | ||
11316 | string value = null; | ||
11317 | |||
11318 | foreach (XAttribute attrib in node.Attributes()) | ||
11319 | { | ||
11320 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
11321 | { | ||
11322 | switch (attrib.Name.LocalName) | ||
11323 | { | ||
11324 | case "Company": | ||
11325 | company = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
11326 | break; | ||
11327 | case "Property": | ||
11328 | property = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
11329 | break; | ||
11330 | case "Value": | ||
11331 | value = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
11332 | break; | ||
11333 | default: | ||
11334 | this.core.UnexpectedAttribute(node, attrib); | ||
11335 | break; | ||
11336 | } | ||
11337 | } | ||
11338 | else | ||
11339 | { | ||
11340 | this.core.ParseExtensionAttribute(node, attrib); | ||
11341 | } | ||
11342 | } | ||
11343 | |||
11344 | if (null == company) | ||
11345 | { | ||
11346 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Company")); | ||
11347 | } | ||
11348 | |||
11349 | if (null == property) | ||
11350 | { | ||
11351 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Property")); | ||
11352 | } | ||
11353 | |||
11354 | if (null == value) | ||
11355 | { | ||
11356 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Value")); | ||
11357 | } | ||
11358 | |||
11359 | this.core.ParseForExtensionElements(node); | ||
11360 | |||
11361 | if (!this.core.EncounteredError) | ||
11362 | { | ||
11363 | Row row = this.core.CreateRow(sourceLineNumbers, "PatchMetadata"); | ||
11364 | row[0] = company; | ||
11365 | row[1] = property; | ||
11366 | row[2] = value; | ||
11367 | } | ||
11368 | } | ||
11369 | |||
11370 | /// <summary> | ||
11371 | /// Parses the OptimizeCustomActions element. | ||
11372 | /// </summary> | ||
11373 | /// <param name="node">Element to parse.</param> | ||
11374 | /// <returns>The combined integer value for callers to store as appropriate.</returns> | ||
11375 | private int ParseOptimizeCustomActionsElement(XElement node) | ||
11376 | { | ||
11377 | SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
11378 | OptimizeCA optimizeCA = OptimizeCA.None; | ||
11379 | |||
11380 | foreach (XAttribute attrib in node.Attributes()) | ||
11381 | { | ||
11382 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
11383 | { | ||
11384 | switch (attrib.Name.LocalName) | ||
11385 | { | ||
11386 | case "SkipAssignment": | ||
11387 | if (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) | ||
11388 | { | ||
11389 | optimizeCA |= OptimizeCA.SkipAssignment; | ||
11390 | } | ||
11391 | break; | ||
11392 | case "SkipImmediate": | ||
11393 | if (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) | ||
11394 | { | ||
11395 | optimizeCA |= OptimizeCA.SkipImmediate; | ||
11396 | } | ||
11397 | break; | ||
11398 | case "SkipDeferred": | ||
11399 | if (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) | ||
11400 | { | ||
11401 | optimizeCA |= OptimizeCA.SkipDeferred; | ||
11402 | } | ||
11403 | break; | ||
11404 | default: | ||
11405 | this.core.UnexpectedAttribute(node, attrib); | ||
11406 | break; | ||
11407 | } | ||
11408 | } | ||
11409 | else | ||
11410 | { | ||
11411 | this.core.ParseExtensionAttribute(node, attrib); | ||
11412 | } | ||
11413 | } | ||
11414 | |||
11415 | return (int)optimizeCA; | ||
11416 | } | ||
11417 | |||
11418 | /// <summary> | ||
11419 | /// Parses a patch information element. | ||
11420 | /// </summary> | ||
11421 | /// <param name="node">Element to parse.</param> | ||
11422 | private void ParsePatchInformationElement(XElement node) | ||
11423 | { | ||
11424 | SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
11425 | string codepage = "65001"; | ||
11426 | string comments = null; | ||
11427 | string keywords = "Installer,Patching,PCP,Database"; | ||
11428 | int msiVersion = 1; // Should always be 1 for patches | ||
11429 | string packageAuthor = null; | ||
11430 | string packageName = this.activeName; | ||
11431 | YesNoDefaultType security = YesNoDefaultType.Default; | ||
11432 | |||
11433 | foreach (XAttribute attrib in node.Attributes()) | ||
11434 | { | ||
11435 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
11436 | { | ||
11437 | switch (attrib.Name.LocalName) | ||
11438 | { | ||
11439 | case "AdminImage": | ||
11440 | this.core.OnMessage(WixWarnings.DeprecatedAttribute(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName)); | ||
11441 | break; | ||
11442 | case "Comments": | ||
11443 | comments = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
11444 | break; | ||
11445 | case "Compressed": | ||
11446 | this.core.OnMessage(WixWarnings.DeprecatedAttribute(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName)); | ||
11447 | break; | ||
11448 | case "Description": | ||
11449 | packageName = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
11450 | break; | ||
11451 | case "Keywords": | ||
11452 | keywords = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
11453 | break; | ||
11454 | case "Languages": | ||
11455 | this.core.OnMessage(WixWarnings.DeprecatedAttribute(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName)); | ||
11456 | break; | ||
11457 | case "Manufacturer": | ||
11458 | packageAuthor = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
11459 | break; | ||
11460 | case "Platforms": | ||
11461 | this.core.OnMessage(WixWarnings.DeprecatedAttribute(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName)); | ||
11462 | break; | ||
11463 | case "ReadOnly": | ||
11464 | security = this.core.GetAttributeYesNoDefaultValue(sourceLineNumbers, attrib); | ||
11465 | break; | ||
11466 | case "ShortNames": | ||
11467 | this.core.OnMessage(WixWarnings.DeprecatedAttribute(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName)); | ||
11468 | break; | ||
11469 | case "SummaryCodepage": | ||
11470 | codepage = this.core.GetAttributeLocalizableCodePageValue(sourceLineNumbers, attrib); | ||
11471 | break; | ||
11472 | default: | ||
11473 | this.core.UnexpectedAttribute(node, attrib); | ||
11474 | break; | ||
11475 | } | ||
11476 | } | ||
11477 | else | ||
11478 | { | ||
11479 | this.core.ParseExtensionAttribute(node, attrib); | ||
11480 | } | ||
11481 | } | ||
11482 | |||
11483 | this.core.ParseForExtensionElements(node); | ||
11484 | |||
11485 | if (!this.core.EncounteredError) | ||
11486 | { | ||
11487 | // PID_CODEPAGE | ||
11488 | Row row = this.core.CreateRow(sourceLineNumbers, "_SummaryInformation"); | ||
11489 | row[0] = 1; | ||
11490 | row[1] = codepage; | ||
11491 | |||
11492 | // PID_TITLE | ||
11493 | row = this.core.CreateRow(sourceLineNumbers, "_SummaryInformation"); | ||
11494 | row[0] = 2; | ||
11495 | row[1] = "Patch"; | ||
11496 | |||
11497 | // PID_SUBJECT | ||
11498 | if (null != packageName) | ||
11499 | { | ||
11500 | row = this.core.CreateRow(sourceLineNumbers, "_SummaryInformation"); | ||
11501 | row[0] = 3; | ||
11502 | row[1] = packageName; | ||
11503 | } | ||
11504 | |||
11505 | // PID_AUTHOR | ||
11506 | if (null != packageAuthor) | ||
11507 | { | ||
11508 | row = this.core.CreateRow(sourceLineNumbers, "_SummaryInformation"); | ||
11509 | row[0] = 4; | ||
11510 | row[1] = packageAuthor; | ||
11511 | } | ||
11512 | |||
11513 | // PID_KEYWORDS | ||
11514 | if (null != keywords) | ||
11515 | { | ||
11516 | row = this.core.CreateRow(sourceLineNumbers, "_SummaryInformation"); | ||
11517 | row[0] = 5; | ||
11518 | row[1] = keywords; | ||
11519 | } | ||
11520 | |||
11521 | // PID_COMMENTS | ||
11522 | if (null != comments) | ||
11523 | { | ||
11524 | row = this.core.CreateRow(sourceLineNumbers, "_SummaryInformation"); | ||
11525 | row[0] = 6; | ||
11526 | row[1] = comments; | ||
11527 | } | ||
11528 | |||
11529 | // PID_PAGECOUNT | ||
11530 | row = this.core.CreateRow(sourceLineNumbers, "_SummaryInformation"); | ||
11531 | row[0] = 14; | ||
11532 | row[1] = msiVersion.ToString(CultureInfo.InvariantCulture); | ||
11533 | |||
11534 | // PID_WORDCOUNT | ||
11535 | row = this.core.CreateRow(sourceLineNumbers, "_SummaryInformation"); | ||
11536 | row[0] = 15; | ||
11537 | row[1] = "0"; | ||
11538 | |||
11539 | // PID_SECURITY | ||
11540 | row = this.core.CreateRow(sourceLineNumbers, "_SummaryInformation"); | ||
11541 | row[0] = 19; | ||
11542 | switch (security) | ||
11543 | { | ||
11544 | case YesNoDefaultType.No: // no restriction | ||
11545 | row[1] = "0"; | ||
11546 | break; | ||
11547 | case YesNoDefaultType.Default: // read-only recommended | ||
11548 | row[1] = "2"; | ||
11549 | break; | ||
11550 | case YesNoDefaultType.Yes: // read-only enforced | ||
11551 | row[1] = "4"; | ||
11552 | break; | ||
11553 | } | ||
11554 | } | ||
11555 | } | ||
11556 | |||
11557 | /// <summary> | ||
11558 | /// Parses an ignore modularization element. | ||
11559 | /// </summary> | ||
11560 | /// <param name="node">XmlNode on an IgnoreModulatization element.</param> | ||
11561 | private void ParseIgnoreModularizationElement(XElement node) | ||
11562 | { | ||
11563 | SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
11564 | string name = null; | ||
11565 | |||
11566 | this.core.OnMessage(WixWarnings.DeprecatedIgnoreModularizationElement(sourceLineNumbers)); | ||
11567 | |||
11568 | foreach (XAttribute attrib in node.Attributes()) | ||
11569 | { | ||
11570 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
11571 | { | ||
11572 | switch (attrib.Name.LocalName) | ||
11573 | { | ||
11574 | case "Name": | ||
11575 | name = this.core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); | ||
11576 | break; | ||
11577 | case "Type": | ||
11578 | // this is actually not used | ||
11579 | break; | ||
11580 | default: | ||
11581 | this.core.UnexpectedAttribute(node, attrib); | ||
11582 | break; | ||
11583 | } | ||
11584 | } | ||
11585 | else | ||
11586 | { | ||
11587 | this.core.ParseExtensionAttribute(node, attrib); | ||
11588 | } | ||
11589 | } | ||
11590 | |||
11591 | if (null == name) | ||
11592 | { | ||
11593 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name")); | ||
11594 | } | ||
11595 | |||
11596 | this.core.ParseForExtensionElements(node); | ||
11597 | |||
11598 | if (!this.core.EncounteredError) | ||
11599 | { | ||
11600 | Row row = this.core.CreateRow(sourceLineNumbers, "WixSuppressModularization"); | ||
11601 | row[0] = name; | ||
11602 | } | ||
11603 | } | ||
11604 | |||
11605 | /// <summary> | ||
11606 | /// Parses a permission element. | ||
11607 | /// </summary> | ||
11608 | /// <param name="node">Element to parse.</param> | ||
11609 | /// <param name="objectId">Identifier of object to be secured.</param> | ||
11610 | /// <param name="tableName">Name of table that contains objectId.</param> | ||
11611 | private void ParsePermissionElement(XElement node, string objectId, string tableName) | ||
11612 | { | ||
11613 | SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
11614 | BitArray bits = new BitArray(32); | ||
11615 | string domain = null; | ||
11616 | int permission = 0; | ||
11617 | string[] specialPermissions = null; | ||
11618 | string user = null; | ||
11619 | |||
11620 | switch (tableName) | ||
11621 | { | ||
11622 | case "CreateFolder": | ||
11623 | specialPermissions = Common.FolderPermissions; | ||
11624 | break; | ||
11625 | case "File": | ||
11626 | specialPermissions = Common.FilePermissions; | ||
11627 | break; | ||
11628 | case "Registry": | ||
11629 | specialPermissions = Common.RegistryPermissions; | ||
11630 | break; | ||
11631 | default: | ||
11632 | this.core.UnexpectedElement(node.Parent, node); | ||
11633 | return; // stop processing this element since no valid permissions are available | ||
11634 | } | ||
11635 | |||
11636 | foreach (XAttribute attrib in node.Attributes()) | ||
11637 | { | ||
11638 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
11639 | { | ||
11640 | switch (attrib.Name.LocalName) | ||
11641 | { | ||
11642 | case "Domain": | ||
11643 | domain = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
11644 | break; | ||
11645 | case "User": | ||
11646 | user = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
11647 | break; | ||
11648 | case "FileAllRights": | ||
11649 | // match the WinNT.h mask FILE_ALL_ACCESS for value 0x001F01FF (aka 1 1111 0000 0001 1111 1111 or 2032127) | ||
11650 | bits[0] = bits[1] = bits[2] = bits[3] = bits[4] = bits[5] = bits[6] = bits[7] = bits[8] = bits[16] = bits[17] = bits[18] = bits[19] = bits[20] = true; | ||
11651 | break; | ||
11652 | case "SpecificRightsAll": | ||
11653 | // match the WinNT.h mask SPECIFIC_RIGHTS_ALL for value 0x0000FFFF (aka 1111 1111 1111 1111) | ||
11654 | bits[0] = bits[1] = bits[2] = bits[3] = bits[4] = bits[5] = bits[6] = bits[7] = bits[8] = bits[9] = bits[10] = bits[11] = bits[12] = bits[13] = bits[14] = bits[15] = true; | ||
11655 | break; | ||
11656 | default: | ||
11657 | YesNoType attribValue = this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
11658 | if (!this.core.TrySetBitFromName(Common.StandardPermissions, attrib.Name.LocalName, attribValue, bits, 16)) | ||
11659 | { | ||
11660 | if (!this.core.TrySetBitFromName(Common.GenericPermissions, attrib.Name.LocalName, attribValue, bits, 28)) | ||
11661 | { | ||
11662 | if (!this.core.TrySetBitFromName(specialPermissions, attrib.Name.LocalName, attribValue, bits, 0)) | ||
11663 | { | ||
11664 | this.core.UnexpectedAttribute(node, attrib); | ||
11665 | break; | ||
11666 | } | ||
11667 | } | ||
11668 | } | ||
11669 | break; | ||
11670 | } | ||
11671 | } | ||
11672 | else | ||
11673 | { | ||
11674 | this.core.ParseExtensionAttribute(node, attrib); | ||
11675 | } | ||
11676 | } | ||
11677 | |||
11678 | permission = this.core.CreateIntegerFromBitArray(bits); | ||
11679 | |||
11680 | if (null == user) | ||
11681 | { | ||
11682 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "User")); | ||
11683 | } | ||
11684 | |||
11685 | if (int.MinValue == permission) // just GENERIC_READ, which is MSI_NULL | ||
11686 | { | ||
11687 | this.core.OnMessage(WixErrors.GenericReadNotAllowed(sourceLineNumbers)); | ||
11688 | } | ||
11689 | |||
11690 | this.core.ParseForExtensionElements(node); | ||
11691 | |||
11692 | if (!this.core.EncounteredError) | ||
11693 | { | ||
11694 | Row row = this.core.CreateRow(sourceLineNumbers, "LockPermissions"); | ||
11695 | row[0] = objectId; | ||
11696 | row[1] = tableName; | ||
11697 | row[2] = domain; | ||
11698 | row[3] = user; | ||
11699 | row[4] = permission; | ||
11700 | } | ||
11701 | } | ||
11702 | |||
11703 | /// <summary> | ||
11704 | /// Parses an extended permission element. | ||
11705 | /// </summary> | ||
11706 | /// <param name="node">Element to parse.</param> | ||
11707 | /// <param name="objectId">Identifier of object to be secured.</param> | ||
11708 | /// <param name="tableName">Name of table that contains objectId.</param> | ||
11709 | private void ParsePermissionExElement(XElement node, string objectId, string tableName) | ||
11710 | { | ||
11711 | SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
11712 | string condition = null; | ||
11713 | Identifier id = null; | ||
11714 | string sddl = null; | ||
11715 | |||
11716 | switch (tableName) | ||
11717 | { | ||
11718 | case "CreateFolder": | ||
11719 | case "File": | ||
11720 | case "Registry": | ||
11721 | case "ServiceInstall": | ||
11722 | break; | ||
11723 | default: | ||
11724 | this.core.UnexpectedElement(node.Parent, node); | ||
11725 | return; // stop processing this element since nothing will be valid. | ||
11726 | } | ||
11727 | |||
11728 | foreach (XAttribute attrib in node.Attributes()) | ||
11729 | { | ||
11730 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
11731 | { | ||
11732 | switch (attrib.Name.LocalName) | ||
11733 | { | ||
11734 | case "Id": | ||
11735 | id = this.core.GetAttributeIdentifier(sourceLineNumbers, attrib); | ||
11736 | break; | ||
11737 | case "Sddl": | ||
11738 | sddl = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
11739 | break; | ||
11740 | default: | ||
11741 | this.core.UnexpectedAttribute(node, attrib); | ||
11742 | break; | ||
11743 | } | ||
11744 | } | ||
11745 | else | ||
11746 | { | ||
11747 | this.core.ParseExtensionAttribute(node, attrib); | ||
11748 | } | ||
11749 | } | ||
11750 | |||
11751 | if (null == sddl) | ||
11752 | { | ||
11753 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Sddl")); | ||
11754 | } | ||
11755 | |||
11756 | if (null == id) | ||
11757 | { | ||
11758 | id = this.core.CreateIdentifier("pme", objectId, tableName, sddl); | ||
11759 | } | ||
11760 | |||
11761 | foreach (XElement child in node.Elements()) | ||
11762 | { | ||
11763 | if (CompilerCore.WixNamespace == child.Name.Namespace) | ||
11764 | { | ||
11765 | switch (child.Name.LocalName) | ||
11766 | { | ||
11767 | case "Condition": | ||
11768 | if (null != condition) | ||
11769 | { | ||
11770 | SourceLineNumber childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
11771 | this.core.OnMessage(WixErrors.TooManyChildren(childSourceLineNumbers, node.Name.LocalName, child.Name.LocalName)); | ||
11772 | } | ||
11773 | |||
11774 | condition = this.ParseConditionElement(child, node.Name.LocalName, null, null); | ||
11775 | break; | ||
11776 | default: | ||
11777 | this.core.UnexpectedElement(node, child); | ||
11778 | break; | ||
11779 | } | ||
11780 | } | ||
11781 | else | ||
11782 | { | ||
11783 | this.core.ParseExtensionElement(node, child); | ||
11784 | } | ||
11785 | } | ||
11786 | |||
11787 | if (!this.core.EncounteredError) | ||
11788 | { | ||
11789 | Row row = this.core.CreateRow(sourceLineNumbers, "MsiLockPermissionsEx", id); | ||
11790 | row[1] = objectId; | ||
11791 | row[2] = tableName; | ||
11792 | row[3] = sddl; | ||
11793 | row[4] = condition; | ||
11794 | } | ||
11795 | } | ||
11796 | |||
11797 | /// <summary> | ||
11798 | /// Parses a product element. | ||
11799 | /// </summary> | ||
11800 | /// <param name="node">Element to parse.</param> | ||
11801 | [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1800:DoNotCastUnnecessarily")] | ||
11802 | private void ParseProductElement(XElement node) | ||
11803 | { | ||
11804 | SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
11805 | int codepage = 0; | ||
11806 | string productCode = null; | ||
11807 | string upgradeCode = null; | ||
11808 | string manufacturer = null; | ||
11809 | string version = null; | ||
11810 | string symbols = null; | ||
11811 | |||
11812 | this.activeName = null; | ||
11813 | this.activeLanguage = null; | ||
11814 | |||
11815 | foreach (XAttribute attrib in node.Attributes()) | ||
11816 | { | ||
11817 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
11818 | { | ||
11819 | switch (attrib.Name.LocalName) | ||
11820 | { | ||
11821 | case "Id": | ||
11822 | productCode = this.core.GetAttributeGuidValue(sourceLineNumbers, attrib, true); | ||
11823 | break; | ||
11824 | case "Codepage": | ||
11825 | codepage = this.core.GetAttributeCodePageValue(sourceLineNumbers, attrib); | ||
11826 | break; | ||
11827 | case "Language": | ||
11828 | this.activeLanguage = this.core.GetAttributeLocalizableIntegerValue(sourceLineNumbers, attrib, 0, short.MaxValue); | ||
11829 | break; | ||
11830 | case "Manufacturer": | ||
11831 | manufacturer = this.core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.MustHaveNonWhitespaceCharacters); | ||
11832 | if ("PUT-COMPANY-NAME-HERE" == manufacturer) | ||
11833 | { | ||
11834 | this.core.OnMessage(WixWarnings.PlaceholderValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, manufacturer)); | ||
11835 | } | ||
11836 | break; | ||
11837 | case "Name": | ||
11838 | this.activeName = this.core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.MustHaveNonWhitespaceCharacters); | ||
11839 | if ("PUT-PRODUCT-NAME-HERE" == this.activeName) | ||
11840 | { | ||
11841 | this.core.OnMessage(WixWarnings.PlaceholderValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, this.activeName)); | ||
11842 | } | ||
11843 | break; | ||
11844 | case "UpgradeCode": | ||
11845 | upgradeCode = this.core.GetAttributeGuidValue(sourceLineNumbers, attrib, false); | ||
11846 | break; | ||
11847 | case "Version": // if the attribute is valid version, use the attribute value as is (so "1.0000.01.01" would *not* get translated to "1.0.1.1"). | ||
11848 | string verifiedVersion = this.core.GetAttributeVersionValue(sourceLineNumbers, attrib); | ||
11849 | if (!String.IsNullOrEmpty(verifiedVersion)) | ||
11850 | { | ||
11851 | version = attrib.Value; | ||
11852 | } | ||
11853 | break; | ||
11854 | default: | ||
11855 | this.core.UnexpectedAttribute(node, attrib); | ||
11856 | break; | ||
11857 | } | ||
11858 | } | ||
11859 | else | ||
11860 | { | ||
11861 | this.core.ParseExtensionAttribute(node, attrib); | ||
11862 | } | ||
11863 | } | ||
11864 | |||
11865 | if (null == productCode) | ||
11866 | { | ||
11867 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); | ||
11868 | } | ||
11869 | |||
11870 | if (null == this.activeLanguage) | ||
11871 | { | ||
11872 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Language")); | ||
11873 | } | ||
11874 | |||
11875 | if (null == manufacturer) | ||
11876 | { | ||
11877 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Manufacturer")); | ||
11878 | } | ||
11879 | |||
11880 | if (null == this.activeName) | ||
11881 | { | ||
11882 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name")); | ||
11883 | } | ||
11884 | |||
11885 | if (null == upgradeCode) | ||
11886 | { | ||
11887 | this.core.OnMessage(WixWarnings.MissingUpgradeCode(sourceLineNumbers)); | ||
11888 | } | ||
11889 | |||
11890 | if (null == version) | ||
11891 | { | ||
11892 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Version")); | ||
11893 | } | ||
11894 | else if (!CompilerCore.IsValidProductVersion(version)) | ||
11895 | { | ||
11896 | this.core.OnMessage(WixErrors.InvalidProductVersion(sourceLineNumbers, version)); | ||
11897 | } | ||
11898 | |||
11899 | if (this.core.EncounteredError) | ||
11900 | { | ||
11901 | return; | ||
11902 | } | ||
11903 | |||
11904 | try | ||
11905 | { | ||
11906 | this.compilingProduct = true; | ||
11907 | this.core.CreateActiveSection(productCode, SectionType.Product, codepage); | ||
11908 | |||
11909 | this.AddProperty(sourceLineNumbers, new Identifier("Manufacturer", AccessModifier.Public), manufacturer, false, false, false, true); | ||
11910 | this.AddProperty(sourceLineNumbers, new Identifier("ProductCode", AccessModifier.Public), productCode, false, false, false, true); | ||
11911 | this.AddProperty(sourceLineNumbers, new Identifier("ProductLanguage", AccessModifier.Public), this.activeLanguage, false, false, false, true); | ||
11912 | this.AddProperty(sourceLineNumbers, new Identifier("ProductName", AccessModifier.Public), this.activeName, false, false, false, true); | ||
11913 | this.AddProperty(sourceLineNumbers, new Identifier("ProductVersion", AccessModifier.Public), version, false, false, false, true); | ||
11914 | if (null != upgradeCode) | ||
11915 | { | ||
11916 | this.AddProperty(sourceLineNumbers, new Identifier("UpgradeCode", AccessModifier.Public), upgradeCode, false, false, false, true); | ||
11917 | } | ||
11918 | |||
11919 | Dictionary<string, string> contextValues = new Dictionary<string, string>(); | ||
11920 | contextValues["ProductLanguage"] = this.activeLanguage; | ||
11921 | contextValues["ProductVersion"] = version; | ||
11922 | contextValues["UpgradeCode"] = upgradeCode; | ||
11923 | |||
11924 | int featureDisplay = 0; | ||
11925 | foreach (XElement child in node.Elements()) | ||
11926 | { | ||
11927 | if (CompilerCore.WixNamespace == child.Name.Namespace) | ||
11928 | { | ||
11929 | switch (child.Name.LocalName) | ||
11930 | { | ||
11931 | case "_locDefinition": | ||
11932 | break; | ||
11933 | case "AdminExecuteSequence": | ||
11934 | case "AdminUISequence": | ||
11935 | case "AdvertiseExecuteSequence": | ||
11936 | case "InstallExecuteSequence": | ||
11937 | case "InstallUISequence": | ||
11938 | this.ParseSequenceElement(child, child.Name.LocalName); | ||
11939 | break; | ||
11940 | case "AppId": | ||
11941 | this.ParseAppIdElement(child, null, YesNoType.Yes, null, null, null); | ||
11942 | break; | ||
11943 | case "Binary": | ||
11944 | this.ParseBinaryElement(child); | ||
11945 | break; | ||
11946 | case "ComplianceCheck": | ||
11947 | this.ParseComplianceCheckElement(child); | ||
11948 | break; | ||
11949 | case "Component": | ||
11950 | this.ParseComponentElement(child, ComplexReferenceParentType.Unknown, null, null, CompilerConstants.IntegerNotSet, null, null); | ||
11951 | break; | ||
11952 | case "ComponentGroup": | ||
11953 | this.ParseComponentGroupElement(child, ComplexReferenceParentType.Unknown, null); | ||
11954 | break; | ||
11955 | case "Condition": | ||
11956 | this.ParseConditionElement(child, node.Name.LocalName, null, null); | ||
11957 | break; | ||
11958 | case "CustomAction": | ||
11959 | this.ParseCustomActionElement(child); | ||
11960 | break; | ||
11961 | case "CustomActionRef": | ||
11962 | this.ParseSimpleRefElement(child, "CustomAction"); | ||
11963 | break; | ||
11964 | case "CustomTable": | ||
11965 | this.ParseCustomTableElement(child); | ||
11966 | break; | ||
11967 | case "Directory": | ||
11968 | this.ParseDirectoryElement(child, null, CompilerConstants.IntegerNotSet, String.Empty); | ||
11969 | break; | ||
11970 | case "DirectoryRef": | ||
11971 | this.ParseDirectoryRefElement(child); | ||
11972 | break; | ||
11973 | case "EmbeddedChainer": | ||
11974 | this.ParseEmbeddedChainerElement(child); | ||
11975 | break; | ||
11976 | case "EmbeddedChainerRef": | ||
11977 | this.ParseSimpleRefElement(child, "MsiEmbeddedChainer"); | ||
11978 | break; | ||
11979 | case "EnsureTable": | ||
11980 | this.ParseEnsureTableElement(child); | ||
11981 | break; | ||
11982 | case "Feature": | ||
11983 | this.ParseFeatureElement(child, ComplexReferenceParentType.Product, productCode, ref featureDisplay); | ||
11984 | break; | ||
11985 | case "FeatureRef": | ||
11986 | this.ParseFeatureRefElement(child, ComplexReferenceParentType.Product, productCode); | ||
11987 | break; | ||
11988 | case "FeatureGroupRef": | ||
11989 | this.ParseFeatureGroupRefElement(child, ComplexReferenceParentType.Product, productCode); | ||
11990 | break; | ||
11991 | case "Icon": | ||
11992 | this.ParseIconElement(child); | ||
11993 | break; | ||
11994 | case "InstanceTransforms": | ||
11995 | this.ParseInstanceTransformsElement(child); | ||
11996 | break; | ||
11997 | case "MajorUpgrade": | ||
11998 | this.ParseMajorUpgradeElement(child, contextValues); | ||
11999 | break; | ||
12000 | case "Media": | ||
12001 | this.ParseMediaElement(child, null); | ||
12002 | break; | ||
12003 | case "MediaTemplate": | ||
12004 | this.ParseMediaTemplateElement(child, null); | ||
12005 | break; | ||
12006 | case "Package": | ||
12007 | this.ParsePackageElement(child, manufacturer, null); | ||
12008 | break; | ||
12009 | case "PackageCertificates": | ||
12010 | case "PatchCertificates": | ||
12011 | this.ParseCertificatesElement(child); | ||
12012 | break; | ||
12013 | case "Property": | ||
12014 | this.ParsePropertyElement(child); | ||
12015 | break; | ||
12016 | case "PropertyRef": | ||
12017 | this.ParseSimpleRefElement(child, "Property"); | ||
12018 | break; | ||
12019 | case "SetDirectory": | ||
12020 | this.ParseSetDirectoryElement(child); | ||
12021 | break; | ||
12022 | case "SetProperty": | ||
12023 | this.ParseSetPropertyElement(child); | ||
12024 | break; | ||
12025 | case "SFPCatalog": | ||
12026 | string parentName = null; | ||
12027 | this.ParseSFPCatalogElement(child, ref parentName); | ||
12028 | break; | ||
12029 | case "SymbolPath": | ||
12030 | if (null != symbols) | ||
12031 | { | ||
12032 | symbols += ";" + this.ParseSymbolPathElement(child); | ||
12033 | } | ||
12034 | else | ||
12035 | { | ||
12036 | symbols = this.ParseSymbolPathElement(child); | ||
12037 | } | ||
12038 | break; | ||
12039 | case "UI": | ||
12040 | this.ParseUIElement(child); | ||
12041 | break; | ||
12042 | case "UIRef": | ||
12043 | this.ParseSimpleRefElement(child, "WixUI"); | ||
12044 | break; | ||
12045 | case "Upgrade": | ||
12046 | this.ParseUpgradeElement(child); | ||
12047 | break; | ||
12048 | case "WixVariable": | ||
12049 | this.ParseWixVariableElement(child); | ||
12050 | break; | ||
12051 | default: | ||
12052 | this.core.UnexpectedElement(node, child); | ||
12053 | break; | ||
12054 | } | ||
12055 | } | ||
12056 | else | ||
12057 | { | ||
12058 | this.core.ParseExtensionElement(node, child); | ||
12059 | } | ||
12060 | } | ||
12061 | |||
12062 | if (!this.core.EncounteredError) | ||
12063 | { | ||
12064 | if (null != symbols) | ||
12065 | { | ||
12066 | WixDeltaPatchSymbolPathsRow symbolRow = (WixDeltaPatchSymbolPathsRow)this.core.CreateRow(sourceLineNumbers, "WixDeltaPatchSymbolPaths"); | ||
12067 | symbolRow.Id = productCode; | ||
12068 | symbolRow.Type = SymbolPathType.Product; | ||
12069 | symbolRow.SymbolPaths = symbols; | ||
12070 | } | ||
12071 | } | ||
12072 | } | ||
12073 | finally | ||
12074 | { | ||
12075 | this.compilingProduct = false; | ||
12076 | } | ||
12077 | } | ||
12078 | |||
12079 | /// <summary> | ||
12080 | /// Parses a progid element | ||
12081 | /// </summary> | ||
12082 | /// <param name="node">Element to parse.</param> | ||
12083 | /// <param name="componentId">Identifier of parent component.</param> | ||
12084 | /// <param name="advertise">Flag if progid is advertised.</param> | ||
12085 | /// <param name="classId">CLSID related to ProgId.</param> | ||
12086 | /// <param name="description">Default description of ProgId</param> | ||
12087 | /// <param name="parent">Optional parent ProgId</param> | ||
12088 | /// <param name="foundExtension">Set to true if an extension is found; used for error-checking.</param> | ||
12089 | /// <param name="firstProgIdForClass">Whether or not this ProgId is the first one found in the parent class.</param> | ||
12090 | /// <returns>This element's Id.</returns> | ||
12091 | private string ParseProgIdElement(XElement node, string componentId, YesNoType advertise, string classId, string description, string parent, ref bool foundExtension, YesNoType firstProgIdForClass) | ||
12092 | { | ||
12093 | SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
12094 | string icon = null; | ||
12095 | int iconIndex = CompilerConstants.IntegerNotSet; | ||
12096 | string noOpen = null; | ||
12097 | string progId = null; | ||
12098 | YesNoType progIdAdvertise = YesNoType.NotSet; | ||
12099 | |||
12100 | foreach (XAttribute attrib in node.Attributes()) | ||
12101 | { | ||
12102 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
12103 | { | ||
12104 | switch (attrib.Name.LocalName) | ||
12105 | { | ||
12106 | case "Id": | ||
12107 | progId = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
12108 | break; | ||
12109 | case "Advertise": | ||
12110 | progIdAdvertise = this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
12111 | break; | ||
12112 | case "Description": | ||
12113 | description = this.core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty); | ||
12114 | break; | ||
12115 | case "Icon": | ||
12116 | icon = this.core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); | ||
12117 | break; | ||
12118 | case "IconIndex": | ||
12119 | iconIndex = this.core.GetAttributeIntegerValue(sourceLineNumbers, attrib, short.MinValue + 1, short.MaxValue); | ||
12120 | break; | ||
12121 | case "NoOpen": | ||
12122 | noOpen = this.core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty); | ||
12123 | break; | ||
12124 | default: | ||
12125 | this.core.UnexpectedAttribute(node, attrib); | ||
12126 | break; | ||
12127 | } | ||
12128 | } | ||
12129 | else | ||
12130 | { | ||
12131 | this.core.ParseExtensionAttribute(node, attrib); | ||
12132 | } | ||
12133 | } | ||
12134 | |||
12135 | if ((YesNoType.No == advertise && YesNoType.Yes == progIdAdvertise) || (YesNoType.Yes == advertise && YesNoType.No == progIdAdvertise)) | ||
12136 | { | ||
12137 | this.core.OnMessage(WixErrors.AdvertiseStateMustMatch(sourceLineNumbers, advertise.ToString(), progIdAdvertise.ToString())); | ||
12138 | } | ||
12139 | else | ||
12140 | { | ||
12141 | advertise = progIdAdvertise; | ||
12142 | } | ||
12143 | |||
12144 | if (YesNoType.NotSet == advertise) | ||
12145 | { | ||
12146 | advertise = YesNoType.No; | ||
12147 | } | ||
12148 | |||
12149 | if (null != parent && (null != icon || CompilerConstants.IntegerNotSet != iconIndex)) | ||
12150 | { | ||
12151 | this.core.OnMessage(WixErrors.VersionIndependentProgIdsCannotHaveIcons(sourceLineNumbers)); | ||
12152 | } | ||
12153 | |||
12154 | YesNoType firstProgIdForNestedClass = YesNoType.Yes; | ||
12155 | foreach (XElement child in node.Elements()) | ||
12156 | { | ||
12157 | if (CompilerCore.WixNamespace == child.Name.Namespace) | ||
12158 | { | ||
12159 | switch (child.Name.LocalName) | ||
12160 | { | ||
12161 | case "Extension": | ||
12162 | this.ParseExtensionElement(child, componentId, advertise, progId); | ||
12163 | foundExtension = true; | ||
12164 | break; | ||
12165 | case "ProgId": | ||
12166 | // Only allow one nested ProgId. If we have a child, we should not have a parent. | ||
12167 | if (null == parent) | ||
12168 | { | ||
12169 | if (YesNoType.Yes == advertise) | ||
12170 | { | ||
12171 | this.ParseProgIdElement(child, componentId, advertise, null, description, progId, ref foundExtension, firstProgIdForNestedClass); | ||
12172 | } | ||
12173 | else if (YesNoType.No == advertise) | ||
12174 | { | ||
12175 | this.ParseProgIdElement(child, componentId, advertise, classId, description, progId, ref foundExtension, firstProgIdForNestedClass); | ||
12176 | } | ||
12177 | |||
12178 | firstProgIdForNestedClass = YesNoType.No; // any ProgId after this one is definitely not the first. | ||
12179 | } | ||
12180 | else | ||
12181 | { | ||
12182 | SourceLineNumber childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child); | ||
12183 | this.core.OnMessage(WixErrors.ProgIdNestedTooDeep(childSourceLineNumbers)); | ||
12184 | } | ||
12185 | break; | ||
12186 | default: | ||
12187 | this.core.UnexpectedElement(node, child); | ||
12188 | break; | ||
12189 | } | ||
12190 | } | ||
12191 | else | ||
12192 | { | ||
12193 | this.core.ParseExtensionElement(node, child); | ||
12194 | } | ||
12195 | } | ||
12196 | |||
12197 | if (YesNoType.Yes == advertise) | ||
12198 | { | ||
12199 | if (!this.core.EncounteredError) | ||
12200 | { | ||
12201 | Row row = this.core.CreateRow(sourceLineNumbers, "ProgId"); | ||
12202 | row[0] = progId; | ||
12203 | row[1] = parent; | ||
12204 | row[2] = classId; | ||
12205 | row[3] = description; | ||
12206 | if (null != icon) | ||
12207 | { | ||
12208 | row[4] = icon; | ||
12209 | this.core.CreateSimpleReference(sourceLineNumbers, "Icon", icon); | ||
12210 | } | ||
12211 | |||
12212 | if (CompilerConstants.IntegerNotSet != iconIndex) | ||
12213 | { | ||
12214 | row[5] = iconIndex; | ||
12215 | } | ||
12216 | |||
12217 | this.core.EnsureTable(sourceLineNumbers, "Class"); | ||
12218 | } | ||
12219 | } | ||
12220 | else if (YesNoType.No == advertise) | ||
12221 | { | ||
12222 | this.core.CreateRegistryRow(sourceLineNumbers, MsiInterop.MsidbRegistryRootClassesRoot, progId, String.Empty, description, componentId); | ||
12223 | if (null != classId) | ||
12224 | { | ||
12225 | this.core.CreateRegistryRow(sourceLineNumbers, MsiInterop.MsidbRegistryRootClassesRoot, String.Concat(progId, "\\CLSID"), String.Empty, classId, componentId); | ||
12226 | if (null != parent) // if this is a version independent ProgId | ||
12227 | { | ||
12228 | if (YesNoType.Yes == firstProgIdForClass) | ||
12229 | { | ||
12230 | this.core.CreateRegistryRow(sourceLineNumbers, MsiInterop.MsidbRegistryRootClassesRoot, String.Concat("CLSID\\", classId, "\\VersionIndependentProgID"), String.Empty, progId, componentId); | ||
12231 | } | ||
12232 | |||
12233 | this.core.CreateRegistryRow(sourceLineNumbers, MsiInterop.MsidbRegistryRootClassesRoot, String.Concat(progId, "\\CurVer"), String.Empty, parent, componentId); | ||
12234 | } | ||
12235 | else | ||
12236 | { | ||
12237 | if (YesNoType.Yes == firstProgIdForClass) | ||
12238 | { | ||
12239 | this.core.CreateRegistryRow(sourceLineNumbers, MsiInterop.MsidbRegistryRootClassesRoot, String.Concat("CLSID\\", classId, "\\ProgID"), String.Empty, progId, componentId); | ||
12240 | } | ||
12241 | } | ||
12242 | } | ||
12243 | |||
12244 | if (null != icon) // ProgId's Default Icon | ||
12245 | { | ||
12246 | this.core.CreateSimpleReference(sourceLineNumbers, "File", icon); | ||
12247 | |||
12248 | icon = String.Format(CultureInfo.InvariantCulture, "\"[#{0}]\"", icon); | ||
12249 | |||
12250 | if (CompilerConstants.IntegerNotSet != iconIndex) | ||
12251 | { | ||
12252 | icon = String.Concat(icon, ",", iconIndex); | ||
12253 | } | ||
12254 | |||
12255 | this.core.CreateRegistryRow(sourceLineNumbers, MsiInterop.MsidbRegistryRootClassesRoot, String.Concat(progId, "\\DefaultIcon"), String.Empty, icon, componentId); | ||
12256 | } | ||
12257 | } | ||
12258 | |||
12259 | if (null != noOpen) | ||
12260 | { | ||
12261 | this.core.CreateRegistryRow(sourceLineNumbers, MsiInterop.MsidbRegistryRootClassesRoot, progId, "NoOpen", noOpen, componentId); // ProgId NoOpen name | ||
12262 | } | ||
12263 | |||
12264 | // raise an error for an orphaned ProgId | ||
12265 | if (YesNoType.Yes == advertise && !foundExtension && null == parent && null == classId) | ||
12266 | { | ||
12267 | this.core.OnMessage(WixWarnings.OrphanedProgId(sourceLineNumbers, progId)); | ||
12268 | } | ||
12269 | |||
12270 | return progId; | ||
12271 | } | ||
12272 | |||
12273 | /// <summary> | ||
12274 | /// Parses a property element. | ||
12275 | /// </summary> | ||
12276 | /// <param name="node">Element to parse.</param> | ||
12277 | private void ParsePropertyElement(XElement node) | ||
12278 | { | ||
12279 | SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
12280 | Identifier id = null; | ||
12281 | bool admin = false; | ||
12282 | bool complianceCheck = false; | ||
12283 | bool hidden = false; | ||
12284 | bool secure = false; | ||
12285 | YesNoType suppressModularization = YesNoType.NotSet; | ||
12286 | string value = null; | ||
12287 | |||
12288 | foreach (XAttribute attrib in node.Attributes()) | ||
12289 | { | ||
12290 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
12291 | { | ||
12292 | switch (attrib.Name.LocalName) | ||
12293 | { | ||
12294 | case "Id": | ||
12295 | id = this.core.GetAttributeIdentifier(sourceLineNumbers, attrib); | ||
12296 | break; | ||
12297 | case "Admin": | ||
12298 | admin = YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
12299 | break; | ||
12300 | case "ComplianceCheck": | ||
12301 | complianceCheck = YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
12302 | break; | ||
12303 | case "Hidden": | ||
12304 | hidden = YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
12305 | break; | ||
12306 | case "Secure": | ||
12307 | secure = YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
12308 | break; | ||
12309 | case "SuppressModularization": | ||
12310 | suppressModularization = this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
12311 | break; | ||
12312 | case "Value": | ||
12313 | value = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
12314 | break; | ||
12315 | default: | ||
12316 | this.core.UnexpectedAttribute(node, attrib); | ||
12317 | break; | ||
12318 | } | ||
12319 | } | ||
12320 | else | ||
12321 | { | ||
12322 | this.core.ParseExtensionAttribute(node, attrib); | ||
12323 | } | ||
12324 | } | ||
12325 | |||
12326 | if (null == id) | ||
12327 | { | ||
12328 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); | ||
12329 | id = Identifier.Invalid; | ||
12330 | } | ||
12331 | else if ("ProductID" == id.Id) | ||
12332 | { | ||
12333 | this.core.OnMessage(WixWarnings.ProductIdAuthored(sourceLineNumbers)); | ||
12334 | } | ||
12335 | else if ("SecureCustomProperties" == id.Id || "AdminProperties" == id.Id || "MsiHiddenProperties" == id.Id) | ||
12336 | { | ||
12337 | this.core.OnMessage(WixErrors.CannotAuthorSpecialProperties(sourceLineNumbers, id.Id)); | ||
12338 | } | ||
12339 | |||
12340 | string innerText = this.core.GetTrimmedInnerText(node); | ||
12341 | if (null != value) | ||
12342 | { | ||
12343 | // cannot specify both the value attribute and inner text | ||
12344 | if (!String.IsNullOrEmpty(innerText)) | ||
12345 | { | ||
12346 | this.core.OnMessage(WixErrors.IllegalAttributeWithInnerText(sourceLineNumbers, node.Name.LocalName, "Value")); | ||
12347 | } | ||
12348 | } | ||
12349 | else // value attribute not specified, use inner text if any. | ||
12350 | { | ||
12351 | value = innerText; | ||
12352 | } | ||
12353 | |||
12354 | if ("ErrorDialog" == id.Id) | ||
12355 | { | ||
12356 | this.core.CreateSimpleReference(sourceLineNumbers, "Dialog", value); | ||
12357 | } | ||
12358 | |||
12359 | foreach (XElement child in node.Elements()) | ||
12360 | { | ||
12361 | if (CompilerCore.WixNamespace == child.Name.Namespace) | ||
12362 | { | ||
12363 | { | ||
12364 | switch (child.Name.LocalName) | ||
12365 | { | ||
12366 | case "ProductSearch": | ||
12367 | this.ParseProductSearchElement(child, id.Id); | ||
12368 | secure = true; | ||
12369 | break; | ||
12370 | default: | ||
12371 | // let ParseSearchSignatures handle standard AppSearch children and unknown elements | ||
12372 | break; | ||
12373 | } | ||
12374 | } | ||
12375 | } | ||
12376 | } | ||
12377 | |||
12378 | // see if this property is used for appSearch | ||
12379 | List<string> signatures = this.ParseSearchSignatures(node); | ||
12380 | |||
12381 | // If we're doing CCP then there must be a signature. | ||
12382 | if (complianceCheck && 0 == signatures.Count) | ||
12383 | { | ||
12384 | this.core.OnMessage(WixErrors.SearchElementRequiredWithAttribute(sourceLineNumbers, node.Name.LocalName, "ComplianceCheck", "yes")); | ||
12385 | } | ||
12386 | |||
12387 | foreach (string sig in signatures) | ||
12388 | { | ||
12389 | if (complianceCheck && !this.core.EncounteredError) | ||
12390 | { | ||
12391 | this.core.CreateRow(sourceLineNumbers, "CCPSearch", new Identifier(sig, AccessModifier.Private)); | ||
12392 | } | ||
12393 | |||
12394 | this.AddAppSearch(sourceLineNumbers, id, sig); | ||
12395 | } | ||
12396 | |||
12397 | // If we're doing AppSearch get that setup. | ||
12398 | if (0 < signatures.Count) | ||
12399 | { | ||
12400 | this.AddProperty(sourceLineNumbers, id, value, admin, secure, hidden, false); | ||
12401 | } | ||
12402 | else // just a normal old property. | ||
12403 | { | ||
12404 | // If the property value is empty and none of the flags are set, print out a warning that we're ignoring | ||
12405 | // the element. | ||
12406 | if (String.IsNullOrEmpty(value) && !admin && !secure && !hidden) | ||
12407 | { | ||
12408 | this.core.OnMessage(WixWarnings.PropertyUseless(sourceLineNumbers, id.Id)); | ||
12409 | } | ||
12410 | else // there is a value and/or a flag set, do that. | ||
12411 | { | ||
12412 | this.AddProperty(sourceLineNumbers, id, value, admin, secure, hidden, false); | ||
12413 | } | ||
12414 | } | ||
12415 | |||
12416 | if (!this.core.EncounteredError && YesNoType.Yes == suppressModularization) | ||
12417 | { | ||
12418 | this.core.OnMessage(WixWarnings.PropertyModularizationSuppressed(sourceLineNumbers)); | ||
12419 | |||
12420 | this.core.CreateRow(sourceLineNumbers, "WixSuppressModularization", id); | ||
12421 | } | ||
12422 | } | ||
12423 | |||
12424 | /// <summary> | ||
12425 | /// Parses a RegistryKey element. | ||
12426 | /// </summary> | ||
12427 | /// <param name="node">Element to parse.</param> | ||
12428 | /// <param name="componentId">Identifier for parent component.</param> | ||
12429 | /// <param name="root">Root specified when element is nested under another Registry element, otherwise CompilerConstants.IntegerNotSet.</param> | ||
12430 | /// <param name="parentKey">Parent key for this Registry element when nested.</param> | ||
12431 | /// <param name="win64Component">true if the component is 64-bit.</param> | ||
12432 | /// <param name="possibleKeyPath">Identifier of this registry key since it could be the component's keypath.</param> | ||
12433 | /// <returns>Yes if this element was marked as the parent component's key path, No if explicitly marked as not being a key path, or NotSet otherwise.</returns> | ||
12434 | [SuppressMessage("Microsoft.Globalization", "CA1308:NormalizeStringsToUppercase", Justification = "Changing the way this string normalizes would result " + | ||
12435 | "in a change to the way the Registry table is generated, potentially causing extra churn in patches on an MSI built from an older version of WiX. " + | ||
12436 | "Furthermore, there is no security hole here, as the strings won't need to make a round trip")] | ||
12437 | private YesNoType ParseRegistryKeyElement(XElement node, string componentId, int root, string parentKey, bool win64Component, out string possibleKeyPath) | ||
12438 | { | ||
12439 | SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
12440 | Identifier id = null; | ||
12441 | string key = parentKey; // default to parent key path | ||
12442 | string action = null; | ||
12443 | bool forceCreateOnInstall = false; | ||
12444 | bool forceDeleteOnUninstall = false; | ||
12445 | Wix.RegistryKey.ActionType actionType = Wix.RegistryKey.ActionType.NotSet; | ||
12446 | YesNoType keyPath = YesNoType.NotSet; | ||
12447 | |||
12448 | possibleKeyPath = null; | ||
12449 | |||
12450 | foreach (XAttribute attrib in node.Attributes()) | ||
12451 | { | ||
12452 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
12453 | { | ||
12454 | switch (attrib.Name.LocalName) | ||
12455 | { | ||
12456 | case "Id": | ||
12457 | id = this.core.GetAttributeIdentifier(sourceLineNumbers, attrib); | ||
12458 | break; | ||
12459 | case "Action": | ||
12460 | this.core.OnMessage(WixWarnings.DeprecatedRegistryKeyActionAttribute(sourceLineNumbers)); | ||
12461 | action = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
12462 | if (0 < action.Length) | ||
12463 | { | ||
12464 | actionType = Wix.RegistryKey.ParseActionType(action); | ||
12465 | switch (actionType) | ||
12466 | { | ||
12467 | case Wix.RegistryKey.ActionType.create: | ||
12468 | forceCreateOnInstall = true; | ||
12469 | break; | ||
12470 | case Wix.RegistryKey.ActionType.createAndRemoveOnUninstall: | ||
12471 | forceCreateOnInstall = true; | ||
12472 | forceDeleteOnUninstall = true; | ||
12473 | break; | ||
12474 | case Wix.RegistryKey.ActionType.none: | ||
12475 | break; | ||
12476 | default: | ||
12477 | this.core.OnMessage(WixErrors.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, action, "create", "createAndRemoveOnUninstall", "none")); | ||
12478 | break; | ||
12479 | } | ||
12480 | } | ||
12481 | break; | ||
12482 | case "ForceCreateOnInstall": | ||
12483 | forceCreateOnInstall = YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
12484 | break; | ||
12485 | case "ForceDeleteOnUninstall": | ||
12486 | forceDeleteOnUninstall = YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
12487 | break; | ||
12488 | case "Key": | ||
12489 | key = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
12490 | if (null != parentKey) | ||
12491 | { | ||
12492 | key = Path.Combine(parentKey, key); | ||
12493 | } | ||
12494 | break; | ||
12495 | case "Root": | ||
12496 | if (CompilerConstants.IntegerNotSet != root) | ||
12497 | { | ||
12498 | this.core.OnMessage(WixErrors.RegistryRootInvalid(sourceLineNumbers)); | ||
12499 | } | ||
12500 | |||
12501 | root = this.core.GetAttributeMsidbRegistryRootValue(sourceLineNumbers, attrib, true); | ||
12502 | break; | ||
12503 | default: | ||
12504 | this.core.UnexpectedAttribute(node, attrib); | ||
12505 | break; | ||
12506 | } | ||
12507 | } | ||
12508 | else | ||
12509 | { | ||
12510 | this.core.ParseExtensionAttribute(node, attrib); | ||
12511 | } | ||
12512 | } | ||
12513 | |||
12514 | string name = forceCreateOnInstall ? (forceDeleteOnUninstall ? "*" : "+") : (forceDeleteOnUninstall ? "-" : null); | ||
12515 | |||
12516 | if (forceCreateOnInstall || forceDeleteOnUninstall) // generates a Registry row, so an Id must be present | ||
12517 | { | ||
12518 | // generate the identifier if it wasn't provided | ||
12519 | if (null == id) | ||
12520 | { | ||
12521 | id = this.core.CreateIdentifier("reg", componentId, root.ToString(CultureInfo.InvariantCulture.NumberFormat), LowercaseOrNull(key), LowercaseOrNull(name)); | ||
12522 | } | ||
12523 | } | ||
12524 | else // does not generate a Registry row, so no Id should be present | ||
12525 | { | ||
12526 | if (null != id) | ||
12527 | { | ||
12528 | this.core.OnMessage(WixErrors.IllegalAttributeWithoutOtherAttributes(sourceLineNumbers, node.Name.LocalName, "Id", "ForceCreateOnInstall", "ForceDeleteOnUninstall", "yes", true)); | ||
12529 | } | ||
12530 | } | ||
12531 | |||
12532 | if (CompilerConstants.IntegerNotSet == root) | ||
12533 | { | ||
12534 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Root")); | ||
12535 | root = CompilerConstants.IllegalInteger; | ||
12536 | } | ||
12537 | |||
12538 | if (null == key) | ||
12539 | { | ||
12540 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Key")); | ||
12541 | key = String.Empty; // set the key to something to prevent null reference exceptions | ||
12542 | } | ||
12543 | |||
12544 | foreach (XElement child in node.Elements()) | ||
12545 | { | ||
12546 | if (CompilerCore.WixNamespace == child.Name.Namespace) | ||
12547 | { | ||
12548 | string possibleChildKeyPath = null; | ||
12549 | |||
12550 | switch (child.Name.LocalName) | ||
12551 | { | ||
12552 | case "RegistryKey": | ||
12553 | if (YesNoType.Yes == this.ParseRegistryKeyElement(child, componentId, root, key, win64Component, out possibleChildKeyPath)) | ||
12554 | { | ||
12555 | if (YesNoType.Yes == keyPath) | ||
12556 | { | ||
12557 | this.core.OnMessage(WixErrors.ComponentMultipleKeyPaths(sourceLineNumbers, child.Name.LocalName, "KeyPath", "yes", "File", "RegistryValue", "ODBCDataSource")); | ||
12558 | } | ||
12559 | |||
12560 | possibleKeyPath = possibleChildKeyPath; // the child is the key path | ||
12561 | keyPath = YesNoType.Yes; | ||
12562 | } | ||
12563 | else if (null == possibleKeyPath && null != possibleChildKeyPath) | ||
12564 | { | ||
12565 | possibleKeyPath = possibleChildKeyPath; | ||
12566 | } | ||
12567 | break; | ||
12568 | case "RegistryValue": | ||
12569 | if (YesNoType.Yes == this.ParseRegistryValueElement(child, componentId, root, key, win64Component, out possibleChildKeyPath)) | ||
12570 | { | ||
12571 | if (YesNoType.Yes == keyPath) | ||
12572 | { | ||
12573 | this.core.OnMessage(WixErrors.ComponentMultipleKeyPaths(sourceLineNumbers, child.Name.LocalName, "KeyPath", "yes", "File", "RegistryValue", "ODBCDataSource")); | ||
12574 | } | ||
12575 | |||
12576 | possibleKeyPath = possibleChildKeyPath; // the child is the key path | ||
12577 | keyPath = YesNoType.Yes; | ||
12578 | } | ||
12579 | else if (null == possibleKeyPath && null != possibleChildKeyPath) | ||
12580 | { | ||
12581 | possibleKeyPath = possibleChildKeyPath; | ||
12582 | } | ||
12583 | break; | ||
12584 | case "Permission": | ||
12585 | if (!forceCreateOnInstall) | ||
12586 | { | ||
12587 | this.core.OnMessage(WixErrors.UnexpectedElementWithAttributeValue(sourceLineNumbers, node.Name.LocalName, child.Name.LocalName, "ForceCreateOnInstall", "yes")); | ||
12588 | } | ||
12589 | this.ParsePermissionElement(child, id.Id, "Registry"); | ||
12590 | break; | ||
12591 | case "PermissionEx": | ||
12592 | if (!forceCreateOnInstall) | ||
12593 | { | ||
12594 | this.core.OnMessage(WixErrors.UnexpectedElementWithAttributeValue(sourceLineNumbers, node.Name.LocalName, child.Name.LocalName, "ForceCreateOnInstall", "yes")); | ||
12595 | } | ||
12596 | this.ParsePermissionExElement(child, id.Id, "Registry"); | ||
12597 | break; | ||
12598 | default: | ||
12599 | this.core.UnexpectedElement(node, child); | ||
12600 | break; | ||
12601 | } | ||
12602 | } | ||
12603 | else | ||
12604 | { | ||
12605 | Dictionary<string, string> context = new Dictionary<string, string>() { { "RegistryId", id.Id }, { "ComponentId", componentId }, { "Win64", win64Component.ToString() } }; | ||
12606 | this.core.ParseExtensionElement(node, child, context); | ||
12607 | } | ||
12608 | } | ||
12609 | |||
12610 | |||
12611 | if (!this.core.EncounteredError && null != name) | ||
12612 | { | ||
12613 | Row row = this.core.CreateRow(sourceLineNumbers, "Registry", id); | ||
12614 | row[1] = root; | ||
12615 | row[2] = key; | ||
12616 | row[3] = name; | ||
12617 | row[4] = null; | ||
12618 | row[5] = componentId; | ||
12619 | } | ||
12620 | |||
12621 | return keyPath; | ||
12622 | } | ||
12623 | |||
12624 | /// <summary> | ||
12625 | /// Parses a RegistryValue element. | ||
12626 | /// </summary> | ||
12627 | /// <param name="node">Element to parse.</param> | ||
12628 | /// <param name="componentId">Identifier for parent component.</param> | ||
12629 | /// <param name="root">Root specified when element is nested under a RegistryKey element, otherwise CompilerConstants.IntegerNotSet.</param> | ||
12630 | /// <param name="parentKey">Root specified when element is nested under a RegistryKey element, otherwise CompilerConstants.IntegerNotSet.</param> | ||
12631 | /// <param name="win64Component">true if the component is 64-bit.</param> | ||
12632 | /// <param name="possibleKeyPath">Identifier of this registry key since it could be the component's keypath.</param> | ||
12633 | /// <returns>Yes if this element was marked as the parent component's key path, No if explicitly marked as not being a key path, or NotSet otherwise.</returns> | ||
12634 | [SuppressMessage("Microsoft.Globalization", "CA1308:NormalizeStringsToUppercase", Justification = "Changing the way this string normalizes would result " + | ||
12635 | "in a change to the way the Registry table is generated, potentially causing extra churn in patches on an MSI built from an older version of WiX. " + | ||
12636 | "Furthermore, there is no security hole here, as the strings won't need to make a round trip")] | ||
12637 | private YesNoType ParseRegistryValueElement(XElement node, string componentId, int root, string parentKey, bool win64Component, out string possibleKeyPath) | ||
12638 | { | ||
12639 | SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
12640 | Identifier id = null; | ||
12641 | string key = parentKey; // default to parent key path | ||
12642 | string name = null; | ||
12643 | string value = null; | ||
12644 | string type = null; | ||
12645 | Wix.RegistryValue.TypeType typeType = Wix.RegistryValue.TypeType.NotSet; | ||
12646 | string action = null; | ||
12647 | Wix.RegistryValue.ActionType actionType = Wix.RegistryValue.ActionType.NotSet; | ||
12648 | YesNoType keyPath = YesNoType.NotSet; | ||
12649 | bool couldBeKeyPath = true; // assume that this is a regular registry key that could become the key path | ||
12650 | |||
12651 | possibleKeyPath = null; | ||
12652 | |||
12653 | foreach (XAttribute attrib in node.Attributes()) | ||
12654 | { | ||
12655 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
12656 | { | ||
12657 | switch (attrib.Name.LocalName) | ||
12658 | { | ||
12659 | case "Id": | ||
12660 | id = this.core.GetAttributeIdentifier(sourceLineNumbers, attrib); | ||
12661 | break; | ||
12662 | case "Action": | ||
12663 | action = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
12664 | if (0 < action.Length) | ||
12665 | { | ||
12666 | if (!Wix.RegistryValue.TryParseActionType(action, out actionType)) | ||
12667 | { | ||
12668 | this.core.OnMessage(WixErrors.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, action, "append", "prepend", "write")); | ||
12669 | } | ||
12670 | } | ||
12671 | break; | ||
12672 | case "Key": | ||
12673 | key = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
12674 | if (null != parentKey) | ||
12675 | { | ||
12676 | if (parentKey.EndsWith("\\", StringComparison.Ordinal)) | ||
12677 | { | ||
12678 | key = String.Concat(parentKey, key); | ||
12679 | } | ||
12680 | else | ||
12681 | { | ||
12682 | key = String.Concat(parentKey, "\\", key); | ||
12683 | } | ||
12684 | } | ||
12685 | break; | ||
12686 | case "KeyPath": | ||
12687 | keyPath = this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
12688 | break; | ||
12689 | case "Name": | ||
12690 | name = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
12691 | break; | ||
12692 | case "Root": | ||
12693 | if (CompilerConstants.IntegerNotSet != root) | ||
12694 | { | ||
12695 | this.core.OnMessage(WixErrors.RegistryRootInvalid(sourceLineNumbers)); | ||
12696 | } | ||
12697 | |||
12698 | root = this.core.GetAttributeMsidbRegistryRootValue(sourceLineNumbers, attrib, true); | ||
12699 | break; | ||
12700 | case "Type": | ||
12701 | type = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
12702 | if (0 < type.Length) | ||
12703 | { | ||
12704 | if (!Wix.RegistryValue.TryParseTypeType(type, out typeType)) | ||
12705 | { | ||
12706 | this.core.OnMessage(WixErrors.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, type, "binary", "expandable", "integer", "multiString", "string")); | ||
12707 | } | ||
12708 | } | ||
12709 | break; | ||
12710 | case "Value": | ||
12711 | value = this.core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty); | ||
12712 | break; | ||
12713 | default: | ||
12714 | this.core.UnexpectedAttribute(node, attrib); | ||
12715 | break; | ||
12716 | } | ||
12717 | } | ||
12718 | else | ||
12719 | { | ||
12720 | this.core.ParseExtensionAttribute(node, attrib); | ||
12721 | } | ||
12722 | } | ||
12723 | |||
12724 | // generate the identifier if it wasn't provided | ||
12725 | if (null == id) | ||
12726 | { | ||
12727 | id = this.core.CreateIdentifier("reg", componentId, root.ToString(CultureInfo.InvariantCulture.NumberFormat), LowercaseOrNull(key), LowercaseOrNull(name)); | ||
12728 | } | ||
12729 | |||
12730 | if ((Wix.RegistryValue.ActionType.append == actionType || Wix.RegistryValue.ActionType.prepend == actionType) && | ||
12731 | Wix.RegistryValue.TypeType.multiString != typeType) | ||
12732 | { | ||
12733 | this.core.OnMessage(WixErrors.IllegalAttributeValueWithoutOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Action", action, "Type", "multiString")); | ||
12734 | } | ||
12735 | |||
12736 | if (null == key) | ||
12737 | { | ||
12738 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Key")); | ||
12739 | } | ||
12740 | |||
12741 | if (CompilerConstants.IntegerNotSet == root) | ||
12742 | { | ||
12743 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Root")); | ||
12744 | } | ||
12745 | |||
12746 | if (null == type) | ||
12747 | { | ||
12748 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Type")); | ||
12749 | } | ||
12750 | |||
12751 | foreach (XElement child in node.Elements()) | ||
12752 | { | ||
12753 | if (CompilerCore.WixNamespace == child.Name.Namespace) | ||
12754 | { | ||
12755 | switch (child.Name.LocalName) | ||
12756 | { | ||
12757 | case "MultiStringValue": | ||
12758 | if (Wix.RegistryValue.TypeType.multiString != typeType && null != value) | ||
12759 | { | ||
12760 | this.core.OnMessage(WixErrors.RegistryMultipleValuesWithoutMultiString(sourceLineNumbers, node.Name.LocalName, "Value", child.Name.LocalName, "Type")); | ||
12761 | } | ||
12762 | else if (null == value) | ||
12763 | { | ||
12764 | value = Common.GetInnerText(child); | ||
12765 | } | ||
12766 | else | ||
12767 | { | ||
12768 | value = String.Concat(value, "[~]", Common.GetInnerText(child)); | ||
12769 | } | ||
12770 | break; | ||
12771 | case "Permission": | ||
12772 | this.ParsePermissionElement(child, id.Id, "Registry"); | ||
12773 | break; | ||
12774 | case "PermissionEx": | ||
12775 | this.ParsePermissionExElement(child, id.Id, "Registry"); | ||
12776 | break; | ||
12777 | default: | ||
12778 | this.core.UnexpectedElement(node, child); | ||
12779 | break; | ||
12780 | } | ||
12781 | } | ||
12782 | else | ||
12783 | { | ||
12784 | Dictionary<string, string> context = new Dictionary<string, string>() { { "RegistryId", id.Id }, { "ComponentId", componentId }, { "Win64", win64Component.ToString() } }; | ||
12785 | this.core.ParseExtensionElement(node, child, context); | ||
12786 | } | ||
12787 | } | ||
12788 | |||
12789 | |||
12790 | switch (typeType) | ||
12791 | { | ||
12792 | case Wix.RegistryValue.TypeType.binary: | ||
12793 | value = String.Concat("#x", value); | ||
12794 | break; | ||
12795 | case Wix.RegistryValue.TypeType.expandable: | ||
12796 | value = String.Concat("#%", value); | ||
12797 | break; | ||
12798 | case Wix.RegistryValue.TypeType.integer: | ||
12799 | value = String.Concat("#", value); | ||
12800 | break; | ||
12801 | case Wix.RegistryValue.TypeType.multiString: | ||
12802 | switch (actionType) | ||
12803 | { | ||
12804 | case Wix.RegistryValue.ActionType.append: | ||
12805 | value = String.Concat("[~]", value); | ||
12806 | break; | ||
12807 | case Wix.RegistryValue.ActionType.prepend: | ||
12808 | value = String.Concat(value, "[~]"); | ||
12809 | break; | ||
12810 | case Wix.RegistryValue.ActionType.write: | ||
12811 | default: | ||
12812 | if (null != value && -1 == value.IndexOf("[~]", StringComparison.Ordinal)) | ||
12813 | { | ||
12814 | value = String.Format(CultureInfo.InvariantCulture, "[~]{0}[~]", value); | ||
12815 | } | ||
12816 | break; | ||
12817 | } | ||
12818 | break; | ||
12819 | case Wix.RegistryValue.TypeType.@string: | ||
12820 | // escape the leading '#' character for string registry keys | ||
12821 | if (null != value && value.StartsWith("#", StringComparison.Ordinal)) | ||
12822 | { | ||
12823 | value = String.Concat("#", value); | ||
12824 | } | ||
12825 | break; | ||
12826 | } | ||
12827 | |||
12828 | // value may be set by child MultiStringValue elements, so it must be checked here | ||
12829 | if (null == value) | ||
12830 | { | ||
12831 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Value")); | ||
12832 | } | ||
12833 | else if (0 == value.Length && ("+" == name || "-" == name || "*" == name)) // prevent accidental authoring of special name values | ||
12834 | { | ||
12835 | this.core.OnMessage(WixErrors.RegistryNameValueIncorrect(sourceLineNumbers, node.Name.LocalName, "Name", name)); | ||
12836 | } | ||
12837 | |||
12838 | if (!this.core.EncounteredError) | ||
12839 | { | ||
12840 | Row row = this.core.CreateRow(sourceLineNumbers, "Registry", id); | ||
12841 | row[1] = root; | ||
12842 | row[2] = key; | ||
12843 | row[3] = name; | ||
12844 | row[4] = value; | ||
12845 | row[5] = componentId; | ||
12846 | } | ||
12847 | |||
12848 | // If this was just a regular registry key (that could be the key path) | ||
12849 | // and no child registry key set the possible key path, let's make this | ||
12850 | // Registry/@Id a possible key path. | ||
12851 | if (couldBeKeyPath && null == possibleKeyPath) | ||
12852 | { | ||
12853 | possibleKeyPath = id.Id; | ||
12854 | } | ||
12855 | |||
12856 | return keyPath; | ||
12857 | } | ||
12858 | |||
12859 | /// <summary> | ||
12860 | /// Parses a RemoveRegistryKey element. | ||
12861 | /// </summary> | ||
12862 | /// <param name="node">The element to parse.</param> | ||
12863 | /// <param name="componentId">The component identifier of the parent element.</param> | ||
12864 | [SuppressMessage("Microsoft.Globalization", "CA1308:NormalizeStringsToUppercase", Justification = "Changing the way this string normalizes would result " + | ||
12865 | "in a change to the way the Registry table is generated, potentially causing extra churn in patches on an MSI built from an older version of WiX. " + | ||
12866 | "Furthermore, there is no security hole here, as the strings won't need to make a round trip")] | ||
12867 | private void ParseRemoveRegistryKeyElement(XElement node, string componentId) | ||
12868 | { | ||
12869 | SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
12870 | Identifier id = null; | ||
12871 | string action = null; | ||
12872 | Wix.RemoveRegistryKey.ActionType actionType = Wix.RemoveRegistryKey.ActionType.NotSet; | ||
12873 | string key = null; | ||
12874 | string name = "-"; | ||
12875 | int root = CompilerConstants.IntegerNotSet; | ||
12876 | |||
12877 | foreach (XAttribute attrib in node.Attributes()) | ||
12878 | { | ||
12879 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
12880 | { | ||
12881 | switch (attrib.Name.LocalName) | ||
12882 | { | ||
12883 | case "Id": | ||
12884 | id = this.core.GetAttributeIdentifier(sourceLineNumbers, attrib); | ||
12885 | break; | ||
12886 | case "Action": | ||
12887 | action = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
12888 | if (0 < action.Length) | ||
12889 | { | ||
12890 | if (!Wix.RemoveRegistryKey.TryParseActionType(action, out actionType)) | ||
12891 | { | ||
12892 | this.core.OnMessage(WixErrors.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, action, "removeOnInstall", "removeOnUninstall")); | ||
12893 | } | ||
12894 | } | ||
12895 | break; | ||
12896 | case "Key": | ||
12897 | key = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
12898 | break; | ||
12899 | case "Root": | ||
12900 | root = this.core.GetAttributeMsidbRegistryRootValue(sourceLineNumbers, attrib, true); | ||
12901 | break; | ||
12902 | default: | ||
12903 | this.core.UnexpectedAttribute(node, attrib); | ||
12904 | break; | ||
12905 | } | ||
12906 | } | ||
12907 | else | ||
12908 | { | ||
12909 | this.core.ParseExtensionAttribute(node, attrib); | ||
12910 | } | ||
12911 | } | ||
12912 | |||
12913 | // generate the identifier if it wasn't provided | ||
12914 | if (null == id) | ||
12915 | { | ||
12916 | id = this.core.CreateIdentifier("reg", componentId, root.ToString(CultureInfo.InvariantCulture.NumberFormat), LowercaseOrNull(key), LowercaseOrNull(name)); | ||
12917 | } | ||
12918 | |||
12919 | if (null == action) | ||
12920 | { | ||
12921 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Action")); | ||
12922 | } | ||
12923 | |||
12924 | if (null == key) | ||
12925 | { | ||
12926 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Key")); | ||
12927 | } | ||
12928 | |||
12929 | if (CompilerConstants.IntegerNotSet == root) | ||
12930 | { | ||
12931 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Root")); | ||
12932 | } | ||
12933 | |||
12934 | this.core.ParseForExtensionElements(node); | ||
12935 | |||
12936 | if (!this.core.EncounteredError) | ||
12937 | { | ||
12938 | Row row = this.core.CreateRow(sourceLineNumbers, (Wix.RemoveRegistryKey.ActionType.removeOnUninstall == actionType ? "Registry" : "RemoveRegistry"), id); | ||
12939 | row[1] = root; | ||
12940 | row[2] = key; | ||
12941 | row[3] = name; | ||
12942 | if (Wix.RemoveRegistryKey.ActionType.removeOnUninstall == actionType) // Registry table | ||
12943 | { | ||
12944 | row[4] = null; | ||
12945 | row[5] = componentId; | ||
12946 | } | ||
12947 | else // RemoveRegistry table | ||
12948 | { | ||
12949 | row[4] = componentId; | ||
12950 | } | ||
12951 | } | ||
12952 | } | ||
12953 | |||
12954 | /// <summary> | ||
12955 | /// Parses a RemoveRegistryValue element. | ||
12956 | /// </summary> | ||
12957 | /// <param name="node">The element to parse.</param> | ||
12958 | /// <param name="componentId">The component identifier of the parent element.</param> | ||
12959 | [SuppressMessage("Microsoft.Globalization", "CA1308:NormalizeStringsToUppercase", Justification = "Changing the way this string normalizes would result " + | ||
12960 | "in a change to the way the Registry table is generated, potentially causing extra churn in patches on an MSI built from an older version of WiX. " + | ||
12961 | "Furthermore, there is no security hole here, as the strings won't need to make a round trip")] | ||
12962 | private void ParseRemoveRegistryValueElement(XElement node, string componentId) | ||
12963 | { | ||
12964 | SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
12965 | Identifier id = null; | ||
12966 | string key = null; | ||
12967 | string name = null; | ||
12968 | int root = CompilerConstants.IntegerNotSet; | ||
12969 | |||
12970 | foreach (XAttribute attrib in node.Attributes()) | ||
12971 | { | ||
12972 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
12973 | { | ||
12974 | switch (attrib.Name.LocalName) | ||
12975 | { | ||
12976 | case "Id": | ||
12977 | id = this.core.GetAttributeIdentifier(sourceLineNumbers, attrib); | ||
12978 | break; | ||
12979 | case "Key": | ||
12980 | key = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
12981 | break; | ||
12982 | case "Name": | ||
12983 | name = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
12984 | break; | ||
12985 | case "Root": | ||
12986 | root = this.core.GetAttributeMsidbRegistryRootValue(sourceLineNumbers, attrib, true); | ||
12987 | break; | ||
12988 | default: | ||
12989 | this.core.UnexpectedAttribute(node, attrib); | ||
12990 | break; | ||
12991 | } | ||
12992 | } | ||
12993 | else | ||
12994 | { | ||
12995 | this.core.ParseExtensionAttribute(node, attrib); | ||
12996 | } | ||
12997 | } | ||
12998 | |||
12999 | // generate the identifier if it wasn't provided | ||
13000 | if (null == id) | ||
13001 | { | ||
13002 | id = this.core.CreateIdentifier("reg", componentId, root.ToString(CultureInfo.InvariantCulture.NumberFormat), LowercaseOrNull(key), LowercaseOrNull(name)); | ||
13003 | } | ||
13004 | |||
13005 | if (null == key) | ||
13006 | { | ||
13007 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Key")); | ||
13008 | } | ||
13009 | |||
13010 | if (CompilerConstants.IntegerNotSet == root) | ||
13011 | { | ||
13012 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Root")); | ||
13013 | } | ||
13014 | |||
13015 | this.core.ParseForExtensionElements(node); | ||
13016 | |||
13017 | if (!this.core.EncounteredError) | ||
13018 | { | ||
13019 | Row row = this.core.CreateRow(sourceLineNumbers, "RemoveRegistry", id); | ||
13020 | row[1] = root; | ||
13021 | row[2] = key; | ||
13022 | row[3] = name; | ||
13023 | row[4] = componentId; | ||
13024 | } | ||
13025 | } | ||
13026 | |||
13027 | /// <summary> | ||
13028 | /// Parses a remove file element. | ||
13029 | /// </summary> | ||
13030 | /// <param name="node">Element to parse.</param> | ||
13031 | /// <param name="componentId">Identifier of parent component.</param> | ||
13032 | /// <param name="parentDirectory">Identifier of the parent component's directory.</param> | ||
13033 | private void ParseRemoveFileElement(XElement node, string componentId, string parentDirectory) | ||
13034 | { | ||
13035 | SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
13036 | Identifier id = null; | ||
13037 | string directory = null; | ||
13038 | string name = null; | ||
13039 | int on = CompilerConstants.IntegerNotSet; | ||
13040 | string property = null; | ||
13041 | string shortName = null; | ||
13042 | |||
13043 | foreach (XAttribute attrib in node.Attributes()) | ||
13044 | { | ||
13045 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
13046 | { | ||
13047 | switch (attrib.Name.LocalName) | ||
13048 | { | ||
13049 | case "Id": | ||
13050 | id = this.core.GetAttributeIdentifier(sourceLineNumbers, attrib); | ||
13051 | break; | ||
13052 | case "Directory": | ||
13053 | directory = this.core.CreateDirectoryReferenceFromInlineSyntax(sourceLineNumbers, attrib, parentDirectory); | ||
13054 | break; | ||
13055 | case "Name": | ||
13056 | name = this.core.GetAttributeLongFilename(sourceLineNumbers, attrib, true); | ||
13057 | break; | ||
13058 | case "On": | ||
13059 | Wix.InstallUninstallType onValue = this.core.GetAttributeInstallUninstallValue(sourceLineNumbers, attrib); | ||
13060 | switch (onValue) | ||
13061 | { | ||
13062 | case Wix.InstallUninstallType.install: | ||
13063 | on = 1; | ||
13064 | break; | ||
13065 | case Wix.InstallUninstallType.uninstall: | ||
13066 | on = 2; | ||
13067 | break; | ||
13068 | case Wix.InstallUninstallType.both: | ||
13069 | on = 3; | ||
13070 | break; | ||
13071 | default: | ||
13072 | on = CompilerConstants.IllegalInteger; | ||
13073 | break; | ||
13074 | } | ||
13075 | break; | ||
13076 | case "Property": | ||
13077 | property = this.core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); | ||
13078 | break; | ||
13079 | case "ShortName": | ||
13080 | shortName = this.core.GetAttributeShortFilename(sourceLineNumbers, attrib, true); | ||
13081 | break; | ||
13082 | default: | ||
13083 | this.core.UnexpectedAttribute(node, attrib); | ||
13084 | break; | ||
13085 | } | ||
13086 | } | ||
13087 | else | ||
13088 | { | ||
13089 | this.core.ParseExtensionAttribute(node, attrib); | ||
13090 | } | ||
13091 | } | ||
13092 | |||
13093 | if (null == name) | ||
13094 | { | ||
13095 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name")); | ||
13096 | } | ||
13097 | else if (0 < name.Length) | ||
13098 | { | ||
13099 | if (this.core.IsValidShortFilename(name, true)) | ||
13100 | { | ||
13101 | if (null == shortName) | ||
13102 | { | ||
13103 | shortName = name; | ||
13104 | name = null; | ||
13105 | } | ||
13106 | else | ||
13107 | { | ||
13108 | this.core.OnMessage(WixErrors.IllegalAttributeValueWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Name", name, "ShortName")); | ||
13109 | } | ||
13110 | } | ||
13111 | else if (null == shortName) // generate a short file name. | ||
13112 | { | ||
13113 | shortName = this.core.CreateShortName(name, true, true, node.Name.LocalName, componentId); | ||
13114 | } | ||
13115 | } | ||
13116 | |||
13117 | if (CompilerConstants.IntegerNotSet == on) | ||
13118 | { | ||
13119 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "On")); | ||
13120 | on = CompilerConstants.IllegalInteger; | ||
13121 | } | ||
13122 | |||
13123 | if (null != directory && null != property) | ||
13124 | { | ||
13125 | this.core.OnMessage(WixErrors.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Property", "Directory", directory)); | ||
13126 | } | ||
13127 | |||
13128 | if (null == id) | ||
13129 | { | ||
13130 | id = this.core.CreateIdentifier("rmf", directory ?? property ?? parentDirectory, LowercaseOrNull(shortName), LowercaseOrNull(name), on.ToString()); | ||
13131 | } | ||
13132 | |||
13133 | this.core.ParseForExtensionElements(node); | ||
13134 | |||
13135 | if (!this.core.EncounteredError) | ||
13136 | { | ||
13137 | Row row = this.core.CreateRow(sourceLineNumbers, "RemoveFile", id); | ||
13138 | row[1] = componentId; | ||
13139 | row[2] = GetMsiFilenameValue(shortName, name); | ||
13140 | if (null != directory) | ||
13141 | { | ||
13142 | row[3] = directory; | ||
13143 | } | ||
13144 | else if (null != property) | ||
13145 | { | ||
13146 | row[3] = property; | ||
13147 | } | ||
13148 | else | ||
13149 | { | ||
13150 | row[3] = parentDirectory; | ||
13151 | } | ||
13152 | row[4] = on; | ||
13153 | } | ||
13154 | } | ||
13155 | |||
13156 | /// <summary> | ||
13157 | /// Parses a RemoveFolder element. | ||
13158 | /// </summary> | ||
13159 | /// <param name="node">Element to parse.</param> | ||
13160 | /// <param name="componentId">Identifier of parent component.</param> | ||
13161 | /// <param name="parentDirectory">Identifier of parent component's directory.</param> | ||
13162 | private void ParseRemoveFolderElement(XElement node, string componentId, string parentDirectory) | ||
13163 | { | ||
13164 | SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
13165 | Identifier id = null; | ||
13166 | string directory = null; | ||
13167 | int on = CompilerConstants.IntegerNotSet; | ||
13168 | string property = null; | ||
13169 | |||
13170 | foreach (XAttribute attrib in node.Attributes()) | ||
13171 | { | ||
13172 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
13173 | { | ||
13174 | switch (attrib.Name.LocalName) | ||
13175 | { | ||
13176 | case "Id": | ||
13177 | id = this.core.GetAttributeIdentifier(sourceLineNumbers, attrib); | ||
13178 | break; | ||
13179 | case "Directory": | ||
13180 | directory = this.core.CreateDirectoryReferenceFromInlineSyntax(sourceLineNumbers, attrib, parentDirectory); | ||
13181 | break; | ||
13182 | case "On": | ||
13183 | Wix.InstallUninstallType onValue = this.core.GetAttributeInstallUninstallValue(sourceLineNumbers, attrib); | ||
13184 | switch (onValue) | ||
13185 | { | ||
13186 | case Wix.InstallUninstallType.install: | ||
13187 | on = 1; | ||
13188 | break; | ||
13189 | case Wix.InstallUninstallType.uninstall: | ||
13190 | on = 2; | ||
13191 | break; | ||
13192 | case Wix.InstallUninstallType.both: | ||
13193 | on = 3; | ||
13194 | break; | ||
13195 | default: | ||
13196 | on = CompilerConstants.IllegalInteger; | ||
13197 | break; | ||
13198 | } | ||
13199 | break; | ||
13200 | case "Property": | ||
13201 | property = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
13202 | break; | ||
13203 | default: | ||
13204 | this.core.UnexpectedAttribute(node, attrib); | ||
13205 | break; | ||
13206 | } | ||
13207 | } | ||
13208 | else | ||
13209 | { | ||
13210 | this.core.ParseExtensionAttribute(node, attrib); | ||
13211 | } | ||
13212 | } | ||
13213 | |||
13214 | if (CompilerConstants.IntegerNotSet == on) | ||
13215 | { | ||
13216 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "On")); | ||
13217 | on = CompilerConstants.IllegalInteger; | ||
13218 | } | ||
13219 | |||
13220 | if (null != directory && null != property) | ||
13221 | { | ||
13222 | this.core.OnMessage(WixErrors.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Property", "Directory", directory)); | ||
13223 | } | ||
13224 | |||
13225 | if (null == id) | ||
13226 | { | ||
13227 | id = this.core.CreateIdentifier("rmf", directory ?? property ?? parentDirectory, on.ToString()); | ||
13228 | } | ||
13229 | |||
13230 | this.core.ParseForExtensionElements(node); | ||
13231 | |||
13232 | if (!this.core.EncounteredError) | ||
13233 | { | ||
13234 | Row row = this.core.CreateRow(sourceLineNumbers, "RemoveFile", id); | ||
13235 | row[1] = componentId; | ||
13236 | row[2] = null; | ||
13237 | if (null != directory) | ||
13238 | { | ||
13239 | row[3] = directory; | ||
13240 | } | ||
13241 | else if (null != property) | ||
13242 | { | ||
13243 | row[3] = property; | ||
13244 | } | ||
13245 | else | ||
13246 | { | ||
13247 | row[3] = parentDirectory; | ||
13248 | } | ||
13249 | row[4] = on; | ||
13250 | } | ||
13251 | } | ||
13252 | |||
13253 | /// <summary> | ||
13254 | /// Parses a reserve cost element. | ||
13255 | /// </summary> | ||
13256 | /// <param name="node">Element to parse.</param> | ||
13257 | /// <param name="componentId">Identifier of parent component.</param> | ||
13258 | /// <param name="directoryId">Optional and default identifier of referenced directory.</param> | ||
13259 | private void ParseReserveCostElement(XElement node, string componentId, string directoryId) | ||
13260 | { | ||
13261 | SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
13262 | Identifier id = null; | ||
13263 | int runFromSource = CompilerConstants.IntegerNotSet; | ||
13264 | int runLocal = CompilerConstants.IntegerNotSet; | ||
13265 | |||
13266 | foreach (XAttribute attrib in node.Attributes()) | ||
13267 | { | ||
13268 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
13269 | { | ||
13270 | switch (attrib.Name.LocalName) | ||
13271 | { | ||
13272 | case "Id": | ||
13273 | id = this.core.GetAttributeIdentifier(sourceLineNumbers, attrib); | ||
13274 | break; | ||
13275 | case "Directory": | ||
13276 | directoryId = this.core.CreateDirectoryReferenceFromInlineSyntax(sourceLineNumbers, attrib, directoryId); | ||
13277 | break; | ||
13278 | case "RunFromSource": | ||
13279 | runFromSource = this.core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, int.MaxValue); | ||
13280 | break; | ||
13281 | case "RunLocal": | ||
13282 | runLocal = this.core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, int.MaxValue); | ||
13283 | break; | ||
13284 | default: | ||
13285 | this.core.UnexpectedAttribute(node, attrib); | ||
13286 | break; | ||
13287 | } | ||
13288 | } | ||
13289 | else | ||
13290 | { | ||
13291 | this.core.ParseExtensionAttribute(node, attrib); | ||
13292 | } | ||
13293 | } | ||
13294 | |||
13295 | if (null == id) | ||
13296 | { | ||
13297 | id = this.core.CreateIdentifier("rc", componentId, directoryId); | ||
13298 | } | ||
13299 | |||
13300 | if (CompilerConstants.IntegerNotSet == runFromSource) | ||
13301 | { | ||
13302 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "RunFromSource")); | ||
13303 | } | ||
13304 | |||
13305 | if (CompilerConstants.IntegerNotSet == runLocal) | ||
13306 | { | ||
13307 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "RunLocal")); | ||
13308 | } | ||
13309 | |||
13310 | this.core.ParseForExtensionElements(node); | ||
13311 | |||
13312 | if (!this.core.EncounteredError) | ||
13313 | { | ||
13314 | Row row = this.core.CreateRow(sourceLineNumbers, "ReserveCost", id); | ||
13315 | row[1] = componentId; | ||
13316 | row[2] = directoryId; | ||
13317 | row[3] = runLocal; | ||
13318 | row[4] = runFromSource; | ||
13319 | } | ||
13320 | } | ||
13321 | |||
13322 | /// <summary> | ||
13323 | /// Parses a sequence element. | ||
13324 | /// </summary> | ||
13325 | /// <param name="node">Element to parse.</param> | ||
13326 | /// <param name="sequenceTable">Name of sequence table.</param> | ||
13327 | private void ParseSequenceElement(XElement node, string sequenceTable) | ||
13328 | { | ||
13329 | // use the proper table name internally | ||
13330 | if ("AdvertiseExecuteSequence" == sequenceTable) | ||
13331 | { | ||
13332 | sequenceTable = "AdvtExecuteSequence"; | ||
13333 | } | ||
13334 | |||
13335 | // Parse each action in the sequence. | ||
13336 | foreach (XElement child in node.Elements()) | ||
13337 | { | ||
13338 | SourceLineNumber childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child); | ||
13339 | string actionName = child.Name.LocalName; | ||
13340 | string afterAction = null; | ||
13341 | string beforeAction = null; | ||
13342 | string condition = null; | ||
13343 | bool customAction = "Custom" == actionName; | ||
13344 | bool overridable = false; | ||
13345 | int exitSequence = CompilerConstants.IntegerNotSet; | ||
13346 | int sequence = CompilerConstants.IntegerNotSet; | ||
13347 | bool showDialog = "Show" == actionName; | ||
13348 | bool specialAction = "InstallExecute" == actionName || "InstallExecuteAgain" == actionName || "RemoveExistingProducts" == actionName || "DisableRollback" == actionName || "ScheduleReboot" == actionName || "ForceReboot" == actionName || "ResolveSource" == actionName; | ||
13349 | bool specialStandardAction = "AppSearch" == actionName || "CCPSearch" == actionName || "RMCCPSearch" == actionName || "LaunchConditions" == actionName || "FindRelatedProducts" == actionName; | ||
13350 | bool suppress = false; | ||
13351 | |||
13352 | foreach (XAttribute attrib in child.Attributes()) | ||
13353 | { | ||
13354 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
13355 | { | ||
13356 | switch (attrib.Name.LocalName) | ||
13357 | { | ||
13358 | case "Action": | ||
13359 | if (customAction) | ||
13360 | { | ||
13361 | actionName = this.core.GetAttributeIdentifierValue(childSourceLineNumbers, attrib); | ||
13362 | this.core.CreateSimpleReference(childSourceLineNumbers, "CustomAction", actionName); | ||
13363 | } | ||
13364 | else | ||
13365 | { | ||
13366 | this.core.UnexpectedAttribute(child, attrib); | ||
13367 | } | ||
13368 | break; | ||
13369 | case "After": | ||
13370 | if (customAction || showDialog || specialAction || specialStandardAction) | ||
13371 | { | ||
13372 | afterAction = this.core.GetAttributeIdentifierValue(childSourceLineNumbers, attrib); | ||
13373 | this.core.CreateSimpleReference(childSourceLineNumbers, "WixAction", sequenceTable, afterAction); | ||
13374 | } | ||
13375 | else | ||
13376 | { | ||
13377 | this.core.UnexpectedAttribute(child, attrib); | ||
13378 | } | ||
13379 | break; | ||
13380 | case "Before": | ||
13381 | if (customAction || showDialog || specialAction || specialStandardAction) | ||
13382 | { | ||
13383 | beforeAction = this.core.GetAttributeIdentifierValue(childSourceLineNumbers, attrib); | ||
13384 | this.core.CreateSimpleReference(childSourceLineNumbers, "WixAction", sequenceTable, beforeAction); | ||
13385 | } | ||
13386 | else | ||
13387 | { | ||
13388 | this.core.UnexpectedAttribute(child, attrib); | ||
13389 | } | ||
13390 | break; | ||
13391 | case "Dialog": | ||
13392 | if (showDialog) | ||
13393 | { | ||
13394 | actionName = this.core.GetAttributeIdentifierValue(childSourceLineNumbers, attrib); | ||
13395 | this.core.CreateSimpleReference(childSourceLineNumbers, "Dialog", actionName); | ||
13396 | } | ||
13397 | else | ||
13398 | { | ||
13399 | this.core.UnexpectedAttribute(child, attrib); | ||
13400 | } | ||
13401 | break; | ||
13402 | case "OnExit": | ||
13403 | if (customAction || showDialog || specialAction) | ||
13404 | { | ||
13405 | Wix.ExitType exitValue = this.core.GetAttributeExitValue(childSourceLineNumbers, attrib); | ||
13406 | switch (exitValue) | ||
13407 | { | ||
13408 | case Wix.ExitType.success: | ||
13409 | exitSequence = -1; | ||
13410 | break; | ||
13411 | case Wix.ExitType.cancel: | ||
13412 | exitSequence = -2; | ||
13413 | break; | ||
13414 | case Wix.ExitType.error: | ||
13415 | exitSequence = -3; | ||
13416 | break; | ||
13417 | case Wix.ExitType.suspend: | ||
13418 | exitSequence = -4; | ||
13419 | break; | ||
13420 | } | ||
13421 | } | ||
13422 | else | ||
13423 | { | ||
13424 | this.core.UnexpectedAttribute(child, attrib); | ||
13425 | } | ||
13426 | break; | ||
13427 | case "Overridable": | ||
13428 | overridable = YesNoType.Yes == this.core.GetAttributeYesNoValue(childSourceLineNumbers, attrib); | ||
13429 | break; | ||
13430 | case "Sequence": | ||
13431 | sequence = this.core.GetAttributeIntegerValue(childSourceLineNumbers, attrib, 1, short.MaxValue); | ||
13432 | break; | ||
13433 | case "Suppress": | ||
13434 | suppress = YesNoType.Yes == this.core.GetAttributeYesNoValue(childSourceLineNumbers, attrib); | ||
13435 | break; | ||
13436 | default: | ||
13437 | this.core.UnexpectedAttribute(node, attrib); | ||
13438 | break; | ||
13439 | } | ||
13440 | } | ||
13441 | else | ||
13442 | { | ||
13443 | this.core.ParseExtensionAttribute(node, attrib); | ||
13444 | } | ||
13445 | } | ||
13446 | |||
13447 | |||
13448 | // Get the condition from the inner text of the element. | ||
13449 | condition = this.core.GetConditionInnerText(child); | ||
13450 | |||
13451 | if (customAction && "Custom" == actionName) | ||
13452 | { | ||
13453 | this.core.OnMessage(WixErrors.ExpectedAttribute(childSourceLineNumbers, child.Name.LocalName, "Action")); | ||
13454 | } | ||
13455 | else if (showDialog && "Show" == actionName) | ||
13456 | { | ||
13457 | this.core.OnMessage(WixErrors.ExpectedAttribute(childSourceLineNumbers, child.Name.LocalName, "Dialog")); | ||
13458 | } | ||
13459 | |||
13460 | if (CompilerConstants.IntegerNotSet != sequence) | ||
13461 | { | ||
13462 | if (CompilerConstants.IntegerNotSet != exitSequence) | ||
13463 | { | ||
13464 | this.core.OnMessage(WixErrors.IllegalAttributeWithOtherAttribute(childSourceLineNumbers, child.Name.LocalName, "Sequence", "OnExit")); | ||
13465 | } | ||
13466 | else if (null != beforeAction || null != afterAction) | ||
13467 | { | ||
13468 | this.core.OnMessage(WixErrors.IllegalAttributeWithOtherAttribute(childSourceLineNumbers, child.Name.LocalName, "Sequence", "Before", "After")); | ||
13469 | } | ||
13470 | } | ||
13471 | else // sequence not specified use OnExit (which may also be not set). | ||
13472 | { | ||
13473 | sequence = exitSequence; | ||
13474 | } | ||
13475 | |||
13476 | if (null != beforeAction && null != afterAction) | ||
13477 | { | ||
13478 | this.core.OnMessage(WixErrors.IllegalAttributeWithOtherAttribute(childSourceLineNumbers, child.Name.LocalName, "After", "Before")); | ||
13479 | } | ||
13480 | else if ((customAction || showDialog || specialAction) && !suppress && CompilerConstants.IntegerNotSet == sequence && null == beforeAction && null == afterAction) | ||
13481 | { | ||
13482 | this.core.OnMessage(WixErrors.NeedSequenceBeforeOrAfter(childSourceLineNumbers, child.Name.LocalName)); | ||
13483 | } | ||
13484 | |||
13485 | // action that is scheduled to occur before/after itself | ||
13486 | if (beforeAction == actionName) | ||
13487 | { | ||
13488 | this.core.OnMessage(WixErrors.ActionScheduledRelativeToItself(childSourceLineNumbers, child.Name.LocalName, "Before", beforeAction)); | ||
13489 | } | ||
13490 | else if (afterAction == actionName) | ||
13491 | { | ||
13492 | this.core.OnMessage(WixErrors.ActionScheduledRelativeToItself(childSourceLineNumbers, child.Name.LocalName, "After", afterAction)); | ||
13493 | } | ||
13494 | |||
13495 | // normal standard actions cannot be set overridable by the user (since they are overridable by default) | ||
13496 | if (overridable && WindowsInstallerStandard.IsStandardAction(actionName) && !specialAction) | ||
13497 | { | ||
13498 | this.core.OnMessage(WixErrors.UnexpectedAttribute(childSourceLineNumbers, child.Name.LocalName, "Overridable")); | ||
13499 | } | ||
13500 | |||
13501 | // suppress cannot be specified at the same time as Before, After, or Sequence | ||
13502 | if (suppress && (null != afterAction || null != beforeAction || CompilerConstants.IntegerNotSet != sequence || overridable)) | ||
13503 | { | ||
13504 | this.core.OnMessage(WixErrors.IllegalAttributeWithOtherAttributes(childSourceLineNumbers, child.Name.LocalName, "Suppress", "Before", "After", "Sequence", "Overridable")); | ||
13505 | } | ||
13506 | |||
13507 | this.core.ParseForExtensionElements(child); | ||
13508 | |||
13509 | // add the row and any references needed | ||
13510 | if (!this.core.EncounteredError) | ||
13511 | { | ||
13512 | if (suppress) | ||
13513 | { | ||
13514 | Row row = this.core.CreateRow(childSourceLineNumbers, "WixSuppressAction"); | ||
13515 | row[0] = sequenceTable; | ||
13516 | row[1] = actionName; | ||
13517 | } | ||
13518 | else | ||
13519 | { | ||
13520 | Row row = this.core.CreateRow(childSourceLineNumbers, "WixAction"); | ||
13521 | row[0] = sequenceTable; | ||
13522 | row[1] = actionName; | ||
13523 | row[2] = condition; | ||
13524 | if (CompilerConstants.IntegerNotSet != sequence) | ||
13525 | { | ||
13526 | row[3] = sequence; | ||
13527 | } | ||
13528 | row[4] = beforeAction; | ||
13529 | row[5] = afterAction; | ||
13530 | row[6] = overridable ? 1 : 0; | ||
13531 | } | ||
13532 | } | ||
13533 | } | ||
13534 | } | ||
13535 | |||
13536 | |||
13537 | /// <summary> | ||
13538 | /// Parses a service config element. | ||
13539 | /// </summary> | ||
13540 | /// <param name="node">Element to parse.</param> | ||
13541 | /// <param name="componentId">Identifier of parent component.</param> | ||
13542 | /// <param name="serviceName">Optional element containing parent's service name.</param> | ||
13543 | private void ParseServiceConfigElement(XElement node, string componentId, string serviceName) | ||
13544 | { | ||
13545 | SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
13546 | Identifier id = null; | ||
13547 | string delayedAutoStart = null; | ||
13548 | string failureActionsWhen = null; | ||
13549 | int events = 0; | ||
13550 | string name = serviceName; | ||
13551 | string preShutdownDelay = null; | ||
13552 | string requiredPrivileges = null; | ||
13553 | string sid = null; | ||
13554 | |||
13555 | this.core.OnMessage(WixWarnings.ServiceConfigFamilyNotSupported(sourceLineNumbers, node.Name.LocalName)); | ||
13556 | |||
13557 | foreach (XAttribute attrib in node.Attributes()) | ||
13558 | { | ||
13559 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
13560 | { | ||
13561 | switch (attrib.Name.LocalName) | ||
13562 | { | ||
13563 | case "Id": | ||
13564 | id = this.core.GetAttributeIdentifier(sourceLineNumbers, attrib); | ||
13565 | break; | ||
13566 | case "DelayedAutoStart": | ||
13567 | delayedAutoStart = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
13568 | if (0 < delayedAutoStart.Length) | ||
13569 | { | ||
13570 | switch (delayedAutoStart) | ||
13571 | { | ||
13572 | case "no": | ||
13573 | delayedAutoStart = "0"; | ||
13574 | break; | ||
13575 | case "yes": | ||
13576 | delayedAutoStart = "1"; | ||
13577 | break; | ||
13578 | default: | ||
13579 | // allow everything else to pass through that are hopefully "formatted" Properties. | ||
13580 | break; | ||
13581 | } | ||
13582 | } | ||
13583 | break; | ||
13584 | case "FailureActionsWhen": | ||
13585 | failureActionsWhen = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
13586 | if (0 < failureActionsWhen.Length) | ||
13587 | { | ||
13588 | switch (failureActionsWhen) | ||
13589 | { | ||
13590 | case "failedToStop": | ||
13591 | failureActionsWhen = "0"; | ||
13592 | break; | ||
13593 | case "failedToStopOrReturnedError": | ||
13594 | failureActionsWhen = "1"; | ||
13595 | break; | ||
13596 | default: | ||
13597 | // allow everything else to pass through that are hopefully "formatted" Properties. | ||
13598 | break; | ||
13599 | } | ||
13600 | } | ||
13601 | break; | ||
13602 | case "OnInstall": | ||
13603 | YesNoType install = this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
13604 | if (YesNoType.Yes == install) | ||
13605 | { | ||
13606 | events |= MsiInterop.MsidbServiceConfigEventInstall; | ||
13607 | } | ||
13608 | break; | ||
13609 | case "OnReinstall": | ||
13610 | YesNoType reinstall = this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
13611 | if (YesNoType.Yes == reinstall) | ||
13612 | { | ||
13613 | events |= MsiInterop.MsidbServiceConfigEventReinstall; | ||
13614 | } | ||
13615 | break; | ||
13616 | case "OnUninstall": | ||
13617 | YesNoType uninstall = this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
13618 | if (YesNoType.Yes == uninstall) | ||
13619 | { | ||
13620 | events |= MsiInterop.MsidbServiceConfigEventUninstall; | ||
13621 | } | ||
13622 | break; | ||
13623 | default: | ||
13624 | this.core.UnexpectedAttribute(node, attrib); | ||
13625 | break; | ||
13626 | case "PreShutdownDelay": | ||
13627 | preShutdownDelay = this.core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty); | ||
13628 | break; | ||
13629 | case "ServiceName": | ||
13630 | if (!String.IsNullOrEmpty(serviceName)) | ||
13631 | { | ||
13632 | this.core.OnMessage(WixErrors.IllegalAttributeWhenNested(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "ServiceInstall")); | ||
13633 | } | ||
13634 | |||
13635 | name = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
13636 | break; | ||
13637 | case "ServiceSid": | ||
13638 | sid = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
13639 | if (0 < sid.Length) | ||
13640 | { | ||
13641 | switch (sid) | ||
13642 | { | ||
13643 | case "none": | ||
13644 | sid = "0"; | ||
13645 | break; | ||
13646 | case "restricted": | ||
13647 | sid = "3"; | ||
13648 | break; | ||
13649 | case "unrestricted": | ||
13650 | sid = "1"; | ||
13651 | break; | ||
13652 | default: | ||
13653 | // allow everything else to pass through that are hopefully "formatted" Properties. | ||
13654 | break; | ||
13655 | } | ||
13656 | } | ||
13657 | break; | ||
13658 | } | ||
13659 | } | ||
13660 | else | ||
13661 | { | ||
13662 | this.core.ParseExtensionAttribute(node, attrib); | ||
13663 | } | ||
13664 | } | ||
13665 | |||
13666 | // Get the ServiceConfig required privilegs. | ||
13667 | foreach (XElement child in node.Elements()) | ||
13668 | { | ||
13669 | if (CompilerCore.WixNamespace == child.Name.Namespace) | ||
13670 | { | ||
13671 | switch (child.Name.LocalName) | ||
13672 | { | ||
13673 | case "RequiredPrivilege": | ||
13674 | string privilege = this.core.GetTrimmedInnerText(child); | ||
13675 | switch (privilege) | ||
13676 | { | ||
13677 | case "assignPrimaryToken": | ||
13678 | privilege = "SeAssignPrimaryTokenPrivilege"; | ||
13679 | break; | ||
13680 | case "audit": | ||
13681 | privilege = "SeAuditPrivilege"; | ||
13682 | break; | ||
13683 | case "backup": | ||
13684 | privilege = "SeBackupPrivilege"; | ||
13685 | break; | ||
13686 | case "changeNotify": | ||
13687 | privilege = "SeChangeNotifyPrivilege"; | ||
13688 | break; | ||
13689 | case "createGlobal": | ||
13690 | privilege = "SeCreateGlobalPrivilege"; | ||
13691 | break; | ||
13692 | case "createPagefile": | ||
13693 | privilege = "SeCreatePagefilePrivilege"; | ||
13694 | break; | ||
13695 | case "createPermanent": | ||
13696 | privilege = "SeCreatePermanentPrivilege"; | ||
13697 | break; | ||
13698 | case "createSymbolicLink": | ||
13699 | privilege = "SeCreateSymbolicLinkPrivilege"; | ||
13700 | break; | ||
13701 | case "createToken": | ||
13702 | privilege = "SeCreateTokenPrivilege"; | ||
13703 | break; | ||
13704 | case "debug": | ||
13705 | privilege = "SeDebugPrivilege"; | ||
13706 | break; | ||
13707 | case "enableDelegation": | ||
13708 | privilege = "SeEnableDelegationPrivilege"; | ||
13709 | break; | ||
13710 | case "impersonate": | ||
13711 | privilege = "SeImpersonatePrivilege"; | ||
13712 | break; | ||
13713 | case "increaseBasePriority": | ||
13714 | privilege = "SeIncreaseBasePriorityPrivilege"; | ||
13715 | break; | ||
13716 | case "increaseQuota": | ||
13717 | privilege = "SeIncreaseQuotaPrivilege"; | ||
13718 | break; | ||
13719 | case "increaseWorkingSet": | ||
13720 | privilege = "SeIncreaseWorkingSetPrivilege"; | ||
13721 | break; | ||
13722 | case "loadDriver": | ||
13723 | privilege = "SeLoadDriverPrivilege"; | ||
13724 | break; | ||
13725 | case "lockMemory": | ||
13726 | privilege = "SeLockMemoryPrivilege"; | ||
13727 | break; | ||
13728 | case "machineAccount": | ||
13729 | privilege = "SeMachineAccountPrivilege"; | ||
13730 | break; | ||
13731 | case "manageVolume": | ||
13732 | privilege = "SeManageVolumePrivilege"; | ||
13733 | break; | ||
13734 | case "profileSingleProcess": | ||
13735 | privilege = "SeProfileSingleProcessPrivilege"; | ||
13736 | break; | ||
13737 | case "relabel": | ||
13738 | privilege = "SeRelabelPrivilege"; | ||
13739 | break; | ||
13740 | case "remoteShutdown": | ||
13741 | privilege = "SeRemoteShutdownPrivilege"; | ||
13742 | break; | ||
13743 | case "restore": | ||
13744 | privilege = "SeRestorePrivilege"; | ||
13745 | break; | ||
13746 | case "security": | ||
13747 | privilege = "SeSecurityPrivilege"; | ||
13748 | break; | ||
13749 | case "shutdown": | ||
13750 | privilege = "SeShutdownPrivilege"; | ||
13751 | break; | ||
13752 | case "syncAgent": | ||
13753 | privilege = "SeSyncAgentPrivilege"; | ||
13754 | break; | ||
13755 | case "systemEnvironment": | ||
13756 | privilege = "SeSystemEnvironmentPrivilege"; | ||
13757 | break; | ||
13758 | case "systemProfile": | ||
13759 | privilege = "SeSystemProfilePrivilege"; | ||
13760 | break; | ||
13761 | case "systemTime": | ||
13762 | case "modifySystemTime": | ||
13763 | privilege = "SeSystemtimePrivilege"; | ||
13764 | break; | ||
13765 | case "takeOwnership": | ||
13766 | privilege = "SeTakeOwnershipPrivilege"; | ||
13767 | break; | ||
13768 | case "tcb": | ||
13769 | case "trustedComputerBase": | ||
13770 | privilege = "SeTcbPrivilege"; | ||
13771 | break; | ||
13772 | case "timeZone": | ||
13773 | case "modifyTimeZone": | ||
13774 | privilege = "SeTimeZonePrivilege"; | ||
13775 | break; | ||
13776 | case "trustedCredManAccess": | ||
13777 | case "trustedCredentialManagerAccess": | ||
13778 | privilege = "SeTrustedCredManAccessPrivilege"; | ||
13779 | break; | ||
13780 | case "undock": | ||
13781 | privilege = "SeUndockPrivilege"; | ||
13782 | break; | ||
13783 | case "unsolicitedInput": | ||
13784 | privilege = "SeUnsolicitedInputPrivilege"; | ||
13785 | break; | ||
13786 | default: | ||
13787 | // allow everything else to pass through that are hopefully "formatted" Properties. | ||
13788 | break; | ||
13789 | } | ||
13790 | |||
13791 | if (null != requiredPrivileges) | ||
13792 | { | ||
13793 | requiredPrivileges = String.Concat(requiredPrivileges, "[~]"); | ||
13794 | } | ||
13795 | requiredPrivileges = String.Concat(requiredPrivileges, privilege); | ||
13796 | break; | ||
13797 | default: | ||
13798 | this.core.UnexpectedElement(node, child); | ||
13799 | break; | ||
13800 | } | ||
13801 | } | ||
13802 | else | ||
13803 | { | ||
13804 | this.core.ParseExtensionElement(node, child); | ||
13805 | } | ||
13806 | } | ||
13807 | |||
13808 | if (String.IsNullOrEmpty(name)) | ||
13809 | { | ||
13810 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "ServiceName")); | ||
13811 | } | ||
13812 | else if (null == id) | ||
13813 | { | ||
13814 | id = this.core.CreateIdentifierFromFilename(name); | ||
13815 | } | ||
13816 | |||
13817 | if (0 == events) | ||
13818 | { | ||
13819 | this.core.OnMessage(WixErrors.ExpectedAttributes(sourceLineNumbers, node.Name.LocalName, "OnInstall", "OnReinstall", "OnUninstall")); | ||
13820 | } | ||
13821 | |||
13822 | if (String.IsNullOrEmpty(delayedAutoStart) && String.IsNullOrEmpty(failureActionsWhen) && String.IsNullOrEmpty(preShutdownDelay) && String.IsNullOrEmpty(requiredPrivileges) && String.IsNullOrEmpty(sid)) | ||
13823 | { | ||
13824 | this.core.OnMessage(WixErrors.ExpectedAttributes(sourceLineNumbers, node.Name.LocalName, "DelayedAutoStart", "FailureActionsWhen", "PreShutdownDelay", "ServiceSid", "RequiredPrivilege")); | ||
13825 | } | ||
13826 | |||
13827 | if (!this.core.EncounteredError) | ||
13828 | { | ||
13829 | if (!String.IsNullOrEmpty(delayedAutoStart)) | ||
13830 | { | ||
13831 | Row row = this.core.CreateRow(sourceLineNumbers, "MsiServiceConfig", new Identifier(String.Concat(id.Id, ".DS"), id.Access)); | ||
13832 | row[1] = name; | ||
13833 | row[2] = events; | ||
13834 | row[3] = 3; | ||
13835 | row[4] = delayedAutoStart; | ||
13836 | row[5] = componentId; | ||
13837 | } | ||
13838 | |||
13839 | if (!String.IsNullOrEmpty(failureActionsWhen)) | ||
13840 | { | ||
13841 | Row row = this.core.CreateRow(sourceLineNumbers, "MsiServiceConfig", new Identifier(String.Concat(id.Id, ".FA"), id.Access)); | ||
13842 | row[1] = name; | ||
13843 | row[2] = events; | ||
13844 | row[3] = 4; | ||
13845 | row[4] = failureActionsWhen; | ||
13846 | row[5] = componentId; | ||
13847 | } | ||
13848 | |||
13849 | if (!String.IsNullOrEmpty(sid)) | ||
13850 | { | ||
13851 | Row row = this.core.CreateRow(sourceLineNumbers, "MsiServiceConfig", new Identifier(String.Concat(id.Id, ".SS"), id.Access)); | ||
13852 | row[1] = name; | ||
13853 | row[2] = events; | ||
13854 | row[3] = 5; | ||
13855 | row[4] = sid; | ||
13856 | row[5] = componentId; | ||
13857 | } | ||
13858 | |||
13859 | if (!String.IsNullOrEmpty(requiredPrivileges)) | ||
13860 | { | ||
13861 | Row row = this.core.CreateRow(sourceLineNumbers, "MsiServiceConfig", new Identifier(String.Concat(id.Id, ".RP"), id.Access)); | ||
13862 | row[1] = name; | ||
13863 | row[2] = events; | ||
13864 | row[3] = 6; | ||
13865 | row[4] = requiredPrivileges; | ||
13866 | row[5] = componentId; | ||
13867 | } | ||
13868 | |||
13869 | if (!String.IsNullOrEmpty(preShutdownDelay)) | ||
13870 | { | ||
13871 | Row row = this.core.CreateRow(sourceLineNumbers, "MsiServiceConfig", new Identifier(String.Concat(id.Id, ".PD"), id.Access)); | ||
13872 | row[1] = name; | ||
13873 | row[2] = events; | ||
13874 | row[3] = 7; | ||
13875 | row[4] = preShutdownDelay; | ||
13876 | row[5] = componentId; | ||
13877 | } | ||
13878 | } | ||
13879 | } | ||
13880 | |||
13881 | /// <summary> | ||
13882 | /// Parses a service config failure actions element. | ||
13883 | /// </summary> | ||
13884 | /// <param name="node">Element to parse.</param> | ||
13885 | /// <param name="componentId">Identifier of parent component.</param> | ||
13886 | /// <param name="serviceName">Optional element containing parent's service name.</param> | ||
13887 | private void ParseServiceConfigFailureActionsElement(XElement node, string componentId, string serviceName) | ||
13888 | { | ||
13889 | SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
13890 | Identifier id = null; | ||
13891 | int events = 0; | ||
13892 | string name = serviceName; | ||
13893 | int resetPeriod = CompilerConstants.IntegerNotSet; | ||
13894 | string rebootMessage = null; | ||
13895 | string command = null; | ||
13896 | string actions = null; | ||
13897 | string actionsDelays = null; | ||
13898 | |||
13899 | this.core.OnMessage(WixWarnings.ServiceConfigFamilyNotSupported(sourceLineNumbers, node.Name.LocalName)); | ||
13900 | |||
13901 | foreach (XAttribute attrib in node.Attributes()) | ||
13902 | { | ||
13903 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
13904 | { | ||
13905 | switch (attrib.Name.LocalName) | ||
13906 | { | ||
13907 | case "Id": | ||
13908 | id = this.core.GetAttributeIdentifier(sourceLineNumbers, attrib); | ||
13909 | break; | ||
13910 | case "Command": | ||
13911 | command = this.core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty); | ||
13912 | break; | ||
13913 | case "OnInstall": | ||
13914 | YesNoType install = this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
13915 | if (YesNoType.Yes == install) | ||
13916 | { | ||
13917 | events |= MsiInterop.MsidbServiceConfigEventInstall; | ||
13918 | } | ||
13919 | break; | ||
13920 | case "OnReinstall": | ||
13921 | YesNoType reinstall = this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
13922 | if (YesNoType.Yes == reinstall) | ||
13923 | { | ||
13924 | events |= MsiInterop.MsidbServiceConfigEventReinstall; | ||
13925 | } | ||
13926 | break; | ||
13927 | case "OnUninstall": | ||
13928 | YesNoType uninstall = this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
13929 | if (YesNoType.Yes == uninstall) | ||
13930 | { | ||
13931 | events |= MsiInterop.MsidbServiceConfigEventUninstall; | ||
13932 | } | ||
13933 | break; | ||
13934 | case "RebootMessage": | ||
13935 | rebootMessage = this.core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty); | ||
13936 | break; | ||
13937 | case "ResetPeriod": | ||
13938 | resetPeriod = this.core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, int.MaxValue); | ||
13939 | break; | ||
13940 | case "ServiceName": | ||
13941 | if (!String.IsNullOrEmpty(serviceName)) | ||
13942 | { | ||
13943 | this.core.OnMessage(WixErrors.IllegalAttributeWhenNested(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "ServiceInstall")); | ||
13944 | } | ||
13945 | |||
13946 | name = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
13947 | break; | ||
13948 | default: | ||
13949 | this.core.UnexpectedAttribute(node, attrib); | ||
13950 | break; | ||
13951 | } | ||
13952 | } | ||
13953 | else | ||
13954 | { | ||
13955 | this.core.ParseExtensionAttribute(node, attrib); | ||
13956 | } | ||
13957 | } | ||
13958 | |||
13959 | // Get the ServiceConfigFailureActions actions. | ||
13960 | foreach (XElement child in node.Elements()) | ||
13961 | { | ||
13962 | if (CompilerCore.WixNamespace == child.Name.Namespace) | ||
13963 | { | ||
13964 | switch (child.Name.LocalName) | ||
13965 | { | ||
13966 | case "Failure": | ||
13967 | string action = null; | ||
13968 | string delay = null; | ||
13969 | SourceLineNumber childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child); | ||
13970 | |||
13971 | foreach (XAttribute childAttrib in child.Attributes()) | ||
13972 | { | ||
13973 | if (String.IsNullOrEmpty(childAttrib.Name.NamespaceName) || CompilerCore.WixNamespace == childAttrib.Name.Namespace) | ||
13974 | { | ||
13975 | switch (childAttrib.Name.LocalName) | ||
13976 | { | ||
13977 | case "Action": | ||
13978 | action = this.core.GetAttributeValue(childSourceLineNumbers, childAttrib); | ||
13979 | switch (action) | ||
13980 | { | ||
13981 | case "none": | ||
13982 | action = "0"; | ||
13983 | break; | ||
13984 | case "restartComputer": | ||
13985 | action = "2"; | ||
13986 | break; | ||
13987 | case "restartService": | ||
13988 | action = "1"; | ||
13989 | break; | ||
13990 | case "runCommand": | ||
13991 | action = "3"; | ||
13992 | break; | ||
13993 | default: | ||
13994 | // allow everything else to pass through that are hopefully "formatted" Properties. | ||
13995 | break; | ||
13996 | } | ||
13997 | break; | ||
13998 | case "Delay": | ||
13999 | delay = this.core.GetAttributeValue(childSourceLineNumbers, childAttrib); | ||
14000 | break; | ||
14001 | default: | ||
14002 | this.core.UnexpectedAttribute(child, childAttrib); | ||
14003 | break; | ||
14004 | } | ||
14005 | } | ||
14006 | } | ||
14007 | |||
14008 | if (String.IsNullOrEmpty(action)) | ||
14009 | { | ||
14010 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, child.Name.LocalName, "Action")); | ||
14011 | } | ||
14012 | |||
14013 | if (String.IsNullOrEmpty(delay)) | ||
14014 | { | ||
14015 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, child.Name.LocalName, "Delay")); | ||
14016 | } | ||
14017 | |||
14018 | if (!String.IsNullOrEmpty(actions)) | ||
14019 | { | ||
14020 | actions = String.Concat(actions, "[~]"); | ||
14021 | } | ||
14022 | actions = String.Concat(actions, action); | ||
14023 | |||
14024 | if (!String.IsNullOrEmpty(actionsDelays)) | ||
14025 | { | ||
14026 | actionsDelays = String.Concat(actionsDelays, "[~]"); | ||
14027 | } | ||
14028 | actionsDelays = String.Concat(actionsDelays, delay); | ||
14029 | break; | ||
14030 | default: | ||
14031 | this.core.UnexpectedElement(node, child); | ||
14032 | break; | ||
14033 | } | ||
14034 | } | ||
14035 | else | ||
14036 | { | ||
14037 | this.core.ParseExtensionElement(node, child); | ||
14038 | } | ||
14039 | } | ||
14040 | |||
14041 | if (String.IsNullOrEmpty(name)) | ||
14042 | { | ||
14043 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "ServiceName")); | ||
14044 | } | ||
14045 | else if (null == id) | ||
14046 | { | ||
14047 | id = this.core.CreateIdentifierFromFilename(name); | ||
14048 | } | ||
14049 | |||
14050 | if (0 == events) | ||
14051 | { | ||
14052 | this.core.OnMessage(WixErrors.ExpectedAttributes(sourceLineNumbers, node.Name.LocalName, "OnInstall", "OnReinstall", "OnUninstall")); | ||
14053 | } | ||
14054 | |||
14055 | if (!this.core.EncounteredError) | ||
14056 | { | ||
14057 | Row row = this.core.CreateRow(sourceLineNumbers, "MsiServiceConfigFailureActions", id); | ||
14058 | row[1] = name; | ||
14059 | row[2] = events; | ||
14060 | if (CompilerConstants.IntegerNotSet != resetPeriod) | ||
14061 | { | ||
14062 | row[3] = resetPeriod; | ||
14063 | } | ||
14064 | row[4] = rebootMessage ?? "[~]"; | ||
14065 | row[5] = command ?? "[~]"; | ||
14066 | row[6] = actions; | ||
14067 | row[7] = actionsDelays; | ||
14068 | row[8] = componentId; | ||
14069 | } | ||
14070 | } | ||
14071 | |||
14072 | /// <summary> | ||
14073 | /// Parses a service control element. | ||
14074 | /// </summary> | ||
14075 | /// <param name="node">Element to parse.</param> | ||
14076 | /// <param name="componentId">Identifier of parent component.</param> | ||
14077 | private void ParseServiceControlElement(XElement node, string componentId) | ||
14078 | { | ||
14079 | SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
14080 | string arguments = null; | ||
14081 | int events = 0; // default is to do nothing | ||
14082 | Identifier id = null; | ||
14083 | string name = null; | ||
14084 | YesNoType wait = YesNoType.NotSet; | ||
14085 | |||
14086 | foreach (XAttribute attrib in node.Attributes()) | ||
14087 | { | ||
14088 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
14089 | { | ||
14090 | switch (attrib.Name.LocalName) | ||
14091 | { | ||
14092 | case "Id": | ||
14093 | id = this.core.GetAttributeIdentifier(sourceLineNumbers, attrib); | ||
14094 | break; | ||
14095 | case "Name": | ||
14096 | name = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
14097 | break; | ||
14098 | case "Remove": | ||
14099 | Wix.InstallUninstallType removeValue = this.core.GetAttributeInstallUninstallValue(sourceLineNumbers, attrib); | ||
14100 | switch (removeValue) | ||
14101 | { | ||
14102 | case Wix.InstallUninstallType.install: | ||
14103 | events |= MsiInterop.MsidbServiceControlEventDelete; | ||
14104 | break; | ||
14105 | case Wix.InstallUninstallType.uninstall: | ||
14106 | events |= MsiInterop.MsidbServiceControlEventUninstallDelete; | ||
14107 | break; | ||
14108 | case Wix.InstallUninstallType.both: | ||
14109 | events |= MsiInterop.MsidbServiceControlEventDelete | MsiInterop.MsidbServiceControlEventUninstallDelete; | ||
14110 | break; | ||
14111 | } | ||
14112 | break; | ||
14113 | case "Start": | ||
14114 | Wix.InstallUninstallType startValue = this.core.GetAttributeInstallUninstallValue(sourceLineNumbers, attrib); | ||
14115 | switch (startValue) | ||
14116 | { | ||
14117 | case Wix.InstallUninstallType.install: | ||
14118 | events |= MsiInterop.MsidbServiceControlEventStart; | ||
14119 | break; | ||
14120 | case Wix.InstallUninstallType.uninstall: | ||
14121 | events |= MsiInterop.MsidbServiceControlEventUninstallStart; | ||
14122 | break; | ||
14123 | case Wix.InstallUninstallType.both: | ||
14124 | events |= MsiInterop.MsidbServiceControlEventStart | MsiInterop.MsidbServiceControlEventUninstallStart; | ||
14125 | break; | ||
14126 | } | ||
14127 | break; | ||
14128 | case "Stop": | ||
14129 | Wix.InstallUninstallType stopValue = this.core.GetAttributeInstallUninstallValue(sourceLineNumbers, attrib); | ||
14130 | switch (stopValue) | ||
14131 | { | ||
14132 | case Wix.InstallUninstallType.install: | ||
14133 | events |= MsiInterop.MsidbServiceControlEventStop; | ||
14134 | break; | ||
14135 | case Wix.InstallUninstallType.uninstall: | ||
14136 | events |= MsiInterop.MsidbServiceControlEventUninstallStop; | ||
14137 | break; | ||
14138 | case Wix.InstallUninstallType.both: | ||
14139 | events |= MsiInterop.MsidbServiceControlEventStop | MsiInterop.MsidbServiceControlEventUninstallStop; | ||
14140 | break; | ||
14141 | } | ||
14142 | break; | ||
14143 | case "Wait": | ||
14144 | wait = this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
14145 | break; | ||
14146 | default: | ||
14147 | this.core.UnexpectedAttribute(node, attrib); | ||
14148 | break; | ||
14149 | } | ||
14150 | } | ||
14151 | else | ||
14152 | { | ||
14153 | this.core.ParseExtensionAttribute(node, attrib); | ||
14154 | } | ||
14155 | } | ||
14156 | |||
14157 | if (null == id) | ||
14158 | { | ||
14159 | id = this.core.CreateIdentifierFromFilename(name); | ||
14160 | } | ||
14161 | |||
14162 | if (null == name) | ||
14163 | { | ||
14164 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name")); | ||
14165 | } | ||
14166 | |||
14167 | // get the ServiceControl arguments | ||
14168 | foreach (XElement child in node.Elements()) | ||
14169 | { | ||
14170 | if (CompilerCore.WixNamespace == child.Name.Namespace) | ||
14171 | { | ||
14172 | switch (child.Name.LocalName) | ||
14173 | { | ||
14174 | case "ServiceArgument": | ||
14175 | if (null != arguments) | ||
14176 | { | ||
14177 | arguments = String.Concat(arguments, "[~]"); | ||
14178 | } | ||
14179 | arguments = String.Concat(arguments, this.core.GetTrimmedInnerText(child)); | ||
14180 | break; | ||
14181 | default: | ||
14182 | this.core.UnexpectedElement(node, child); | ||
14183 | break; | ||
14184 | } | ||
14185 | } | ||
14186 | else | ||
14187 | { | ||
14188 | this.core.ParseExtensionElement(node, child); | ||
14189 | } | ||
14190 | } | ||
14191 | |||
14192 | if (!this.core.EncounteredError) | ||
14193 | { | ||
14194 | Row row = this.core.CreateRow(sourceLineNumbers, "ServiceControl", id); | ||
14195 | row[1] = name; | ||
14196 | row[2] = events; | ||
14197 | row[3] = arguments; | ||
14198 | if (YesNoType.NotSet != wait) | ||
14199 | { | ||
14200 | row[4] = YesNoType.Yes == wait ? 1 : 0; | ||
14201 | } | ||
14202 | row[5] = componentId; | ||
14203 | } | ||
14204 | } | ||
14205 | |||
14206 | /// <summary> | ||
14207 | /// Parses a service dependency element. | ||
14208 | /// </summary> | ||
14209 | /// <param name="node">Element to parse.</param> | ||
14210 | /// <returns>Parsed sevice dependency name.</returns> | ||
14211 | private string ParseServiceDependencyElement(XElement node) | ||
14212 | { | ||
14213 | SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
14214 | string dependency = null; | ||
14215 | bool group = false; | ||
14216 | |||
14217 | foreach (XAttribute attrib in node.Attributes()) | ||
14218 | { | ||
14219 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
14220 | { | ||
14221 | switch (attrib.Name.LocalName) | ||
14222 | { | ||
14223 | case "Id": | ||
14224 | dependency = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
14225 | break; | ||
14226 | case "Group": | ||
14227 | group = YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
14228 | break; | ||
14229 | default: | ||
14230 | this.core.UnexpectedAttribute(node, attrib); | ||
14231 | break; | ||
14232 | } | ||
14233 | } | ||
14234 | else | ||
14235 | { | ||
14236 | this.core.ParseExtensionAttribute(node, attrib); | ||
14237 | } | ||
14238 | } | ||
14239 | |||
14240 | if (null == dependency) | ||
14241 | { | ||
14242 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); | ||
14243 | } | ||
14244 | |||
14245 | this.core.ParseForExtensionElements(node); | ||
14246 | |||
14247 | return group ? String.Concat("+", dependency) : dependency; | ||
14248 | } | ||
14249 | |||
14250 | /// <summary> | ||
14251 | /// Parses a service install element. | ||
14252 | /// </summary> | ||
14253 | /// <param name="node">Element to parse.</param> | ||
14254 | /// <param name="componentId">Identifier of parent component.</param> | ||
14255 | private void ParseServiceInstallElement(XElement node, string componentId, bool win64Component) | ||
14256 | { | ||
14257 | SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
14258 | Identifier id = null; | ||
14259 | string account = null; | ||
14260 | string arguments = null; | ||
14261 | string dependencies = null; | ||
14262 | string description = null; | ||
14263 | string displayName = null; | ||
14264 | bool eraseDescription = false; | ||
14265 | int errorbits = 0; | ||
14266 | string loadOrderGroup = null; | ||
14267 | string name = null; | ||
14268 | string password = null; | ||
14269 | int startType = 0; | ||
14270 | int typebits = 0; | ||
14271 | |||
14272 | foreach (XAttribute attrib in node.Attributes()) | ||
14273 | { | ||
14274 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
14275 | { | ||
14276 | switch (attrib.Name.LocalName) | ||
14277 | { | ||
14278 | case "Id": | ||
14279 | id = this.core.GetAttributeIdentifier(sourceLineNumbers, attrib); | ||
14280 | break; | ||
14281 | case "Account": | ||
14282 | account = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
14283 | break; | ||
14284 | case "Arguments": | ||
14285 | arguments = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
14286 | break; | ||
14287 | case "Description": | ||
14288 | description = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
14289 | break; | ||
14290 | case "DisplayName": | ||
14291 | displayName = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
14292 | break; | ||
14293 | case "EraseDescription": | ||
14294 | eraseDescription = YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
14295 | break; | ||
14296 | case "ErrorControl": | ||
14297 | string errorControlValue = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
14298 | if (0 < errorControlValue.Length) | ||
14299 | { | ||
14300 | Wix.ServiceInstall.ErrorControlType errorControlType = Wix.ServiceInstall.ParseErrorControlType(errorControlValue); | ||
14301 | switch (errorControlType) | ||
14302 | { | ||
14303 | case Wix.ServiceInstall.ErrorControlType.ignore: | ||
14304 | errorbits |= MsiInterop.MsidbServiceInstallErrorIgnore; | ||
14305 | break; | ||
14306 | case Wix.ServiceInstall.ErrorControlType.normal: | ||
14307 | errorbits |= MsiInterop.MsidbServiceInstallErrorNormal; | ||
14308 | break; | ||
14309 | case Wix.ServiceInstall.ErrorControlType.critical: | ||
14310 | errorbits |= MsiInterop.MsidbServiceInstallErrorCritical; | ||
14311 | break; | ||
14312 | default: | ||
14313 | this.core.OnMessage(WixErrors.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, errorControlValue, "ignore", "normal", "critical")); | ||
14314 | break; | ||
14315 | } | ||
14316 | } | ||
14317 | break; | ||
14318 | case "Interactive": | ||
14319 | if (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) | ||
14320 | { | ||
14321 | typebits |= MsiInterop.MsidbServiceInstallInteractive; | ||
14322 | } | ||
14323 | break; | ||
14324 | case "LoadOrderGroup": | ||
14325 | loadOrderGroup = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
14326 | break; | ||
14327 | case "Name": | ||
14328 | name = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
14329 | break; | ||
14330 | case "Password": | ||
14331 | password = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
14332 | break; | ||
14333 | case "Start": | ||
14334 | string startValue = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
14335 | if (0 < startValue.Length) | ||
14336 | { | ||
14337 | Wix.ServiceInstall.StartType start = Wix.ServiceInstall.ParseStartType(startValue); | ||
14338 | switch (start) | ||
14339 | { | ||
14340 | case Wix.ServiceInstall.StartType.auto: | ||
14341 | startType = MsiInterop.MsidbServiceInstallAutoStart; | ||
14342 | break; | ||
14343 | case Wix.ServiceInstall.StartType.demand: | ||
14344 | startType = MsiInterop.MsidbServiceInstallDemandStart; | ||
14345 | break; | ||
14346 | case Wix.ServiceInstall.StartType.disabled: | ||
14347 | startType = MsiInterop.MsidbServiceInstallDisabled; | ||
14348 | break; | ||
14349 | case Wix.ServiceInstall.StartType.boot: | ||
14350 | case Wix.ServiceInstall.StartType.system: | ||
14351 | this.core.OnMessage(WixErrors.ValueNotSupported(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, startValue)); | ||
14352 | break; | ||
14353 | default: | ||
14354 | this.core.OnMessage(WixErrors.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, startValue, "auto", "demand", "disabled")); | ||
14355 | break; | ||
14356 | } | ||
14357 | } | ||
14358 | break; | ||
14359 | case "Type": | ||
14360 | string typeValue = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
14361 | if (0 < typeValue.Length) | ||
14362 | { | ||
14363 | Wix.ServiceInstall.TypeType typeType = Wix.ServiceInstall.ParseTypeType(typeValue); | ||
14364 | switch (typeType) | ||
14365 | { | ||
14366 | case Wix.ServiceInstall.TypeType.ownProcess: | ||
14367 | typebits |= MsiInterop.MsidbServiceInstallOwnProcess; | ||
14368 | break; | ||
14369 | case Wix.ServiceInstall.TypeType.shareProcess: | ||
14370 | typebits |= MsiInterop.MsidbServiceInstallShareProcess; | ||
14371 | break; | ||
14372 | case Wix.ServiceInstall.TypeType.kernelDriver: | ||
14373 | case Wix.ServiceInstall.TypeType.systemDriver: | ||
14374 | this.core.OnMessage(WixErrors.ValueNotSupported(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, typeValue)); | ||
14375 | break; | ||
14376 | default: | ||
14377 | this.core.OnMessage(WixErrors.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, node.Name.LocalName, typeValue, "ownProcess", "shareProcess")); | ||
14378 | break; | ||
14379 | } | ||
14380 | } | ||
14381 | break; | ||
14382 | case "Vital": | ||
14383 | if (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) | ||
14384 | { | ||
14385 | errorbits |= MsiInterop.MsidbServiceInstallErrorControlVital; | ||
14386 | } | ||
14387 | break; | ||
14388 | default: | ||
14389 | this.core.UnexpectedAttribute(node, attrib); | ||
14390 | break; | ||
14391 | } | ||
14392 | } | ||
14393 | else | ||
14394 | { | ||
14395 | this.core.ParseExtensionAttribute(node, attrib); | ||
14396 | } | ||
14397 | } | ||
14398 | |||
14399 | if (String.IsNullOrEmpty(name)) | ||
14400 | { | ||
14401 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name")); | ||
14402 | } | ||
14403 | else if (null == id) | ||
14404 | { | ||
14405 | id = this.core.CreateIdentifierFromFilename(name); | ||
14406 | } | ||
14407 | |||
14408 | if (0 == startType) | ||
14409 | { | ||
14410 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Start")); | ||
14411 | } | ||
14412 | |||
14413 | if (eraseDescription) | ||
14414 | { | ||
14415 | description = "[~]"; | ||
14416 | } | ||
14417 | |||
14418 | // get the ServiceInstall dependencies and config | ||
14419 | foreach (XElement child in node.Elements()) | ||
14420 | { | ||
14421 | if (CompilerCore.WixNamespace == child.Name.Namespace) | ||
14422 | { | ||
14423 | switch (child.Name.LocalName) | ||
14424 | { | ||
14425 | case "PermissionEx": | ||
14426 | this.ParsePermissionExElement(child, id.Id, "ServiceInstall"); | ||
14427 | break; | ||
14428 | case "ServiceConfig": | ||
14429 | this.ParseServiceConfigElement(child, componentId, name); | ||
14430 | break; | ||
14431 | case "ServiceConfigFailureActions": | ||
14432 | this.ParseServiceConfigFailureActionsElement(child, componentId, name); | ||
14433 | break; | ||
14434 | case "ServiceDependency": | ||
14435 | dependencies = String.Concat(dependencies, this.ParseServiceDependencyElement(child), "[~]"); | ||
14436 | break; | ||
14437 | default: | ||
14438 | this.core.UnexpectedElement(node, child); | ||
14439 | break; | ||
14440 | } | ||
14441 | } | ||
14442 | else | ||
14443 | { | ||
14444 | Dictionary<string, string> context = new Dictionary<string, string>() { { "ServiceInstallId", id.Id }, { "ServiceInstallName", name }, { "ServiceInstallComponentId", componentId }, { "Win64", win64Component.ToString() } }; | ||
14445 | this.core.ParseExtensionElement(node, child, context); | ||
14446 | } | ||
14447 | } | ||
14448 | |||
14449 | if (null != dependencies) | ||
14450 | { | ||
14451 | dependencies = String.Concat(dependencies, "[~]"); | ||
14452 | } | ||
14453 | |||
14454 | if (!this.core.EncounteredError) | ||
14455 | { | ||
14456 | Row row = this.core.CreateRow(sourceLineNumbers, "ServiceInstall", id); | ||
14457 | row[1] = name; | ||
14458 | row[2] = displayName; | ||
14459 | row[3] = typebits; | ||
14460 | row[4] = startType; | ||
14461 | row[5] = errorbits; | ||
14462 | row[6] = loadOrderGroup; | ||
14463 | row[7] = dependencies; | ||
14464 | row[8] = account; | ||
14465 | row[9] = password; | ||
14466 | row[10] = arguments; | ||
14467 | row[11] = componentId; | ||
14468 | row[12] = description; | ||
14469 | } | ||
14470 | } | ||
14471 | |||
14472 | /// <summary> | ||
14473 | /// Parses a SetDirectory element. | ||
14474 | /// </summary> | ||
14475 | /// <param name="node">Element to parse.</param> | ||
14476 | private void ParseSetDirectoryElement(XElement node) | ||
14477 | { | ||
14478 | SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
14479 | string actionName = null; | ||
14480 | string id = null; | ||
14481 | string condition = null; | ||
14482 | string[] sequences = new string[] { "InstallUISequence", "InstallExecuteSequence" }; // default to "both" | ||
14483 | int extraBits = 0; | ||
14484 | string value = null; | ||
14485 | |||
14486 | foreach (XAttribute attrib in node.Attributes()) | ||
14487 | { | ||
14488 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
14489 | { | ||
14490 | switch (attrib.Name.LocalName) | ||
14491 | { | ||
14492 | case "Action": | ||
14493 | actionName = this.core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); | ||
14494 | break; | ||
14495 | case "Id": | ||
14496 | id = this.core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); | ||
14497 | this.core.CreateSimpleReference(sourceLineNumbers, "Directory", id); | ||
14498 | break; | ||
14499 | case "Sequence": | ||
14500 | string sequenceValue = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
14501 | if (0 < sequenceValue.Length) | ||
14502 | { | ||
14503 | Wix.SequenceType sequenceType = Wix.Enums.ParseSequenceType(sequenceValue); | ||
14504 | switch (sequenceType) | ||
14505 | { | ||
14506 | case Wix.SequenceType.execute: | ||
14507 | sequences = new string[] { "InstallExecuteSequence" }; | ||
14508 | break; | ||
14509 | case Wix.SequenceType.ui: | ||
14510 | sequences = new string[] { "InstallUISequence" }; | ||
14511 | break; | ||
14512 | case Wix.SequenceType.first: | ||
14513 | extraBits = MsiInterop.MsidbCustomActionTypeFirstSequence; | ||
14514 | // default puts it in both sequence which is what we want | ||
14515 | break; | ||
14516 | case Wix.SequenceType.both: | ||
14517 | // default so no work necessary. | ||
14518 | break; | ||
14519 | default: | ||
14520 | this.core.OnMessage(WixErrors.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, sequenceValue, "execute", "ui", "both")); | ||
14521 | break; | ||
14522 | } | ||
14523 | } | ||
14524 | break; | ||
14525 | case "Value": | ||
14526 | value = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
14527 | break; | ||
14528 | default: | ||
14529 | this.core.UnexpectedAttribute(node, attrib); | ||
14530 | break; | ||
14531 | } | ||
14532 | } | ||
14533 | else | ||
14534 | { | ||
14535 | this.core.ParseExtensionAttribute(node, attrib); | ||
14536 | } | ||
14537 | } | ||
14538 | |||
14539 | condition = this.core.GetConditionInnerText(node); | ||
14540 | |||
14541 | if (null == id) | ||
14542 | { | ||
14543 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); | ||
14544 | } | ||
14545 | else if (String.IsNullOrEmpty(actionName)) | ||
14546 | { | ||
14547 | actionName = String.Concat("Set", id); | ||
14548 | } | ||
14549 | |||
14550 | if (null == value) | ||
14551 | { | ||
14552 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Value")); | ||
14553 | } | ||
14554 | |||
14555 | this.core.ParseForExtensionElements(node); | ||
14556 | |||
14557 | // add the row and any references needed | ||
14558 | if (!this.core.EncounteredError) | ||
14559 | { | ||
14560 | Row row = this.core.CreateRow(sourceLineNumbers, "CustomAction"); | ||
14561 | row[0] = actionName; | ||
14562 | row[1] = MsiInterop.MsidbCustomActionTypeProperty | MsiInterop.MsidbCustomActionTypeTextData | extraBits; | ||
14563 | row[2] = id; | ||
14564 | row[3] = value; | ||
14565 | |||
14566 | foreach (string sequence in sequences) | ||
14567 | { | ||
14568 | Row sequenceRow = this.core.CreateRow(sourceLineNumbers, "WixAction"); | ||
14569 | sequenceRow[0] = sequence; | ||
14570 | sequenceRow[1] = actionName; | ||
14571 | sequenceRow[2] = condition; | ||
14572 | // no explicit sequence | ||
14573 | // no before action | ||
14574 | sequenceRow[5] = "CostInitialize"; | ||
14575 | sequenceRow[6] = 0; // not overridable | ||
14576 | } | ||
14577 | } | ||
14578 | } | ||
14579 | |||
14580 | /// <summary> | ||
14581 | /// Parses a SetProperty element. | ||
14582 | /// </summary> | ||
14583 | /// <param name="node">Element to parse.</param> | ||
14584 | private void ParseSetPropertyElement(XElement node) | ||
14585 | { | ||
14586 | SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
14587 | string actionName = null; | ||
14588 | string id = null; | ||
14589 | string afterAction = null; | ||
14590 | string beforeAction = null; | ||
14591 | string condition = null; | ||
14592 | string[] sequences = new string[] { "InstallUISequence", "InstallExecuteSequence" }; // default to "both" | ||
14593 | int extraBits = 0; | ||
14594 | string value = null; | ||
14595 | |||
14596 | foreach (XAttribute attrib in node.Attributes()) | ||
14597 | { | ||
14598 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
14599 | { | ||
14600 | switch (attrib.Name.LocalName) | ||
14601 | { | ||
14602 | case "Action": | ||
14603 | actionName = this.core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); | ||
14604 | break; | ||
14605 | case "Id": | ||
14606 | id = this.core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); | ||
14607 | break; | ||
14608 | case "After": | ||
14609 | afterAction = this.core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); | ||
14610 | break; | ||
14611 | case "Before": | ||
14612 | beforeAction = this.core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); | ||
14613 | break; | ||
14614 | case "Sequence": | ||
14615 | string sequenceValue = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
14616 | if (0 < sequenceValue.Length) | ||
14617 | { | ||
14618 | Wix.SequenceType sequenceType = Wix.Enums.ParseSequenceType(sequenceValue); | ||
14619 | switch (sequenceType) | ||
14620 | { | ||
14621 | case Wix.SequenceType.execute: | ||
14622 | sequences = new string[] { "InstallExecuteSequence" }; | ||
14623 | break; | ||
14624 | case Wix.SequenceType.ui: | ||
14625 | sequences = new string[] { "InstallUISequence" }; | ||
14626 | break; | ||
14627 | case Wix.SequenceType.first: | ||
14628 | extraBits = MsiInterop.MsidbCustomActionTypeFirstSequence; | ||
14629 | // default puts it in both sequence which is what we want | ||
14630 | break; | ||
14631 | case Wix.SequenceType.both: | ||
14632 | // default so no work necessary. | ||
14633 | break; | ||
14634 | default: | ||
14635 | this.core.OnMessage(WixErrors.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, sequenceValue, "execute", "ui", "both")); | ||
14636 | break; | ||
14637 | } | ||
14638 | } | ||
14639 | break; | ||
14640 | case "Value": | ||
14641 | value = this.core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty); | ||
14642 | break; | ||
14643 | default: | ||
14644 | this.core.UnexpectedAttribute(node, attrib); | ||
14645 | break; | ||
14646 | } | ||
14647 | } | ||
14648 | else | ||
14649 | { | ||
14650 | this.core.ParseExtensionAttribute(node, attrib); | ||
14651 | } | ||
14652 | } | ||
14653 | |||
14654 | condition = this.core.GetConditionInnerText(node); | ||
14655 | |||
14656 | if (null == id) | ||
14657 | { | ||
14658 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); | ||
14659 | } | ||
14660 | else if (String.IsNullOrEmpty(actionName)) | ||
14661 | { | ||
14662 | actionName = String.Concat("Set", id); | ||
14663 | } | ||
14664 | |||
14665 | if (null == value) | ||
14666 | { | ||
14667 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Value")); | ||
14668 | } | ||
14669 | |||
14670 | if (null != beforeAction && null != afterAction) | ||
14671 | { | ||
14672 | this.core.OnMessage(WixErrors.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "After", "Before")); | ||
14673 | } | ||
14674 | else if (null == beforeAction && null == afterAction) | ||
14675 | { | ||
14676 | this.core.OnMessage(WixErrors.ExpectedAttributesWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "After", "Before", "Id")); | ||
14677 | } | ||
14678 | |||
14679 | this.core.ParseForExtensionElements(node); | ||
14680 | |||
14681 | // add the row and any references needed | ||
14682 | if (!this.core.EncounteredError) | ||
14683 | { | ||
14684 | // action that is scheduled to occur before/after itself | ||
14685 | if (beforeAction == actionName) | ||
14686 | { | ||
14687 | this.core.OnMessage(WixErrors.ActionScheduledRelativeToItself(sourceLineNumbers, node.Name.LocalName, "Before", beforeAction)); | ||
14688 | } | ||
14689 | else if (afterAction == actionName) | ||
14690 | { | ||
14691 | this.core.OnMessage(WixErrors.ActionScheduledRelativeToItself(sourceLineNumbers, node.Name.LocalName, "After", afterAction)); | ||
14692 | } | ||
14693 | |||
14694 | Row row = this.core.CreateRow(sourceLineNumbers, "CustomAction"); | ||
14695 | row[0] = actionName; | ||
14696 | row[1] = MsiInterop.MsidbCustomActionTypeProperty | MsiInterop.MsidbCustomActionTypeTextData | extraBits; | ||
14697 | row[2] = id; | ||
14698 | row[3] = value; | ||
14699 | |||
14700 | foreach (string sequence in sequences) | ||
14701 | { | ||
14702 | Row sequenceRow = this.core.CreateRow(sourceLineNumbers, "WixAction"); | ||
14703 | sequenceRow[0] = sequence; | ||
14704 | sequenceRow[1] = actionName; | ||
14705 | sequenceRow[2] = condition; | ||
14706 | // no explicit sequence | ||
14707 | sequenceRow[4] = beforeAction; | ||
14708 | sequenceRow[5] = afterAction; | ||
14709 | sequenceRow[6] = 0; // not overridable | ||
14710 | |||
14711 | if (null != beforeAction) | ||
14712 | { | ||
14713 | if (WindowsInstallerStandard.IsStandardAction(beforeAction)) | ||
14714 | { | ||
14715 | this.core.CreateSimpleReference(sourceLineNumbers, "WixAction", sequence, beforeAction); | ||
14716 | } | ||
14717 | else | ||
14718 | { | ||
14719 | this.core.CreateSimpleReference(sourceLineNumbers, "CustomAction", beforeAction); | ||
14720 | } | ||
14721 | } | ||
14722 | |||
14723 | if (null != afterAction) | ||
14724 | { | ||
14725 | if (WindowsInstallerStandard.IsStandardAction(afterAction)) | ||
14726 | { | ||
14727 | this.core.CreateSimpleReference(sourceLineNumbers, "WixAction", sequence, afterAction); | ||
14728 | } | ||
14729 | else | ||
14730 | { | ||
14731 | this.core.CreateSimpleReference(sourceLineNumbers, "CustomAction", afterAction); | ||
14732 | } | ||
14733 | } | ||
14734 | } | ||
14735 | } | ||
14736 | } | ||
14737 | |||
14738 | /// <summary> | ||
14739 | /// Parses a SFP catalog element. | ||
14740 | /// </summary> | ||
14741 | /// <param name="node">Element to parse.</param> | ||
14742 | /// <param name="parentSFPCatalog">Parent SFPCatalog.</param> | ||
14743 | private void ParseSFPFileElement(XElement node, string parentSFPCatalog) | ||
14744 | { | ||
14745 | SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
14746 | string id = null; | ||
14747 | |||
14748 | foreach (XAttribute attrib in node.Attributes()) | ||
14749 | { | ||
14750 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
14751 | { | ||
14752 | switch (attrib.Name.LocalName) | ||
14753 | { | ||
14754 | case "Id": | ||
14755 | id = this.core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); | ||
14756 | break; | ||
14757 | default: | ||
14758 | this.core.UnexpectedAttribute(node, attrib); | ||
14759 | break; | ||
14760 | } | ||
14761 | } | ||
14762 | else | ||
14763 | { | ||
14764 | this.core.ParseExtensionAttribute(node, attrib); | ||
14765 | } | ||
14766 | } | ||
14767 | |||
14768 | if (null == id) | ||
14769 | { | ||
14770 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); | ||
14771 | } | ||
14772 | |||
14773 | this.core.ParseForExtensionElements(node); | ||
14774 | |||
14775 | if (!this.core.EncounteredError) | ||
14776 | { | ||
14777 | Row row = this.core.CreateRow(sourceLineNumbers, "FileSFPCatalog"); | ||
14778 | row[0] = id; | ||
14779 | row[1] = parentSFPCatalog; | ||
14780 | } | ||
14781 | } | ||
14782 | |||
14783 | /// <summary> | ||
14784 | /// Parses a SFP catalog element. | ||
14785 | /// </summary> | ||
14786 | /// <param name="node">Element to parse.</param> | ||
14787 | /// <param name="parentSFPCatalog">Parent SFPCatalog.</param> | ||
14788 | private void ParseSFPCatalogElement(XElement node, ref string parentSFPCatalog) | ||
14789 | { | ||
14790 | SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
14791 | string parentName = null; | ||
14792 | string dependency = null; | ||
14793 | string name = null; | ||
14794 | string sourceFile = null; | ||
14795 | |||
14796 | foreach (XAttribute attrib in node.Attributes()) | ||
14797 | { | ||
14798 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
14799 | { | ||
14800 | switch (attrib.Name.LocalName) | ||
14801 | { | ||
14802 | case "Dependency": | ||
14803 | dependency = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
14804 | break; | ||
14805 | case "Name": | ||
14806 | name = this.core.GetAttributeShortFilename(sourceLineNumbers, attrib, false); | ||
14807 | parentSFPCatalog = name; | ||
14808 | break; | ||
14809 | case "SourceFile": | ||
14810 | sourceFile = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
14811 | break; | ||
14812 | default: | ||
14813 | this.core.UnexpectedAttribute(node, attrib); | ||
14814 | break; | ||
14815 | } | ||
14816 | } | ||
14817 | else | ||
14818 | { | ||
14819 | this.core.ParseExtensionAttribute(node, attrib); | ||
14820 | } | ||
14821 | } | ||
14822 | |||
14823 | if (null == name) | ||
14824 | { | ||
14825 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name")); | ||
14826 | } | ||
14827 | |||
14828 | if (null == sourceFile) | ||
14829 | { | ||
14830 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "SourceFile")); | ||
14831 | } | ||
14832 | |||
14833 | foreach (XElement child in node.Elements()) | ||
14834 | { | ||
14835 | if (CompilerCore.WixNamespace == child.Name.Namespace) | ||
14836 | { | ||
14837 | switch (child.Name.LocalName) | ||
14838 | { | ||
14839 | case "SFPCatalog": | ||
14840 | this.ParseSFPCatalogElement(child, ref parentName); | ||
14841 | if (null != dependency && parentName == dependency) | ||
14842 | { | ||
14843 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Dependency")); | ||
14844 | } | ||
14845 | dependency = parentName; | ||
14846 | break; | ||
14847 | case "SFPFile": | ||
14848 | this.ParseSFPFileElement(child, name); | ||
14849 | break; | ||
14850 | default: | ||
14851 | this.core.UnexpectedElement(node, child); | ||
14852 | break; | ||
14853 | } | ||
14854 | } | ||
14855 | else | ||
14856 | { | ||
14857 | this.core.ParseExtensionElement(node, child); | ||
14858 | } | ||
14859 | } | ||
14860 | |||
14861 | if (null == dependency) | ||
14862 | { | ||
14863 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Dependency")); | ||
14864 | } | ||
14865 | |||
14866 | if (!this.core.EncounteredError) | ||
14867 | { | ||
14868 | Row row = this.core.CreateRow(sourceLineNumbers, "SFPCatalog"); | ||
14869 | row[0] = name; | ||
14870 | row[1] = sourceFile; | ||
14871 | row[2] = dependency; | ||
14872 | } | ||
14873 | } | ||
14874 | |||
14875 | /// <summary> | ||
14876 | /// Parses a shortcut element. | ||
14877 | /// </summary> | ||
14878 | /// <param name="node">Element to parse.</param> | ||
14879 | /// <param name="componentId">Identifer for parent component.</param> | ||
14880 | /// <param name="parentElementLocalName">Local name of parent element.</param> | ||
14881 | /// <param name="defaultTarget">Default identifier of parent (which is usually the target).</param> | ||
14882 | /// <param name="parentKeyPath">Flag to indicate whether the parent element is the keypath of a component or not (will only be true for file parent elements).</param> | ||
14883 | private void ParseShortcutElement(XElement node, string componentId, string parentElementLocalName, string defaultTarget, YesNoType parentKeyPath) | ||
14884 | { | ||
14885 | SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
14886 | Identifier id = null; | ||
14887 | bool advertise = false; | ||
14888 | string arguments = null; | ||
14889 | string description = null; | ||
14890 | string descriptionResourceDll = null; | ||
14891 | int descriptionResourceId = CompilerConstants.IntegerNotSet; | ||
14892 | string directory = null; | ||
14893 | string displayResourceDll = null; | ||
14894 | int displayResourceId = CompilerConstants.IntegerNotSet; | ||
14895 | int hotkey = CompilerConstants.IntegerNotSet; | ||
14896 | string icon = null; | ||
14897 | int iconIndex = CompilerConstants.IntegerNotSet; | ||
14898 | string name = null; | ||
14899 | string shortName = null; | ||
14900 | int show = CompilerConstants.IntegerNotSet; | ||
14901 | string target = null; | ||
14902 | string workingDirectory = null; | ||
14903 | |||
14904 | foreach (XAttribute attrib in node.Attributes()) | ||
14905 | { | ||
14906 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
14907 | { | ||
14908 | switch (attrib.Name.LocalName) | ||
14909 | { | ||
14910 | case "Id": | ||
14911 | id = this.core.GetAttributeIdentifier(sourceLineNumbers, attrib); | ||
14912 | break; | ||
14913 | case "Advertise": | ||
14914 | advertise = YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
14915 | break; | ||
14916 | case "Arguments": | ||
14917 | arguments = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
14918 | break; | ||
14919 | case "Description": | ||
14920 | description = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
14921 | break; | ||
14922 | case "DescriptionResourceDll": | ||
14923 | descriptionResourceDll = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
14924 | break; | ||
14925 | case "DescriptionResourceId": | ||
14926 | descriptionResourceId = this.core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, short.MaxValue); | ||
14927 | break; | ||
14928 | case "Directory": | ||
14929 | directory = this.core.CreateDirectoryReferenceFromInlineSyntax(sourceLineNumbers, attrib, null); | ||
14930 | break; | ||
14931 | case "DisplayResourceDll": | ||
14932 | displayResourceDll = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
14933 | break; | ||
14934 | case "DisplayResourceId": | ||
14935 | displayResourceId = this.core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, short.MaxValue); | ||
14936 | break; | ||
14937 | case "Hotkey": | ||
14938 | hotkey = this.core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, short.MaxValue); | ||
14939 | break; | ||
14940 | case "Icon": | ||
14941 | icon = this.core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); | ||
14942 | this.core.CreateSimpleReference(sourceLineNumbers, "Icon", icon); | ||
14943 | break; | ||
14944 | case "IconIndex": | ||
14945 | iconIndex = this.core.GetAttributeIntegerValue(sourceLineNumbers, attrib, short.MinValue + 1, short.MaxValue); | ||
14946 | break; | ||
14947 | case "Name": | ||
14948 | name = this.core.GetAttributeLongFilename(sourceLineNumbers, attrib, false); | ||
14949 | break; | ||
14950 | case "ShortName": | ||
14951 | shortName = this.core.GetAttributeShortFilename(sourceLineNumbers, attrib, false); | ||
14952 | break; | ||
14953 | case "Show": | ||
14954 | string showValue = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
14955 | if (showValue.Length == 0) | ||
14956 | { | ||
14957 | show = CompilerConstants.IllegalInteger; | ||
14958 | } | ||
14959 | else | ||
14960 | { | ||
14961 | Wix.Shortcut.ShowType showType = Wix.Shortcut.ParseShowType(showValue); | ||
14962 | switch (showType) | ||
14963 | { | ||
14964 | case Wix.Shortcut.ShowType.normal: | ||
14965 | show = 1; | ||
14966 | break; | ||
14967 | case Wix.Shortcut.ShowType.maximized: | ||
14968 | show = 3; | ||
14969 | break; | ||
14970 | case Wix.Shortcut.ShowType.minimized: | ||
14971 | show = 7; | ||
14972 | break; | ||
14973 | default: | ||
14974 | this.core.OnMessage(WixErrors.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, "Show", showValue, "normal", "maximized", "minimized")); | ||
14975 | show = CompilerConstants.IllegalInteger; | ||
14976 | break; | ||
14977 | } | ||
14978 | } | ||
14979 | break; | ||
14980 | case "Target": | ||
14981 | target = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
14982 | break; | ||
14983 | case "WorkingDirectory": | ||
14984 | workingDirectory = this.core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); | ||
14985 | break; | ||
14986 | default: | ||
14987 | this.core.UnexpectedAttribute(node, attrib); | ||
14988 | break; | ||
14989 | } | ||
14990 | } | ||
14991 | else | ||
14992 | { | ||
14993 | this.core.ParseExtensionAttribute(node, attrib); | ||
14994 | } | ||
14995 | } | ||
14996 | |||
14997 | if (advertise && null != target) | ||
14998 | { | ||
14999 | this.core.OnMessage(WixErrors.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Target", "Advertise", "yes")); | ||
15000 | } | ||
15001 | |||
15002 | if (null == directory) | ||
15003 | { | ||
15004 | if ("Component" == parentElementLocalName) | ||
15005 | { | ||
15006 | directory = defaultTarget; | ||
15007 | } | ||
15008 | else | ||
15009 | { | ||
15010 | this.core.OnMessage(WixErrors.ExpectedAttributeWhenElementNotUnderElement(sourceLineNumbers, node.Name.LocalName, "Directory", "Component")); | ||
15011 | } | ||
15012 | } | ||
15013 | |||
15014 | if (null != descriptionResourceDll) | ||
15015 | { | ||
15016 | if (CompilerConstants.IntegerNotSet == descriptionResourceId) | ||
15017 | { | ||
15018 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "DescriptionResourceDll", "DescriptionResourceId")); | ||
15019 | } | ||
15020 | } | ||
15021 | else | ||
15022 | { | ||
15023 | if (CompilerConstants.IntegerNotSet != descriptionResourceId) | ||
15024 | { | ||
15025 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "DescriptionResourceId", "DescriptionResourceDll")); | ||
15026 | } | ||
15027 | } | ||
15028 | |||
15029 | if (null != displayResourceDll) | ||
15030 | { | ||
15031 | if (CompilerConstants.IntegerNotSet == displayResourceId) | ||
15032 | { | ||
15033 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "DisplayResourceDll", "DisplayResourceId")); | ||
15034 | } | ||
15035 | } | ||
15036 | else | ||
15037 | { | ||
15038 | if (CompilerConstants.IntegerNotSet != displayResourceId) | ||
15039 | { | ||
15040 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "DisplayResourceId", "DisplayResourceDll")); | ||
15041 | } | ||
15042 | } | ||
15043 | |||
15044 | if (null == name) | ||
15045 | { | ||
15046 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name")); | ||
15047 | } | ||
15048 | else if (0 < name.Length) | ||
15049 | { | ||
15050 | if (this.core.IsValidShortFilename(name, false)) | ||
15051 | { | ||
15052 | if (null == shortName) | ||
15053 | { | ||
15054 | shortName = name; | ||
15055 | name = null; | ||
15056 | } | ||
15057 | else | ||
15058 | { | ||
15059 | this.core.OnMessage(WixErrors.IllegalAttributeValueWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Name", name, "ShortName")); | ||
15060 | } | ||
15061 | } | ||
15062 | else if (null == shortName) // generate a short file name. | ||
15063 | { | ||
15064 | shortName = this.core.CreateShortName(name, true, false, node.Name.LocalName, componentId, directory); | ||
15065 | } | ||
15066 | } | ||
15067 | |||
15068 | if ("Component" != parentElementLocalName && null != target) | ||
15069 | { | ||
15070 | this.core.OnMessage(WixErrors.IllegalAttributeWhenNested(sourceLineNumbers, node.Name.LocalName, "Target", parentElementLocalName)); | ||
15071 | } | ||
15072 | |||
15073 | if (null == id) | ||
15074 | { | ||
15075 | id = this.core.CreateIdentifier("sct", directory, LowercaseOrNull(name) ?? LowercaseOrNull(shortName)); | ||
15076 | } | ||
15077 | |||
15078 | foreach (XElement child in node.Elements()) | ||
15079 | { | ||
15080 | if (CompilerCore.WixNamespace == child.Name.Namespace) | ||
15081 | { | ||
15082 | switch (child.Name.LocalName) | ||
15083 | { | ||
15084 | case "Icon": | ||
15085 | icon = this.ParseIconElement(child); | ||
15086 | break; | ||
15087 | case "ShortcutProperty": | ||
15088 | this.ParseShortcutPropertyElement(child, id.Id); | ||
15089 | break; | ||
15090 | default: | ||
15091 | this.core.UnexpectedElement(node, child); | ||
15092 | break; | ||
15093 | } | ||
15094 | } | ||
15095 | else | ||
15096 | { | ||
15097 | this.core.ParseExtensionElement(node, child); | ||
15098 | } | ||
15099 | } | ||
15100 | |||
15101 | if (!this.core.EncounteredError) | ||
15102 | { | ||
15103 | Row row = this.core.CreateRow(sourceLineNumbers, "Shortcut", id); | ||
15104 | row[1] = directory; | ||
15105 | row[2] = GetMsiFilenameValue(shortName, name); | ||
15106 | row[3] = componentId; | ||
15107 | if (advertise) | ||
15108 | { | ||
15109 | if (YesNoType.Yes != parentKeyPath && "Component" != parentElementLocalName) | ||
15110 | { | ||
15111 | this.core.OnMessage(WixWarnings.UnclearShortcut(sourceLineNumbers, id.Id, componentId, defaultTarget)); | ||
15112 | } | ||
15113 | row[4] = Guid.Empty.ToString("B"); | ||
15114 | } | ||
15115 | else if (null != target) | ||
15116 | { | ||
15117 | row[4] = target; | ||
15118 | } | ||
15119 | else if ("Component" == parentElementLocalName || "CreateFolder" == parentElementLocalName) | ||
15120 | { | ||
15121 | row[4] = String.Format(CultureInfo.InvariantCulture, "[{0}]", defaultTarget); | ||
15122 | } | ||
15123 | else if ("File" == parentElementLocalName) | ||
15124 | { | ||
15125 | row[4] = String.Format(CultureInfo.InvariantCulture, "[#{0}]", defaultTarget); | ||
15126 | } | ||
15127 | row[5] = arguments; | ||
15128 | row[6] = description; | ||
15129 | if (CompilerConstants.IntegerNotSet != hotkey) | ||
15130 | { | ||
15131 | row[7] = hotkey; | ||
15132 | } | ||
15133 | row[8] = icon; | ||
15134 | if (CompilerConstants.IntegerNotSet != iconIndex) | ||
15135 | { | ||
15136 | row[9] = iconIndex; | ||
15137 | } | ||
15138 | |||
15139 | if (CompilerConstants.IntegerNotSet != show) | ||
15140 | { | ||
15141 | row[10] = show; | ||
15142 | } | ||
15143 | row[11] = workingDirectory; | ||
15144 | row[12] = displayResourceDll; | ||
15145 | if (CompilerConstants.IntegerNotSet != displayResourceId) | ||
15146 | { | ||
15147 | row[13] = displayResourceId; | ||
15148 | } | ||
15149 | row[14] = descriptionResourceDll; | ||
15150 | if (CompilerConstants.IntegerNotSet != descriptionResourceId) | ||
15151 | { | ||
15152 | row[15] = descriptionResourceId; | ||
15153 | } | ||
15154 | } | ||
15155 | } | ||
15156 | |||
15157 | /// <summary> | ||
15158 | /// Parses a shortcut property element. | ||
15159 | /// </summary> | ||
15160 | /// <param name="node">Element to parse.</param> | ||
15161 | private void ParseShortcutPropertyElement(XElement node, string shortcutId) | ||
15162 | { | ||
15163 | SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
15164 | Identifier id = null; | ||
15165 | string key = null; | ||
15166 | string value = null; | ||
15167 | |||
15168 | foreach (XAttribute attrib in node.Attributes()) | ||
15169 | { | ||
15170 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
15171 | { | ||
15172 | switch (attrib.Name.LocalName) | ||
15173 | { | ||
15174 | case "Id": | ||
15175 | id = this.core.GetAttributeIdentifier(sourceLineNumbers, attrib); | ||
15176 | break; | ||
15177 | case "Key": | ||
15178 | key = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
15179 | break; | ||
15180 | case "Value": | ||
15181 | value = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
15182 | break; | ||
15183 | default: | ||
15184 | this.core.UnexpectedAttribute(node, attrib); | ||
15185 | break; | ||
15186 | } | ||
15187 | } | ||
15188 | else | ||
15189 | { | ||
15190 | this.core.ParseExtensionAttribute(node, attrib); | ||
15191 | } | ||
15192 | } | ||
15193 | |||
15194 | if (String.IsNullOrEmpty(key)) | ||
15195 | { | ||
15196 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Key")); | ||
15197 | } | ||
15198 | else if (null == id) | ||
15199 | { | ||
15200 | id = this.core.CreateIdentifier("scp", shortcutId, key.ToUpperInvariant()); | ||
15201 | } | ||
15202 | |||
15203 | string innerText = this.core.GetTrimmedInnerText(node); | ||
15204 | if (!String.IsNullOrEmpty(innerText)) | ||
15205 | { | ||
15206 | if (String.IsNullOrEmpty(value)) | ||
15207 | { | ||
15208 | value = innerText; | ||
15209 | } | ||
15210 | else // cannot specify both the value attribute and inner text | ||
15211 | { | ||
15212 | this.core.OnMessage(WixErrors.IllegalAttributeWithInnerText(sourceLineNumbers, node.Name.LocalName, "Value")); | ||
15213 | } | ||
15214 | } | ||
15215 | |||
15216 | if (String.IsNullOrEmpty(value)) | ||
15217 | { | ||
15218 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Value")); | ||
15219 | } | ||
15220 | |||
15221 | this.core.ParseForExtensionElements(node); | ||
15222 | |||
15223 | if (!this.core.EncounteredError) | ||
15224 | { | ||
15225 | Row row = this.core.CreateRow(sourceLineNumbers, "MsiShortcutProperty", id); | ||
15226 | row[1] = shortcutId; | ||
15227 | row[2] = key; | ||
15228 | row[3] = value; | ||
15229 | } | ||
15230 | } | ||
15231 | |||
15232 | /// <summary> | ||
15233 | /// Parses a typelib element. | ||
15234 | /// </summary> | ||
15235 | /// <param name="node">Element to parse.</param> | ||
15236 | /// <param name="componentId">Identifier of parent component.</param> | ||
15237 | /// <param name="fileServer">Identifier of file that acts as typelib server.</param> | ||
15238 | /// <param name="win64Component">true if the component is 64-bit.</param> | ||
15239 | private void ParseTypeLibElement(XElement node, string componentId, string fileServer, bool win64Component) | ||
15240 | { | ||
15241 | SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
15242 | string id = null; | ||
15243 | YesNoType advertise = YesNoType.NotSet; | ||
15244 | int cost = CompilerConstants.IntegerNotSet; | ||
15245 | string description = null; | ||
15246 | int flags = 0; | ||
15247 | string helpDirectory = null; | ||
15248 | int language = CompilerConstants.IntegerNotSet; | ||
15249 | int majorVersion = CompilerConstants.IntegerNotSet; | ||
15250 | int minorVersion = CompilerConstants.IntegerNotSet; | ||
15251 | long resourceId = CompilerConstants.LongNotSet; | ||
15252 | |||
15253 | foreach (XAttribute attrib in node.Attributes()) | ||
15254 | { | ||
15255 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
15256 | { | ||
15257 | switch (attrib.Name.LocalName) | ||
15258 | { | ||
15259 | case "Id": | ||
15260 | id = this.core.GetAttributeGuidValue(sourceLineNumbers, attrib, false); | ||
15261 | break; | ||
15262 | case "Advertise": | ||
15263 | advertise = this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
15264 | break; | ||
15265 | case "Control": | ||
15266 | if (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) | ||
15267 | { | ||
15268 | flags |= 2; | ||
15269 | } | ||
15270 | break; | ||
15271 | case "Cost": | ||
15272 | cost = this.core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, int.MaxValue); | ||
15273 | break; | ||
15274 | case "Description": | ||
15275 | description = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
15276 | break; | ||
15277 | case "HasDiskImage": | ||
15278 | if (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) | ||
15279 | { | ||
15280 | flags |= 8; | ||
15281 | } | ||
15282 | break; | ||
15283 | case "HelpDirectory": | ||
15284 | helpDirectory = this.core.CreateDirectoryReferenceFromInlineSyntax(sourceLineNumbers, attrib, null); | ||
15285 | break; | ||
15286 | case "Hidden": | ||
15287 | if (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) | ||
15288 | { | ||
15289 | flags |= 4; | ||
15290 | } | ||
15291 | break; | ||
15292 | case "Language": | ||
15293 | language = this.core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, short.MaxValue); | ||
15294 | break; | ||
15295 | case "MajorVersion": | ||
15296 | majorVersion = this.core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, ushort.MaxValue); | ||
15297 | break; | ||
15298 | case "MinorVersion": | ||
15299 | minorVersion = this.core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, byte.MaxValue); | ||
15300 | break; | ||
15301 | case "ResourceId": | ||
15302 | resourceId = this.core.GetAttributeLongValue(sourceLineNumbers, attrib, int.MinValue, int.MaxValue); | ||
15303 | break; | ||
15304 | case "Restricted": | ||
15305 | if (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) | ||
15306 | { | ||
15307 | flags |= 1; | ||
15308 | } | ||
15309 | break; | ||
15310 | default: | ||
15311 | this.core.UnexpectedAttribute(node, attrib); | ||
15312 | break; | ||
15313 | } | ||
15314 | } | ||
15315 | else | ||
15316 | { | ||
15317 | this.core.ParseExtensionAttribute(node, attrib); | ||
15318 | } | ||
15319 | } | ||
15320 | |||
15321 | if (null == id) | ||
15322 | { | ||
15323 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); | ||
15324 | } | ||
15325 | |||
15326 | if (CompilerConstants.IntegerNotSet == language) | ||
15327 | { | ||
15328 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Language")); | ||
15329 | language = CompilerConstants.IllegalInteger; | ||
15330 | } | ||
15331 | |||
15332 | // build up the typelib version string for the registry if the major or minor version was specified | ||
15333 | string registryVersion = null; | ||
15334 | if (CompilerConstants.IntegerNotSet != majorVersion || CompilerConstants.IntegerNotSet != minorVersion) | ||
15335 | { | ||
15336 | if (CompilerConstants.IntegerNotSet != majorVersion) | ||
15337 | { | ||
15338 | registryVersion = majorVersion.ToString("x", CultureInfo.InvariantCulture.NumberFormat); | ||
15339 | } | ||
15340 | else | ||
15341 | { | ||
15342 | registryVersion = "0"; | ||
15343 | } | ||
15344 | |||
15345 | if (CompilerConstants.IntegerNotSet != minorVersion) | ||
15346 | { | ||
15347 | registryVersion = String.Concat(registryVersion, ".", minorVersion.ToString("x", CultureInfo.InvariantCulture.NumberFormat)); | ||
15348 | } | ||
15349 | else | ||
15350 | { | ||
15351 | registryVersion = String.Concat(registryVersion, ".0"); | ||
15352 | } | ||
15353 | } | ||
15354 | |||
15355 | // if the advertise state has not been set, default to non-advertised | ||
15356 | if (YesNoType.NotSet == advertise) | ||
15357 | { | ||
15358 | advertise = YesNoType.No; | ||
15359 | } | ||
15360 | |||
15361 | foreach (XElement child in node.Elements()) | ||
15362 | { | ||
15363 | if (CompilerCore.WixNamespace == child.Name.Namespace) | ||
15364 | { | ||
15365 | switch (child.Name.LocalName) | ||
15366 | { | ||
15367 | case "AppId": | ||
15368 | this.ParseAppIdElement(child, componentId, YesNoType.NotSet, fileServer, id, registryVersion); | ||
15369 | break; | ||
15370 | case "Class": | ||
15371 | this.ParseClassElement(child, componentId, YesNoType.NotSet, fileServer, id, registryVersion, null); | ||
15372 | break; | ||
15373 | case "Interface": | ||
15374 | this.ParseInterfaceElement(child, componentId, null, null, id, registryVersion); | ||
15375 | break; | ||
15376 | default: | ||
15377 | this.core.UnexpectedElement(node, child); | ||
15378 | break; | ||
15379 | } | ||
15380 | } | ||
15381 | else | ||
15382 | { | ||
15383 | this.core.ParseExtensionElement(node, child); | ||
15384 | } | ||
15385 | } | ||
15386 | |||
15387 | |||
15388 | if (YesNoType.Yes == advertise) | ||
15389 | { | ||
15390 | if (CompilerConstants.LongNotSet != resourceId) | ||
15391 | { | ||
15392 | this.core.OnMessage(WixErrors.IllegalAttributeWhenAdvertised(sourceLineNumbers, node.Name.LocalName, "ResourceId")); | ||
15393 | } | ||
15394 | |||
15395 | if (0 != flags) | ||
15396 | { | ||
15397 | if (0x1 == (flags & 0x1)) | ||
15398 | { | ||
15399 | this.core.OnMessage(WixErrors.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Restricted", "Advertise", "yes")); | ||
15400 | } | ||
15401 | |||
15402 | if (0x2 == (flags & 0x2)) | ||
15403 | { | ||
15404 | this.core.OnMessage(WixErrors.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Control", "Advertise", "yes")); | ||
15405 | } | ||
15406 | |||
15407 | if (0x4 == (flags & 0x4)) | ||
15408 | { | ||
15409 | this.core.OnMessage(WixErrors.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Hidden", "Advertise", "yes")); | ||
15410 | } | ||
15411 | |||
15412 | if (0x8 == (flags & 0x8)) | ||
15413 | { | ||
15414 | this.core.OnMessage(WixErrors.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "HasDiskImage", "Advertise", "yes")); | ||
15415 | } | ||
15416 | } | ||
15417 | |||
15418 | if (!this.core.EncounteredError) | ||
15419 | { | ||
15420 | Row row = this.core.CreateRow(sourceLineNumbers, "TypeLib"); | ||
15421 | row[0] = id; | ||
15422 | row[1] = language; | ||
15423 | row[2] = componentId; | ||
15424 | if (CompilerConstants.IntegerNotSet != majorVersion || CompilerConstants.IntegerNotSet != minorVersion) | ||
15425 | { | ||
15426 | row[3] = (CompilerConstants.IntegerNotSet != majorVersion ? majorVersion * 256 : 0) + (CompilerConstants.IntegerNotSet != minorVersion ? minorVersion : 0); | ||
15427 | } | ||
15428 | row[4] = description; | ||
15429 | row[5] = helpDirectory; | ||
15430 | row[6] = Guid.Empty.ToString("B"); | ||
15431 | if (CompilerConstants.IntegerNotSet != cost) | ||
15432 | { | ||
15433 | row[7] = cost; | ||
15434 | } | ||
15435 | } | ||
15436 | } | ||
15437 | else if (YesNoType.No == advertise) | ||
15438 | { | ||
15439 | if (CompilerConstants.IntegerNotSet != cost && CompilerConstants.IllegalInteger != cost) | ||
15440 | { | ||
15441 | this.core.OnMessage(WixErrors.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Cost", "Advertise", "no")); | ||
15442 | } | ||
15443 | |||
15444 | if (null == fileServer) | ||
15445 | { | ||
15446 | this.core.OnMessage(WixErrors.MissingTypeLibFile(sourceLineNumbers, node.Name.LocalName, "File")); | ||
15447 | } | ||
15448 | |||
15449 | if (null == registryVersion) | ||
15450 | { | ||
15451 | this.core.OnMessage(WixErrors.ExpectedAttributesWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "MajorVersion", "MinorVersion", "Advertise", "no")); | ||
15452 | } | ||
15453 | |||
15454 | // HKCR\TypeLib\[ID]\[MajorVersion].[MinorVersion], (Default) = [Description] | ||
15455 | this.core.CreateRegistryRow(sourceLineNumbers, MsiInterop.MsidbRegistryRootClassesRoot, String.Format(CultureInfo.InvariantCulture, @"TypeLib\{0}\{1}", id, registryVersion), null, description, componentId); | ||
15456 | |||
15457 | // HKCR\TypeLib\[ID]\[MajorVersion].[MinorVersion]\[Language]\[win16|win32|win64], (Default) = [TypeLibPath]\[ResourceId] | ||
15458 | string path = String.Concat("[#", fileServer, "]"); | ||
15459 | if (CompilerConstants.LongNotSet != resourceId) | ||
15460 | { | ||
15461 | path = String.Concat(path, Path.DirectorySeparatorChar, resourceId.ToString(CultureInfo.InvariantCulture.NumberFormat)); | ||
15462 | } | ||
15463 | this.core.CreateRegistryRow(sourceLineNumbers, MsiInterop.MsidbRegistryRootClassesRoot, String.Format(CultureInfo.InvariantCulture, @"TypeLib\{0}\{1}\{2}\{3}", id, registryVersion, language, (win64Component ? "win64" : "win32")), null, path, componentId); | ||
15464 | |||
15465 | // HKCR\TypeLib\[ID]\[MajorVersion].[MinorVersion]\FLAGS, (Default) = [TypeLibFlags] | ||
15466 | this.core.CreateRegistryRow(sourceLineNumbers, MsiInterop.MsidbRegistryRootClassesRoot, String.Format(CultureInfo.InvariantCulture, @"TypeLib\{0}\{1}\FLAGS", id, registryVersion), null, flags.ToString(CultureInfo.InvariantCulture.NumberFormat), componentId); | ||
15467 | |||
15468 | if (null != helpDirectory) | ||
15469 | { | ||
15470 | // HKCR\TypeLib\[ID]\[MajorVersion].[MinorVersion]\HELPDIR, (Default) = [HelpDirectory] | ||
15471 | this.core.CreateRegistryRow(sourceLineNumbers, MsiInterop.MsidbRegistryRootClassesRoot, String.Format(CultureInfo.InvariantCulture, @"TypeLib\{0}\{1}\HELPDIR", id, registryVersion), null, String.Concat("[", helpDirectory, "]"), componentId); | ||
15472 | } | ||
15473 | } | ||
15474 | } | ||
15475 | |||
15476 | /// <summary> | ||
15477 | /// Parses an EmbeddedChaniner element. | ||
15478 | /// </summary> | ||
15479 | /// <param name="node">Element to parse.</param> | ||
15480 | private void ParseEmbeddedChainerElement(XElement node) | ||
15481 | { | ||
15482 | SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
15483 | Identifier id = null; | ||
15484 | string commandLine = null; | ||
15485 | string condition = null; | ||
15486 | string source = null; | ||
15487 | int type = 0; | ||
15488 | |||
15489 | foreach (XAttribute attrib in node.Attributes()) | ||
15490 | { | ||
15491 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
15492 | { | ||
15493 | switch (attrib.Name.LocalName) | ||
15494 | { | ||
15495 | case "Id": | ||
15496 | id = this.core.GetAttributeIdentifier(sourceLineNumbers, attrib); | ||
15497 | break; | ||
15498 | case "BinarySource": | ||
15499 | if (null != source) | ||
15500 | { | ||
15501 | this.core.OnMessage(WixErrors.IllegalAttributeWithOtherAttributes(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "FileSource", "PropertySource")); | ||
15502 | } | ||
15503 | source = this.core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); | ||
15504 | type = MsiInterop.MsidbCustomActionTypeExe + MsiInterop.MsidbCustomActionTypeBinaryData; | ||
15505 | this.core.CreateSimpleReference(sourceLineNumbers, "Binary", source); // add a reference to the appropriate Binary | ||
15506 | break; | ||
15507 | case "CommandLine": | ||
15508 | commandLine = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
15509 | break; | ||
15510 | case "FileSource": | ||
15511 | if (null != source) | ||
15512 | { | ||
15513 | this.core.OnMessage(WixErrors.IllegalAttributeWithOtherAttributes(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "BinarySource", "PropertySource")); | ||
15514 | } | ||
15515 | source = this.core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); | ||
15516 | type = MsiInterop.MsidbCustomActionTypeExe + MsiInterop.MsidbCustomActionTypeSourceFile; | ||
15517 | this.core.CreateSimpleReference(sourceLineNumbers, "File", source); // add a reference to the appropriate File | ||
15518 | break; | ||
15519 | case "PropertySource": | ||
15520 | if (null != source) | ||
15521 | { | ||
15522 | this.core.OnMessage(WixErrors.IllegalAttributeWithOtherAttributes(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "BinarySource", "FileSource")); | ||
15523 | } | ||
15524 | source = this.core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); | ||
15525 | type = MsiInterop.MsidbCustomActionTypeExe + MsiInterop.MsidbCustomActionTypeProperty; | ||
15526 | // cannot add a reference to a Property because it may be created at runtime. | ||
15527 | break; | ||
15528 | default: | ||
15529 | this.core.UnexpectedAttribute(node, attrib); | ||
15530 | break; | ||
15531 | } | ||
15532 | } | ||
15533 | else | ||
15534 | { | ||
15535 | this.core.ParseExtensionAttribute(node, attrib); | ||
15536 | } | ||
15537 | } | ||
15538 | |||
15539 | // Get the condition from the inner text of the element. | ||
15540 | condition = this.core.GetConditionInnerText(node); | ||
15541 | |||
15542 | if (null == id) | ||
15543 | { | ||
15544 | id = this.core.CreateIdentifier("mec", source, type.ToString()); | ||
15545 | } | ||
15546 | |||
15547 | if (null == source) | ||
15548 | { | ||
15549 | this.core.OnMessage(WixErrors.ExpectedAttributes(sourceLineNumbers, node.Name.LocalName, "BinarySource", "FileSource", "PropertySource")); | ||
15550 | } | ||
15551 | |||
15552 | if (!this.core.EncounteredError) | ||
15553 | { | ||
15554 | Row row = this.core.CreateRow(sourceLineNumbers, "MsiEmbeddedChainer", id); | ||
15555 | row[1] = condition; | ||
15556 | row[2] = commandLine; | ||
15557 | row[3] = source; | ||
15558 | row[4] = type; | ||
15559 | } | ||
15560 | } | ||
15561 | |||
15562 | /// <summary> | ||
15563 | /// Parses UI elements. | ||
15564 | /// </summary> | ||
15565 | /// <param name="node">Element to parse.</param> | ||
15566 | private void ParseUIElement(XElement node) | ||
15567 | { | ||
15568 | SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
15569 | Identifier id = null; | ||
15570 | int embeddedUICount = 0; | ||
15571 | |||
15572 | foreach (XAttribute attrib in node.Attributes()) | ||
15573 | { | ||
15574 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
15575 | { | ||
15576 | switch (attrib.Name.LocalName) | ||
15577 | { | ||
15578 | case "Id": | ||
15579 | id = this.core.GetAttributeIdentifier(sourceLineNumbers, attrib); | ||
15580 | break; | ||
15581 | default: | ||
15582 | this.core.UnexpectedAttribute(node, attrib); | ||
15583 | break; | ||
15584 | } | ||
15585 | } | ||
15586 | else | ||
15587 | { | ||
15588 | this.core.ParseExtensionAttribute(node, attrib); | ||
15589 | } | ||
15590 | } | ||
15591 | |||
15592 | foreach (XElement child in node.Elements()) | ||
15593 | { | ||
15594 | if (CompilerCore.WixNamespace == child.Name.Namespace) | ||
15595 | { | ||
15596 | switch (child.Name.LocalName) | ||
15597 | { | ||
15598 | case "BillboardAction": | ||
15599 | this.ParseBillboardActionElement(child); | ||
15600 | break; | ||
15601 | case "ComboBox": | ||
15602 | this.ParseControlGroupElement(child, this.tableDefinitions["ComboBox"], "ListItem"); | ||
15603 | break; | ||
15604 | case "Dialog": | ||
15605 | this.ParseDialogElement(child); | ||
15606 | break; | ||
15607 | case "DialogRef": | ||
15608 | this.ParseSimpleRefElement(child, "Dialog"); | ||
15609 | break; | ||
15610 | case "EmbeddedUI": | ||
15611 | if (0 < embeddedUICount) // there can be only one embedded UI | ||
15612 | { | ||
15613 | SourceLineNumber childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child); | ||
15614 | this.core.OnMessage(WixErrors.TooManyChildren(childSourceLineNumbers, node.Name.LocalName, child.Name.LocalName)); | ||
15615 | } | ||
15616 | this.ParseEmbeddedUIElement(child); | ||
15617 | ++embeddedUICount; | ||
15618 | break; | ||
15619 | case "Error": | ||
15620 | this.ParseErrorElement(child); | ||
15621 | break; | ||
15622 | case "ListBox": | ||
15623 | this.ParseControlGroupElement(child, this.tableDefinitions["ListBox"], "ListItem"); | ||
15624 | break; | ||
15625 | case "ListView": | ||
15626 | this.ParseControlGroupElement(child, this.tableDefinitions["ListView"], "ListItem"); | ||
15627 | break; | ||
15628 | case "ProgressText": | ||
15629 | this.ParseActionTextElement(child); | ||
15630 | break; | ||
15631 | case "Publish": | ||
15632 | int order = 0; | ||
15633 | this.ParsePublishElement(child, null, null, ref order); | ||
15634 | break; | ||
15635 | case "RadioButtonGroup": | ||
15636 | RadioButtonType radioButtonType = this.ParseRadioButtonGroupElement(child, null, RadioButtonType.NotSet); | ||
15637 | if (RadioButtonType.Bitmap == radioButtonType || RadioButtonType.Icon == radioButtonType) | ||
15638 | { | ||
15639 | SourceLineNumber childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child); | ||
15640 | this.core.OnMessage(WixErrors.RadioButtonBitmapAndIconDisallowed(childSourceLineNumbers)); | ||
15641 | } | ||
15642 | break; | ||
15643 | case "TextStyle": | ||
15644 | this.ParseTextStyleElement(child); | ||
15645 | break; | ||
15646 | case "UIText": | ||
15647 | this.ParseUITextElement(child); | ||
15648 | break; | ||
15649 | |||
15650 | // the following are available indentically under the UI and Product elements for document organization use only | ||
15651 | case "AdminUISequence": | ||
15652 | case "InstallUISequence": | ||
15653 | this.ParseSequenceElement(child, child.Name.LocalName); | ||
15654 | break; | ||
15655 | case "Binary": | ||
15656 | this.ParseBinaryElement(child); | ||
15657 | break; | ||
15658 | case "Property": | ||
15659 | this.ParsePropertyElement(child); | ||
15660 | break; | ||
15661 | case "PropertyRef": | ||
15662 | this.ParseSimpleRefElement(child, "Property"); | ||
15663 | break; | ||
15664 | case "UIRef": | ||
15665 | this.ParseSimpleRefElement(child, "WixUI"); | ||
15666 | break; | ||
15667 | |||
15668 | default: | ||
15669 | this.core.UnexpectedElement(node, child); | ||
15670 | break; | ||
15671 | } | ||
15672 | } | ||
15673 | else | ||
15674 | { | ||
15675 | this.core.ParseExtensionElement(node, child); | ||
15676 | } | ||
15677 | } | ||
15678 | |||
15679 | if (null != id && !this.core.EncounteredError) | ||
15680 | { | ||
15681 | this.core.CreateRow(sourceLineNumbers, "WixUI", id); | ||
15682 | } | ||
15683 | } | ||
15684 | |||
15685 | /// <summary> | ||
15686 | /// Parses a list item element. | ||
15687 | /// </summary> | ||
15688 | /// <param name="node">Element to parse.</param> | ||
15689 | /// <param name="table">Table to add row to.</param> | ||
15690 | /// <param name="property">Identifier of property referred to by list item.</param> | ||
15691 | /// <param name="order">Relative order of list items.</param> | ||
15692 | private void ParseListItemElement(XElement node, TableDefinition table, string property, ref int order) | ||
15693 | { | ||
15694 | SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
15695 | string icon = null; | ||
15696 | string text = null; | ||
15697 | string value = null; | ||
15698 | |||
15699 | foreach (XAttribute attrib in node.Attributes()) | ||
15700 | { | ||
15701 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
15702 | { | ||
15703 | switch (attrib.Name.LocalName) | ||
15704 | { | ||
15705 | case "Icon": | ||
15706 | if ("ListView" == table.Name) | ||
15707 | { | ||
15708 | icon = this.core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); | ||
15709 | this.core.CreateSimpleReference(sourceLineNumbers, "Binary", icon); | ||
15710 | } | ||
15711 | else | ||
15712 | { | ||
15713 | this.core.OnMessage(WixErrors.IllegalAttributeExceptOnElement(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "ListView")); | ||
15714 | } | ||
15715 | break; | ||
15716 | case "Text": | ||
15717 | text = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
15718 | break; | ||
15719 | case "Value": | ||
15720 | value = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
15721 | break; | ||
15722 | default: | ||
15723 | this.core.UnexpectedAttribute(node, attrib); | ||
15724 | break; | ||
15725 | } | ||
15726 | } | ||
15727 | else | ||
15728 | { | ||
15729 | this.core.ParseExtensionAttribute(node, attrib); | ||
15730 | } | ||
15731 | } | ||
15732 | |||
15733 | if (null == value) | ||
15734 | { | ||
15735 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Value")); | ||
15736 | } | ||
15737 | |||
15738 | this.core.ParseForExtensionElements(node); | ||
15739 | |||
15740 | if (!this.core.EncounteredError) | ||
15741 | { | ||
15742 | Row row = this.core.CreateRow(sourceLineNumbers, table.Name); | ||
15743 | row[0] = property; | ||
15744 | row[1] = ++order; | ||
15745 | row[2] = value; | ||
15746 | row[3] = text; | ||
15747 | if (null != icon) | ||
15748 | { | ||
15749 | row[4] = icon; | ||
15750 | } | ||
15751 | } | ||
15752 | } | ||
15753 | |||
15754 | /// <summary> | ||
15755 | /// Parses a radio button element. | ||
15756 | /// </summary> | ||
15757 | /// <param name="node">Element to parse.</param> | ||
15758 | /// <param name="property">Identifier of property referred to by radio button.</param> | ||
15759 | /// <param name="order">Relative order of radio buttons.</param> | ||
15760 | /// <returns>Type of this radio button.</returns> | ||
15761 | private RadioButtonType ParseRadioButtonElement(XElement node, string property, ref int order) | ||
15762 | { | ||
15763 | SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
15764 | RadioButtonType type = RadioButtonType.NotSet; | ||
15765 | string value = null; | ||
15766 | string x = null; | ||
15767 | string y = null; | ||
15768 | string width = null; | ||
15769 | string height = null; | ||
15770 | string text = null; | ||
15771 | string tooltip = null; | ||
15772 | string help = null; | ||
15773 | |||
15774 | foreach (XAttribute attrib in node.Attributes()) | ||
15775 | { | ||
15776 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
15777 | { | ||
15778 | switch (attrib.Name.LocalName) | ||
15779 | { | ||
15780 | case "Bitmap": | ||
15781 | if (RadioButtonType.NotSet != type) | ||
15782 | { | ||
15783 | this.core.OnMessage(WixErrors.IllegalAttributeWithOtherAttributes(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "Icon", "Text")); | ||
15784 | } | ||
15785 | text = this.core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); | ||
15786 | this.core.CreateSimpleReference(sourceLineNumbers, "Binary", text); | ||
15787 | type = RadioButtonType.Bitmap; | ||
15788 | break; | ||
15789 | case "Height": | ||
15790 | height = this.core.GetAttributeLocalizableIntegerValue(sourceLineNumbers, attrib, 0, short.MaxValue); | ||
15791 | break; | ||
15792 | case "Help": | ||
15793 | help = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
15794 | break; | ||
15795 | case "Icon": | ||
15796 | if (RadioButtonType.NotSet != type) | ||
15797 | { | ||
15798 | this.core.OnMessage(WixErrors.IllegalAttributeWithOtherAttributes(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "Bitmap", "Text")); | ||
15799 | } | ||
15800 | text = this.core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); | ||
15801 | this.core.CreateSimpleReference(sourceLineNumbers, "Binary", text); | ||
15802 | type = RadioButtonType.Icon; | ||
15803 | break; | ||
15804 | case "Text": | ||
15805 | if (RadioButtonType.NotSet != type) | ||
15806 | { | ||
15807 | this.core.OnMessage(WixErrors.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "Bitmap", "Icon")); | ||
15808 | } | ||
15809 | text = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
15810 | type = RadioButtonType.Text; | ||
15811 | break; | ||
15812 | case "ToolTip": | ||
15813 | tooltip = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
15814 | break; | ||
15815 | case "Value": | ||
15816 | value = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
15817 | break; | ||
15818 | case "Width": | ||
15819 | width = this.core.GetAttributeLocalizableIntegerValue(sourceLineNumbers, attrib, 0, short.MaxValue); | ||
15820 | break; | ||
15821 | case "X": | ||
15822 | x = this.core.GetAttributeLocalizableIntegerValue(sourceLineNumbers, attrib, 0, short.MaxValue); | ||
15823 | break; | ||
15824 | case "Y": | ||
15825 | y = this.core.GetAttributeLocalizableIntegerValue(sourceLineNumbers, attrib, 0, short.MaxValue); | ||
15826 | break; | ||
15827 | default: | ||
15828 | this.core.UnexpectedAttribute(node, attrib); | ||
15829 | break; | ||
15830 | } | ||
15831 | } | ||
15832 | else | ||
15833 | { | ||
15834 | this.core.ParseExtensionAttribute(node, attrib); | ||
15835 | } | ||
15836 | } | ||
15837 | |||
15838 | if (null == value) | ||
15839 | { | ||
15840 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Value")); | ||
15841 | } | ||
15842 | |||
15843 | if (null == x) | ||
15844 | { | ||
15845 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "X")); | ||
15846 | } | ||
15847 | |||
15848 | if (null == y) | ||
15849 | { | ||
15850 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Y")); | ||
15851 | } | ||
15852 | |||
15853 | if (null == width) | ||
15854 | { | ||
15855 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Width")); | ||
15856 | } | ||
15857 | |||
15858 | if (null == height) | ||
15859 | { | ||
15860 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Height")); | ||
15861 | } | ||
15862 | |||
15863 | this.core.ParseForExtensionElements(node); | ||
15864 | |||
15865 | if (!this.core.EncounteredError) | ||
15866 | { | ||
15867 | Row row = this.core.CreateRow(sourceLineNumbers, "RadioButton"); | ||
15868 | row[0] = property; | ||
15869 | row[1] = ++order; | ||
15870 | row[2] = value; | ||
15871 | row[3] = x; | ||
15872 | row[4] = y; | ||
15873 | row[5] = width; | ||
15874 | row[6] = height; | ||
15875 | row[7] = text; | ||
15876 | if (null != tooltip || null != help) | ||
15877 | { | ||
15878 | row[8] = String.Concat(tooltip, "|", help); | ||
15879 | } | ||
15880 | } | ||
15881 | |||
15882 | return type; | ||
15883 | } | ||
15884 | |||
15885 | /// <summary> | ||
15886 | /// Parses a billboard element. | ||
15887 | /// </summary> | ||
15888 | /// <param name="node">Element to parse.</param> | ||
15889 | private void ParseBillboardActionElement(XElement node) | ||
15890 | { | ||
15891 | SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
15892 | string action = null; | ||
15893 | int order = 0; | ||
15894 | |||
15895 | foreach (XAttribute attrib in node.Attributes()) | ||
15896 | { | ||
15897 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
15898 | { | ||
15899 | switch (attrib.Name.LocalName) | ||
15900 | { | ||
15901 | case "Id": | ||
15902 | action = this.core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); | ||
15903 | this.core.CreateSimpleReference(sourceLineNumbers, "WixAction", "InstallExecuteSequence", action); | ||
15904 | break; | ||
15905 | default: | ||
15906 | this.core.UnexpectedAttribute(node, attrib); | ||
15907 | break; | ||
15908 | } | ||
15909 | } | ||
15910 | else | ||
15911 | { | ||
15912 | this.core.ParseExtensionAttribute(node, attrib); | ||
15913 | } | ||
15914 | } | ||
15915 | |||
15916 | if (null == action) | ||
15917 | { | ||
15918 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); | ||
15919 | } | ||
15920 | |||
15921 | foreach (XElement child in node.Elements()) | ||
15922 | { | ||
15923 | if (CompilerCore.WixNamespace == child.Name.Namespace) | ||
15924 | { | ||
15925 | switch (child.Name.LocalName) | ||
15926 | { | ||
15927 | case "Billboard": | ||
15928 | order = order + 1; | ||
15929 | this.ParseBillboardElement(child, action, order); | ||
15930 | break; | ||
15931 | default: | ||
15932 | this.core.UnexpectedElement(node, child); | ||
15933 | break; | ||
15934 | } | ||
15935 | } | ||
15936 | else | ||
15937 | { | ||
15938 | this.core.ParseExtensionElement(node, child); | ||
15939 | } | ||
15940 | } | ||
15941 | } | ||
15942 | |||
15943 | /// <summary> | ||
15944 | /// Parses a billboard element. | ||
15945 | /// </summary> | ||
15946 | /// <param name="node">Element to parse.</param> | ||
15947 | /// <param name="action">Action for the billboard.</param> | ||
15948 | /// <param name="order">Order of the billboard.</param> | ||
15949 | private void ParseBillboardElement(XElement node, string action, int order) | ||
15950 | { | ||
15951 | SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
15952 | Identifier id = null; | ||
15953 | string feature = null; | ||
15954 | |||
15955 | foreach (XAttribute attrib in node.Attributes()) | ||
15956 | { | ||
15957 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
15958 | { | ||
15959 | switch (attrib.Name.LocalName) | ||
15960 | { | ||
15961 | case "Id": | ||
15962 | id = this.core.GetAttributeIdentifier(sourceLineNumbers, attrib); | ||
15963 | break; | ||
15964 | case "Feature": | ||
15965 | feature = this.core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); | ||
15966 | this.core.CreateSimpleReference(sourceLineNumbers, "Feature", feature); | ||
15967 | break; | ||
15968 | default: | ||
15969 | this.core.UnexpectedAttribute(node, attrib); | ||
15970 | break; | ||
15971 | } | ||
15972 | } | ||
15973 | else | ||
15974 | { | ||
15975 | this.core.ParseExtensionAttribute(node, attrib); | ||
15976 | } | ||
15977 | } | ||
15978 | |||
15979 | if (null == id) | ||
15980 | { | ||
15981 | id = this.core.CreateIdentifier("bil", action, order.ToString(), feature); | ||
15982 | } | ||
15983 | |||
15984 | foreach (XElement child in node.Elements()) | ||
15985 | { | ||
15986 | if (CompilerCore.WixNamespace == child.Name.Namespace) | ||
15987 | { | ||
15988 | switch (child.Name.LocalName) | ||
15989 | { | ||
15990 | case "Control": | ||
15991 | // These are all thrown away. | ||
15992 | Row lastTabRow = null; | ||
15993 | string firstControl = null; | ||
15994 | string defaultControl = null; | ||
15995 | string cancelControl = null; | ||
15996 | |||
15997 | this.ParseControlElement(child, id.Id, this.tableDefinitions["BBControl"], ref lastTabRow, ref firstControl, ref defaultControl, ref cancelControl, false); | ||
15998 | break; | ||
15999 | default: | ||
16000 | this.core.UnexpectedElement(node, child); | ||
16001 | break; | ||
16002 | } | ||
16003 | } | ||
16004 | else | ||
16005 | { | ||
16006 | this.core.ParseExtensionElement(node, child); | ||
16007 | } | ||
16008 | } | ||
16009 | |||
16010 | |||
16011 | if (!this.core.EncounteredError) | ||
16012 | { | ||
16013 | Row row = this.core.CreateRow(sourceLineNumbers, "Billboard", id); | ||
16014 | row[1] = feature; | ||
16015 | row[2] = action; | ||
16016 | row[3] = order; | ||
16017 | } | ||
16018 | } | ||
16019 | |||
16020 | /// <summary> | ||
16021 | /// Parses a control group element. | ||
16022 | /// </summary> | ||
16023 | /// <param name="node">Element to parse.</param> | ||
16024 | /// <param name="table">Table referred to by control group.</param> | ||
16025 | /// <param name="childTag">Expected child elements.</param> | ||
16026 | private void ParseControlGroupElement(XElement node, TableDefinition table, string childTag) | ||
16027 | { | ||
16028 | SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
16029 | int order = 0; | ||
16030 | string property = null; | ||
16031 | |||
16032 | foreach (XAttribute attrib in node.Attributes()) | ||
16033 | { | ||
16034 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
16035 | { | ||
16036 | switch (attrib.Name.LocalName) | ||
16037 | { | ||
16038 | case "Property": | ||
16039 | property = this.core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); | ||
16040 | break; | ||
16041 | default: | ||
16042 | this.core.UnexpectedAttribute(node, attrib); | ||
16043 | break; | ||
16044 | } | ||
16045 | } | ||
16046 | else | ||
16047 | { | ||
16048 | this.core.ParseExtensionAttribute(node, attrib); | ||
16049 | } | ||
16050 | } | ||
16051 | |||
16052 | if (null == property) | ||
16053 | { | ||
16054 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Property")); | ||
16055 | } | ||
16056 | |||
16057 | foreach (XElement child in node.Elements()) | ||
16058 | { | ||
16059 | if (CompilerCore.WixNamespace == child.Name.Namespace) | ||
16060 | { | ||
16061 | if (childTag != child.Name.LocalName) | ||
16062 | { | ||
16063 | this.core.UnexpectedElement(node, child); | ||
16064 | } | ||
16065 | |||
16066 | switch (child.Name.LocalName) | ||
16067 | { | ||
16068 | case "ListItem": | ||
16069 | this.ParseListItemElement(child, table, property, ref order); | ||
16070 | break; | ||
16071 | case "Property": | ||
16072 | this.ParsePropertyElement(child); | ||
16073 | break; | ||
16074 | default: | ||
16075 | this.core.UnexpectedElement(node, child); | ||
16076 | break; | ||
16077 | } | ||
16078 | } | ||
16079 | else | ||
16080 | { | ||
16081 | this.core.ParseExtensionElement(node, child); | ||
16082 | } | ||
16083 | } | ||
16084 | |||
16085 | } | ||
16086 | |||
16087 | /// <summary> | ||
16088 | /// Parses a radio button control group element. | ||
16089 | /// </summary> | ||
16090 | /// <param name="node">Element to parse.</param> | ||
16091 | /// <param name="property">Property associated with this radio button group.</param> | ||
16092 | /// <param name="groupType">Specifies the current type of radio buttons in the group.</param> | ||
16093 | /// <returns>The current type of radio buttons in the group.</returns> | ||
16094 | private RadioButtonType ParseRadioButtonGroupElement(XElement node, string property, RadioButtonType groupType) | ||
16095 | { | ||
16096 | SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
16097 | int order = 0; | ||
16098 | |||
16099 | foreach (XAttribute attrib in node.Attributes()) | ||
16100 | { | ||
16101 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
16102 | { | ||
16103 | switch (attrib.Name.LocalName) | ||
16104 | { | ||
16105 | case "Property": | ||
16106 | property = this.core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); | ||
16107 | this.core.CreateSimpleReference(sourceLineNumbers, "Property", property); | ||
16108 | break; | ||
16109 | default: | ||
16110 | this.core.UnexpectedAttribute(node, attrib); | ||
16111 | break; | ||
16112 | } | ||
16113 | } | ||
16114 | else | ||
16115 | { | ||
16116 | this.core.ParseExtensionAttribute(node, attrib); | ||
16117 | } | ||
16118 | } | ||
16119 | |||
16120 | if (null == property) | ||
16121 | { | ||
16122 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Property")); | ||
16123 | } | ||
16124 | |||
16125 | foreach (XElement child in node.Elements()) | ||
16126 | { | ||
16127 | if (CompilerCore.WixNamespace == child.Name.Namespace) | ||
16128 | { | ||
16129 | switch (child.Name.LocalName) | ||
16130 | { | ||
16131 | case "RadioButton": | ||
16132 | RadioButtonType type = this.ParseRadioButtonElement(child, property, ref order); | ||
16133 | if (RadioButtonType.NotSet == groupType) | ||
16134 | { | ||
16135 | groupType = type; | ||
16136 | } | ||
16137 | else if (groupType != type) | ||
16138 | { | ||
16139 | SourceLineNumber childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child); | ||
16140 | this.core.OnMessage(WixErrors.RadioButtonTypeInconsistent(childSourceLineNumbers)); | ||
16141 | } | ||
16142 | break; | ||
16143 | default: | ||
16144 | this.core.UnexpectedElement(node, child); | ||
16145 | break; | ||
16146 | } | ||
16147 | } | ||
16148 | else | ||
16149 | { | ||
16150 | this.core.ParseExtensionElement(node, child); | ||
16151 | } | ||
16152 | } | ||
16153 | |||
16154 | |||
16155 | return groupType; | ||
16156 | } | ||
16157 | |||
16158 | /// <summary> | ||
16159 | /// Parses an action text element. | ||
16160 | /// </summary> | ||
16161 | /// <param name="node">Element to parse.</param> | ||
16162 | private void ParseActionTextElement(XElement node) | ||
16163 | { | ||
16164 | SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
16165 | string action = null; | ||
16166 | string template = null; | ||
16167 | |||
16168 | foreach (XAttribute attrib in node.Attributes()) | ||
16169 | { | ||
16170 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
16171 | { | ||
16172 | switch (attrib.Name.LocalName) | ||
16173 | { | ||
16174 | case "Action": | ||
16175 | action = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
16176 | break; | ||
16177 | case "Template": | ||
16178 | template = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
16179 | break; | ||
16180 | default: | ||
16181 | this.core.UnexpectedAttribute(node, attrib); | ||
16182 | break; | ||
16183 | } | ||
16184 | } | ||
16185 | else | ||
16186 | { | ||
16187 | this.core.ParseExtensionAttribute(node, attrib); | ||
16188 | } | ||
16189 | } | ||
16190 | |||
16191 | if (null == action) | ||
16192 | { | ||
16193 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Action")); | ||
16194 | } | ||
16195 | |||
16196 | this.core.ParseForExtensionElements(node); | ||
16197 | |||
16198 | if (!this.core.EncounteredError) | ||
16199 | { | ||
16200 | Row row = this.core.CreateRow(sourceLineNumbers, "ActionText"); | ||
16201 | row[0] = action; | ||
16202 | row[1] = Common.GetInnerText(node); | ||
16203 | row[2] = template; | ||
16204 | } | ||
16205 | } | ||
16206 | |||
16207 | /// <summary> | ||
16208 | /// Parses an ui text element. | ||
16209 | /// </summary> | ||
16210 | /// <param name="node">Element to parse.</param> | ||
16211 | private void ParseUITextElement(XElement node) | ||
16212 | { | ||
16213 | SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
16214 | Identifier id = null; | ||
16215 | string text = null; | ||
16216 | |||
16217 | foreach (XAttribute attrib in node.Attributes()) | ||
16218 | { | ||
16219 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
16220 | { | ||
16221 | switch (attrib.Name.LocalName) | ||
16222 | { | ||
16223 | case "Id": | ||
16224 | id = this.core.GetAttributeIdentifier(sourceLineNumbers, attrib); | ||
16225 | break; | ||
16226 | default: | ||
16227 | this.core.UnexpectedAttribute(node, attrib); | ||
16228 | break; | ||
16229 | } | ||
16230 | } | ||
16231 | else | ||
16232 | { | ||
16233 | this.core.ParseExtensionAttribute(node, attrib); | ||
16234 | } | ||
16235 | } | ||
16236 | |||
16237 | text = Common.GetInnerText(node); | ||
16238 | |||
16239 | if (null == id) | ||
16240 | { | ||
16241 | id = this.core.CreateIdentifier("txt", text); | ||
16242 | } | ||
16243 | |||
16244 | this.core.ParseForExtensionElements(node); | ||
16245 | |||
16246 | if (!this.core.EncounteredError) | ||
16247 | { | ||
16248 | Row row = this.core.CreateRow(sourceLineNumbers, "UIText", id); | ||
16249 | row[1] = text; | ||
16250 | } | ||
16251 | } | ||
16252 | |||
16253 | /// <summary> | ||
16254 | /// Parses a text style element. | ||
16255 | /// </summary> | ||
16256 | /// <param name="node">Element to parse.</param> | ||
16257 | private void ParseTextStyleElement(XElement node) | ||
16258 | { | ||
16259 | SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
16260 | Identifier id = null; | ||
16261 | int bits = 0; | ||
16262 | int color = CompilerConstants.IntegerNotSet; | ||
16263 | string faceName = null; | ||
16264 | string size = "0"; | ||
16265 | |||
16266 | foreach (XAttribute attrib in node.Attributes()) | ||
16267 | { | ||
16268 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
16269 | { | ||
16270 | switch (attrib.Name.LocalName) | ||
16271 | { | ||
16272 | case "Id": | ||
16273 | id = this.core.GetAttributeIdentifier(sourceLineNumbers, attrib); | ||
16274 | break; | ||
16275 | |||
16276 | // RGB Values | ||
16277 | case "Red": | ||
16278 | int redColor = this.core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, byte.MaxValue); | ||
16279 | if (CompilerConstants.IllegalInteger != redColor) | ||
16280 | { | ||
16281 | if (CompilerConstants.IntegerNotSet == color) | ||
16282 | { | ||
16283 | color = redColor; | ||
16284 | } | ||
16285 | else | ||
16286 | { | ||
16287 | color += redColor; | ||
16288 | } | ||
16289 | } | ||
16290 | break; | ||
16291 | case "Green": | ||
16292 | int greenColor = this.core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, byte.MaxValue); | ||
16293 | if (CompilerConstants.IllegalInteger != greenColor) | ||
16294 | { | ||
16295 | if (CompilerConstants.IntegerNotSet == color) | ||
16296 | { | ||
16297 | color = greenColor * 256; | ||
16298 | } | ||
16299 | else | ||
16300 | { | ||
16301 | color += greenColor * 256; | ||
16302 | } | ||
16303 | } | ||
16304 | break; | ||
16305 | case "Blue": | ||
16306 | int blueColor = this.core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, byte.MaxValue); | ||
16307 | if (CompilerConstants.IllegalInteger != blueColor) | ||
16308 | { | ||
16309 | if (CompilerConstants.IntegerNotSet == color) | ||
16310 | { | ||
16311 | color = blueColor * 65536; | ||
16312 | } | ||
16313 | else | ||
16314 | { | ||
16315 | color += blueColor * 65536; | ||
16316 | } | ||
16317 | } | ||
16318 | break; | ||
16319 | |||
16320 | // Style values | ||
16321 | case "Bold": | ||
16322 | if (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) | ||
16323 | { | ||
16324 | bits |= MsiInterop.MsidbTextStyleStyleBitsBold; | ||
16325 | } | ||
16326 | break; | ||
16327 | case "Italic": | ||
16328 | if (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) | ||
16329 | { | ||
16330 | bits |= MsiInterop.MsidbTextStyleStyleBitsItalic; | ||
16331 | } | ||
16332 | break; | ||
16333 | case "Strike": | ||
16334 | if (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) | ||
16335 | { | ||
16336 | bits |= MsiInterop.MsidbTextStyleStyleBitsStrike; | ||
16337 | } | ||
16338 | break; | ||
16339 | case "Underline": | ||
16340 | if (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) | ||
16341 | { | ||
16342 | bits |= MsiInterop.MsidbTextStyleStyleBitsUnderline; | ||
16343 | } | ||
16344 | break; | ||
16345 | |||
16346 | // Font values | ||
16347 | case "FaceName": | ||
16348 | faceName = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
16349 | break; | ||
16350 | case "Size": | ||
16351 | size = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
16352 | break; | ||
16353 | |||
16354 | default: | ||
16355 | this.core.UnexpectedAttribute(node, attrib); | ||
16356 | break; | ||
16357 | } | ||
16358 | } | ||
16359 | else | ||
16360 | { | ||
16361 | this.core.ParseExtensionAttribute(node, attrib); | ||
16362 | } | ||
16363 | } | ||
16364 | |||
16365 | if (null == id) | ||
16366 | { | ||
16367 | this.core.CreateIdentifier("txs", faceName, size.ToString(), color.ToString(), bits.ToString()); | ||
16368 | } | ||
16369 | |||
16370 | if (null == faceName) | ||
16371 | { | ||
16372 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "FaceName")); | ||
16373 | } | ||
16374 | |||
16375 | this.core.ParseForExtensionElements(node); | ||
16376 | |||
16377 | if (!this.core.EncounteredError) | ||
16378 | { | ||
16379 | Row row = this.core.CreateRow(sourceLineNumbers, "TextStyle", id); | ||
16380 | row[1] = faceName; | ||
16381 | row[2] = size; | ||
16382 | if (0 <= color) | ||
16383 | { | ||
16384 | row[3] = color; | ||
16385 | } | ||
16386 | |||
16387 | if (0 < bits) | ||
16388 | { | ||
16389 | row[4] = bits; | ||
16390 | } | ||
16391 | } | ||
16392 | } | ||
16393 | |||
16394 | /// <summary> | ||
16395 | /// Parses a dialog element. | ||
16396 | /// </summary> | ||
16397 | /// <param name="node">Element to parse.</param> | ||
16398 | private void ParseDialogElement(XElement node) | ||
16399 | { | ||
16400 | SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
16401 | Identifier id = null; | ||
16402 | int bits = MsiInterop.MsidbDialogAttributesVisible | MsiInterop.MsidbDialogAttributesModal | MsiInterop.MsidbDialogAttributesMinimize; | ||
16403 | int height = 0; | ||
16404 | string title = null; | ||
16405 | bool trackDiskSpace = false; | ||
16406 | int width = 0; | ||
16407 | int x = 50; | ||
16408 | int y = 50; | ||
16409 | |||
16410 | foreach (XAttribute attrib in node.Attributes()) | ||
16411 | { | ||
16412 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
16413 | { | ||
16414 | switch (attrib.Name.LocalName) | ||
16415 | { | ||
16416 | case "Id": | ||
16417 | id = this.core.GetAttributeIdentifier(sourceLineNumbers, attrib); | ||
16418 | break; | ||
16419 | case "Height": | ||
16420 | height = this.core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, short.MaxValue); | ||
16421 | break; | ||
16422 | case "Title": | ||
16423 | title = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
16424 | break; | ||
16425 | case "Width": | ||
16426 | width = this.core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, short.MaxValue); | ||
16427 | break; | ||
16428 | case "X": | ||
16429 | x = this.core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, 100); | ||
16430 | break; | ||
16431 | case "Y": | ||
16432 | y = this.core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, 100); | ||
16433 | break; | ||
16434 | |||
16435 | case "CustomPalette": | ||
16436 | if (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) | ||
16437 | { | ||
16438 | bits ^= MsiInterop.MsidbDialogAttributesUseCustomPalette; | ||
16439 | } | ||
16440 | break; | ||
16441 | case "ErrorDialog": | ||
16442 | if (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) | ||
16443 | { | ||
16444 | bits ^= MsiInterop.MsidbDialogAttributesError; | ||
16445 | } | ||
16446 | break; | ||
16447 | case "Hidden": | ||
16448 | if (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) | ||
16449 | { | ||
16450 | bits ^= MsiInterop.MsidbDialogAttributesVisible; | ||
16451 | } | ||
16452 | break; | ||
16453 | case "KeepModeless": | ||
16454 | if (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) | ||
16455 | { | ||
16456 | bits ^= MsiInterop.MsidbDialogAttributesKeepModeless; | ||
16457 | } | ||
16458 | break; | ||
16459 | case "LeftScroll": | ||
16460 | if (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) | ||
16461 | { | ||
16462 | bits ^= MsiInterop.MsidbDialogAttributesLeftScroll; | ||
16463 | } | ||
16464 | break; | ||
16465 | case "Modeless": | ||
16466 | if (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) | ||
16467 | { | ||
16468 | bits ^= MsiInterop.MsidbDialogAttributesModal; | ||
16469 | } | ||
16470 | break; | ||
16471 | case "NoMinimize": | ||
16472 | if (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) | ||
16473 | { | ||
16474 | bits ^= MsiInterop.MsidbDialogAttributesMinimize; | ||
16475 | } | ||
16476 | break; | ||
16477 | case "RightAligned": | ||
16478 | if (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) | ||
16479 | { | ||
16480 | bits ^= MsiInterop.MsidbDialogAttributesRightAligned; | ||
16481 | } | ||
16482 | break; | ||
16483 | case "RightToLeft": | ||
16484 | if (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) | ||
16485 | { | ||
16486 | bits ^= MsiInterop.MsidbDialogAttributesRTLRO; | ||
16487 | } | ||
16488 | break; | ||
16489 | case "SystemModal": | ||
16490 | if (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) | ||
16491 | { | ||
16492 | bits ^= MsiInterop.MsidbDialogAttributesSysModal; | ||
16493 | } | ||
16494 | break; | ||
16495 | case "TrackDiskSpace": | ||
16496 | if (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) | ||
16497 | { | ||
16498 | bits ^= MsiInterop.MsidbDialogAttributesTrackDiskSpace; | ||
16499 | trackDiskSpace = true; | ||
16500 | } | ||
16501 | break; | ||
16502 | |||
16503 | default: | ||
16504 | this.core.UnexpectedAttribute(node, attrib); | ||
16505 | break; | ||
16506 | } | ||
16507 | } | ||
16508 | else | ||
16509 | { | ||
16510 | this.core.ParseExtensionAttribute(node, attrib); | ||
16511 | } | ||
16512 | } | ||
16513 | |||
16514 | if (null == id) | ||
16515 | { | ||
16516 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); | ||
16517 | id = Identifier.Invalid; | ||
16518 | } | ||
16519 | |||
16520 | Row lastTabRow = null; | ||
16521 | string cancelControl = null; | ||
16522 | string defaultControl = null; | ||
16523 | string firstControl = null; | ||
16524 | |||
16525 | foreach (XElement child in node.Elements()) | ||
16526 | { | ||
16527 | if (CompilerCore.WixNamespace == child.Name.Namespace) | ||
16528 | { | ||
16529 | switch (child.Name.LocalName) | ||
16530 | { | ||
16531 | case "Control": | ||
16532 | this.ParseControlElement(child, id.Id, this.tableDefinitions["Control"], ref lastTabRow, ref firstControl, ref defaultControl, ref cancelControl, trackDiskSpace); | ||
16533 | break; | ||
16534 | default: | ||
16535 | this.core.UnexpectedElement(node, child); | ||
16536 | break; | ||
16537 | } | ||
16538 | } | ||
16539 | else | ||
16540 | { | ||
16541 | this.core.ParseExtensionElement(node, child); | ||
16542 | } | ||
16543 | } | ||
16544 | |||
16545 | |||
16546 | if (null != lastTabRow && null != lastTabRow[1]) | ||
16547 | { | ||
16548 | if (firstControl != lastTabRow[1].ToString()) | ||
16549 | { | ||
16550 | lastTabRow[10] = firstControl; | ||
16551 | } | ||
16552 | } | ||
16553 | |||
16554 | if (null == firstControl) | ||
16555 | { | ||
16556 | this.core.OnMessage(WixErrors.NoFirstControlSpecified(sourceLineNumbers, id.Id)); | ||
16557 | } | ||
16558 | |||
16559 | if (!this.core.EncounteredError) | ||
16560 | { | ||
16561 | Row row = this.core.CreateRow(sourceLineNumbers, "Dialog", id); | ||
16562 | row[1] = x; | ||
16563 | row[2] = y; | ||
16564 | row[3] = width; | ||
16565 | row[4] = height; | ||
16566 | row[5] = bits; | ||
16567 | row[6] = title; | ||
16568 | row[7] = firstControl; | ||
16569 | row[8] = defaultControl; | ||
16570 | row[9] = cancelControl; | ||
16571 | } | ||
16572 | } | ||
16573 | |||
16574 | /// <summary> | ||
16575 | /// Parses an EmbeddedUI element. | ||
16576 | /// </summary> | ||
16577 | /// <param name="node">Element to parse.</param> | ||
16578 | private void ParseEmbeddedUIElement(XElement node) | ||
16579 | { | ||
16580 | SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
16581 | Identifier id = null; | ||
16582 | string name = null; | ||
16583 | int attributes = MsiInterop.MsidbEmbeddedUI; // by default this is the primary DLL that does not support basic UI. | ||
16584 | int messageFilter = MsiInterop.INSTALLLOGMODE_FATALEXIT | MsiInterop.INSTALLLOGMODE_ERROR | MsiInterop.INSTALLLOGMODE_WARNING | MsiInterop.INSTALLLOGMODE_USER | ||
16585 | | MsiInterop.INSTALLLOGMODE_INFO | MsiInterop.INSTALLLOGMODE_FILESINUSE | MsiInterop.INSTALLLOGMODE_RESOLVESOURCE | ||
16586 | | MsiInterop.INSTALLLOGMODE_OUTOFDISKSPACE | MsiInterop.INSTALLLOGMODE_ACTIONSTART | MsiInterop.INSTALLLOGMODE_ACTIONDATA | ||
16587 | | MsiInterop.INSTALLLOGMODE_PROGRESS | MsiInterop.INSTALLLOGMODE_COMMONDATA | MsiInterop.INSTALLLOGMODE_INITIALIZE | ||
16588 | | MsiInterop.INSTALLLOGMODE_TERMINATE | MsiInterop.INSTALLLOGMODE_SHOWDIALOG | MsiInterop.INSTALLLOGMODE_RMFILESINUSE | ||
16589 | | MsiInterop.INSTALLLOGMODE_INSTALLSTART | MsiInterop.INSTALLLOGMODE_INSTALLEND; | ||
16590 | string sourceFile = null; | ||
16591 | |||
16592 | foreach (XAttribute attrib in node.Attributes()) | ||
16593 | { | ||
16594 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
16595 | { | ||
16596 | switch (attrib.Name.LocalName) | ||
16597 | { | ||
16598 | case "Id": | ||
16599 | id = this.core.GetAttributeIdentifier(sourceLineNumbers, attrib); | ||
16600 | break; | ||
16601 | case "Name": | ||
16602 | name = this.core.GetAttributeLongFilename(sourceLineNumbers, attrib, false); | ||
16603 | break; | ||
16604 | case "IgnoreFatalExit": | ||
16605 | if (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) | ||
16606 | { | ||
16607 | messageFilter ^= MsiInterop.INSTALLLOGMODE_FATALEXIT; | ||
16608 | } | ||
16609 | break; | ||
16610 | case "IgnoreError": | ||
16611 | if (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) | ||
16612 | { | ||
16613 | messageFilter ^= MsiInterop.INSTALLLOGMODE_ERROR; | ||
16614 | } | ||
16615 | break; | ||
16616 | case "IgnoreWarning": | ||
16617 | if (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) | ||
16618 | { | ||
16619 | messageFilter ^= MsiInterop.INSTALLLOGMODE_WARNING; | ||
16620 | } | ||
16621 | break; | ||
16622 | case "IgnoreUser": | ||
16623 | if (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) | ||
16624 | { | ||
16625 | messageFilter ^= MsiInterop.INSTALLLOGMODE_USER; | ||
16626 | } | ||
16627 | break; | ||
16628 | case "IgnoreInfo": | ||
16629 | if (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) | ||
16630 | { | ||
16631 | messageFilter ^= MsiInterop.INSTALLLOGMODE_INFO; | ||
16632 | } | ||
16633 | break; | ||
16634 | case "IgnoreFilesInUse": | ||
16635 | if (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) | ||
16636 | { | ||
16637 | messageFilter ^= MsiInterop.INSTALLLOGMODE_FILESINUSE; | ||
16638 | } | ||
16639 | break; | ||
16640 | case "IgnoreResolveSource": | ||
16641 | if (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) | ||
16642 | { | ||
16643 | messageFilter ^= MsiInterop.INSTALLLOGMODE_RESOLVESOURCE; | ||
16644 | } | ||
16645 | break; | ||
16646 | case "IgnoreOutOfDiskSpace": | ||
16647 | if (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) | ||
16648 | { | ||
16649 | messageFilter ^= MsiInterop.INSTALLLOGMODE_OUTOFDISKSPACE; | ||
16650 | } | ||
16651 | break; | ||
16652 | case "IgnoreActionStart": | ||
16653 | if (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) | ||
16654 | { | ||
16655 | messageFilter ^= MsiInterop.INSTALLLOGMODE_ACTIONSTART; | ||
16656 | } | ||
16657 | break; | ||
16658 | case "IgnoreActionData": | ||
16659 | if (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) | ||
16660 | { | ||
16661 | messageFilter ^= MsiInterop.INSTALLLOGMODE_ACTIONDATA; | ||
16662 | } | ||
16663 | break; | ||
16664 | case "IgnoreProgress": | ||
16665 | if (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) | ||
16666 | { | ||
16667 | messageFilter ^= MsiInterop.INSTALLLOGMODE_PROGRESS; | ||
16668 | } | ||
16669 | break; | ||
16670 | case "IgnoreCommonData": | ||
16671 | if (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) | ||
16672 | { | ||
16673 | messageFilter ^= MsiInterop.INSTALLLOGMODE_COMMONDATA; | ||
16674 | } | ||
16675 | break; | ||
16676 | case "IgnoreInitialize": | ||
16677 | if (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) | ||
16678 | { | ||
16679 | messageFilter ^= MsiInterop.INSTALLLOGMODE_INITIALIZE; | ||
16680 | } | ||
16681 | break; | ||
16682 | case "IgnoreTerminate": | ||
16683 | if (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) | ||
16684 | { | ||
16685 | messageFilter ^= MsiInterop.INSTALLLOGMODE_TERMINATE; | ||
16686 | } | ||
16687 | break; | ||
16688 | case "IgnoreShowDialog": | ||
16689 | if (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) | ||
16690 | { | ||
16691 | messageFilter ^= MsiInterop.INSTALLLOGMODE_SHOWDIALOG; | ||
16692 | } | ||
16693 | break; | ||
16694 | case "IgnoreRMFilesInUse": | ||
16695 | if (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) | ||
16696 | { | ||
16697 | messageFilter ^= MsiInterop.INSTALLLOGMODE_RMFILESINUSE; | ||
16698 | } | ||
16699 | break; | ||
16700 | case "IgnoreInstallStart": | ||
16701 | if (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) | ||
16702 | { | ||
16703 | messageFilter ^= MsiInterop.INSTALLLOGMODE_INSTALLSTART; | ||
16704 | } | ||
16705 | break; | ||
16706 | case "IgnoreInstallEnd": | ||
16707 | if (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) | ||
16708 | { | ||
16709 | messageFilter ^= MsiInterop.INSTALLLOGMODE_INSTALLEND; | ||
16710 | } | ||
16711 | break; | ||
16712 | case "SourceFile": | ||
16713 | sourceFile = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
16714 | break; | ||
16715 | case "SupportBasicUI": | ||
16716 | if (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) | ||
16717 | { | ||
16718 | attributes |= MsiInterop.MsidbEmbeddedHandlesBasic; | ||
16719 | } | ||
16720 | break; | ||
16721 | default: | ||
16722 | this.core.UnexpectedAttribute(node, attrib); | ||
16723 | break; | ||
16724 | } | ||
16725 | } | ||
16726 | else | ||
16727 | { | ||
16728 | this.core.ParseExtensionAttribute(node, attrib); | ||
16729 | } | ||
16730 | } | ||
16731 | |||
16732 | if (String.IsNullOrEmpty(sourceFile)) | ||
16733 | { | ||
16734 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "SourceFile")); | ||
16735 | } | ||
16736 | else if (String.IsNullOrEmpty(name)) | ||
16737 | { | ||
16738 | name = Path.GetFileName(sourceFile); | ||
16739 | if (!this.core.IsValidLongFilename(name, false)) | ||
16740 | { | ||
16741 | this.core.OnMessage(WixErrors.IllegalLongFilename(sourceLineNumbers, node.Name.LocalName, "Source", name)); | ||
16742 | } | ||
16743 | } | ||
16744 | |||
16745 | if (null == id) | ||
16746 | { | ||
16747 | if (!String.IsNullOrEmpty(name)) | ||
16748 | { | ||
16749 | id = this.core.CreateIdentifierFromFilename(name); | ||
16750 | } | ||
16751 | |||
16752 | if (null == id) | ||
16753 | { | ||
16754 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); | ||
16755 | } | ||
16756 | else if (!Common.IsIdentifier(id.Id)) | ||
16757 | { | ||
16758 | this.core.OnMessage(WixErrors.IllegalIdentifier(sourceLineNumbers, node.Name.LocalName, "Id", id.Id)); | ||
16759 | } | ||
16760 | } | ||
16761 | else if (String.IsNullOrEmpty(name)) | ||
16762 | { | ||
16763 | name = id.Id; | ||
16764 | } | ||
16765 | |||
16766 | if (!name.Contains(".")) | ||
16767 | { | ||
16768 | this.core.OnMessage(WixErrors.InvalidEmbeddedUIFileName(sourceLineNumbers, name)); | ||
16769 | } | ||
16770 | |||
16771 | foreach (XElement child in node.Elements()) | ||
16772 | { | ||
16773 | if (CompilerCore.WixNamespace == child.Name.Namespace) | ||
16774 | { | ||
16775 | switch (child.Name.LocalName) | ||
16776 | { | ||
16777 | case "EmbeddedUIResource": | ||
16778 | this.ParseEmbeddedUIResourceElement(child); | ||
16779 | break; | ||
16780 | default: | ||
16781 | this.core.UnexpectedElement(node, child); | ||
16782 | break; | ||
16783 | } | ||
16784 | } | ||
16785 | else | ||
16786 | { | ||
16787 | this.core.ParseExtensionElement(node, child); | ||
16788 | } | ||
16789 | } | ||
16790 | |||
16791 | if (!this.core.EncounteredError) | ||
16792 | { | ||
16793 | Row row = this.core.CreateRow(sourceLineNumbers, "MsiEmbeddedUI", id); | ||
16794 | row[1] = name; | ||
16795 | row[2] = attributes; | ||
16796 | row[3] = messageFilter; | ||
16797 | row[4] = sourceFile; | ||
16798 | } | ||
16799 | } | ||
16800 | |||
16801 | /// <summary> | ||
16802 | /// Parses a embedded UI resource element. | ||
16803 | /// </summary> | ||
16804 | /// <param name="node">Element to parse.</param> | ||
16805 | /// <param name="parentId">Identifier of parent EmbeddedUI element.</param> | ||
16806 | private void ParseEmbeddedUIResourceElement(XElement node) | ||
16807 | { | ||
16808 | SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
16809 | Identifier id = null; | ||
16810 | string name = null; | ||
16811 | string sourceFile = null; | ||
16812 | |||
16813 | foreach (XAttribute attrib in node.Attributes()) | ||
16814 | { | ||
16815 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
16816 | { | ||
16817 | switch (attrib.Name.LocalName) | ||
16818 | { | ||
16819 | case "Id": | ||
16820 | id = this.core.GetAttributeIdentifier(sourceLineNumbers, attrib); | ||
16821 | break; | ||
16822 | case "Name": | ||
16823 | name = this.core.GetAttributeLongFilename(sourceLineNumbers, attrib, false); | ||
16824 | break; | ||
16825 | case "SourceFile": | ||
16826 | sourceFile = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
16827 | break; | ||
16828 | default: | ||
16829 | this.core.UnexpectedAttribute(node, attrib); | ||
16830 | break; | ||
16831 | } | ||
16832 | } | ||
16833 | else | ||
16834 | { | ||
16835 | this.core.ParseExtensionAttribute(node, attrib); | ||
16836 | } | ||
16837 | } | ||
16838 | |||
16839 | if (String.IsNullOrEmpty(sourceFile)) | ||
16840 | { | ||
16841 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "SourceFile")); | ||
16842 | } | ||
16843 | else if (String.IsNullOrEmpty(name)) | ||
16844 | { | ||
16845 | name = Path.GetFileName(sourceFile); | ||
16846 | if (!this.core.IsValidLongFilename(name, false)) | ||
16847 | { | ||
16848 | this.core.OnMessage(WixErrors.IllegalLongFilename(sourceLineNumbers, node.Name.LocalName, "Source", name)); | ||
16849 | } | ||
16850 | } | ||
16851 | |||
16852 | if (null == id) | ||
16853 | { | ||
16854 | if (!String.IsNullOrEmpty(name)) | ||
16855 | { | ||
16856 | id = this.core.CreateIdentifierFromFilename(name); | ||
16857 | } | ||
16858 | |||
16859 | if (null == id) | ||
16860 | { | ||
16861 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); | ||
16862 | } | ||
16863 | else if (!Common.IsIdentifier(id.Id)) | ||
16864 | { | ||
16865 | this.core.OnMessage(WixErrors.IllegalIdentifier(sourceLineNumbers, node.Name.LocalName, "Id", id.Id)); | ||
16866 | } | ||
16867 | } | ||
16868 | else if (String.IsNullOrEmpty(name)) | ||
16869 | { | ||
16870 | name = id.Id; | ||
16871 | } | ||
16872 | |||
16873 | this.core.ParseForExtensionElements(node); | ||
16874 | |||
16875 | if (!this.core.EncounteredError) | ||
16876 | { | ||
16877 | Row row = this.core.CreateRow(sourceLineNumbers, "MsiEmbeddedUI", id); | ||
16878 | row[1] = name; | ||
16879 | row[2] = 0; // embedded UI resources always set this to 0 | ||
16880 | row[3] = null; | ||
16881 | row[4] = sourceFile; | ||
16882 | } | ||
16883 | } | ||
16884 | |||
16885 | /// <summary> | ||
16886 | /// Parses a control element. | ||
16887 | /// </summary> | ||
16888 | /// <param name="node">Element to parse.</param> | ||
16889 | /// <param name="dialog">Identifier for parent dialog.</param> | ||
16890 | /// <param name="table">Table control belongs in.</param> | ||
16891 | /// <param name="lastTabRow">Last row in the tab order.</param> | ||
16892 | /// <param name="firstControl">Name of the first control in the tab order.</param> | ||
16893 | /// <param name="defaultControl">Name of the default control.</param> | ||
16894 | /// <param name="cancelControl">Name of the candle control.</param> | ||
16895 | /// <param name="trackDiskSpace">True if the containing dialog tracks disk space.</param> | ||
16896 | private void ParseControlElement(XElement node, string dialog, TableDefinition table, ref Row lastTabRow, ref string firstControl, ref string defaultControl, ref string cancelControl, bool trackDiskSpace) | ||
16897 | { | ||
16898 | SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
16899 | Identifier id = null; | ||
16900 | BitArray bits = new BitArray(32); | ||
16901 | int attributes = 0; | ||
16902 | string checkBoxPropertyRef = null; | ||
16903 | string checkboxValue = null; | ||
16904 | string controlType = null; | ||
16905 | bool disabled = false; | ||
16906 | string height = null; | ||
16907 | string help = null; | ||
16908 | bool isCancel = false; | ||
16909 | bool isDefault = false; | ||
16910 | bool notTabbable = false; | ||
16911 | string property = null; | ||
16912 | int publishOrder = 0; | ||
16913 | string[] specialAttributes = null; | ||
16914 | string sourceFile = null; | ||
16915 | string text = null; | ||
16916 | string tooltip = null; | ||
16917 | RadioButtonType radioButtonsType = RadioButtonType.NotSet; | ||
16918 | string width = null; | ||
16919 | string x = null; | ||
16920 | string y = null; | ||
16921 | |||
16922 | // The rest of the method relies on the control's Type, so we have to get that first. | ||
16923 | XAttribute typeAttribute = node.Attribute("Type"); | ||
16924 | if (null == typeAttribute) | ||
16925 | { | ||
16926 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Type")); | ||
16927 | } | ||
16928 | else | ||
16929 | { | ||
16930 | controlType = this.core.GetAttributeValue(sourceLineNumbers, typeAttribute); | ||
16931 | } | ||
16932 | |||
16933 | switch (controlType) | ||
16934 | { | ||
16935 | case "Billboard": | ||
16936 | specialAttributes = null; | ||
16937 | notTabbable = true; | ||
16938 | disabled = true; | ||
16939 | |||
16940 | this.core.EnsureTable(sourceLineNumbers, "Billboard"); | ||
16941 | break; | ||
16942 | case "Bitmap": | ||
16943 | specialAttributes = MsiInterop.BitmapControlAttributes; | ||
16944 | notTabbable = true; | ||
16945 | disabled = true; | ||
16946 | break; | ||
16947 | case "CheckBox": | ||
16948 | specialAttributes = MsiInterop.CheckboxControlAttributes; | ||
16949 | break; | ||
16950 | case "ComboBox": | ||
16951 | specialAttributes = MsiInterop.ComboboxControlAttributes; | ||
16952 | break; | ||
16953 | case "DirectoryCombo": | ||
16954 | specialAttributes = MsiInterop.VolumeControlAttributes; | ||
16955 | break; | ||
16956 | case "DirectoryList": | ||
16957 | specialAttributes = null; | ||
16958 | break; | ||
16959 | case "Edit": | ||
16960 | specialAttributes = MsiInterop.EditControlAttributes; | ||
16961 | break; | ||
16962 | case "GroupBox": | ||
16963 | specialAttributes = null; | ||
16964 | notTabbable = true; | ||
16965 | break; | ||
16966 | case "Hyperlink": | ||
16967 | specialAttributes = MsiInterop.HyperlinkControlAttributes; | ||
16968 | break; | ||
16969 | case "Icon": | ||
16970 | specialAttributes = MsiInterop.IconControlAttributes; | ||
16971 | notTabbable = true; | ||
16972 | disabled = true; | ||
16973 | break; | ||
16974 | case "Line": | ||
16975 | specialAttributes = null; | ||
16976 | notTabbable = true; | ||
16977 | disabled = true; | ||
16978 | break; | ||
16979 | case "ListBox": | ||
16980 | specialAttributes = MsiInterop.ListboxControlAttributes; | ||
16981 | break; | ||
16982 | case "ListView": | ||
16983 | specialAttributes = MsiInterop.ListviewControlAttributes; | ||
16984 | break; | ||
16985 | case "MaskedEdit": | ||
16986 | specialAttributes = MsiInterop.EditControlAttributes; | ||
16987 | break; | ||
16988 | case "PathEdit": | ||
16989 | specialAttributes = MsiInterop.EditControlAttributes; | ||
16990 | break; | ||
16991 | case "ProgressBar": | ||
16992 | specialAttributes = MsiInterop.ProgressControlAttributes; | ||
16993 | notTabbable = true; | ||
16994 | disabled = true; | ||
16995 | break; | ||
16996 | case "PushButton": | ||
16997 | specialAttributes = MsiInterop.ButtonControlAttributes; | ||
16998 | break; | ||
16999 | case "RadioButtonGroup": | ||
17000 | specialAttributes = MsiInterop.RadioControlAttributes; | ||
17001 | break; | ||
17002 | case "ScrollableText": | ||
17003 | specialAttributes = null; | ||
17004 | break; | ||
17005 | case "SelectionTree": | ||
17006 | specialAttributes = null; | ||
17007 | break; | ||
17008 | case "Text": | ||
17009 | specialAttributes = MsiInterop.TextControlAttributes; | ||
17010 | notTabbable = true; | ||
17011 | break; | ||
17012 | case "VolumeCostList": | ||
17013 | specialAttributes = MsiInterop.VolumeControlAttributes; | ||
17014 | notTabbable = true; | ||
17015 | break; | ||
17016 | case "VolumeSelectCombo": | ||
17017 | specialAttributes = MsiInterop.VolumeControlAttributes; | ||
17018 | break; | ||
17019 | default: | ||
17020 | specialAttributes = null; | ||
17021 | notTabbable = true; | ||
17022 | break; | ||
17023 | } | ||
17024 | |||
17025 | foreach (XAttribute attrib in node.Attributes()) | ||
17026 | { | ||
17027 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
17028 | { | ||
17029 | switch (attrib.Name.LocalName) | ||
17030 | { | ||
17031 | case "Id": | ||
17032 | id = this.core.GetAttributeIdentifier(sourceLineNumbers, attrib); | ||
17033 | break; | ||
17034 | case "Type": // already processed | ||
17035 | break; | ||
17036 | case "Cancel": | ||
17037 | isCancel = YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
17038 | break; | ||
17039 | case "CheckBoxPropertyRef": | ||
17040 | checkBoxPropertyRef = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
17041 | break; | ||
17042 | case "CheckBoxValue": | ||
17043 | checkboxValue = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
17044 | break; | ||
17045 | case "Default": | ||
17046 | isDefault = YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
17047 | break; | ||
17048 | case "Height": | ||
17049 | height = this.core.GetAttributeLocalizableIntegerValue(sourceLineNumbers, attrib, 0, short.MaxValue); | ||
17050 | break; | ||
17051 | case "Help": | ||
17052 | help = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
17053 | break; | ||
17054 | case "IconSize": | ||
17055 | string iconSizeValue = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
17056 | if (null != specialAttributes) | ||
17057 | { | ||
17058 | if (0 < iconSizeValue.Length) | ||
17059 | { | ||
17060 | Wix.Control.IconSizeType iconsSizeType = Wix.Control.ParseIconSizeType(iconSizeValue); | ||
17061 | switch (iconsSizeType) | ||
17062 | { | ||
17063 | case Wix.Control.IconSizeType.Item16: | ||
17064 | this.core.TrySetBitFromName(specialAttributes, "Icon16", YesNoType.Yes, bits, 16); | ||
17065 | break; | ||
17066 | case Wix.Control.IconSizeType.Item32: | ||
17067 | this.core.TrySetBitFromName(specialAttributes, "Icon32", YesNoType.Yes, bits, 16); | ||
17068 | break; | ||
17069 | case Wix.Control.IconSizeType.Item48: | ||
17070 | this.core.TrySetBitFromName(specialAttributes, "Icon16", YesNoType.Yes, bits, 16); | ||
17071 | this.core.TrySetBitFromName(specialAttributes, "Icon32", YesNoType.Yes, bits, 16); | ||
17072 | break; | ||
17073 | default: | ||
17074 | this.core.OnMessage(WixErrors.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, iconSizeValue, "16", "32", "48")); | ||
17075 | break; | ||
17076 | } | ||
17077 | } | ||
17078 | } | ||
17079 | else | ||
17080 | { | ||
17081 | this.core.OnMessage(WixErrors.IllegalAttributeValueWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, iconSizeValue, "Type")); | ||
17082 | } | ||
17083 | break; | ||
17084 | case "Property": | ||
17085 | property = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
17086 | break; | ||
17087 | case "TabSkip": | ||
17088 | notTabbable = YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
17089 | break; | ||
17090 | case "Text": | ||
17091 | text = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
17092 | break; | ||
17093 | case "ToolTip": | ||
17094 | tooltip = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
17095 | break; | ||
17096 | case "Width": | ||
17097 | width = this.core.GetAttributeLocalizableIntegerValue(sourceLineNumbers, attrib, 0, short.MaxValue); | ||
17098 | break; | ||
17099 | case "X": | ||
17100 | x = this.core.GetAttributeLocalizableIntegerValue(sourceLineNumbers, attrib, 0, short.MaxValue); | ||
17101 | break; | ||
17102 | case "Y": | ||
17103 | y = this.core.GetAttributeLocalizableIntegerValue(sourceLineNumbers, attrib, 0, short.MaxValue); | ||
17104 | break; | ||
17105 | default: | ||
17106 | YesNoType attribValue = this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
17107 | if (!this.core.TrySetBitFromName(MsiInterop.CommonControlAttributes, attrib.Name.LocalName, attribValue, bits, 0)) | ||
17108 | { | ||
17109 | if (null == specialAttributes || !this.core.TrySetBitFromName(specialAttributes, attrib.Name.LocalName, attribValue, bits, 16)) | ||
17110 | { | ||
17111 | this.core.UnexpectedAttribute(node, attrib); | ||
17112 | } | ||
17113 | } | ||
17114 | break; | ||
17115 | } | ||
17116 | } | ||
17117 | else | ||
17118 | { | ||
17119 | this.core.ParseExtensionAttribute(node, attrib); | ||
17120 | } | ||
17121 | } | ||
17122 | |||
17123 | attributes = this.core.CreateIntegerFromBitArray(bits); | ||
17124 | |||
17125 | if (disabled) | ||
17126 | { | ||
17127 | attributes |= MsiInterop.MsidbControlAttributesEnabled; // bit will be inverted when stored | ||
17128 | } | ||
17129 | |||
17130 | if (null == height) | ||
17131 | { | ||
17132 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Height")); | ||
17133 | } | ||
17134 | |||
17135 | if (null == width) | ||
17136 | { | ||
17137 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Width")); | ||
17138 | } | ||
17139 | |||
17140 | if (null == x) | ||
17141 | { | ||
17142 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "X")); | ||
17143 | } | ||
17144 | |||
17145 | if (null == y) | ||
17146 | { | ||
17147 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Y")); | ||
17148 | } | ||
17149 | |||
17150 | if (null == id) | ||
17151 | { | ||
17152 | id = this.core.CreateIdentifier("ctl", dialog, x, y, height, width); | ||
17153 | } | ||
17154 | |||
17155 | if (isCancel) | ||
17156 | { | ||
17157 | cancelControl = id.Id; | ||
17158 | } | ||
17159 | |||
17160 | if (isDefault) | ||
17161 | { | ||
17162 | defaultControl = id.Id; | ||
17163 | } | ||
17164 | |||
17165 | foreach (XElement child in node.Elements()) | ||
17166 | { | ||
17167 | if (CompilerCore.WixNamespace == child.Name.Namespace) | ||
17168 | { | ||
17169 | SourceLineNumber childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child); | ||
17170 | switch (child.Name.LocalName) | ||
17171 | { | ||
17172 | case "Binary": | ||
17173 | this.ParseBinaryElement(child); | ||
17174 | break; | ||
17175 | case "ComboBox": | ||
17176 | this.ParseControlGroupElement(child, this.tableDefinitions["ComboBox"], "ListItem"); | ||
17177 | break; | ||
17178 | case "Condition": | ||
17179 | this.ParseConditionElement(child, node.Name.LocalName, id.Id, dialog); | ||
17180 | break; | ||
17181 | case "ListBox": | ||
17182 | this.ParseControlGroupElement(child, this.tableDefinitions["ListBox"], "ListItem"); | ||
17183 | break; | ||
17184 | case "ListView": | ||
17185 | this.ParseControlGroupElement(child, this.tableDefinitions["ListView"], "ListItem"); | ||
17186 | break; | ||
17187 | case "Property": | ||
17188 | this.ParsePropertyElement(child); | ||
17189 | break; | ||
17190 | case "Publish": | ||
17191 | this.ParsePublishElement(child, dialog ?? String.Empty, id.Id, ref publishOrder); | ||
17192 | break; | ||
17193 | case "RadioButtonGroup": | ||
17194 | radioButtonsType = this.ParseRadioButtonGroupElement(child, property, radioButtonsType); | ||
17195 | break; | ||
17196 | case "Subscribe": | ||
17197 | this.ParseSubscribeElement(child, dialog, id.Id); | ||
17198 | break; | ||
17199 | case "Text": | ||
17200 | foreach (XAttribute attrib in child.Attributes()) | ||
17201 | { | ||
17202 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
17203 | { | ||
17204 | switch (attrib.Name.LocalName) | ||
17205 | { | ||
17206 | case "SourceFile": | ||
17207 | sourceFile = this.core.GetAttributeValue(childSourceLineNumbers, attrib); | ||
17208 | break; | ||
17209 | default: | ||
17210 | this.core.UnexpectedAttribute(child, attrib); | ||
17211 | break; | ||
17212 | } | ||
17213 | } | ||
17214 | else | ||
17215 | { | ||
17216 | this.core.ParseExtensionAttribute(child, attrib); | ||
17217 | } | ||
17218 | } | ||
17219 | |||
17220 | text = Common.GetInnerText(child); | ||
17221 | if (!String.IsNullOrEmpty(text) && null != sourceFile) | ||
17222 | { | ||
17223 | this.core.OnMessage(WixErrors.IllegalAttributeWithInnerText(childSourceLineNumbers, child.Name.LocalName, "SourceFile")); | ||
17224 | } | ||
17225 | break; | ||
17226 | default: | ||
17227 | this.core.UnexpectedElement(node, child); | ||
17228 | break; | ||
17229 | } | ||
17230 | } | ||
17231 | else | ||
17232 | { | ||
17233 | this.core.ParseExtensionElement(node, child); | ||
17234 | } | ||
17235 | } | ||
17236 | |||
17237 | // If the radio buttons have icons, then we need to add the icon attribute. | ||
17238 | switch (radioButtonsType) | ||
17239 | { | ||
17240 | case RadioButtonType.Bitmap: | ||
17241 | attributes |= MsiInterop.MsidbControlAttributesBitmap; | ||
17242 | break; | ||
17243 | case RadioButtonType.Icon: | ||
17244 | attributes |= MsiInterop.MsidbControlAttributesIcon; | ||
17245 | break; | ||
17246 | case RadioButtonType.Text: | ||
17247 | // Text is the default so nothing needs to be added bits | ||
17248 | break; | ||
17249 | } | ||
17250 | |||
17251 | // If we're tracking disk space, and this is a non-FormatSize Text control, and the text attribute starts with | ||
17252 | // '[' and ends with ']', add a space. It is not necessary for the whole string to be a property, just | ||
17253 | // those two characters matter. | ||
17254 | if (trackDiskSpace && "Text" == controlType && | ||
17255 | MsiInterop.MsidbControlAttributesFormatSize != (attributes & MsiInterop.MsidbControlAttributesFormatSize) && | ||
17256 | null != text && text.StartsWith("[", StringComparison.Ordinal) && text.EndsWith("]", StringComparison.Ordinal)) | ||
17257 | { | ||
17258 | text = String.Concat(text, " "); | ||
17259 | } | ||
17260 | |||
17261 | // the logic for creating control rows is a little tricky because of the way tabable controls are set | ||
17262 | Row row = null; | ||
17263 | if (!this.core.EncounteredError) | ||
17264 | { | ||
17265 | if ("CheckBox" == controlType) | ||
17266 | { | ||
17267 | if (String.IsNullOrEmpty(property) && String.IsNullOrEmpty(checkBoxPropertyRef)) | ||
17268 | { | ||
17269 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Property", "CheckBoxPropertyRef", true)); | ||
17270 | } | ||
17271 | else if (!String.IsNullOrEmpty(property) && !String.IsNullOrEmpty(checkBoxPropertyRef)) | ||
17272 | { | ||
17273 | this.core.OnMessage(WixErrors.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Property", "CheckBoxPropertyRef")); | ||
17274 | } | ||
17275 | else if (!String.IsNullOrEmpty(property)) | ||
17276 | { | ||
17277 | row = this.core.CreateRow(sourceLineNumbers, "CheckBox"); | ||
17278 | row[0] = property; | ||
17279 | row[1] = checkboxValue; | ||
17280 | } | ||
17281 | else | ||
17282 | { | ||
17283 | this.core.CreateSimpleReference(sourceLineNumbers, "CheckBox", checkBoxPropertyRef); | ||
17284 | } | ||
17285 | } | ||
17286 | |||
17287 | row = this.core.CreateRow(sourceLineNumbers, table.Name); | ||
17288 | row.Access = id.Access; | ||
17289 | row[0] = dialog; | ||
17290 | row[1] = id.Id; | ||
17291 | row[2] = controlType; | ||
17292 | row[3] = x; | ||
17293 | row[4] = y; | ||
17294 | row[5] = width; | ||
17295 | row[6] = height; | ||
17296 | row[7] = attributes ^ (MsiInterop.MsidbControlAttributesVisible | MsiInterop.MsidbControlAttributesEnabled); | ||
17297 | if ("BBControl" == table.Name) | ||
17298 | { | ||
17299 | row[8] = text; // BBControl.Text | ||
17300 | |||
17301 | if (null != sourceFile) | ||
17302 | { | ||
17303 | Row wixBBControlRow = this.core.CreateRow(sourceLineNumbers, "WixBBControl"); | ||
17304 | wixBBControlRow.Access = id.Access; | ||
17305 | wixBBControlRow[0] = dialog; | ||
17306 | wixBBControlRow[1] = id.Id; | ||
17307 | wixBBControlRow[2] = sourceFile; | ||
17308 | } | ||
17309 | } | ||
17310 | else | ||
17311 | { | ||
17312 | row[8] = !String.IsNullOrEmpty(property) ? property : checkBoxPropertyRef; | ||
17313 | row[9] = text; | ||
17314 | if (null != tooltip || null != help) | ||
17315 | { | ||
17316 | row[11] = String.Concat(tooltip, "|", help); // Separator is required, even if only one is non-null. | ||
17317 | } | ||
17318 | |||
17319 | if (null != sourceFile) | ||
17320 | { | ||
17321 | Row wixControlRow = this.core.CreateRow(sourceLineNumbers, "WixControl"); | ||
17322 | wixControlRow.Access = id.Access; | ||
17323 | wixControlRow[0] = dialog; | ||
17324 | wixControlRow[1] = id.Id; | ||
17325 | wixControlRow[2] = sourceFile; | ||
17326 | } | ||
17327 | } | ||
17328 | } | ||
17329 | |||
17330 | if (!notTabbable) | ||
17331 | { | ||
17332 | if ("BBControl" == table.Name) | ||
17333 | { | ||
17334 | this.core.OnMessage(WixErrors.TabbableControlNotAllowedInBillboard(sourceLineNumbers, node.Name.LocalName, controlType)); | ||
17335 | } | ||
17336 | |||
17337 | if (null == firstControl) | ||
17338 | { | ||
17339 | firstControl = id.Id; | ||
17340 | } | ||
17341 | |||
17342 | if (null != lastTabRow) | ||
17343 | { | ||
17344 | lastTabRow[10] = id.Id; | ||
17345 | } | ||
17346 | lastTabRow = row; | ||
17347 | } | ||
17348 | |||
17349 | // bitmap and icon controls contain a foreign key into the binary table in the text column; | ||
17350 | // add a reference if the identifier of the binary entry is known during compilation | ||
17351 | if (("Bitmap" == controlType || "Icon" == controlType) && Common.IsIdentifier(text)) | ||
17352 | { | ||
17353 | this.core.CreateSimpleReference(sourceLineNumbers, "Binary", text); | ||
17354 | } | ||
17355 | } | ||
17356 | |||
17357 | /// <summary> | ||
17358 | /// Parses a publish control event element. | ||
17359 | /// </summary> | ||
17360 | /// <param name="node">Element to parse.</param> | ||
17361 | /// <param name="dialog">Identifier of parent dialog.</param> | ||
17362 | /// <param name="control">Identifier of parent control.</param> | ||
17363 | /// <param name="order">Relative order of controls.</param> | ||
17364 | private void ParsePublishElement(XElement node, string dialog, string control, ref int order) | ||
17365 | { | ||
17366 | SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
17367 | string argument = null; | ||
17368 | string condition = null; | ||
17369 | string controlEvent = null; | ||
17370 | string property = null; | ||
17371 | |||
17372 | // give this control event a unique ordering | ||
17373 | order++; | ||
17374 | |||
17375 | foreach (XAttribute attrib in node.Attributes()) | ||
17376 | { | ||
17377 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
17378 | { | ||
17379 | switch (attrib.Name.LocalName) | ||
17380 | { | ||
17381 | case "Control": | ||
17382 | if (null != control) | ||
17383 | { | ||
17384 | this.core.OnMessage(WixErrors.IllegalAttributeWhenNested(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, node.Parent.Name.LocalName)); | ||
17385 | } | ||
17386 | control = this.core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); | ||
17387 | break; | ||
17388 | case "Dialog": | ||
17389 | if (null != dialog) | ||
17390 | { | ||
17391 | this.core.OnMessage(WixErrors.IllegalAttributeWhenNested(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, node.Parent.Name.LocalName)); | ||
17392 | } | ||
17393 | dialog = this.core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); | ||
17394 | this.core.CreateSimpleReference(sourceLineNumbers, "Dialog", dialog); | ||
17395 | break; | ||
17396 | case "Event": | ||
17397 | controlEvent = Compiler.UppercaseFirstChar(this.core.GetAttributeValue(sourceLineNumbers, attrib)); | ||
17398 | break; | ||
17399 | case "Order": | ||
17400 | order = this.core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, 2147483647); | ||
17401 | break; | ||
17402 | case "Property": | ||
17403 | property = String.Concat("[", this.core.GetAttributeValue(sourceLineNumbers, attrib), "]"); | ||
17404 | break; | ||
17405 | case "Value": | ||
17406 | argument = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
17407 | break; | ||
17408 | default: | ||
17409 | this.core.UnexpectedAttribute(node, attrib); | ||
17410 | break; | ||
17411 | } | ||
17412 | } | ||
17413 | else | ||
17414 | { | ||
17415 | this.core.ParseExtensionAttribute(node, attrib); | ||
17416 | } | ||
17417 | } | ||
17418 | |||
17419 | condition = this.core.GetConditionInnerText(node); | ||
17420 | |||
17421 | if (null == control) | ||
17422 | { | ||
17423 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Control")); | ||
17424 | } | ||
17425 | |||
17426 | if (null == dialog) | ||
17427 | { | ||
17428 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Dialog")); | ||
17429 | } | ||
17430 | |||
17431 | if (null == controlEvent && null == property) // need to specify at least one | ||
17432 | { | ||
17433 | this.core.OnMessage(WixErrors.ExpectedAttributes(sourceLineNumbers, node.Name.LocalName, "Event", "Property")); | ||
17434 | } | ||
17435 | else if (null != controlEvent && null != property) // cannot specify both | ||
17436 | { | ||
17437 | this.core.OnMessage(WixErrors.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Event", "Property")); | ||
17438 | } | ||
17439 | |||
17440 | if (null == argument) | ||
17441 | { | ||
17442 | if (null != controlEvent) | ||
17443 | { | ||
17444 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Value", "Event")); | ||
17445 | } | ||
17446 | else if (null != property) | ||
17447 | { | ||
17448 | // if this is setting a property to null, put a special value in the argument column | ||
17449 | argument = "{}"; | ||
17450 | } | ||
17451 | } | ||
17452 | |||
17453 | this.core.ParseForExtensionElements(node); | ||
17454 | |||
17455 | if (!this.core.EncounteredError) | ||
17456 | { | ||
17457 | Row row = this.core.CreateRow(sourceLineNumbers, "ControlEvent"); | ||
17458 | row[0] = dialog; | ||
17459 | row[1] = control; | ||
17460 | row[2] = (null != controlEvent ? controlEvent : property); | ||
17461 | row[3] = argument; | ||
17462 | row[4] = condition; | ||
17463 | row[5] = order; | ||
17464 | } | ||
17465 | |||
17466 | if ("DoAction" == controlEvent && null != argument) | ||
17467 | { | ||
17468 | // if we're not looking at a standard action or a formatted string then create a reference | ||
17469 | // to the custom action. | ||
17470 | if (!WindowsInstallerStandard.IsStandardAction(argument) && !Common.ContainsProperty(argument)) | ||
17471 | { | ||
17472 | this.core.CreateSimpleReference(sourceLineNumbers, "CustomAction", argument); | ||
17473 | } | ||
17474 | } | ||
17475 | |||
17476 | // if we're referring to a dialog but not through a property, add it to the references | ||
17477 | if (("NewDialog" == controlEvent || "SpawnDialog" == controlEvent || "SpawnWaitDialog" == controlEvent || "SelectionBrowse" == controlEvent) && Common.IsIdentifier(argument)) | ||
17478 | { | ||
17479 | this.core.CreateSimpleReference(sourceLineNumbers, "Dialog", argument); | ||
17480 | } | ||
17481 | } | ||
17482 | |||
17483 | /// <summary> | ||
17484 | /// Parses a control subscription element. | ||
17485 | /// </summary> | ||
17486 | /// <param name="node">Element to parse.</param> | ||
17487 | /// <param name="dialog">Identifier of dialog.</param> | ||
17488 | /// <param name="control">Identifier of control.</param> | ||
17489 | private void ParseSubscribeElement(XElement node, string dialog, string control) | ||
17490 | { | ||
17491 | SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
17492 | string controlAttribute = null; | ||
17493 | string eventMapping = null; | ||
17494 | |||
17495 | foreach (XAttribute attrib in node.Attributes()) | ||
17496 | { | ||
17497 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
17498 | { | ||
17499 | switch (attrib.Name.LocalName) | ||
17500 | { | ||
17501 | case "Attribute": | ||
17502 | controlAttribute = Compiler.UppercaseFirstChar(this.core.GetAttributeIdentifierValue(sourceLineNumbers, attrib)); | ||
17503 | break; | ||
17504 | case "Event": | ||
17505 | eventMapping = Compiler.UppercaseFirstChar(this.core.GetAttributeIdentifierValue(sourceLineNumbers, attrib)); | ||
17506 | break; | ||
17507 | default: | ||
17508 | this.core.UnexpectedAttribute(node, attrib); | ||
17509 | break; | ||
17510 | } | ||
17511 | } | ||
17512 | else | ||
17513 | { | ||
17514 | this.core.ParseExtensionAttribute(node, attrib); | ||
17515 | } | ||
17516 | } | ||
17517 | |||
17518 | this.core.ParseForExtensionElements(node); | ||
17519 | |||
17520 | if (!this.core.EncounteredError) | ||
17521 | { | ||
17522 | Row row = this.core.CreateRow(sourceLineNumbers, "EventMapping"); | ||
17523 | row[0] = dialog; | ||
17524 | row[1] = control; | ||
17525 | row[2] = eventMapping; | ||
17526 | row[3] = controlAttribute; | ||
17527 | } | ||
17528 | } | ||
17529 | |||
17530 | /// <summary> | ||
17531 | /// Parses an upgrade element. | ||
17532 | /// </summary> | ||
17533 | /// <param name="node">Element to parse.</param> | ||
17534 | private void ParseUpgradeElement(XElement node) | ||
17535 | { | ||
17536 | SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
17537 | string id = null; | ||
17538 | |||
17539 | foreach (XAttribute attrib in node.Attributes()) | ||
17540 | { | ||
17541 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
17542 | { | ||
17543 | switch (attrib.Name.LocalName) | ||
17544 | { | ||
17545 | case "Id": | ||
17546 | id = this.core.GetAttributeGuidValue(sourceLineNumbers, attrib, false); | ||
17547 | break; | ||
17548 | default: | ||
17549 | this.core.UnexpectedAttribute(node, attrib); | ||
17550 | break; | ||
17551 | } | ||
17552 | } | ||
17553 | else | ||
17554 | { | ||
17555 | this.core.ParseExtensionAttribute(node, attrib); | ||
17556 | } | ||
17557 | } | ||
17558 | |||
17559 | if (null == id) | ||
17560 | { | ||
17561 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); | ||
17562 | } | ||
17563 | |||
17564 | // process the UpgradeVersion children here | ||
17565 | foreach (XElement child in node.Elements()) | ||
17566 | { | ||
17567 | if (CompilerCore.WixNamespace == child.Name.Namespace) | ||
17568 | { | ||
17569 | SourceLineNumber childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child); | ||
17570 | |||
17571 | switch (child.Name.LocalName) | ||
17572 | { | ||
17573 | case "Property": | ||
17574 | this.ParsePropertyElement(child); | ||
17575 | this.core.OnMessage(WixWarnings.DeprecatedUpgradeProperty(childSourceLineNumbers)); | ||
17576 | break; | ||
17577 | case "UpgradeVersion": | ||
17578 | this.ParseUpgradeVersionElement(child, id); | ||
17579 | break; | ||
17580 | default: | ||
17581 | this.core.UnexpectedElement(node, child); | ||
17582 | break; | ||
17583 | } | ||
17584 | } | ||
17585 | else | ||
17586 | { | ||
17587 | this.core.ParseExtensionElement(node, child); | ||
17588 | } | ||
17589 | } | ||
17590 | |||
17591 | |||
17592 | // No rows created here. All row creation is done in ParseUpgradeVersionElement. | ||
17593 | } | ||
17594 | |||
17595 | /// <summary> | ||
17596 | /// Parse upgrade version element. | ||
17597 | /// </summary> | ||
17598 | /// <param name="node">Element to parse.</param> | ||
17599 | /// <param name="upgradeId">Upgrade code.</param> | ||
17600 | private void ParseUpgradeVersionElement(XElement node, string upgradeId) | ||
17601 | { | ||
17602 | SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
17603 | |||
17604 | string actionProperty = null; | ||
17605 | string language = null; | ||
17606 | string maximum = null; | ||
17607 | string minimum = null; | ||
17608 | int options = 256; | ||
17609 | string removeFeatures = null; | ||
17610 | |||
17611 | foreach (XAttribute attrib in node.Attributes()) | ||
17612 | { | ||
17613 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
17614 | { | ||
17615 | switch (attrib.Name.LocalName) | ||
17616 | { | ||
17617 | case "ExcludeLanguages": | ||
17618 | if (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) | ||
17619 | { | ||
17620 | options |= MsiInterop.MsidbUpgradeAttributesLanguagesExclusive; | ||
17621 | } | ||
17622 | break; | ||
17623 | case "IgnoreRemoveFailure": | ||
17624 | if (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) | ||
17625 | { | ||
17626 | options |= MsiInterop.MsidbUpgradeAttributesIgnoreRemoveFailure; | ||
17627 | } | ||
17628 | break; | ||
17629 | case "IncludeMaximum": | ||
17630 | if (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) | ||
17631 | { | ||
17632 | options |= MsiInterop.MsidbUpgradeAttributesVersionMaxInclusive; | ||
17633 | } | ||
17634 | break; | ||
17635 | case "IncludeMinimum": // this is "yes" by default | ||
17636 | if (YesNoType.No == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) | ||
17637 | { | ||
17638 | options &= ~MsiInterop.MsidbUpgradeAttributesVersionMinInclusive; | ||
17639 | } | ||
17640 | break; | ||
17641 | case "Language": | ||
17642 | language = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
17643 | break; | ||
17644 | case "Minimum": | ||
17645 | minimum = this.core.GetAttributeVersionValue(sourceLineNumbers, attrib); | ||
17646 | break; | ||
17647 | case "Maximum": | ||
17648 | maximum = this.core.GetAttributeVersionValue(sourceLineNumbers, attrib); | ||
17649 | break; | ||
17650 | case "MigrateFeatures": | ||
17651 | if (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) | ||
17652 | { | ||
17653 | options |= MsiInterop.MsidbUpgradeAttributesMigrateFeatures; | ||
17654 | } | ||
17655 | break; | ||
17656 | case "OnlyDetect": | ||
17657 | if (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) | ||
17658 | { | ||
17659 | options |= MsiInterop.MsidbUpgradeAttributesOnlyDetect; | ||
17660 | } | ||
17661 | break; | ||
17662 | case "Property": | ||
17663 | actionProperty = this.core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); | ||
17664 | break; | ||
17665 | case "RemoveFeatures": | ||
17666 | removeFeatures = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
17667 | break; | ||
17668 | default: | ||
17669 | this.core.UnexpectedAttribute(node, attrib); | ||
17670 | break; | ||
17671 | } | ||
17672 | } | ||
17673 | else | ||
17674 | { | ||
17675 | this.core.ParseExtensionAttribute(node, attrib); | ||
17676 | } | ||
17677 | } | ||
17678 | |||
17679 | if (null == actionProperty) | ||
17680 | { | ||
17681 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Property")); | ||
17682 | } | ||
17683 | else if (actionProperty.ToUpper(CultureInfo.InvariantCulture) != actionProperty) | ||
17684 | { | ||
17685 | this.core.OnMessage(WixErrors.SecurePropertyNotUppercase(sourceLineNumbers, node.Name.LocalName, "Property", actionProperty)); | ||
17686 | } | ||
17687 | |||
17688 | if (null == minimum && null == maximum) | ||
17689 | { | ||
17690 | this.core.OnMessage(WixErrors.ExpectedAttributes(sourceLineNumbers, node.Name.LocalName, "Minimum", "Maximum")); | ||
17691 | } | ||
17692 | |||
17693 | this.core.ParseForExtensionElements(node); | ||
17694 | |||
17695 | if (!this.core.EncounteredError) | ||
17696 | { | ||
17697 | Row row = this.core.CreateRow(sourceLineNumbers, "Upgrade"); | ||
17698 | row[0] = upgradeId; | ||
17699 | row[1] = minimum; | ||
17700 | row[2] = maximum; | ||
17701 | row[3] = language; | ||
17702 | row[4] = options; | ||
17703 | row[5] = removeFeatures; | ||
17704 | row[6] = actionProperty; | ||
17705 | |||
17706 | // Ensure the action property is secure. | ||
17707 | this.AddWixPropertyRow(sourceLineNumbers, new Identifier(actionProperty, AccessModifier.Private), false, true, false); | ||
17708 | |||
17709 | // Ensure that RemoveExistingProducts is authored in InstallExecuteSequence | ||
17710 | // if at least one row in Upgrade table lacks the OnlyDetect attribute. | ||
17711 | if (0 == (options & MsiInterop.MsidbUpgradeAttributesOnlyDetect)) | ||
17712 | { | ||
17713 | this.core.CreateSimpleReference(sourceLineNumbers, "WixAction", "InstallExecuteSequence", "RemoveExistingProducts"); | ||
17714 | } | ||
17715 | } | ||
17716 | } | ||
17717 | |||
17718 | /// <summary> | ||
17719 | /// Parses a verb element. | ||
17720 | /// </summary> | ||
17721 | /// <param name="node">Element to parse.</param> | ||
17722 | /// <param name="extension">Extension verb is releated to.</param> | ||
17723 | /// <param name="progId">Optional progId for extension.</param> | ||
17724 | /// <param name="componentId">Identifier for parent component.</param> | ||
17725 | /// <param name="advertise">Flag if verb is advertised.</param> | ||
17726 | private void ParseVerbElement(XElement node, string extension, string progId, string componentId, YesNoType advertise) | ||
17727 | { | ||
17728 | SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
17729 | string id = null; | ||
17730 | string argument = null; | ||
17731 | string command = null; | ||
17732 | int sequence = CompilerConstants.IntegerNotSet; | ||
17733 | string target = null; | ||
17734 | string targetFile = null; | ||
17735 | string targetProperty = null; | ||
17736 | |||
17737 | foreach (XAttribute attrib in node.Attributes()) | ||
17738 | { | ||
17739 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
17740 | { | ||
17741 | switch (attrib.Name.LocalName) | ||
17742 | { | ||
17743 | case "Id": | ||
17744 | id = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
17745 | break; | ||
17746 | case "Argument": | ||
17747 | argument = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
17748 | break; | ||
17749 | case "Command": | ||
17750 | command = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
17751 | break; | ||
17752 | case "Sequence": | ||
17753 | sequence = this.core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 1, short.MaxValue); | ||
17754 | break; | ||
17755 | case "Target": | ||
17756 | target = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
17757 | this.core.OnMessage(WixWarnings.DeprecatedAttribute(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "TargetFile", "TargetProperty")); | ||
17758 | break; | ||
17759 | case "TargetFile": | ||
17760 | targetFile = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
17761 | this.core.CreateSimpleReference(sourceLineNumbers, "File", targetFile); | ||
17762 | break; | ||
17763 | case "TargetProperty": | ||
17764 | targetProperty = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
17765 | break; | ||
17766 | default: | ||
17767 | this.core.UnexpectedAttribute(node, attrib); | ||
17768 | break; | ||
17769 | } | ||
17770 | } | ||
17771 | else | ||
17772 | { | ||
17773 | this.core.ParseExtensionAttribute(node, attrib); | ||
17774 | } | ||
17775 | } | ||
17776 | |||
17777 | if (null == id) | ||
17778 | { | ||
17779 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); | ||
17780 | } | ||
17781 | |||
17782 | if (null != target && null != targetFile) | ||
17783 | { | ||
17784 | this.core.OnMessage(WixErrors.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Target", "TargetFile")); | ||
17785 | } | ||
17786 | |||
17787 | if (null != target && null != targetProperty) | ||
17788 | { | ||
17789 | this.core.OnMessage(WixErrors.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Target", "TargetProperty")); | ||
17790 | } | ||
17791 | |||
17792 | if (null != targetFile && null != targetProperty) | ||
17793 | { | ||
17794 | this.core.OnMessage(WixErrors.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "TargetFile", "TargetProperty")); | ||
17795 | } | ||
17796 | |||
17797 | this.core.ParseForExtensionElements(node); | ||
17798 | |||
17799 | if (YesNoType.Yes == advertise) | ||
17800 | { | ||
17801 | if (null != target) | ||
17802 | { | ||
17803 | this.core.OnMessage(WixErrors.IllegalAttributeWhenAdvertised(sourceLineNumbers, node.Name.LocalName, "Target")); | ||
17804 | } | ||
17805 | |||
17806 | if (null != targetFile) | ||
17807 | { | ||
17808 | this.core.OnMessage(WixErrors.IllegalAttributeWhenAdvertised(sourceLineNumbers, node.Name.LocalName, "TargetFile")); | ||
17809 | } | ||
17810 | |||
17811 | if (null != targetProperty) | ||
17812 | { | ||
17813 | this.core.OnMessage(WixErrors.IllegalAttributeWhenAdvertised(sourceLineNumbers, node.Name.LocalName, "TargetProperty")); | ||
17814 | } | ||
17815 | |||
17816 | if (!this.core.EncounteredError) | ||
17817 | { | ||
17818 | Row row = this.core.CreateRow(sourceLineNumbers, "Verb"); | ||
17819 | row[0] = extension; | ||
17820 | row[1] = id; | ||
17821 | if (CompilerConstants.IntegerNotSet != sequence) | ||
17822 | { | ||
17823 | row[2] = sequence; | ||
17824 | } | ||
17825 | row[3] = command; | ||
17826 | row[4] = argument; | ||
17827 | } | ||
17828 | } | ||
17829 | else if (YesNoType.No == advertise) | ||
17830 | { | ||
17831 | if (CompilerConstants.IntegerNotSet != sequence) | ||
17832 | { | ||
17833 | this.core.OnMessage(WixErrors.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Sequence", "Advertise", "no")); | ||
17834 | } | ||
17835 | |||
17836 | if (null == target && null == targetFile && null == targetProperty) | ||
17837 | { | ||
17838 | this.core.OnMessage(WixErrors.ExpectedAttributesWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "TargetFile", "TargetProperty", "Advertise", "no")); | ||
17839 | } | ||
17840 | |||
17841 | if (null == target) | ||
17842 | { | ||
17843 | if (null != targetFile) | ||
17844 | { | ||
17845 | target = String.Concat("\"[#", targetFile, "]\""); | ||
17846 | } | ||
17847 | |||
17848 | if (null != targetProperty) | ||
17849 | { | ||
17850 | target = String.Concat("\"[", targetProperty, "]\""); | ||
17851 | } | ||
17852 | } | ||
17853 | |||
17854 | if (null != argument) | ||
17855 | { | ||
17856 | target = String.Concat(target, " ", argument); | ||
17857 | } | ||
17858 | |||
17859 | string prefix = (null != progId ? progId : String.Concat(".", extension)); | ||
17860 | |||
17861 | if (null != command) | ||
17862 | { | ||
17863 | this.core.CreateRegistryRow(sourceLineNumbers, MsiInterop.MsidbRegistryRootClassesRoot, String.Concat(prefix, "\\shell\\", id), String.Empty, command, componentId); | ||
17864 | } | ||
17865 | |||
17866 | this.core.CreateRegistryRow(sourceLineNumbers, MsiInterop.MsidbRegistryRootClassesRoot, String.Concat(prefix, "\\shell\\", id, "\\command"), String.Empty, target, componentId); | ||
17867 | } | ||
17868 | } | ||
17869 | |||
17870 | |||
17871 | /// <summary> | ||
17872 | /// Parses an ApprovedExeForElevation element. | ||
17873 | /// </summary> | ||
17874 | /// <param name="node">Element to parse</param> | ||
17875 | private void ParseApprovedExeForElevation(XElement node) | ||
17876 | { | ||
17877 | SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
17878 | Identifier id = null; | ||
17879 | string key = null; | ||
17880 | string valueName = null; | ||
17881 | YesNoType win64 = YesNoType.NotSet; | ||
17882 | |||
17883 | foreach (XAttribute attrib in node.Attributes()) | ||
17884 | { | ||
17885 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
17886 | { | ||
17887 | switch (attrib.Name.LocalName) | ||
17888 | { | ||
17889 | case "Id": | ||
17890 | id = this.core.GetAttributeIdentifier(sourceLineNumbers, attrib); | ||
17891 | break; | ||
17892 | case "Key": | ||
17893 | key = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
17894 | break; | ||
17895 | case "Value": | ||
17896 | valueName = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
17897 | break; | ||
17898 | case "Win64": | ||
17899 | win64 = this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
17900 | break; | ||
17901 | default: | ||
17902 | this.core.UnexpectedAttribute(node, attrib); | ||
17903 | break; | ||
17904 | } | ||
17905 | } | ||
17906 | else | ||
17907 | { | ||
17908 | this.core.ParseExtensionAttribute(node, attrib); | ||
17909 | } | ||
17910 | } | ||
17911 | |||
17912 | if (null == id) | ||
17913 | { | ||
17914 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); | ||
17915 | } | ||
17916 | |||
17917 | if (null == key) | ||
17918 | { | ||
17919 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Key")); | ||
17920 | } | ||
17921 | |||
17922 | BundleApprovedExeForElevationAttributes attributes = BundleApprovedExeForElevationAttributes.None; | ||
17923 | |||
17924 | if (win64 == YesNoType.Yes) | ||
17925 | { | ||
17926 | attributes |= BundleApprovedExeForElevationAttributes.Win64; | ||
17927 | } | ||
17928 | |||
17929 | this.core.ParseForExtensionElements(node); | ||
17930 | |||
17931 | if (!this.core.EncounteredError) | ||
17932 | { | ||
17933 | WixApprovedExeForElevationRow wixApprovedExeForElevationRow = (WixApprovedExeForElevationRow)this.core.CreateRow(sourceLineNumbers, "WixApprovedExeForElevation", id); | ||
17934 | wixApprovedExeForElevationRow.Key = key; | ||
17935 | wixApprovedExeForElevationRow.ValueName = valueName; | ||
17936 | wixApprovedExeForElevationRow.Attributes = attributes; | ||
17937 | } | ||
17938 | } | ||
17939 | |||
17940 | /// <summary> | ||
17941 | /// Parses a Bundle element. | ||
17942 | /// </summary> | ||
17943 | /// <param name="node">Element to parse</param> | ||
17944 | private void ParseBundleElement(XElement node) | ||
17945 | { | ||
17946 | SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
17947 | string copyright = null; | ||
17948 | string aboutUrl = null; | ||
17949 | YesNoDefaultType compressed = YesNoDefaultType.Default; | ||
17950 | int disableModify = -1; | ||
17951 | YesNoType disableRemove = YesNoType.NotSet; | ||
17952 | string helpTelephone = null; | ||
17953 | string helpUrl = null; | ||
17954 | string manufacturer = null; | ||
17955 | string name = null; | ||
17956 | string tag = null; | ||
17957 | string updateUrl = null; | ||
17958 | string upgradeCode = null; | ||
17959 | string version = null; | ||
17960 | string condition = null; | ||
17961 | string parentName = null; | ||
17962 | |||
17963 | string fileSystemSafeBundleName = null; | ||
17964 | string logVariablePrefixAndExtension = null; | ||
17965 | string iconSourceFile = null; | ||
17966 | string splashScreenSourceFile = null; | ||
17967 | |||
17968 | // Process only standard attributes until the active section is initialized. | ||
17969 | foreach (XAttribute attrib in node.Attributes()) | ||
17970 | { | ||
17971 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
17972 | { | ||
17973 | switch (attrib.Name.LocalName) | ||
17974 | { | ||
17975 | case "AboutUrl": | ||
17976 | aboutUrl = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
17977 | break; | ||
17978 | case "Compressed": | ||
17979 | compressed = this.core.GetAttributeYesNoDefaultValue(sourceLineNumbers, attrib); | ||
17980 | break; | ||
17981 | case "Condition": | ||
17982 | condition = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
17983 | break; | ||
17984 | case "Copyright": | ||
17985 | copyright = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
17986 | break; | ||
17987 | case "DisableModify": | ||
17988 | string value = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
17989 | switch (value) | ||
17990 | { | ||
17991 | case "button": | ||
17992 | disableModify = 2; | ||
17993 | break; | ||
17994 | case "yes": | ||
17995 | disableModify = 1; | ||
17996 | break; | ||
17997 | case "no": | ||
17998 | disableModify = 0; | ||
17999 | break; | ||
18000 | default: | ||
18001 | this.core.OnMessage(WixErrors.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, value, "button", "yes", "no")); | ||
18002 | break; | ||
18003 | } | ||
18004 | break; | ||
18005 | case "DisableRemove": | ||
18006 | disableRemove = this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
18007 | break; | ||
18008 | case "DisableRepair": | ||
18009 | this.core.OnMessage(WixWarnings.DeprecatedAttribute(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName)); | ||
18010 | break; | ||
18011 | case "HelpTelephone": | ||
18012 | helpTelephone = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
18013 | break; | ||
18014 | case "HelpUrl": | ||
18015 | helpUrl = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
18016 | break; | ||
18017 | case "Manufacturer": | ||
18018 | manufacturer = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
18019 | break; | ||
18020 | case "IconSourceFile": | ||
18021 | iconSourceFile = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
18022 | break; | ||
18023 | case "Name": | ||
18024 | name = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
18025 | break; | ||
18026 | case "ParentName": | ||
18027 | parentName = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
18028 | break; | ||
18029 | case "SplashScreenSourceFile": | ||
18030 | splashScreenSourceFile = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
18031 | break; | ||
18032 | case "Tag": | ||
18033 | tag = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
18034 | break; | ||
18035 | case "UpdateUrl": | ||
18036 | updateUrl = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
18037 | break; | ||
18038 | case "UpgradeCode": | ||
18039 | upgradeCode = this.core.GetAttributeGuidValue(sourceLineNumbers, attrib, false); | ||
18040 | break; | ||
18041 | case "Version": | ||
18042 | version = this.core.GetAttributeVersionValue(sourceLineNumbers, attrib); | ||
18043 | break; | ||
18044 | default: | ||
18045 | this.core.UnexpectedAttribute(node, attrib); | ||
18046 | break; | ||
18047 | } | ||
18048 | } | ||
18049 | } | ||
18050 | |||
18051 | if (String.IsNullOrEmpty(version)) | ||
18052 | { | ||
18053 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Version")); | ||
18054 | } | ||
18055 | else if (!CompilerCore.IsValidModuleOrBundleVersion(version)) | ||
18056 | { | ||
18057 | this.core.OnMessage(WixWarnings.InvalidModuleOrBundleVersion(sourceLineNumbers, "Bundle", version)); | ||
18058 | } | ||
18059 | |||
18060 | if (String.IsNullOrEmpty(upgradeCode)) | ||
18061 | { | ||
18062 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "UpgradeCode")); | ||
18063 | } | ||
18064 | |||
18065 | if (String.IsNullOrEmpty(copyright)) | ||
18066 | { | ||
18067 | if (String.IsNullOrEmpty(manufacturer)) | ||
18068 | { | ||
18069 | copyright = "Copyright (c). All rights reserved."; | ||
18070 | } | ||
18071 | else | ||
18072 | { | ||
18073 | copyright = String.Format("Copyright (c) {0}. All rights reserved.", manufacturer); | ||
18074 | } | ||
18075 | } | ||
18076 | |||
18077 | if (String.IsNullOrEmpty(name)) | ||
18078 | { | ||
18079 | logVariablePrefixAndExtension = String.Concat("WixBundleLog:Setup.log"); | ||
18080 | } | ||
18081 | else | ||
18082 | { | ||
18083 | // Ensure only allowable path characters are in "name" (and change spaces to underscores). | ||
18084 | fileSystemSafeBundleName = CompilerCore.MakeValidLongFileName(name.Replace(' ', '_'), "_"); | ||
18085 | logVariablePrefixAndExtension = String.Concat("WixBundleLog:", fileSystemSafeBundleName, ".log"); | ||
18086 | } | ||
18087 | |||
18088 | this.activeName = String.IsNullOrEmpty(name) ? Common.GenerateGuid() : name; | ||
18089 | this.core.CreateActiveSection(this.activeName, SectionType.Bundle, 0); | ||
18090 | |||
18091 | // Now that the active section is initialized, process only extension attributes. | ||
18092 | foreach (XAttribute attrib in node.Attributes()) | ||
18093 | { | ||
18094 | if (!String.IsNullOrEmpty(attrib.Name.NamespaceName) && CompilerCore.WixNamespace != attrib.Name.Namespace) | ||
18095 | { | ||
18096 | this.core.ParseExtensionAttribute(node, attrib); | ||
18097 | } | ||
18098 | } | ||
18099 | |||
18100 | bool baSeen = false; | ||
18101 | bool chainSeen = false; | ||
18102 | bool logSeen = false; | ||
18103 | |||
18104 | foreach (XElement child in node.Elements()) | ||
18105 | { | ||
18106 | if (CompilerCore.WixNamespace == child.Name.Namespace) | ||
18107 | { | ||
18108 | switch (child.Name.LocalName) | ||
18109 | { | ||
18110 | case "ApprovedExeForElevation": | ||
18111 | this.ParseApprovedExeForElevation(child); | ||
18112 | break; | ||
18113 | case "BootstrapperApplication": | ||
18114 | if (baSeen) | ||
18115 | { | ||
18116 | SourceLineNumber childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child); | ||
18117 | this.core.OnMessage(WixErrors.TooManyChildren(childSourceLineNumbers, node.Name.LocalName, "BootstrapperApplication")); | ||
18118 | } | ||
18119 | this.ParseBootstrapperApplicationElement(child); | ||
18120 | baSeen = true; | ||
18121 | break; | ||
18122 | case "BootstrapperApplicationRef": | ||
18123 | this.ParseBootstrapperApplicationRefElement(child); | ||
18124 | break; | ||
18125 | case "OptionalUpdateRegistration": | ||
18126 | this.ParseOptionalUpdateRegistrationElement(child, manufacturer, parentName, name); | ||
18127 | break; | ||
18128 | case "Catalog": | ||
18129 | this.ParseCatalogElement(child); | ||
18130 | break; | ||
18131 | case "Chain": | ||
18132 | if (chainSeen) | ||
18133 | { | ||
18134 | SourceLineNumber childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child); | ||
18135 | this.core.OnMessage(WixErrors.TooManyChildren(childSourceLineNumbers, node.Name.LocalName, "Chain")); | ||
18136 | } | ||
18137 | this.ParseChainElement(child); | ||
18138 | chainSeen = true; | ||
18139 | break; | ||
18140 | case "Container": | ||
18141 | this.ParseContainerElement(child); | ||
18142 | break; | ||
18143 | case "ContainerRef": | ||
18144 | this.ParseSimpleRefElement(child, "WixBundleContainer"); | ||
18145 | break; | ||
18146 | case "Log": | ||
18147 | if (logSeen) | ||
18148 | { | ||
18149 | SourceLineNumber childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child); | ||
18150 | this.core.OnMessage(WixErrors.TooManyChildren(childSourceLineNumbers, node.Name.LocalName, "Log")); | ||
18151 | } | ||
18152 | logVariablePrefixAndExtension = this.ParseLogElement(child, fileSystemSafeBundleName); | ||
18153 | logSeen = true; | ||
18154 | break; | ||
18155 | case "PayloadGroup": | ||
18156 | this.ParsePayloadGroupElement(child, ComplexReferenceParentType.Layout, "BundleLayoutOnlyPayloads"); | ||
18157 | break; | ||
18158 | case "PayloadGroupRef": | ||
18159 | this.ParsePayloadGroupRefElement(child, ComplexReferenceParentType.Layout, "BundleLayoutOnlyPayloads", ComplexReferenceChildType.Unknown, null); | ||
18160 | break; | ||
18161 | case "RelatedBundle": | ||
18162 | this.ParseRelatedBundleElement(child); | ||
18163 | break; | ||
18164 | case "Update": | ||
18165 | this.ParseUpdateElement(child); | ||
18166 | break; | ||
18167 | case "Variable": | ||
18168 | this.ParseVariableElement(child); | ||
18169 | break; | ||
18170 | case "WixVariable": | ||
18171 | this.ParseWixVariableElement(child); | ||
18172 | break; | ||
18173 | default: | ||
18174 | this.core.UnexpectedElement(node, child); | ||
18175 | break; | ||
18176 | } | ||
18177 | } | ||
18178 | else | ||
18179 | { | ||
18180 | this.core.ParseExtensionElement(node, child); | ||
18181 | } | ||
18182 | } | ||
18183 | |||
18184 | |||
18185 | if (!chainSeen) | ||
18186 | { | ||
18187 | this.core.OnMessage(WixErrors.ExpectedElement(sourceLineNumbers, node.Name.LocalName, "Chain")); | ||
18188 | } | ||
18189 | |||
18190 | if (!this.core.EncounteredError) | ||
18191 | { | ||
18192 | if (null != upgradeCode) | ||
18193 | { | ||
18194 | Row relatedBundleRow = this.core.CreateRow(sourceLineNumbers, "WixRelatedBundle"); | ||
18195 | relatedBundleRow[0] = upgradeCode; | ||
18196 | relatedBundleRow[1] = (int)Wix.RelatedBundle.ActionType.Upgrade; | ||
18197 | } | ||
18198 | |||
18199 | WixBundleContainerRow containerRow = (WixBundleContainerRow)this.core.CreateRow(sourceLineNumbers, "WixBundleContainer"); | ||
18200 | containerRow.Id = Compiler.BurnDefaultAttachedContainerId; | ||
18201 | containerRow.Name = "bundle-attached.cab"; | ||
18202 | containerRow.Type = ContainerType.Attached; | ||
18203 | |||
18204 | Row row = this.core.CreateRow(sourceLineNumbers, "WixBundle"); | ||
18205 | row[0] = version; | ||
18206 | row[1] = copyright; | ||
18207 | row[2] = name; | ||
18208 | row[3] = aboutUrl; | ||
18209 | if (-1 != disableModify) | ||
18210 | { | ||
18211 | row[4] = disableModify; | ||
18212 | } | ||
18213 | if (YesNoType.NotSet != disableRemove) | ||
18214 | { | ||
18215 | row[5] = (YesNoType.Yes == disableRemove) ? 1 : 0; | ||
18216 | } | ||
18217 | // row[6] - (deprecated) "disable repair" | ||
18218 | row[7] = helpTelephone; | ||
18219 | row[8] = helpUrl; | ||
18220 | row[9] = manufacturer; | ||
18221 | row[10] = updateUrl; | ||
18222 | if (YesNoDefaultType.Default != compressed) | ||
18223 | { | ||
18224 | row[11] = (YesNoDefaultType.Yes == compressed) ? 1 : 0; | ||
18225 | } | ||
18226 | |||
18227 | row[12] = logVariablePrefixAndExtension; | ||
18228 | row[13] = iconSourceFile; | ||
18229 | row[14] = splashScreenSourceFile; | ||
18230 | row[15] = condition; | ||
18231 | row[16] = tag; | ||
18232 | row[17] = this.CurrentPlatform.ToString(); | ||
18233 | row[18] = parentName; | ||
18234 | row[19] = upgradeCode; | ||
18235 | |||
18236 | // Ensure that the bundle stores the well-known persisted values. | ||
18237 | WixBundleVariableRow bundleNameWellKnownVariable = (WixBundleVariableRow)this.core.CreateRow(sourceLineNumbers, "WixBundleVariable"); | ||
18238 | bundleNameWellKnownVariable.Id = Compiler.BURN_BUNDLE_NAME; | ||
18239 | bundleNameWellKnownVariable.Hidden = false; | ||
18240 | bundleNameWellKnownVariable.Persisted = true; | ||
18241 | |||
18242 | WixBundleVariableRow bundleOriginalSourceWellKnownVariable = (WixBundleVariableRow)this.core.CreateRow(sourceLineNumbers, "WixBundleVariable"); | ||
18243 | bundleOriginalSourceWellKnownVariable.Id = Compiler.BURN_BUNDLE_ORIGINAL_SOURCE; | ||
18244 | bundleOriginalSourceWellKnownVariable.Hidden = false; | ||
18245 | bundleOriginalSourceWellKnownVariable.Persisted = true; | ||
18246 | |||
18247 | WixBundleVariableRow bundleOriginalSourceFolderWellKnownVariable = (WixBundleVariableRow)this.core.CreateRow(sourceLineNumbers, "WixBundleVariable"); | ||
18248 | bundleOriginalSourceFolderWellKnownVariable.Id = Compiler.BURN_BUNDLE_ORIGINAL_SOURCE_FOLDER; | ||
18249 | bundleOriginalSourceFolderWellKnownVariable.Hidden = false; | ||
18250 | bundleOriginalSourceFolderWellKnownVariable.Persisted = true; | ||
18251 | |||
18252 | WixBundleVariableRow bundleLastUsedSourceWellKnownVariable = (WixBundleVariableRow)this.core.CreateRow(sourceLineNumbers, "WixBundleVariable"); | ||
18253 | bundleLastUsedSourceWellKnownVariable.Id = Compiler.BURN_BUNDLE_LAST_USED_SOURCE; | ||
18254 | bundleLastUsedSourceWellKnownVariable.Hidden = false; | ||
18255 | bundleLastUsedSourceWellKnownVariable.Persisted = true; | ||
18256 | } | ||
18257 | } | ||
18258 | |||
18259 | /// <summary> | ||
18260 | /// Parse a Container element. | ||
18261 | /// </summary> | ||
18262 | /// <param name="node">Element to parse</param> | ||
18263 | private string ParseLogElement(XElement node, string fileSystemSafeBundleName) | ||
18264 | { | ||
18265 | SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
18266 | YesNoType disableLog = YesNoType.NotSet; | ||
18267 | string variable = "WixBundleLog"; | ||
18268 | string logPrefix = fileSystemSafeBundleName ?? "Setup"; | ||
18269 | string logExtension = ".log"; | ||
18270 | |||
18271 | foreach (XAttribute attrib in node.Attributes()) | ||
18272 | { | ||
18273 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
18274 | { | ||
18275 | switch (attrib.Name.LocalName) | ||
18276 | { | ||
18277 | case "Disable": | ||
18278 | disableLog = this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
18279 | break; | ||
18280 | case "PathVariable": | ||
18281 | variable = this.core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty); | ||
18282 | break; | ||
18283 | case "Prefix": | ||
18284 | logPrefix = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
18285 | break; | ||
18286 | case "Extension": | ||
18287 | logExtension = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
18288 | break; | ||
18289 | default: | ||
18290 | this.core.UnexpectedAttribute(node, attrib); | ||
18291 | break; | ||
18292 | } | ||
18293 | } | ||
18294 | else | ||
18295 | { | ||
18296 | this.core.ParseExtensionAttribute(node, attrib); | ||
18297 | } | ||
18298 | } | ||
18299 | |||
18300 | if (!logExtension.StartsWith(".", StringComparison.Ordinal)) | ||
18301 | { | ||
18302 | logExtension = String.Concat(".", logExtension); | ||
18303 | } | ||
18304 | |||
18305 | this.core.ParseForExtensionElements(node); | ||
18306 | |||
18307 | return YesNoType.Yes == disableLog ? null : String.Concat(variable, ":", logPrefix, logExtension); | ||
18308 | } | ||
18309 | |||
18310 | /// <summary> | ||
18311 | /// Parse a Catalog element. | ||
18312 | /// </summary> | ||
18313 | /// <param name="node">Element to parse</param> | ||
18314 | private void ParseCatalogElement(XElement node) | ||
18315 | { | ||
18316 | SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
18317 | Identifier id = null; | ||
18318 | string sourceFile = null; | ||
18319 | |||
18320 | foreach (XAttribute attrib in node.Attributes()) | ||
18321 | { | ||
18322 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
18323 | { | ||
18324 | switch (attrib.Name.LocalName) | ||
18325 | { | ||
18326 | case "Id": | ||
18327 | id = this.core.GetAttributeIdentifier(sourceLineNumbers, attrib); | ||
18328 | break; | ||
18329 | case "SourceFile": | ||
18330 | sourceFile = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
18331 | break; | ||
18332 | default: | ||
18333 | this.core.UnexpectedAttribute(node, attrib); | ||
18334 | break; | ||
18335 | } | ||
18336 | } | ||
18337 | } | ||
18338 | |||
18339 | if (null == id) | ||
18340 | { | ||
18341 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); | ||
18342 | } | ||
18343 | |||
18344 | if (null == sourceFile) | ||
18345 | { | ||
18346 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "SourceFile")); | ||
18347 | } | ||
18348 | |||
18349 | this.core.ParseForExtensionElements(node); | ||
18350 | |||
18351 | // Create catalog row | ||
18352 | if (!this.core.EncounteredError) | ||
18353 | { | ||
18354 | this.CreatePayloadRow(sourceLineNumbers, id, Path.GetFileName(sourceFile), sourceFile, null, ComplexReferenceParentType.Container, Compiler.BurnUXContainerId, ComplexReferenceChildType.Unknown, null, YesNoDefaultType.Yes, YesNoType.Yes, null, null, null); | ||
18355 | |||
18356 | WixBundleCatalogRow wixCatalogRow = (WixBundleCatalogRow)this.core.CreateRow(sourceLineNumbers, "WixBundleCatalog", id); | ||
18357 | wixCatalogRow.Payload = id.Id; | ||
18358 | } | ||
18359 | } | ||
18360 | |||
18361 | /// <summary> | ||
18362 | /// Parse a Container element. | ||
18363 | /// </summary> | ||
18364 | /// <param name="node">Element to parse</param> | ||
18365 | private void ParseContainerElement(XElement node) | ||
18366 | { | ||
18367 | SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
18368 | Identifier id = null; | ||
18369 | string downloadUrl = null; | ||
18370 | string name = null; | ||
18371 | ContainerType type = ContainerType.Detached; | ||
18372 | |||
18373 | foreach (XAttribute attrib in node.Attributes()) | ||
18374 | { | ||
18375 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
18376 | { | ||
18377 | switch (attrib.Name.LocalName) | ||
18378 | { | ||
18379 | case "Id": | ||
18380 | id = this.core.GetAttributeIdentifier(sourceLineNumbers, attrib); | ||
18381 | break; | ||
18382 | case "DownloadUrl": | ||
18383 | downloadUrl = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
18384 | break; | ||
18385 | case "Name": | ||
18386 | name = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
18387 | break; | ||
18388 | case "Type": | ||
18389 | string typeString = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
18390 | if (!Enum.TryParse<ContainerType>(typeString, out type)) | ||
18391 | { | ||
18392 | this.core.OnMessage(WixErrors.IllegalAttributeValueWithLegalList(sourceLineNumbers, node.Name.LocalName, "Type", typeString, "attached, detached")); | ||
18393 | } | ||
18394 | break; | ||
18395 | default: | ||
18396 | this.core.UnexpectedAttribute(node, attrib); | ||
18397 | break; | ||
18398 | } | ||
18399 | } | ||
18400 | else | ||
18401 | { | ||
18402 | this.core.ParseExtensionAttribute(node, attrib); | ||
18403 | } | ||
18404 | } | ||
18405 | |||
18406 | if (null == id) | ||
18407 | { | ||
18408 | if (!String.IsNullOrEmpty(name)) | ||
18409 | { | ||
18410 | id = this.core.CreateIdentifierFromFilename(name); | ||
18411 | } | ||
18412 | |||
18413 | if (null == id) | ||
18414 | { | ||
18415 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); | ||
18416 | id = Identifier.Invalid; | ||
18417 | } | ||
18418 | else if (!Common.IsIdentifier(id.Id)) | ||
18419 | { | ||
18420 | this.core.OnMessage(WixErrors.IllegalIdentifier(sourceLineNumbers, node.Name.LocalName, "Id", id.Id)); | ||
18421 | } | ||
18422 | } | ||
18423 | else if (null == name) | ||
18424 | { | ||
18425 | name = id.Id; | ||
18426 | } | ||
18427 | |||
18428 | if (!String.IsNullOrEmpty(downloadUrl) && ContainerType.Detached != type) | ||
18429 | { | ||
18430 | this.core.OnMessage(WixErrors.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "DownloadUrl", "Type", "attached")); | ||
18431 | } | ||
18432 | |||
18433 | foreach (XElement child in node.Elements()) | ||
18434 | { | ||
18435 | if (CompilerCore.WixNamespace == child.Name.Namespace) | ||
18436 | { | ||
18437 | switch (child.Name.LocalName) | ||
18438 | { | ||
18439 | case "PackageGroupRef": | ||
18440 | this.ParsePackageGroupRefElement(child, ComplexReferenceParentType.Container, id.Id); | ||
18441 | break; | ||
18442 | default: | ||
18443 | this.core.UnexpectedElement(node, child); | ||
18444 | break; | ||
18445 | } | ||
18446 | } | ||
18447 | else | ||
18448 | { | ||
18449 | this.core.ParseExtensionElement(node, child); | ||
18450 | } | ||
18451 | } | ||
18452 | |||
18453 | |||
18454 | if (!this.core.EncounteredError) | ||
18455 | { | ||
18456 | WixBundleContainerRow row = (WixBundleContainerRow)this.core.CreateRow(sourceLineNumbers, "WixBundleContainer", id); | ||
18457 | row.Name = name; | ||
18458 | row.Type = type; | ||
18459 | row.DownloadUrl = downloadUrl; | ||
18460 | } | ||
18461 | } | ||
18462 | |||
18463 | /// <summary> | ||
18464 | /// Parse the BoostrapperApplication element. | ||
18465 | /// </summary> | ||
18466 | /// <param name="node">Element to parse</param> | ||
18467 | private void ParseBootstrapperApplicationElement(XElement node) | ||
18468 | { | ||
18469 | SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
18470 | string id = null; | ||
18471 | string previousId = null; | ||
18472 | ComplexReferenceChildType previousType = ComplexReferenceChildType.Unknown; | ||
18473 | |||
18474 | // The BootstrapperApplication element acts like a Payload element so delegate to the "Payload" attribute parsing code to parse and create a Payload entry. | ||
18475 | id = this.ParsePayloadElementContent(node, ComplexReferenceParentType.Container, Compiler.BurnUXContainerId, previousType, previousId, false); | ||
18476 | if (null != id) | ||
18477 | { | ||
18478 | previousId = id; | ||
18479 | previousType = ComplexReferenceChildType.Payload; | ||
18480 | } | ||
18481 | |||
18482 | foreach (XElement child in node.Elements()) | ||
18483 | { | ||
18484 | if (CompilerCore.WixNamespace == child.Name.Namespace) | ||
18485 | { | ||
18486 | switch (child.Name.LocalName) | ||
18487 | { | ||
18488 | case "Payload": | ||
18489 | previousId = this.ParsePayloadElement(child, ComplexReferenceParentType.Container, Compiler.BurnUXContainerId, previousType, previousId); | ||
18490 | previousType = ComplexReferenceChildType.Payload; | ||
18491 | break; | ||
18492 | case "PayloadGroupRef": | ||
18493 | previousId = this.ParsePayloadGroupRefElement(child, ComplexReferenceParentType.Container, Compiler.BurnUXContainerId, previousType, previousId); | ||
18494 | previousType = ComplexReferenceChildType.PayloadGroup; | ||
18495 | break; | ||
18496 | default: | ||
18497 | this.core.UnexpectedElement(node, child); | ||
18498 | break; | ||
18499 | } | ||
18500 | } | ||
18501 | else | ||
18502 | { | ||
18503 | this.core.ParseExtensionElement(node, child); | ||
18504 | } | ||
18505 | } | ||
18506 | |||
18507 | if (null == previousId) | ||
18508 | { | ||
18509 | // We need *either* <Payload> or <PayloadGroupRef> or even just @SourceFile on the BA... | ||
18510 | // but we just say there's a missing <Payload>. | ||
18511 | // TODO: Is there a better message for this? | ||
18512 | this.core.OnMessage(WixErrors.ExpectedElement(sourceLineNumbers, node.Name.LocalName, "Payload")); | ||
18513 | } | ||
18514 | |||
18515 | // Add the application as an attached container and if an Id was provided add that too. | ||
18516 | if (!this.core.EncounteredError) | ||
18517 | { | ||
18518 | WixBundleContainerRow containerRow = (WixBundleContainerRow)this.core.CreateRow(sourceLineNumbers, "WixBundleContainer"); | ||
18519 | containerRow.Id = Compiler.BurnUXContainerId; | ||
18520 | containerRow.Name = "bundle-ux.cab"; | ||
18521 | containerRow.Type = ContainerType.Attached; | ||
18522 | |||
18523 | if (!String.IsNullOrEmpty(id)) | ||
18524 | { | ||
18525 | Row row = this.core.CreateRow(sourceLineNumbers, "WixBootstrapperApplication"); | ||
18526 | row[0] = id; | ||
18527 | } | ||
18528 | } | ||
18529 | } | ||
18530 | |||
18531 | /// <summary> | ||
18532 | /// Parse the BoostrapperApplicationRef element. | ||
18533 | /// </summary> | ||
18534 | /// <param name="node">Element to parse</param> | ||
18535 | private void ParseBootstrapperApplicationRefElement(XElement node) | ||
18536 | { | ||
18537 | SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
18538 | string id = null; | ||
18539 | string previousId = null; | ||
18540 | ComplexReferenceChildType previousType = ComplexReferenceChildType.Unknown; | ||
18541 | |||
18542 | foreach (XAttribute attrib in node.Attributes()) | ||
18543 | { | ||
18544 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
18545 | { | ||
18546 | switch (attrib.Name.LocalName) | ||
18547 | { | ||
18548 | case "Id": | ||
18549 | id = this.core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); | ||
18550 | break; | ||
18551 | default: | ||
18552 | this.core.UnexpectedAttribute(node, attrib); | ||
18553 | break; | ||
18554 | } | ||
18555 | } | ||
18556 | else | ||
18557 | { | ||
18558 | this.core.ParseExtensionAttribute(node, attrib); | ||
18559 | } | ||
18560 | } | ||
18561 | |||
18562 | foreach (XElement child in node.Elements()) | ||
18563 | { | ||
18564 | if (CompilerCore.WixNamespace == child.Name.Namespace) | ||
18565 | { | ||
18566 | switch (child.Name.LocalName) | ||
18567 | { | ||
18568 | case "Payload": | ||
18569 | previousId = this.ParsePayloadElement(child, ComplexReferenceParentType.Container, Compiler.BurnUXContainerId, previousType, previousId); | ||
18570 | previousType = ComplexReferenceChildType.Payload; | ||
18571 | break; | ||
18572 | case "PayloadGroupRef": | ||
18573 | previousId = this.ParsePayloadGroupRefElement(child, ComplexReferenceParentType.Container, Compiler.BurnUXContainerId, previousType, previousId); | ||
18574 | previousType = ComplexReferenceChildType.PayloadGroup; | ||
18575 | break; | ||
18576 | default: | ||
18577 | this.core.UnexpectedElement(node, child); | ||
18578 | break; | ||
18579 | } | ||
18580 | } | ||
18581 | else | ||
18582 | { | ||
18583 | this.core.ParseExtensionElement(node, child); | ||
18584 | } | ||
18585 | } | ||
18586 | |||
18587 | |||
18588 | if (String.IsNullOrEmpty(id)) | ||
18589 | { | ||
18590 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); | ||
18591 | } | ||
18592 | else | ||
18593 | { | ||
18594 | this.core.CreateSimpleReference(sourceLineNumbers, "WixBootstrapperApplication", id); | ||
18595 | } | ||
18596 | } | ||
18597 | |||
18598 | /// <summary> | ||
18599 | /// Parse the OptionalUpdateRegistration element. | ||
18600 | /// </summary> | ||
18601 | /// <param name="node">The element to parse.</param> | ||
18602 | /// <param name="defaultManufacturer">The manufacturer.</param> | ||
18603 | /// <param name="defaultProductFamily">The product family.</param> | ||
18604 | /// <param name="defaultName">The bundle name.</param> | ||
18605 | private void ParseOptionalUpdateRegistrationElement(XElement node, string defaultManufacturer, string defaultProductFamily, string defaultName) | ||
18606 | { | ||
18607 | const string defaultClassification = "Update"; | ||
18608 | |||
18609 | SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
18610 | string manufacturer = null; | ||
18611 | string department = null; | ||
18612 | string productFamily = null; | ||
18613 | string name = null; | ||
18614 | string classification = defaultClassification; | ||
18615 | |||
18616 | foreach (XAttribute attrib in node.Attributes()) | ||
18617 | { | ||
18618 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
18619 | { | ||
18620 | switch (attrib.Name.LocalName) | ||
18621 | { | ||
18622 | case "Manufacturer": | ||
18623 | manufacturer = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
18624 | break; | ||
18625 | case "Department": | ||
18626 | department = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
18627 | break; | ||
18628 | case "ProductFamily": | ||
18629 | productFamily = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
18630 | break; | ||
18631 | case "Name": | ||
18632 | name = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
18633 | break; | ||
18634 | case "Classification": | ||
18635 | classification = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
18636 | break; | ||
18637 | default: | ||
18638 | this.core.UnexpectedAttribute(node, attrib); | ||
18639 | break; | ||
18640 | } | ||
18641 | } | ||
18642 | else | ||
18643 | { | ||
18644 | this.core.ParseExtensionAttribute(node, attrib); | ||
18645 | } | ||
18646 | } | ||
18647 | |||
18648 | if (String.IsNullOrEmpty(manufacturer)) | ||
18649 | { | ||
18650 | if (!String.IsNullOrEmpty(defaultManufacturer)) | ||
18651 | { | ||
18652 | manufacturer = defaultManufacturer; | ||
18653 | } | ||
18654 | else | ||
18655 | { | ||
18656 | this.core.OnMessage(WixErrors.ExpectedAttributeInElementOrParent(sourceLineNumbers, node.Name.LocalName, "Manufacturer", node.Parent.Name.LocalName)); | ||
18657 | } | ||
18658 | } | ||
18659 | |||
18660 | if (String.IsNullOrEmpty(productFamily)) | ||
18661 | { | ||
18662 | if (!String.IsNullOrEmpty(defaultProductFamily)) | ||
18663 | { | ||
18664 | productFamily = defaultProductFamily; | ||
18665 | } | ||
18666 | } | ||
18667 | |||
18668 | if (String.IsNullOrEmpty(name)) | ||
18669 | { | ||
18670 | if (!String.IsNullOrEmpty(defaultName)) | ||
18671 | { | ||
18672 | name = defaultName; | ||
18673 | } | ||
18674 | else | ||
18675 | { | ||
18676 | this.core.OnMessage(WixErrors.ExpectedAttributeInElementOrParent(sourceLineNumbers, node.Name.LocalName, "Name", node.Parent.Name.LocalName)); | ||
18677 | } | ||
18678 | } | ||
18679 | |||
18680 | if (String.IsNullOrEmpty(classification)) | ||
18681 | { | ||
18682 | this.core.OnMessage(WixErrors.IllegalEmptyAttributeValue(sourceLineNumbers, node.Name.LocalName, "Classification", defaultClassification)); | ||
18683 | } | ||
18684 | |||
18685 | this.core.ParseForExtensionElements(node); | ||
18686 | |||
18687 | if (!this.core.EncounteredError) | ||
18688 | { | ||
18689 | Row row = this.core.CreateRow(sourceLineNumbers, "WixUpdateRegistration"); | ||
18690 | row[0] = manufacturer; | ||
18691 | row[1] = department; | ||
18692 | row[2] = productFamily; | ||
18693 | row[3] = name; | ||
18694 | row[4] = classification; | ||
18695 | } | ||
18696 | } | ||
18697 | |||
18698 | /// <summary> | ||
18699 | /// Parse Payload element. | ||
18700 | /// </summary> | ||
18701 | /// <param name="node">Element to parse</param> | ||
18702 | /// <param name="parentType">ComplexReferenceParentType of parent element. (BA or PayloadGroup)</param> | ||
18703 | /// <param name="parentId">Identifier of parent element.</param> | ||
18704 | private string ParsePayloadElement(XElement node, ComplexReferenceParentType parentType, string parentId, ComplexReferenceChildType previousType, string previousId) | ||
18705 | { | ||
18706 | Debug.Assert(ComplexReferenceParentType.PayloadGroup == parentType || ComplexReferenceParentType.Package == parentType || ComplexReferenceParentType.Container == parentType); | ||
18707 | Debug.Assert(ComplexReferenceChildType.Unknown == previousType || ComplexReferenceChildType.PayloadGroup == previousType || ComplexReferenceChildType.Payload == previousType); | ||
18708 | |||
18709 | string id = ParsePayloadElementContent(node, parentType, parentId, previousType, previousId, true); | ||
18710 | Dictionary<string, string> context = new Dictionary<string, string>(); | ||
18711 | context["Id"] = id; | ||
18712 | |||
18713 | foreach (XElement child in node.Elements()) | ||
18714 | { | ||
18715 | if (CompilerCore.WixNamespace == child.Name.Namespace) | ||
18716 | { | ||
18717 | switch (child.Name.LocalName) | ||
18718 | { | ||
18719 | default: | ||
18720 | this.core.UnexpectedElement(node, child); | ||
18721 | break; | ||
18722 | } | ||
18723 | } | ||
18724 | else | ||
18725 | { | ||
18726 | this.core.ParseExtensionElement(node, child, context); | ||
18727 | } | ||
18728 | } | ||
18729 | |||
18730 | return id; | ||
18731 | } | ||
18732 | |||
18733 | /// <summary> | ||
18734 | /// Parse the attributes of the Payload element. | ||
18735 | /// </summary> | ||
18736 | /// <param name="node">Element to parse</param> | ||
18737 | /// <param name="parentType">ComplexReferenceParentType of parent element.</param> | ||
18738 | /// <param name="parentId">Identifier of parent element.</param> | ||
18739 | private string ParsePayloadElementContent(XElement node, ComplexReferenceParentType parentType, string parentId, ComplexReferenceChildType previousType, string previousId, bool required) | ||
18740 | { | ||
18741 | Debug.Assert(ComplexReferenceParentType.PayloadGroup == parentType || ComplexReferenceParentType.Package == parentType || ComplexReferenceParentType.Container == parentType); | ||
18742 | |||
18743 | SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
18744 | YesNoDefaultType compressed = YesNoDefaultType.Default; | ||
18745 | YesNoType enableSignatureVerification = YesNoType.No; | ||
18746 | Identifier id = null; | ||
18747 | string name = null; | ||
18748 | string sourceFile = null; | ||
18749 | string downloadUrl = null; | ||
18750 | Wix.RemotePayload remotePayload = null; | ||
18751 | |||
18752 | // This list lets us evaluate extension attributes *after* all core attributes | ||
18753 | // have been parsed and dealt with, regardless of authoring order. | ||
18754 | List<XAttribute> extensionAttributes = new List<XAttribute>(); | ||
18755 | |||
18756 | foreach (XAttribute attrib in node.Attributes()) | ||
18757 | { | ||
18758 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
18759 | { | ||
18760 | switch (attrib.Name.LocalName) | ||
18761 | { | ||
18762 | case "Id": | ||
18763 | id = this.core.GetAttributeIdentifier(sourceLineNumbers, attrib); | ||
18764 | break; | ||
18765 | case "Compressed": | ||
18766 | compressed = this.core.GetAttributeYesNoDefaultValue(sourceLineNumbers, attrib); | ||
18767 | break; | ||
18768 | case "Name": | ||
18769 | name = this.core.GetAttributeLongFilename(sourceLineNumbers, attrib, false, true); | ||
18770 | break; | ||
18771 | case "SourceFile": | ||
18772 | sourceFile = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
18773 | break; | ||
18774 | case "DownloadUrl": | ||
18775 | downloadUrl = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
18776 | break; | ||
18777 | case "EnableSignatureVerification": | ||
18778 | enableSignatureVerification = this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
18779 | break; | ||
18780 | default: | ||
18781 | this.core.UnexpectedAttribute(node, attrib); | ||
18782 | break; | ||
18783 | } | ||
18784 | } | ||
18785 | else | ||
18786 | { | ||
18787 | extensionAttributes.Add(attrib); | ||
18788 | } | ||
18789 | } | ||
18790 | |||
18791 | if (!required && null == sourceFile) | ||
18792 | { | ||
18793 | // Nothing left to do! | ||
18794 | return null; | ||
18795 | } | ||
18796 | |||
18797 | if (null == id) | ||
18798 | { | ||
18799 | id = this.core.CreateIdentifier("pay", (null != sourceFile) ? sourceFile.ToUpperInvariant() : String.Empty); | ||
18800 | } | ||
18801 | |||
18802 | // Now that the PayloadId is known, we can parse the extension attributes. | ||
18803 | Dictionary<string, string> context = new Dictionary<string, string>(); | ||
18804 | context["Id"] = id.Id; | ||
18805 | |||
18806 | foreach (XAttribute extensionAttribute in extensionAttributes) | ||
18807 | { | ||
18808 | this.core.ParseExtensionAttribute(node, extensionAttribute, context); | ||
18809 | } | ||
18810 | |||
18811 | // We only handle the elements we care about. Let caller handle other children. | ||
18812 | foreach (XElement child in node.Elements(CompilerCore.WixNamespace + "RemotePayload")) | ||
18813 | { | ||
18814 | SourceLineNumber childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child); | ||
18815 | |||
18816 | if (CompilerCore.WixNamespace == node.Name.Namespace && node.Name.LocalName != "ExePackage") | ||
18817 | { | ||
18818 | this.core.OnMessage(WixErrors.RemotePayloadUnsupported(childSourceLineNumbers)); | ||
18819 | continue; | ||
18820 | } | ||
18821 | |||
18822 | if (null != remotePayload) | ||
18823 | { | ||
18824 | this.core.OnMessage(WixErrors.TooManyChildren(childSourceLineNumbers, node.Name.LocalName, child.Name.LocalName)); | ||
18825 | } | ||
18826 | |||
18827 | remotePayload = this.ParseRemotePayloadElement(child); | ||
18828 | } | ||
18829 | |||
18830 | if (null != sourceFile && null != remotePayload) | ||
18831 | { | ||
18832 | this.core.OnMessage(WixErrors.UnexpectedElementWithAttribute(sourceLineNumbers, node.Name.LocalName, "RemotePayload", "SourceFile")); | ||
18833 | } | ||
18834 | else if (null == sourceFile && null == remotePayload) | ||
18835 | { | ||
18836 | this.core.OnMessage(WixErrors.ExpectedAttributeOrElement(sourceLineNumbers, node.Name.LocalName, "SourceFile", "RemotePayload")); | ||
18837 | } | ||
18838 | else if (null == sourceFile) | ||
18839 | { | ||
18840 | sourceFile = String.Empty; | ||
18841 | } | ||
18842 | |||
18843 | if (null == downloadUrl && null != remotePayload) | ||
18844 | { | ||
18845 | this.core.OnMessage(WixErrors.ExpectedAttributeWithElement(sourceLineNumbers, node.Name.LocalName, "DownloadUrl", "RemotePayload")); | ||
18846 | } | ||
18847 | |||
18848 | if (Compiler.BurnUXContainerId == parentId) | ||
18849 | { | ||
18850 | if (compressed == YesNoDefaultType.No) | ||
18851 | { | ||
18852 | core.OnMessage(WixWarnings.UxPayloadsOnlySupportEmbedding(sourceLineNumbers, sourceFile)); | ||
18853 | } | ||
18854 | |||
18855 | compressed = YesNoDefaultType.Yes; | ||
18856 | } | ||
18857 | |||
18858 | this.CreatePayloadRow(sourceLineNumbers, id, name, sourceFile, downloadUrl, parentType, parentId, previousType, previousId, compressed, enableSignatureVerification, null, null, remotePayload); | ||
18859 | |||
18860 | return id.Id; | ||
18861 | } | ||
18862 | |||
18863 | private Wix.RemotePayload ParseRemotePayloadElement(XElement node) | ||
18864 | { | ||
18865 | SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
18866 | Wix.RemotePayload remotePayload = new Wix.RemotePayload(); | ||
18867 | |||
18868 | foreach (XAttribute attrib in node.Attributes()) | ||
18869 | { | ||
18870 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
18871 | { | ||
18872 | switch (attrib.Name.LocalName) | ||
18873 | { | ||
18874 | case "CertificatePublicKey": | ||
18875 | remotePayload.CertificatePublicKey = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
18876 | break; | ||
18877 | case "CertificateThumbprint": | ||
18878 | remotePayload.CertificateThumbprint = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
18879 | break; | ||
18880 | case "Description": | ||
18881 | remotePayload.Description = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
18882 | break; | ||
18883 | case "Hash": | ||
18884 | remotePayload.Hash = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
18885 | break; | ||
18886 | case "ProductName": | ||
18887 | remotePayload.ProductName = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
18888 | break; | ||
18889 | case "Size": | ||
18890 | remotePayload.Size = this.core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, int.MaxValue); | ||
18891 | break; | ||
18892 | case "Version": | ||
18893 | remotePayload.Version = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
18894 | break; | ||
18895 | default: | ||
18896 | this.core.UnexpectedAttribute(node, attrib); | ||
18897 | break; | ||
18898 | } | ||
18899 | } | ||
18900 | else | ||
18901 | { | ||
18902 | this.core.ParseExtensionAttribute(node, attrib); | ||
18903 | } | ||
18904 | } | ||
18905 | |||
18906 | if (String.IsNullOrEmpty(remotePayload.ProductName)) | ||
18907 | { | ||
18908 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "ProductName")); | ||
18909 | } | ||
18910 | |||
18911 | if (String.IsNullOrEmpty(remotePayload.Description)) | ||
18912 | { | ||
18913 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Description")); | ||
18914 | } | ||
18915 | |||
18916 | if (String.IsNullOrEmpty(remotePayload.Hash)) | ||
18917 | { | ||
18918 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Hash")); | ||
18919 | } | ||
18920 | |||
18921 | if (0 == remotePayload.Size) | ||
18922 | { | ||
18923 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Size")); | ||
18924 | } | ||
18925 | |||
18926 | if (String.IsNullOrEmpty(remotePayload.Version)) | ||
18927 | { | ||
18928 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Version")); | ||
18929 | } | ||
18930 | |||
18931 | return remotePayload; | ||
18932 | } | ||
18933 | |||
18934 | /// <summary> | ||
18935 | /// Creates the row for a Payload. | ||
18936 | /// </summary> | ||
18937 | /// <param name="node">Element to parse</param> | ||
18938 | /// <param name="parentType">ComplexReferenceParentType of parent element</param> | ||
18939 | /// <param name="parentId">Identifier of parent element.</param> | ||
18940 | private WixBundlePayloadRow CreatePayloadRow(SourceLineNumber sourceLineNumbers, Identifier id, string name, string sourceFile, string downloadUrl, ComplexReferenceParentType parentType, | ||
18941 | string parentId, ComplexReferenceChildType previousType, string previousId, YesNoDefaultType compressed, YesNoType enableSignatureVerification, string displayName, string description, | ||
18942 | Wix.RemotePayload remotePayload) | ||
18943 | { | ||
18944 | WixBundlePayloadRow row = null; | ||
18945 | |||
18946 | if (!this.core.EncounteredError) | ||
18947 | { | ||
18948 | row = (WixBundlePayloadRow)this.core.CreateRow(sourceLineNumbers, "WixBundlePayload", id); | ||
18949 | row.Name = String.IsNullOrEmpty(name) ? Path.GetFileName(sourceFile) : name; | ||
18950 | row.SourceFile = sourceFile; | ||
18951 | row.DownloadUrl = downloadUrl; | ||
18952 | row.Compressed = compressed; | ||
18953 | row.UnresolvedSourceFile = sourceFile; // duplicate of sourceFile but in a string column so it won't get resolved to a full path during binding. | ||
18954 | row.DisplayName = displayName; | ||
18955 | row.Description = description; | ||
18956 | row.EnableSignatureValidation = (YesNoType.Yes == enableSignatureVerification); | ||
18957 | |||
18958 | if (null != remotePayload) | ||
18959 | { | ||
18960 | row.Description = remotePayload.Description; | ||
18961 | row.DisplayName = remotePayload.ProductName; | ||
18962 | row.Hash = remotePayload.Hash; | ||
18963 | row.PublicKey = remotePayload.CertificatePublicKey; | ||
18964 | row.Thumbprint = remotePayload.CertificateThumbprint; | ||
18965 | row.FileSize = remotePayload.Size; | ||
18966 | row.Version = remotePayload.Version; | ||
18967 | } | ||
18968 | |||
18969 | this.CreateGroupAndOrderingRows(sourceLineNumbers, parentType, parentId, ComplexReferenceChildType.Payload, id.Id, previousType, previousId); | ||
18970 | } | ||
18971 | |||
18972 | return row; | ||
18973 | } | ||
18974 | |||
18975 | /// <summary> | ||
18976 | /// Parse PayloadGroup element. | ||
18977 | /// </summary> | ||
18978 | /// <param name="node">Element to parse</param> | ||
18979 | /// <param name="parentType">Optional ComplexReferenceParentType of parent element. (typically another PayloadGroup)</param> | ||
18980 | /// <param name="parentId">Identifier of parent element.</param> | ||
18981 | private void ParsePayloadGroupElement(XElement node, ComplexReferenceParentType parentType, string parentId) | ||
18982 | { | ||
18983 | Debug.Assert(ComplexReferenceParentType.Unknown == parentType || ComplexReferenceParentType.Layout == parentType || ComplexReferenceParentType.PayloadGroup == parentType); | ||
18984 | |||
18985 | SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
18986 | Identifier id = null; | ||
18987 | |||
18988 | foreach (XAttribute attrib in node.Attributes()) | ||
18989 | { | ||
18990 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
18991 | { | ||
18992 | switch (attrib.Name.LocalName) | ||
18993 | { | ||
18994 | case "Id": | ||
18995 | id = this.core.GetAttributeIdentifier(sourceLineNumbers, attrib); | ||
18996 | break; | ||
18997 | default: | ||
18998 | this.core.UnexpectedAttribute(node, attrib); | ||
18999 | break; | ||
19000 | } | ||
19001 | } | ||
19002 | else | ||
19003 | { | ||
19004 | this.core.ParseExtensionAttribute(node, attrib); | ||
19005 | } | ||
19006 | } | ||
19007 | |||
19008 | if (null == id) | ||
19009 | { | ||
19010 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); | ||
19011 | id = Identifier.Invalid; | ||
19012 | } | ||
19013 | |||
19014 | ComplexReferenceChildType previousType = ComplexReferenceChildType.Unknown; | ||
19015 | string previousId = null; | ||
19016 | foreach (XElement child in node.Elements()) | ||
19017 | { | ||
19018 | if (CompilerCore.WixNamespace == child.Name.Namespace) | ||
19019 | { | ||
19020 | switch (child.Name.LocalName) | ||
19021 | { | ||
19022 | case "Payload": | ||
19023 | previousId = this.ParsePayloadElement(child, ComplexReferenceParentType.PayloadGroup, id.Id, previousType, previousId); | ||
19024 | previousType = ComplexReferenceChildType.Payload; | ||
19025 | break; | ||
19026 | case "PayloadGroupRef": | ||
19027 | previousId = this.ParsePayloadGroupRefElement(child, ComplexReferenceParentType.PayloadGroup, id.Id, previousType, previousId); | ||
19028 | previousType = ComplexReferenceChildType.PayloadGroup; | ||
19029 | break; | ||
19030 | default: | ||
19031 | this.core.UnexpectedElement(node, child); | ||
19032 | break; | ||
19033 | } | ||
19034 | } | ||
19035 | else | ||
19036 | { | ||
19037 | this.core.ParseExtensionElement(node, child); | ||
19038 | } | ||
19039 | } | ||
19040 | |||
19041 | |||
19042 | if (!this.core.EncounteredError) | ||
19043 | { | ||
19044 | this.core.CreateRow(sourceLineNumbers, "WixBundlePayloadGroup", id); | ||
19045 | |||
19046 | this.CreateGroupAndOrderingRows(sourceLineNumbers, parentType, parentId, ComplexReferenceChildType.PayloadGroup, id.Id, ComplexReferenceChildType.Unknown, null); | ||
19047 | } | ||
19048 | } | ||
19049 | |||
19050 | /// <summary> | ||
19051 | /// Parses a payload group reference element. | ||
19052 | /// </summary> | ||
19053 | /// <param name="node">Element to parse.</param> | ||
19054 | /// <param name="parentType">ComplexReferenceParentType of parent element (BA or PayloadGroup).</param> | ||
19055 | /// <param name="parentId">Identifier of parent element.</param> | ||
19056 | private string ParsePayloadGroupRefElement(XElement node, ComplexReferenceParentType parentType, string parentId, ComplexReferenceChildType previousType, string previousId) | ||
19057 | { | ||
19058 | Debug.Assert(ComplexReferenceParentType.Layout == parentType || ComplexReferenceParentType.PayloadGroup == parentType || ComplexReferenceParentType.Package == parentType || ComplexReferenceParentType.Container == parentType); | ||
19059 | Debug.Assert(ComplexReferenceChildType.Unknown == previousType || ComplexReferenceChildType.PayloadGroup == previousType || ComplexReferenceChildType.Payload == previousType); | ||
19060 | |||
19061 | SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
19062 | string id = null; | ||
19063 | |||
19064 | foreach (XAttribute attrib in node.Attributes()) | ||
19065 | { | ||
19066 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
19067 | { | ||
19068 | switch (attrib.Name.LocalName) | ||
19069 | { | ||
19070 | case "Id": | ||
19071 | id = this.core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); | ||
19072 | this.core.CreateSimpleReference(sourceLineNumbers, "WixBundlePayloadGroup", id); | ||
19073 | break; | ||
19074 | default: | ||
19075 | this.core.UnexpectedAttribute(node, attrib); | ||
19076 | break; | ||
19077 | } | ||
19078 | } | ||
19079 | else | ||
19080 | { | ||
19081 | this.core.ParseExtensionAttribute(node, attrib); | ||
19082 | } | ||
19083 | } | ||
19084 | |||
19085 | if (null == id) | ||
19086 | { | ||
19087 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); | ||
19088 | } | ||
19089 | |||
19090 | this.core.ParseForExtensionElements(node); | ||
19091 | |||
19092 | this.CreateGroupAndOrderingRows(sourceLineNumbers, parentType, parentId, ComplexReferenceChildType.PayloadGroup, id, previousType, previousId); | ||
19093 | |||
19094 | return id; | ||
19095 | } | ||
19096 | |||
19097 | /// <summary> | ||
19098 | /// Creates group and ordering information. | ||
19099 | /// </summary> | ||
19100 | /// <param name="sourceLineNumbers">Source line numbers.</param> | ||
19101 | /// <param name="parentType">Type of parent group, if known.</param> | ||
19102 | /// <param name="parentId">Identifier of parent group, if known.</param> | ||
19103 | /// <param name="type">Type of this item.</param> | ||
19104 | /// <param name="id">Identifier for this item.</param> | ||
19105 | /// <param name="previousType">Type of previous item, if known.</param> | ||
19106 | /// <param name="previousId">Identifier of previous item, if known</param> | ||
19107 | private void CreateGroupAndOrderingRows(SourceLineNumber sourceLineNumbers, | ||
19108 | ComplexReferenceParentType parentType, string parentId, | ||
19109 | ComplexReferenceChildType type, string id, | ||
19110 | ComplexReferenceChildType previousType, string previousId) | ||
19111 | { | ||
19112 | if (ComplexReferenceParentType.Unknown != parentType && null != parentId) | ||
19113 | { | ||
19114 | this.core.CreateWixGroupRow(sourceLineNumbers, parentType, parentId, type, id); | ||
19115 | } | ||
19116 | |||
19117 | if (ComplexReferenceChildType.Unknown != previousType && null != previousId) | ||
19118 | { | ||
19119 | this.CreateWixOrderingRow(sourceLineNumbers, type, id, previousType, previousId); | ||
19120 | } | ||
19121 | } | ||
19122 | |||
19123 | /// <summary> | ||
19124 | /// Parse ExitCode element. | ||
19125 | /// </summary> | ||
19126 | /// <param name="node">Element to parse</param> | ||
19127 | /// <param name="packageId">Id of parent element</param> | ||
19128 | private void ParseExitCodeElement(XElement node, string packageId) | ||
19129 | { | ||
19130 | SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
19131 | int value = CompilerConstants.IntegerNotSet; | ||
19132 | ExitCodeBehaviorType behavior = ExitCodeBehaviorType.NotSet; | ||
19133 | |||
19134 | foreach (XAttribute attrib in node.Attributes()) | ||
19135 | { | ||
19136 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
19137 | { | ||
19138 | switch (attrib.Name.LocalName) | ||
19139 | { | ||
19140 | case "Value": | ||
19141 | value = this.core.GetAttributeIntegerValue(sourceLineNumbers, attrib, int.MinValue + 2, int.MaxValue); | ||
19142 | break; | ||
19143 | case "Behavior": | ||
19144 | string behaviorString = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
19145 | if (!Enum.TryParse<ExitCodeBehaviorType>(behaviorString, true, out behavior)) | ||
19146 | { | ||
19147 | this.core.OnMessage(WixErrors.IllegalAttributeValueWithLegalList(sourceLineNumbers, node.Name.LocalName, "Behavior", behaviorString, "success, error, scheduleReboot, forceReboot")); | ||
19148 | } | ||
19149 | break; | ||
19150 | default: | ||
19151 | this.core.UnexpectedAttribute(node, attrib); | ||
19152 | break; | ||
19153 | } | ||
19154 | } | ||
19155 | else | ||
19156 | { | ||
19157 | this.core.ParseExtensionAttribute(node, attrib); | ||
19158 | } | ||
19159 | } | ||
19160 | |||
19161 | if (ExitCodeBehaviorType.NotSet == behavior) | ||
19162 | { | ||
19163 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Behavior")); | ||
19164 | } | ||
19165 | |||
19166 | this.core.ParseForExtensionElements(node); | ||
19167 | |||
19168 | if (!this.core.EncounteredError) | ||
19169 | { | ||
19170 | WixBundlePackageExitCodeRow row = (WixBundlePackageExitCodeRow)this.core.CreateRow(sourceLineNumbers, "WixBundlePackageExitCode"); | ||
19171 | row.ChainPackageId = packageId; | ||
19172 | row.Code = value; | ||
19173 | row.Behavior = behavior; | ||
19174 | } | ||
19175 | } | ||
19176 | |||
19177 | /// <summary> | ||
19178 | /// Parse Chain element. | ||
19179 | /// </summary> | ||
19180 | /// <param name="node">Element to parse</param> | ||
19181 | private void ParseChainElement(XElement node) | ||
19182 | { | ||
19183 | SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
19184 | WixChainAttributes attributes = WixChainAttributes.None; | ||
19185 | |||
19186 | foreach (XAttribute attrib in node.Attributes()) | ||
19187 | { | ||
19188 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
19189 | { | ||
19190 | switch (attrib.Name.LocalName) | ||
19191 | { | ||
19192 | case "DisableRollback": | ||
19193 | if (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) | ||
19194 | { | ||
19195 | attributes |= WixChainAttributes.DisableRollback; | ||
19196 | } | ||
19197 | break; | ||
19198 | case "DisableSystemRestore": | ||
19199 | if (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) | ||
19200 | { | ||
19201 | attributes |= WixChainAttributes.DisableSystemRestore; | ||
19202 | } | ||
19203 | break; | ||
19204 | case "ParallelCache": | ||
19205 | if (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) | ||
19206 | { | ||
19207 | attributes |= WixChainAttributes.ParallelCache; | ||
19208 | } | ||
19209 | break; | ||
19210 | default: | ||
19211 | this.core.UnexpectedAttribute(node, attrib); | ||
19212 | break; | ||
19213 | } | ||
19214 | } | ||
19215 | else | ||
19216 | { | ||
19217 | this.core.ParseExtensionAttribute(node, attrib); | ||
19218 | } | ||
19219 | } | ||
19220 | |||
19221 | // Ensure there is always a rollback boundary at the beginning of the chain. | ||
19222 | this.CreateRollbackBoundary(sourceLineNumbers, new Identifier("WixDefaultBoundary", AccessModifier.Public), YesNoType.Yes, YesNoType.No, ComplexReferenceParentType.PackageGroup, "WixChain", ComplexReferenceChildType.Unknown, null); | ||
19223 | |||
19224 | string previousId = "WixDefaultBoundary"; | ||
19225 | ComplexReferenceChildType previousType = ComplexReferenceChildType.Package; | ||
19226 | |||
19227 | foreach (XElement child in node.Elements()) | ||
19228 | { | ||
19229 | if (CompilerCore.WixNamespace == child.Name.Namespace) | ||
19230 | { | ||
19231 | switch (child.Name.LocalName) | ||
19232 | { | ||
19233 | case "MsiPackage": | ||
19234 | previousId = this.ParseMsiPackageElement(child, ComplexReferenceParentType.PackageGroup, "WixChain", previousType, previousId); | ||
19235 | previousType = ComplexReferenceChildType.Package; | ||
19236 | break; | ||
19237 | case "MspPackage": | ||
19238 | previousId = this.ParseMspPackageElement(child, ComplexReferenceParentType.PackageGroup, "WixChain", previousType, previousId); | ||
19239 | previousType = ComplexReferenceChildType.Package; | ||
19240 | break; | ||
19241 | case "MsuPackage": | ||
19242 | previousId = this.ParseMsuPackageElement(child, ComplexReferenceParentType.PackageGroup, "WixChain", previousType, previousId); | ||
19243 | previousType = ComplexReferenceChildType.Package; | ||
19244 | break; | ||
19245 | case "ExePackage": | ||
19246 | previousId = this.ParseExePackageElement(child, ComplexReferenceParentType.PackageGroup, "WixChain", previousType, previousId); | ||
19247 | previousType = ComplexReferenceChildType.Package; | ||
19248 | break; | ||
19249 | case "RollbackBoundary": | ||
19250 | previousId = this.ParseRollbackBoundaryElement(child, ComplexReferenceParentType.PackageGroup, "WixChain", previousType, previousId); | ||
19251 | previousType = ComplexReferenceChildType.Package; | ||
19252 | break; | ||
19253 | case "PackageGroupRef": | ||
19254 | previousId = this.ParsePackageGroupRefElement(child, ComplexReferenceParentType.PackageGroup, "WixChain", previousType, previousId); | ||
19255 | previousType = ComplexReferenceChildType.PackageGroup; | ||
19256 | break; | ||
19257 | default: | ||
19258 | this.core.UnexpectedElement(node, child); | ||
19259 | break; | ||
19260 | } | ||
19261 | } | ||
19262 | else | ||
19263 | { | ||
19264 | this.core.ParseExtensionElement(node, child); | ||
19265 | } | ||
19266 | } | ||
19267 | |||
19268 | |||
19269 | if (null == previousId) | ||
19270 | { | ||
19271 | this.core.OnMessage(WixErrors.ExpectedElement(sourceLineNumbers, node.Name.LocalName, "MsiPackage", "ExePackage", "PackageGroupRef")); | ||
19272 | } | ||
19273 | |||
19274 | if (!this.core.EncounteredError) | ||
19275 | { | ||
19276 | WixChainRow row = (WixChainRow)this.core.CreateRow(sourceLineNumbers, "WixChain"); | ||
19277 | row.Attributes = attributes; | ||
19278 | } | ||
19279 | } | ||
19280 | |||
19281 | /// <summary> | ||
19282 | /// Parse MsiPackage element | ||
19283 | /// </summary> | ||
19284 | /// <param name="node">Element to parse</param> | ||
19285 | /// <param name="parentType">Type of parent group, if known.</param> | ||
19286 | /// <param name="parentId">Identifier of parent group, if known.</param> | ||
19287 | /// <param name="previousType">Type of previous item, if known.</param> | ||
19288 | /// <param name="previousId">Identifier of previous item, if known</param> | ||
19289 | /// <returns>Identifier for package element.</returns> | ||
19290 | private string ParseMsiPackageElement(XElement node, ComplexReferenceParentType parentType, string parentId, ComplexReferenceChildType previousType, string previousId) | ||
19291 | { | ||
19292 | return ParseChainPackage(node, WixBundlePackageType.Msi, parentType, parentId, previousType, previousId); | ||
19293 | } | ||
19294 | |||
19295 | /// <summary> | ||
19296 | /// Parse MspPackage element | ||
19297 | /// </summary> | ||
19298 | /// <param name="node">Element to parse</param> | ||
19299 | /// <param name="parentType">Type of parent group, if known.</param> | ||
19300 | /// <param name="parentId">Identifier of parent group, if known.</param> | ||
19301 | /// <param name="previousType">Type of previous item, if known.</param> | ||
19302 | /// <param name="previousId">Identifier of previous item, if known</param> | ||
19303 | /// <returns>Identifier for package element.</returns> | ||
19304 | private string ParseMspPackageElement(XElement node, ComplexReferenceParentType parentType, string parentId, ComplexReferenceChildType previousType, string previousId) | ||
19305 | { | ||
19306 | return ParseChainPackage(node, WixBundlePackageType.Msp, parentType, parentId, previousType, previousId); | ||
19307 | } | ||
19308 | |||
19309 | /// <summary> | ||
19310 | /// Parse MsuPackage element | ||
19311 | /// </summary> | ||
19312 | /// <param name="node">Element to parse</param> | ||
19313 | /// <param name="parentType">Type of parent group, if known.</param> | ||
19314 | /// <param name="parentId">Identifier of parent group, if known.</param> | ||
19315 | /// <param name="previousType">Type of previous item, if known.</param> | ||
19316 | /// <param name="previousId">Identifier of previous item, if known</param> | ||
19317 | /// <returns>Identifier for package element.</returns> | ||
19318 | private string ParseMsuPackageElement(XElement node, ComplexReferenceParentType parentType, string parentId, ComplexReferenceChildType previousType, string previousId) | ||
19319 | { | ||
19320 | return ParseChainPackage(node, WixBundlePackageType.Msu, parentType, parentId, previousType, previousId); | ||
19321 | } | ||
19322 | |||
19323 | /// <summary> | ||
19324 | /// Parse ExePackage element | ||
19325 | /// </summary> | ||
19326 | /// <param name="node">Element to parse</param> | ||
19327 | /// <param name="parentType">Type of parent group, if known.</param> | ||
19328 | /// <param name="parentId">Identifier of parent group, if known.</param> | ||
19329 | /// <param name="previousType">Type of previous item, if known.</param> | ||
19330 | /// <param name="previousId">Identifier of previous item, if known</param> | ||
19331 | /// <returns>Identifier for package element.</returns> | ||
19332 | private string ParseExePackageElement(XElement node, ComplexReferenceParentType parentType, string parentId, ComplexReferenceChildType previousType, string previousId) | ||
19333 | { | ||
19334 | return ParseChainPackage(node, WixBundlePackageType.Exe, parentType, parentId, previousType, previousId); | ||
19335 | } | ||
19336 | |||
19337 | /// <summary> | ||
19338 | /// Parse RollbackBoundary element | ||
19339 | /// </summary> | ||
19340 | /// <param name="node">Element to parse</param> | ||
19341 | /// <param name="parentType">Type of parent group, if known.</param> | ||
19342 | /// <param name="parentId">Identifier of parent group, if known.</param> | ||
19343 | /// <param name="previousType">Type of previous item, if known.</param> | ||
19344 | /// <param name="previousId">Identifier of previous item, if known</param> | ||
19345 | /// <returns>Identifier for package element.</returns> | ||
19346 | private string ParseRollbackBoundaryElement(XElement node, ComplexReferenceParentType parentType, string parentId, ComplexReferenceChildType previousType, string previousId) | ||
19347 | { | ||
19348 | Debug.Assert(ComplexReferenceParentType.PackageGroup == parentType); | ||
19349 | Debug.Assert(ComplexReferenceChildType.Unknown == previousType || ComplexReferenceChildType.PackageGroup == previousType || ComplexReferenceChildType.Package == previousType); | ||
19350 | |||
19351 | SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
19352 | Identifier id = null; | ||
19353 | YesNoType vital = YesNoType.Yes; | ||
19354 | YesNoType transaction = YesNoType.No; | ||
19355 | |||
19356 | // This list lets us evaluate extension attributes *after* all core attributes | ||
19357 | // have been parsed and dealt with, regardless of authoring order. | ||
19358 | List<XAttribute> extensionAttributes = new List<XAttribute>(); | ||
19359 | |||
19360 | foreach (XAttribute attrib in node.Attributes()) | ||
19361 | { | ||
19362 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
19363 | { | ||
19364 | bool allowed = true; | ||
19365 | switch (attrib.Name.LocalName) | ||
19366 | { | ||
19367 | case "Id": | ||
19368 | id = this.core.GetAttributeIdentifier(sourceLineNumbers, attrib); | ||
19369 | break; | ||
19370 | case "Vital": | ||
19371 | vital = this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
19372 | break; | ||
19373 | case "Transaction": | ||
19374 | transaction = this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
19375 | break; | ||
19376 | default: | ||
19377 | allowed = false; | ||
19378 | break; | ||
19379 | } | ||
19380 | |||
19381 | if (!allowed) | ||
19382 | { | ||
19383 | this.core.UnexpectedAttribute(node, attrib); | ||
19384 | } | ||
19385 | } | ||
19386 | else | ||
19387 | { | ||
19388 | // Save the extension attributes for later... | ||
19389 | extensionAttributes.Add(attrib); | ||
19390 | } | ||
19391 | } | ||
19392 | |||
19393 | if (null == id) | ||
19394 | { | ||
19395 | if (!String.IsNullOrEmpty(previousId)) | ||
19396 | { | ||
19397 | id = this.core.CreateIdentifier("rba", previousId); | ||
19398 | } | ||
19399 | |||
19400 | if (null == id) | ||
19401 | { | ||
19402 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); | ||
19403 | id = Identifier.Invalid; | ||
19404 | } | ||
19405 | else if (!Common.IsIdentifier(id.Id)) | ||
19406 | { | ||
19407 | this.core.OnMessage(WixErrors.IllegalIdentifier(sourceLineNumbers, node.Name.LocalName, "Id", id.Id)); | ||
19408 | } | ||
19409 | } | ||
19410 | |||
19411 | // Now that the rollback identifier is known, we can parse the extension attributes... | ||
19412 | Dictionary<string, string> contextValues = new Dictionary<string, string>(); | ||
19413 | contextValues["RollbackBoundaryId"] = id.Id; | ||
19414 | foreach (XAttribute attribute in extensionAttributes) | ||
19415 | { | ||
19416 | this.core.ParseExtensionAttribute(node, attribute, contextValues); | ||
19417 | } | ||
19418 | |||
19419 | this.core.ParseForExtensionElements(node); | ||
19420 | |||
19421 | if (!this.core.EncounteredError) | ||
19422 | { | ||
19423 | this.CreateRollbackBoundary(sourceLineNumbers, id, vital, transaction, parentType, parentId, previousType, previousId); | ||
19424 | } | ||
19425 | |||
19426 | return id.Id; | ||
19427 | } | ||
19428 | |||
19429 | /// <summary> | ||
19430 | /// Parses one of the ChainPackage elements | ||
19431 | /// </summary> | ||
19432 | /// <param name="node">Element to parse</param> | ||
19433 | /// <param name="packageType">Type of package to parse</param> | ||
19434 | /// <param name="parentType">Type of parent group, if known.</param> | ||
19435 | /// <param name="parentId">Identifier of parent group, if known.</param> | ||
19436 | /// <param name="previousType">Type of previous item, if known.</param> | ||
19437 | /// <param name="previousId">Identifier of previous item, if known</param> | ||
19438 | /// <returns>Identifier for package element.</returns> | ||
19439 | /// <remarks>This method contains the shared logic for parsing all of the ChainPackage | ||
19440 | /// types, as there is more in common between them than different.</remarks> | ||
19441 | private string ParseChainPackage(XElement node, WixBundlePackageType packageType, ComplexReferenceParentType parentType, string parentId, ComplexReferenceChildType previousType, string previousId) | ||
19442 | { | ||
19443 | Debug.Assert(ComplexReferenceParentType.PackageGroup == parentType); | ||
19444 | Debug.Assert(ComplexReferenceChildType.Unknown == previousType || ComplexReferenceChildType.PackageGroup == previousType || ComplexReferenceChildType.Package == previousType); | ||
19445 | |||
19446 | SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
19447 | Identifier id = null; | ||
19448 | string name = null; | ||
19449 | string sourceFile = null; | ||
19450 | string downloadUrl = null; | ||
19451 | string after = null; | ||
19452 | string installCondition = null; | ||
19453 | YesNoAlwaysType cache = YesNoAlwaysType.Yes; // the default is to cache everything in tradeoff for stability over disk space. | ||
19454 | string cacheId = null; | ||
19455 | string description = null; | ||
19456 | string displayName = null; | ||
19457 | string logPathVariable = (packageType == WixBundlePackageType.Msu) ? String.Empty : null; | ||
19458 | string rollbackPathVariable = (packageType == WixBundlePackageType.Msu) ? String.Empty : null; | ||
19459 | YesNoType permanent = YesNoType.NotSet; | ||
19460 | YesNoType visible = YesNoType.NotSet; | ||
19461 | YesNoType vital = YesNoType.Yes; | ||
19462 | string installCommand = null; | ||
19463 | string repairCommand = null; | ||
19464 | YesNoType repairable = YesNoType.NotSet; | ||
19465 | string uninstallCommand = null; | ||
19466 | YesNoDefaultType perMachine = YesNoDefaultType.NotSet; | ||
19467 | string detectCondition = null; | ||
19468 | string protocol = null; | ||
19469 | int installSize = CompilerConstants.IntegerNotSet; | ||
19470 | string msuKB = null; | ||
19471 | YesNoType suppressLooseFilePayloadGeneration = YesNoType.NotSet; | ||
19472 | YesNoType enableSignatureVerification = YesNoType.No; | ||
19473 | YesNoDefaultType compressed = YesNoDefaultType.Default; | ||
19474 | YesNoType displayInternalUI = YesNoType.NotSet; | ||
19475 | YesNoType enableFeatureSelection = YesNoType.NotSet; | ||
19476 | YesNoType forcePerMachine = YesNoType.NotSet; | ||
19477 | Wix.RemotePayload remotePayload = null; | ||
19478 | YesNoType slipstream = YesNoType.NotSet; | ||
19479 | |||
19480 | string[] expectedNetFx4Args = new string[] { "/q", "/norestart", "/chainingpackage" }; | ||
19481 | |||
19482 | // This list lets us evaluate extension attributes *after* all core attributes | ||
19483 | // have been parsed and dealt with, regardless of authoring order. | ||
19484 | List<XAttribute> extensionAttributes = new List<XAttribute>(); | ||
19485 | |||
19486 | foreach (XAttribute attrib in node.Attributes()) | ||
19487 | { | ||
19488 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
19489 | { | ||
19490 | bool allowed = true; | ||
19491 | switch (attrib.Name.LocalName) | ||
19492 | { | ||
19493 | case "Id": | ||
19494 | id = this.core.GetAttributeIdentifier(sourceLineNumbers, attrib); | ||
19495 | break; | ||
19496 | case "Name": | ||
19497 | name = this.core.GetAttributeLongFilename(sourceLineNumbers, attrib, false, true); | ||
19498 | if (!this.core.IsValidLongFilename(name, false, true)) | ||
19499 | { | ||
19500 | this.core.OnMessage(WixErrors.IllegalLongFilename(sourceLineNumbers, node.Name.LocalName, "Name", name)); | ||
19501 | } | ||
19502 | break; | ||
19503 | case "SourceFile": | ||
19504 | sourceFile = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
19505 | break; | ||
19506 | case "DownloadUrl": | ||
19507 | downloadUrl = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
19508 | break; | ||
19509 | case "After": | ||
19510 | after = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
19511 | break; | ||
19512 | case "InstallCondition": | ||
19513 | installCondition = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
19514 | break; | ||
19515 | case "Cache": | ||
19516 | cache = this.core.GetAttributeYesNoAlwaysValue(sourceLineNumbers, attrib); | ||
19517 | break; | ||
19518 | case "CacheId": | ||
19519 | cacheId = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
19520 | break; | ||
19521 | case "Description": | ||
19522 | description = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
19523 | break; | ||
19524 | case "DisplayName": | ||
19525 | displayName = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
19526 | break; | ||
19527 | case "DisplayInternalUI": | ||
19528 | displayInternalUI = this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
19529 | allowed = (packageType == WixBundlePackageType.Msi || packageType == WixBundlePackageType.Msp); | ||
19530 | break; | ||
19531 | case "EnableFeatureSelection": | ||
19532 | enableFeatureSelection = this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
19533 | allowed = (packageType == WixBundlePackageType.Msi); | ||
19534 | break; | ||
19535 | case "ForcePerMachine": | ||
19536 | forcePerMachine = this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
19537 | allowed = (packageType == WixBundlePackageType.Msi); | ||
19538 | break; | ||
19539 | case "LogPathVariable": | ||
19540 | logPathVariable = this.core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty); | ||
19541 | break; | ||
19542 | case "RollbackLogPathVariable": | ||
19543 | rollbackPathVariable = this.core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty); | ||
19544 | break; | ||
19545 | case "Permanent": | ||
19546 | permanent = this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
19547 | break; | ||
19548 | case "Visible": | ||
19549 | visible = this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
19550 | allowed = (packageType == WixBundlePackageType.Msi); | ||
19551 | break; | ||
19552 | case "Vital": | ||
19553 | vital = this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
19554 | break; | ||
19555 | case "InstallCommand": | ||
19556 | installCommand = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
19557 | allowed = (packageType == WixBundlePackageType.Exe); | ||
19558 | break; | ||
19559 | case "RepairCommand": | ||
19560 | repairCommand = this.core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty); | ||
19561 | repairable = YesNoType.Yes; | ||
19562 | allowed = (packageType == WixBundlePackageType.Exe); | ||
19563 | break; | ||
19564 | case "UninstallCommand": | ||
19565 | uninstallCommand = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
19566 | allowed = (packageType == WixBundlePackageType.Exe); | ||
19567 | break; | ||
19568 | case "PerMachine": | ||
19569 | perMachine = this.core.GetAttributeYesNoDefaultValue(sourceLineNumbers, attrib); | ||
19570 | allowed = (packageType == WixBundlePackageType.Exe || packageType == WixBundlePackageType.Msp); | ||
19571 | break; | ||
19572 | case "DetectCondition": | ||
19573 | detectCondition = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
19574 | allowed = (packageType == WixBundlePackageType.Exe || packageType == WixBundlePackageType.Msu); | ||
19575 | break; | ||
19576 | case "Protocol": | ||
19577 | protocol = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
19578 | allowed = (packageType == WixBundlePackageType.Exe); | ||
19579 | break; | ||
19580 | case "InstallSize": | ||
19581 | installSize = this.core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, int.MaxValue); | ||
19582 | break; | ||
19583 | case "KB": | ||
19584 | msuKB = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
19585 | allowed = (packageType == WixBundlePackageType.Msu); | ||
19586 | break; | ||
19587 | case "Compressed": | ||
19588 | compressed = this.core.GetAttributeYesNoDefaultValue(sourceLineNumbers, attrib); | ||
19589 | break; | ||
19590 | case "SuppressLooseFilePayloadGeneration": | ||
19591 | this.core.OnMessage(WixWarnings.DeprecatedAttribute(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName)); | ||
19592 | suppressLooseFilePayloadGeneration = this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
19593 | allowed = (packageType == WixBundlePackageType.Msi); | ||
19594 | break; | ||
19595 | case "EnableSignatureVerification": | ||
19596 | enableSignatureVerification = this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
19597 | break; | ||
19598 | case "Slipstream": | ||
19599 | slipstream = this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
19600 | allowed = (packageType == WixBundlePackageType.Msp); | ||
19601 | break; | ||
19602 | default: | ||
19603 | allowed = false; | ||
19604 | break; | ||
19605 | } | ||
19606 | |||
19607 | if (!allowed) | ||
19608 | { | ||
19609 | this.core.UnexpectedAttribute(node, attrib); | ||
19610 | } | ||
19611 | } | ||
19612 | else | ||
19613 | { | ||
19614 | // Save the extension attributes for later... | ||
19615 | extensionAttributes.Add(attrib); | ||
19616 | } | ||
19617 | } | ||
19618 | |||
19619 | // We need to handle RemotePayload up front because it effects value of sourceFile which is used in Id generation. Id is needed by other child elements. | ||
19620 | foreach (XElement child in node.Elements(CompilerCore.WixNamespace + "RemotePayload")) | ||
19621 | { | ||
19622 | SourceLineNumber childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child); | ||
19623 | |||
19624 | if (CompilerCore.WixNamespace == node.Name.Namespace && node.Name.LocalName != "ExePackage" && node.Name.LocalName != "MsuPackage") | ||
19625 | { | ||
19626 | this.core.OnMessage(WixErrors.RemotePayloadUnsupported(childSourceLineNumbers)); | ||
19627 | continue; | ||
19628 | } | ||
19629 | |||
19630 | if (null != remotePayload) | ||
19631 | { | ||
19632 | this.core.OnMessage(WixErrors.TooManyChildren(childSourceLineNumbers, node.Name.LocalName, child.Name.LocalName)); | ||
19633 | } | ||
19634 | |||
19635 | remotePayload = this.ParseRemotePayloadElement(child); | ||
19636 | } | ||
19637 | |||
19638 | if (String.IsNullOrEmpty(sourceFile)) | ||
19639 | { | ||
19640 | if (String.IsNullOrEmpty(name)) | ||
19641 | { | ||
19642 | this.core.OnMessage(WixErrors.ExpectedAttributesWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Name", "SourceFile")); | ||
19643 | } | ||
19644 | else if (null == remotePayload) | ||
19645 | { | ||
19646 | sourceFile = Path.Combine("SourceDir", name); | ||
19647 | } | ||
19648 | else | ||
19649 | { | ||
19650 | sourceFile = String.Empty; // SourceFile is required it cannot be null. | ||
19651 | } | ||
19652 | } | ||
19653 | else if (null != remotePayload) | ||
19654 | { | ||
19655 | this.core.OnMessage(WixErrors.UnexpectedElementWithAttribute(sourceLineNumbers, node.Name.LocalName, "RemotePayload", "SourceFile")); | ||
19656 | } | ||
19657 | else if (sourceFile.EndsWith(Path.DirectorySeparatorChar.ToString(), StringComparison.Ordinal)) | ||
19658 | { | ||
19659 | if (String.IsNullOrEmpty(name)) | ||
19660 | { | ||
19661 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name", "SourceFile", sourceFile)); | ||
19662 | } | ||
19663 | else | ||
19664 | { | ||
19665 | sourceFile = Path.Combine(sourceFile, Path.GetFileName(name)); | ||
19666 | } | ||
19667 | } | ||
19668 | |||
19669 | if (null == downloadUrl && null != remotePayload) | ||
19670 | { | ||
19671 | this.core.OnMessage(WixErrors.ExpectedAttributeWithElement(sourceLineNumbers, node.Name.LocalName, "DownloadUrl", "RemotePayload")); | ||
19672 | } | ||
19673 | |||
19674 | if (YesNoDefaultType.No != compressed && null != remotePayload) | ||
19675 | { | ||
19676 | compressed = YesNoDefaultType.No; | ||
19677 | this.core.OnMessage(WixWarnings.RemotePayloadsMustNotAlsoBeCompressed(sourceLineNumbers, node.Name.LocalName)); | ||
19678 | } | ||
19679 | |||
19680 | if (null == id) | ||
19681 | { | ||
19682 | if (!String.IsNullOrEmpty(name)) | ||
19683 | { | ||
19684 | id = this.core.CreateIdentifierFromFilename(Path.GetFileName(name)); | ||
19685 | } | ||
19686 | else if (!String.IsNullOrEmpty(sourceFile)) | ||
19687 | { | ||
19688 | id = this.core.CreateIdentifierFromFilename(Path.GetFileName(sourceFile)); | ||
19689 | } | ||
19690 | |||
19691 | if (null == id) | ||
19692 | { | ||
19693 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); | ||
19694 | id = Identifier.Invalid; | ||
19695 | } | ||
19696 | else if (!Common.IsIdentifier(id.Id)) | ||
19697 | { | ||
19698 | this.core.OnMessage(WixErrors.IllegalIdentifier(sourceLineNumbers, node.Name.LocalName, "Id", id.Id)); | ||
19699 | } | ||
19700 | } | ||
19701 | |||
19702 | if (null == logPathVariable) | ||
19703 | { | ||
19704 | logPathVariable = String.Concat("WixBundleLog_", id.Id); | ||
19705 | } | ||
19706 | |||
19707 | if (null == rollbackPathVariable) | ||
19708 | { | ||
19709 | rollbackPathVariable = String.Concat("WixBundleRollbackLog_", id.Id); | ||
19710 | } | ||
19711 | |||
19712 | if (!String.IsNullOrEmpty(protocol) && !protocol.Equals("burn", StringComparison.Ordinal) && !protocol.Equals("netfx4", StringComparison.Ordinal) && !protocol.Equals("none", StringComparison.Ordinal)) | ||
19713 | { | ||
19714 | this.core.OnMessage(WixErrors.IllegalAttributeValueWithLegalList(sourceLineNumbers, node.Name.LocalName, "Protocol", protocol, "none, burn, netfx4")); | ||
19715 | } | ||
19716 | |||
19717 | if (!String.IsNullOrEmpty(protocol) && protocol.Equals("netfx4", StringComparison.Ordinal)) | ||
19718 | { | ||
19719 | foreach (string expectedArgument in expectedNetFx4Args) | ||
19720 | { | ||
19721 | if (null == installCommand || -1 == installCommand.IndexOf(expectedArgument, StringComparison.OrdinalIgnoreCase)) | ||
19722 | { | ||
19723 | this.core.OnMessage(WixWarnings.AttributeShouldContain(sourceLineNumbers, node.Name.LocalName, "InstallCommand", installCommand, expectedArgument, "Protocol", "netfx4")); | ||
19724 | } | ||
19725 | |||
19726 | if (null == repairCommand || -1 == repairCommand.IndexOf(expectedArgument, StringComparison.OrdinalIgnoreCase)) | ||
19727 | { | ||
19728 | this.core.OnMessage(WixWarnings.AttributeShouldContain(sourceLineNumbers, node.Name.LocalName, "RepairCommand", repairCommand, expectedArgument, "Protocol", "netfx4")); | ||
19729 | } | ||
19730 | |||
19731 | if (null == uninstallCommand || -1 == uninstallCommand.IndexOf(expectedArgument, StringComparison.OrdinalIgnoreCase)) | ||
19732 | { | ||
19733 | this.core.OnMessage(WixWarnings.AttributeShouldContain(sourceLineNumbers, node.Name.LocalName, "UninstallCommand", uninstallCommand, expectedArgument, "Protocol", "netfx4")); | ||
19734 | } | ||
19735 | } | ||
19736 | } | ||
19737 | |||
19738 | // Only set default scope for EXEs and MSPs if not already set. | ||
19739 | if ((WixBundlePackageType.Exe == packageType || WixBundlePackageType.Msp == packageType) && YesNoDefaultType.NotSet == perMachine) | ||
19740 | { | ||
19741 | perMachine = YesNoDefaultType.Default; | ||
19742 | } | ||
19743 | |||
19744 | // Now that the package ID is known, we can parse the extension attributes... | ||
19745 | Dictionary<string, string> contextValues = new Dictionary<string, string>() { { "PackageId", id.Id } }; | ||
19746 | foreach (XAttribute attribute in extensionAttributes) | ||
19747 | { | ||
19748 | this.core.ParseExtensionAttribute(node, attribute, contextValues); | ||
19749 | } | ||
19750 | |||
19751 | foreach (XElement child in node.Elements()) | ||
19752 | { | ||
19753 | if (CompilerCore.WixNamespace == child.Name.Namespace) | ||
19754 | { | ||
19755 | bool allowed = true; | ||
19756 | switch (child.Name.LocalName) | ||
19757 | { | ||
19758 | case "SlipstreamMsp": | ||
19759 | allowed = (packageType == WixBundlePackageType.Msi); | ||
19760 | if (allowed) | ||
19761 | { | ||
19762 | this.ParseSlipstreamMspElement(child, id.Id); | ||
19763 | } | ||
19764 | break; | ||
19765 | case "MsiProperty": | ||
19766 | allowed = (packageType == WixBundlePackageType.Msi || packageType == WixBundlePackageType.Msp); | ||
19767 | if (allowed) | ||
19768 | { | ||
19769 | this.ParseMsiPropertyElement(child, id.Id); | ||
19770 | } | ||
19771 | break; | ||
19772 | case "Payload": | ||
19773 | this.ParsePayloadElement(child, ComplexReferenceParentType.Package, id.Id, ComplexReferenceChildType.Unknown, null); | ||
19774 | break; | ||
19775 | case "PayloadGroupRef": | ||
19776 | this.ParsePayloadGroupRefElement(child, ComplexReferenceParentType.Package, id.Id, ComplexReferenceChildType.Unknown, null); | ||
19777 | break; | ||
19778 | case "ExitCode": | ||
19779 | allowed = (packageType == WixBundlePackageType.Exe); | ||
19780 | if (allowed) | ||
19781 | { | ||
19782 | this.ParseExitCodeElement(child, id.Id); | ||
19783 | } | ||
19784 | break; | ||
19785 | case "CommandLine": | ||
19786 | allowed = (packageType == WixBundlePackageType.Exe); | ||
19787 | if (allowed) | ||
19788 | { | ||
19789 | this.ParseCommandLineElement(child, id.Id); | ||
19790 | } | ||
19791 | break; | ||
19792 | case "RemotePayload": | ||
19793 | // Handled previously | ||
19794 | break; | ||
19795 | default: | ||
19796 | allowed = false; | ||
19797 | break; | ||
19798 | } | ||
19799 | |||
19800 | if (!allowed) | ||
19801 | { | ||
19802 | this.core.UnexpectedElement(node, child); | ||
19803 | } | ||
19804 | } | ||
19805 | else | ||
19806 | { | ||
19807 | Dictionary<string, string> context = new Dictionary<string, string>() { { "Id", id.Id } }; | ||
19808 | this.core.ParseExtensionElement(node, child, context); | ||
19809 | } | ||
19810 | } | ||
19811 | |||
19812 | if (!this.core.EncounteredError) | ||
19813 | { | ||
19814 | // We create the package contents as a payload with this package as the parent | ||
19815 | this.CreatePayloadRow(sourceLineNumbers, id, name, sourceFile, downloadUrl, ComplexReferenceParentType.Package, id.Id, | ||
19816 | ComplexReferenceChildType.Unknown, null, compressed, enableSignatureVerification, displayName, description, remotePayload); | ||
19817 | |||
19818 | WixChainItemRow chainItemRow = (WixChainItemRow)this.core.CreateRow(sourceLineNumbers, "WixChainItem", id); | ||
19819 | |||
19820 | WixBundlePackageAttributes attributes = 0; | ||
19821 | attributes |= (YesNoType.Yes == permanent) ? WixBundlePackageAttributes.Permanent : 0; | ||
19822 | attributes |= (YesNoType.Yes == visible) ? WixBundlePackageAttributes.Visible : 0; | ||
19823 | |||
19824 | WixBundlePackageRow chainPackageRow = (WixBundlePackageRow)this.core.CreateRow(sourceLineNumbers, "WixBundlePackage", id); | ||
19825 | chainPackageRow.Type = packageType; | ||
19826 | chainPackageRow.PackagePayload = id.Id; | ||
19827 | chainPackageRow.Attributes = attributes; | ||
19828 | |||
19829 | chainPackageRow.InstallCondition = installCondition; | ||
19830 | |||
19831 | if (YesNoAlwaysType.NotSet != cache) | ||
19832 | { | ||
19833 | chainPackageRow.Cache = cache; | ||
19834 | } | ||
19835 | |||
19836 | chainPackageRow.CacheId = cacheId; | ||
19837 | |||
19838 | if (YesNoType.NotSet != vital) | ||
19839 | { | ||
19840 | chainPackageRow.Vital = vital; | ||
19841 | } | ||
19842 | |||
19843 | if (YesNoDefaultType.NotSet != perMachine) | ||
19844 | { | ||
19845 | chainPackageRow.PerMachine = perMachine; | ||
19846 | } | ||
19847 | |||
19848 | chainPackageRow.LogPathVariable = logPathVariable; | ||
19849 | chainPackageRow.RollbackLogPathVariable = rollbackPathVariable; | ||
19850 | |||
19851 | if (CompilerConstants.IntegerNotSet != installSize) | ||
19852 | { | ||
19853 | chainPackageRow.InstallSize = installSize; | ||
19854 | } | ||
19855 | |||
19856 | switch (packageType) | ||
19857 | { | ||
19858 | case WixBundlePackageType.Exe: | ||
19859 | WixBundleExePackageAttributes exeAttributes = 0; | ||
19860 | exeAttributes |= (YesNoType.Yes == repairable) ? WixBundleExePackageAttributes.Repairable : 0; | ||
19861 | |||
19862 | WixBundleExePackageRow exeRow = (WixBundleExePackageRow)this.core.CreateRow(sourceLineNumbers, "WixBundleExePackage", id); | ||
19863 | exeRow.Attributes = exeAttributes; | ||
19864 | exeRow.DetectCondition = detectCondition; | ||
19865 | exeRow.InstallCommand = installCommand; | ||
19866 | exeRow.RepairCommand = repairCommand; | ||
19867 | exeRow.UninstallCommand = uninstallCommand; | ||
19868 | exeRow.ExeProtocol = protocol; | ||
19869 | break; | ||
19870 | |||
19871 | case WixBundlePackageType.Msi: | ||
19872 | WixBundleMsiPackageAttributes msiAttributes = 0; | ||
19873 | msiAttributes |= (YesNoType.Yes == displayInternalUI) ? WixBundleMsiPackageAttributes.DisplayInternalUI : 0; | ||
19874 | msiAttributes |= (YesNoType.Yes == enableFeatureSelection) ? WixBundleMsiPackageAttributes.EnableFeatureSelection : 0; | ||
19875 | msiAttributes |= (YesNoType.Yes == forcePerMachine) ? WixBundleMsiPackageAttributes.ForcePerMachine : 0; | ||
19876 | msiAttributes |= (YesNoType.Yes == suppressLooseFilePayloadGeneration) ? WixBundleMsiPackageAttributes.SuppressLooseFilePayloadGeneration : 0; | ||
19877 | |||
19878 | WixBundleMsiPackageRow msiRow = (WixBundleMsiPackageRow)this.core.CreateRow(sourceLineNumbers, "WixBundleMsiPackage", id); | ||
19879 | msiRow.Attributes = msiAttributes; | ||
19880 | break; | ||
19881 | |||
19882 | case WixBundlePackageType.Msp: | ||
19883 | WixBundleMspPackageAttributes mspAttributes = 0; | ||
19884 | mspAttributes |= (YesNoType.Yes == displayInternalUI) ? WixBundleMspPackageAttributes.DisplayInternalUI : 0; | ||
19885 | mspAttributes |= (YesNoType.Yes == slipstream) ? WixBundleMspPackageAttributes.Slipstream : 0; | ||
19886 | |||
19887 | WixBundleMspPackageRow mspRow = (WixBundleMspPackageRow)this.core.CreateRow(sourceLineNumbers, "WixBundleMspPackage", id); | ||
19888 | mspRow.Attributes = mspAttributes; | ||
19889 | break; | ||
19890 | |||
19891 | case WixBundlePackageType.Msu: | ||
19892 | WixBundleMsuPackageRow msuRow = (WixBundleMsuPackageRow)this.core.CreateRow(sourceLineNumbers, "WixBundleMsuPackage", id); | ||
19893 | msuRow.DetectCondition = detectCondition; | ||
19894 | msuRow.MsuKB = msuKB; | ||
19895 | break; | ||
19896 | } | ||
19897 | |||
19898 | this.CreateChainPackageMetaRows(sourceLineNumbers, parentType, parentId, ComplexReferenceChildType.Package, id.Id, previousType, previousId, after); | ||
19899 | } | ||
19900 | |||
19901 | return id.Id; | ||
19902 | } | ||
19903 | |||
19904 | /// <summary> | ||
19905 | /// Parse CommandLine element. | ||
19906 | /// </summary> | ||
19907 | /// <param name="node">Element to parse</param> | ||
19908 | private void ParseCommandLineElement(XElement node, string packageId) | ||
19909 | { | ||
19910 | SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
19911 | string installArgument = null; | ||
19912 | string uninstallArgument = null; | ||
19913 | string repairArgument = null; | ||
19914 | string condition = null; | ||
19915 | |||
19916 | foreach (XAttribute attrib in node.Attributes()) | ||
19917 | { | ||
19918 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
19919 | { | ||
19920 | switch (attrib.Name.LocalName) | ||
19921 | { | ||
19922 | case "InstallArgument": | ||
19923 | installArgument = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
19924 | break; | ||
19925 | case "UninstallArgument": | ||
19926 | uninstallArgument = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
19927 | break; | ||
19928 | case "RepairArgument": | ||
19929 | repairArgument = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
19930 | break; | ||
19931 | case "Condition": | ||
19932 | condition = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
19933 | break; | ||
19934 | default: | ||
19935 | this.core.UnexpectedAttribute(node, attrib); | ||
19936 | break; | ||
19937 | } | ||
19938 | } | ||
19939 | else | ||
19940 | { | ||
19941 | this.core.ParseExtensionAttribute(node, attrib); | ||
19942 | } | ||
19943 | } | ||
19944 | |||
19945 | if (String.IsNullOrEmpty(condition)) | ||
19946 | { | ||
19947 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Condition")); | ||
19948 | } | ||
19949 | |||
19950 | this.core.ParseForExtensionElements(node); | ||
19951 | |||
19952 | if (!this.core.EncounteredError) | ||
19953 | { | ||
19954 | WixBundlePackageCommandLineRow row = (WixBundlePackageCommandLineRow)this.core.CreateRow(sourceLineNumbers, "WixBundlePackageCommandLine"); | ||
19955 | row.ChainPackageId = packageId; | ||
19956 | row.InstallArgument = installArgument; | ||
19957 | row.UninstallArgument = uninstallArgument; | ||
19958 | row.RepairArgument = repairArgument; | ||
19959 | row.Condition = condition; | ||
19960 | } | ||
19961 | } | ||
19962 | |||
19963 | /// <summary> | ||
19964 | /// Parse PackageGroup element. | ||
19965 | /// </summary> | ||
19966 | /// <param name="node">Element to parse</param> | ||
19967 | private void ParsePackageGroupElement(XElement node) | ||
19968 | { | ||
19969 | SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
19970 | Identifier id = null; | ||
19971 | |||
19972 | foreach (XAttribute attrib in node.Attributes()) | ||
19973 | { | ||
19974 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
19975 | { | ||
19976 | switch (attrib.Name.LocalName) | ||
19977 | { | ||
19978 | case "Id": | ||
19979 | id = this.core.GetAttributeIdentifier(sourceLineNumbers, attrib); | ||
19980 | break; | ||
19981 | default: | ||
19982 | this.core.UnexpectedAttribute(node, attrib); | ||
19983 | break; | ||
19984 | } | ||
19985 | } | ||
19986 | else | ||
19987 | { | ||
19988 | this.core.ParseExtensionAttribute(node, attrib); | ||
19989 | } | ||
19990 | } | ||
19991 | |||
19992 | if (null == id) | ||
19993 | { | ||
19994 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); | ||
19995 | id = Identifier.Invalid; | ||
19996 | } | ||
19997 | |||
19998 | ComplexReferenceChildType previousType = ComplexReferenceChildType.Unknown; | ||
19999 | string previousId = null; | ||
20000 | foreach (XElement child in node.Elements()) | ||
20001 | { | ||
20002 | if (CompilerCore.WixNamespace == child.Name.Namespace) | ||
20003 | { | ||
20004 | switch (child.Name.LocalName) | ||
20005 | { | ||
20006 | case "MsiPackage": | ||
20007 | previousId = this.ParseMsiPackageElement(child, ComplexReferenceParentType.PackageGroup, id.Id, previousType, previousId); | ||
20008 | previousType = ComplexReferenceChildType.Package; | ||
20009 | break; | ||
20010 | case "MspPackage": | ||
20011 | previousId = this.ParseMspPackageElement(child, ComplexReferenceParentType.PackageGroup, id.Id, previousType, previousId); | ||
20012 | previousType = ComplexReferenceChildType.Package; | ||
20013 | break; | ||
20014 | case "MsuPackage": | ||
20015 | previousId = this.ParseMsuPackageElement(child, ComplexReferenceParentType.PackageGroup, id.Id, previousType, previousId); | ||
20016 | previousType = ComplexReferenceChildType.Package; | ||
20017 | break; | ||
20018 | case "ExePackage": | ||
20019 | previousId = this.ParseExePackageElement(child, ComplexReferenceParentType.PackageGroup, id.Id, previousType, previousId); | ||
20020 | previousType = ComplexReferenceChildType.Package; | ||
20021 | break; | ||
20022 | case "RollbackBoundary": | ||
20023 | previousId = this.ParseRollbackBoundaryElement(child, ComplexReferenceParentType.PackageGroup, id.Id, previousType, previousId); | ||
20024 | previousType = ComplexReferenceChildType.Package; | ||
20025 | break; | ||
20026 | case "PackageGroupRef": | ||
20027 | previousId = this.ParsePackageGroupRefElement(child, ComplexReferenceParentType.PackageGroup, id.Id, previousType, previousId); | ||
20028 | previousType = ComplexReferenceChildType.PackageGroup; | ||
20029 | break; | ||
20030 | default: | ||
20031 | this.core.UnexpectedElement(node, child); | ||
20032 | break; | ||
20033 | } | ||
20034 | } | ||
20035 | else | ||
20036 | { | ||
20037 | this.core.ParseExtensionElement(node, child); | ||
20038 | } | ||
20039 | } | ||
20040 | |||
20041 | |||
20042 | if (!this.core.EncounteredError) | ||
20043 | { | ||
20044 | this.core.CreateRow(sourceLineNumbers, "WixBundlePackageGroup", id); | ||
20045 | } | ||
20046 | } | ||
20047 | |||
20048 | /// <summary> | ||
20049 | /// Parses a package group reference element. | ||
20050 | /// </summary> | ||
20051 | /// <param name="node">Element to parse.</param> | ||
20052 | /// <param name="parentType">ComplexReferenceParentType of parent element (Unknown or PackageGroup).</param> | ||
20053 | /// <param name="parentId">Identifier of parent element.</param> | ||
20054 | /// <returns>Identifier for package group element.</rereturns> | ||
20055 | private string ParsePackageGroupRefElement(XElement node, ComplexReferenceParentType parentType, string parentId) | ||
20056 | { | ||
20057 | return this.ParsePackageGroupRefElement(node, parentType, parentId, ComplexReferenceChildType.Unknown, null); | ||
20058 | } | ||
20059 | |||
20060 | /// <summary> | ||
20061 | /// Parses a package group reference element. | ||
20062 | /// </summary> | ||
20063 | /// <param name="node">Element to parse.</param> | ||
20064 | /// <param name="parentType">ComplexReferenceParentType of parent element (Unknown or PackageGroup).</param> | ||
20065 | /// <param name="parentId">Identifier of parent element.</param> | ||
20066 | /// <param name="parentType">ComplexReferenceParentType of previous element (Unknown, Package, or PackageGroup).</param> | ||
20067 | /// <param name="parentId">Identifier of parent element.</param> | ||
20068 | /// <returns>Identifier for package group element.</rereturns> | ||
20069 | private string ParsePackageGroupRefElement(XElement node, ComplexReferenceParentType parentType, string parentId, ComplexReferenceChildType previousType, string previousId) | ||
20070 | { | ||
20071 | Debug.Assert(ComplexReferenceParentType.Unknown == parentType || ComplexReferenceParentType.PackageGroup == parentType || ComplexReferenceParentType.Container == parentType); | ||
20072 | Debug.Assert(ComplexReferenceChildType.Unknown == previousType || ComplexReferenceChildType.PackageGroup == previousType || ComplexReferenceChildType.Package == previousType); | ||
20073 | |||
20074 | SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
20075 | string id = null; | ||
20076 | string after = null; | ||
20077 | |||
20078 | foreach (XAttribute attrib in node.Attributes()) | ||
20079 | { | ||
20080 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
20081 | { | ||
20082 | switch (attrib.Name.LocalName) | ||
20083 | { | ||
20084 | case "Id": | ||
20085 | id = this.core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); | ||
20086 | this.core.CreateSimpleReference(sourceLineNumbers, "WixBundlePackageGroup", id); | ||
20087 | break; | ||
20088 | case "After": | ||
20089 | after = this.core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); | ||
20090 | break; | ||
20091 | default: | ||
20092 | this.core.UnexpectedAttribute(node, attrib); | ||
20093 | break; | ||
20094 | } | ||
20095 | } | ||
20096 | else | ||
20097 | { | ||
20098 | this.core.ParseExtensionAttribute(node, attrib); | ||
20099 | |||
20100 | } | ||
20101 | } | ||
20102 | |||
20103 | if (null == id) | ||
20104 | { | ||
20105 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); | ||
20106 | } | ||
20107 | |||
20108 | if (null != after && ComplexReferenceParentType.Container == parentType) | ||
20109 | { | ||
20110 | this.core.OnMessage(WixErrors.IllegalAttributeWhenNested(sourceLineNumbers, node.Name.LocalName, "After", parentId)); | ||
20111 | } | ||
20112 | |||
20113 | this.core.ParseForExtensionElements(node); | ||
20114 | |||
20115 | if (ComplexReferenceParentType.Container == parentType) | ||
20116 | { | ||
20117 | this.core.CreateWixGroupRow(sourceLineNumbers, ComplexReferenceParentType.Container, parentId, ComplexReferenceChildType.PackageGroup, id); | ||
20118 | } | ||
20119 | else | ||
20120 | { | ||
20121 | this.CreateChainPackageMetaRows(sourceLineNumbers, parentType, parentId, ComplexReferenceChildType.PackageGroup, id, previousType, previousId, after); | ||
20122 | } | ||
20123 | |||
20124 | return id; | ||
20125 | } | ||
20126 | |||
20127 | /// <summary> | ||
20128 | /// Creates rollback boundary. | ||
20129 | /// </summary> | ||
20130 | /// <param name="sourceLineNumbers">Source line numbers.</param> | ||
20131 | /// <param name="id">Identifier for the rollback boundary.</param> | ||
20132 | /// <param name="vital">Indicates whether the rollback boundary is vital or not.</param> | ||
20133 | /// <param name="parentType">Type of parent group.</param> | ||
20134 | /// <param name="parentId">Identifier of parent group.</param> | ||
20135 | /// <param name="previousType">Type of previous item, if any.</param> | ||
20136 | /// <param name="previousId">Identifier of previous item, if any.</param> | ||
20137 | private void CreateRollbackBoundary(SourceLineNumber sourceLineNumbers, Identifier id, YesNoType vital, YesNoType transaction, ComplexReferenceParentType parentType, string parentId, ComplexReferenceChildType previousType, string previousId) | ||
20138 | { | ||
20139 | WixChainItemRow row = (WixChainItemRow)this.core.CreateRow(sourceLineNumbers, "WixChainItem", id); | ||
20140 | |||
20141 | WixBundleRollbackBoundaryRow rollbackBoundary = (WixBundleRollbackBoundaryRow)this.core.CreateRow(sourceLineNumbers, "WixBundleRollbackBoundary", id); | ||
20142 | |||
20143 | if (YesNoType.NotSet != vital) | ||
20144 | { | ||
20145 | rollbackBoundary.Vital = vital; | ||
20146 | } | ||
20147 | if (YesNoType.NotSet != transaction) | ||
20148 | { | ||
20149 | rollbackBoundary.Transaction = transaction; | ||
20150 | } | ||
20151 | |||
20152 | this.CreateChainPackageMetaRows(sourceLineNumbers, parentType, parentId, ComplexReferenceChildType.Package, id.Id, previousType, previousId, null); | ||
20153 | } | ||
20154 | |||
20155 | /// <summary> | ||
20156 | /// Creates group and ordering information for packages | ||
20157 | /// </summary> | ||
20158 | /// <param name="sourceLineNumbers">Source line numbers.</param> | ||
20159 | /// <param name="parentType">Type of parent group, if known.</param> | ||
20160 | /// <param name="parentId">Identifier of parent group, if known.</param> | ||
20161 | /// <param name="type">Type of this item.</param> | ||
20162 | /// <param name="id">Identifier for this item.</param> | ||
20163 | /// <param name="previousType">Type of previous item, if known.</param> | ||
20164 | /// <param name="previousId">Identifier of previous item, if known</param> | ||
20165 | /// <param name="afterId">Identifier of explicit 'After' attribute, if given.</param> | ||
20166 | private void CreateChainPackageMetaRows(SourceLineNumber sourceLineNumbers, | ||
20167 | ComplexReferenceParentType parentType, string parentId, | ||
20168 | ComplexReferenceChildType type, string id, | ||
20169 | ComplexReferenceChildType previousType, string previousId, string afterId) | ||
20170 | { | ||
20171 | // If there's an explicit 'After' attribute, it overrides the inferred previous item. | ||
20172 | if (null != afterId) | ||
20173 | { | ||
20174 | previousType = ComplexReferenceChildType.Package; | ||
20175 | previousId = afterId; | ||
20176 | } | ||
20177 | |||
20178 | this.CreateGroupAndOrderingRows(sourceLineNumbers, parentType, parentId, type, id, previousType, previousId); | ||
20179 | } | ||
20180 | |||
20181 | // TODO: Should we define our own enum for this, just to ensure there's no "cross-contamination"? | ||
20182 | // TODO: Also, we could potentially include an 'Attributes' field to track things like | ||
20183 | // 'before' vs. 'after', and explicit vs. inferred dependencies. | ||
20184 | private void CreateWixOrderingRow(SourceLineNumber sourceLineNumbers, | ||
20185 | ComplexReferenceChildType itemType, string itemId, | ||
20186 | ComplexReferenceChildType dependsOnType, string dependsOnId) | ||
20187 | { | ||
20188 | if (!this.core.EncounteredError) | ||
20189 | { | ||
20190 | Row row = this.core.CreateRow(sourceLineNumbers, "WixOrdering"); | ||
20191 | row[0] = itemType.ToString(); | ||
20192 | row[1] = itemId; | ||
20193 | row[2] = dependsOnType.ToString(); | ||
20194 | row[3] = dependsOnId; | ||
20195 | } | ||
20196 | } | ||
20197 | |||
20198 | /// <summary> | ||
20199 | /// Parse MsiProperty element | ||
20200 | /// </summary> | ||
20201 | /// <param name="node">Element to parse</param> | ||
20202 | /// <param name="packageId">Id of parent element</param> | ||
20203 | private void ParseMsiPropertyElement(XElement node, string packageId) | ||
20204 | { | ||
20205 | SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
20206 | string name = null; | ||
20207 | string value = null; | ||
20208 | string condition = null; | ||
20209 | |||
20210 | foreach (XAttribute attrib in node.Attributes()) | ||
20211 | { | ||
20212 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
20213 | { | ||
20214 | switch (attrib.Name.LocalName) | ||
20215 | { | ||
20216 | case "Name": | ||
20217 | name = this.core.GetAttributeMsiPropertyNameValue(sourceLineNumbers, attrib); | ||
20218 | break; | ||
20219 | case "Value": | ||
20220 | value = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
20221 | break; | ||
20222 | case "Condition": | ||
20223 | condition = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
20224 | break; | ||
20225 | default: | ||
20226 | this.core.UnexpectedAttribute(node, attrib); | ||
20227 | break; | ||
20228 | } | ||
20229 | } | ||
20230 | else | ||
20231 | { | ||
20232 | this.core.ParseExtensionAttribute(node, attrib); | ||
20233 | } | ||
20234 | } | ||
20235 | |||
20236 | if (null == name) | ||
20237 | { | ||
20238 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name")); | ||
20239 | } | ||
20240 | |||
20241 | if (null == value) | ||
20242 | { | ||
20243 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Value")); | ||
20244 | } | ||
20245 | |||
20246 | this.core.ParseForExtensionElements(node); | ||
20247 | |||
20248 | if (!this.core.EncounteredError) | ||
20249 | { | ||
20250 | WixBundleMsiPropertyRow row = (WixBundleMsiPropertyRow)this.core.CreateRow(sourceLineNumbers, "WixBundleMsiProperty"); | ||
20251 | row.ChainPackageId = packageId; | ||
20252 | row.Name = name; | ||
20253 | row.Value = value; | ||
20254 | |||
20255 | if (!String.IsNullOrEmpty(condition)) | ||
20256 | { | ||
20257 | row.Condition = condition; | ||
20258 | } | ||
20259 | } | ||
20260 | } | ||
20261 | |||
20262 | /// <summary> | ||
20263 | /// Parse SlipstreamMsp element | ||
20264 | /// </summary> | ||
20265 | /// <param name="node">Element to parse</param> | ||
20266 | /// <param name="packageId">Id of parent element</param> | ||
20267 | private void ParseSlipstreamMspElement(XElement node, string packageId) | ||
20268 | { | ||
20269 | SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
20270 | string id = null; | ||
20271 | |||
20272 | foreach (XAttribute attrib in node.Attributes()) | ||
20273 | { | ||
20274 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
20275 | { | ||
20276 | switch (attrib.Name.LocalName) | ||
20277 | { | ||
20278 | case "Id": | ||
20279 | id = this.core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); | ||
20280 | this.core.CreateSimpleReference(sourceLineNumbers, "WixBundlePackage", id); | ||
20281 | break; | ||
20282 | default: | ||
20283 | this.core.UnexpectedAttribute(node, attrib); | ||
20284 | break; | ||
20285 | } | ||
20286 | } | ||
20287 | else | ||
20288 | { | ||
20289 | this.core.ParseExtensionAttribute(node, attrib); | ||
20290 | } | ||
20291 | } | ||
20292 | |||
20293 | if (null == id) | ||
20294 | { | ||
20295 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); | ||
20296 | } | ||
20297 | |||
20298 | this.core.ParseForExtensionElements(node); | ||
20299 | |||
20300 | if (!this.core.EncounteredError) | ||
20301 | { | ||
20302 | WixBundleSlipstreamMspRow row = (WixBundleSlipstreamMspRow)this.core.CreateRow(sourceLineNumbers, "WixBundleSlipstreamMsp"); | ||
20303 | row.ChainPackageId = packageId; | ||
20304 | row.MspPackageId = id; | ||
20305 | } | ||
20306 | } | ||
20307 | |||
20308 | /// <summary> | ||
20309 | /// Parse RelatedBundle element | ||
20310 | /// </summary> | ||
20311 | /// <param name="node">Element to parse</param> | ||
20312 | private void ParseRelatedBundleElement(XElement node) | ||
20313 | { | ||
20314 | SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
20315 | string id = null; | ||
20316 | string action = null; | ||
20317 | Wix.RelatedBundle.ActionType actionType = Wix.RelatedBundle.ActionType.Detect; | ||
20318 | |||
20319 | foreach (XAttribute attrib in node.Attributes()) | ||
20320 | { | ||
20321 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
20322 | { | ||
20323 | switch (attrib.Name.LocalName) | ||
20324 | { | ||
20325 | case "Id": | ||
20326 | id = this.core.GetAttributeGuidValue(sourceLineNumbers, attrib, false); | ||
20327 | break; | ||
20328 | case "Action": | ||
20329 | action = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
20330 | break; | ||
20331 | default: | ||
20332 | this.core.UnexpectedAttribute(node, attrib); | ||
20333 | break; | ||
20334 | } | ||
20335 | } | ||
20336 | else | ||
20337 | { | ||
20338 | this.core.ParseExtensionAttribute(node, attrib); | ||
20339 | } | ||
20340 | } | ||
20341 | |||
20342 | if (null == id) | ||
20343 | { | ||
20344 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); | ||
20345 | } | ||
20346 | |||
20347 | if (!String.IsNullOrEmpty(action)) | ||
20348 | { | ||
20349 | actionType = Wix.RelatedBundle.ParseActionType(action); | ||
20350 | switch (actionType) | ||
20351 | { | ||
20352 | case Wix.RelatedBundle.ActionType.Detect: | ||
20353 | break; | ||
20354 | case Wix.RelatedBundle.ActionType.Upgrade: | ||
20355 | break; | ||
20356 | case Wix.RelatedBundle.ActionType.Addon: | ||
20357 | break; | ||
20358 | case Wix.RelatedBundle.ActionType.Patch: | ||
20359 | break; | ||
20360 | default: | ||
20361 | this.core.OnMessage(WixErrors.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, "Action", action, "Detect", "Upgrade", "Addon", "Patch")); | ||
20362 | break; | ||
20363 | } | ||
20364 | } | ||
20365 | |||
20366 | this.core.ParseForExtensionElements(node); | ||
20367 | |||
20368 | if (!this.core.EncounteredError) | ||
20369 | { | ||
20370 | Row row = this.core.CreateRow(sourceLineNumbers, "WixRelatedBundle"); | ||
20371 | row[0] = id; | ||
20372 | row[1] = (int)actionType; | ||
20373 | } | ||
20374 | } | ||
20375 | |||
20376 | /// <summary> | ||
20377 | /// Parse Update element | ||
20378 | /// </summary> | ||
20379 | /// <param name="node">Element to parse</param> | ||
20380 | private void ParseUpdateElement(XElement node) | ||
20381 | { | ||
20382 | SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
20383 | string location = null; | ||
20384 | |||
20385 | foreach (XAttribute attrib in node.Attributes()) | ||
20386 | { | ||
20387 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
20388 | { | ||
20389 | switch (attrib.Name.LocalName) | ||
20390 | { | ||
20391 | case "Location": | ||
20392 | location = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
20393 | break; | ||
20394 | default: | ||
20395 | this.core.UnexpectedAttribute(node, attrib); | ||
20396 | break; | ||
20397 | } | ||
20398 | } | ||
20399 | else | ||
20400 | { | ||
20401 | this.core.ParseExtensionAttribute(node, attrib); | ||
20402 | } | ||
20403 | } | ||
20404 | |||
20405 | if (null == location) | ||
20406 | { | ||
20407 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Location")); | ||
20408 | } | ||
20409 | |||
20410 | this.core.ParseForExtensionElements(node); | ||
20411 | |||
20412 | if (!this.core.EncounteredError) | ||
20413 | { | ||
20414 | Row row = this.core.CreateRow(sourceLineNumbers, "WixBundleUpdate"); | ||
20415 | row[0] = location; | ||
20416 | } | ||
20417 | } | ||
20418 | |||
20419 | /// <summary> | ||
20420 | /// Parse Variable element | ||
20421 | /// </summary> | ||
20422 | /// <param name="node">Element to parse</param> | ||
20423 | private void ParseVariableElement(XElement node) | ||
20424 | { | ||
20425 | SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
20426 | bool hidden = false; | ||
20427 | string name = null; | ||
20428 | bool persisted = false; | ||
20429 | string value = null; | ||
20430 | string type = null; | ||
20431 | |||
20432 | foreach (XAttribute attrib in node.Attributes()) | ||
20433 | { | ||
20434 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
20435 | { | ||
20436 | switch (attrib.Name.LocalName) | ||
20437 | { | ||
20438 | case "Hidden": | ||
20439 | if (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) | ||
20440 | { | ||
20441 | hidden = true; | ||
20442 | } | ||
20443 | break; | ||
20444 | case "Name": | ||
20445 | name = this.core.GetAttributeBundleVariableValue(sourceLineNumbers, attrib); | ||
20446 | break; | ||
20447 | case "Persisted": | ||
20448 | if (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) | ||
20449 | { | ||
20450 | persisted = true; | ||
20451 | } | ||
20452 | break; | ||
20453 | case "Value": | ||
20454 | value = this.core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty); | ||
20455 | break; | ||
20456 | case "Type": | ||
20457 | type = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
20458 | break; | ||
20459 | default: | ||
20460 | this.core.UnexpectedAttribute(node, attrib); | ||
20461 | break; | ||
20462 | } | ||
20463 | } | ||
20464 | else | ||
20465 | { | ||
20466 | this.core.ParseExtensionAttribute(node, attrib); | ||
20467 | } | ||
20468 | } | ||
20469 | |||
20470 | if (null == name) | ||
20471 | { | ||
20472 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name")); | ||
20473 | } | ||
20474 | else if (name.StartsWith("Wix", StringComparison.OrdinalIgnoreCase)) | ||
20475 | { | ||
20476 | this.core.OnMessage(WixErrors.ReservedNamespaceViolation(sourceLineNumbers, node.Name.LocalName, "Name", "Wix")); | ||
20477 | } | ||
20478 | |||
20479 | if (null == type && null != value) | ||
20480 | { | ||
20481 | // Infer the type from the current value... | ||
20482 | if (value.StartsWith("v", StringComparison.OrdinalIgnoreCase)) | ||
20483 | { | ||
20484 | // Version constructor does not support simple "v#" syntax so check to see if the value is | ||
20485 | // non-negative real quick. | ||
20486 | Int32 number; | ||
20487 | if (Int32.TryParse(value.Substring(1), NumberStyles.None, CultureInfo.InvariantCulture.NumberFormat, out number)) | ||
20488 | { | ||
20489 | type = "version"; | ||
20490 | } | ||
20491 | else | ||
20492 | { | ||
20493 | // Sadly, Version doesn't have a TryParse() method until .NET 4, so we have to try/catch to see if it parses. | ||
20494 | try | ||
20495 | { | ||
20496 | Version version = new Version(value.Substring(1)); | ||
20497 | type = "version"; | ||
20498 | } | ||
20499 | catch (Exception) | ||
20500 | { | ||
20501 | } | ||
20502 | } | ||
20503 | } | ||
20504 | |||
20505 | // Not a version, check for numeric. | ||
20506 | if (null == type) | ||
20507 | { | ||
20508 | Int64 number; | ||
20509 | if (Int64.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture.NumberFormat, out number)) | ||
20510 | { | ||
20511 | type = "numeric"; | ||
20512 | } | ||
20513 | else | ||
20514 | { | ||
20515 | type = "string"; | ||
20516 | } | ||
20517 | } | ||
20518 | } | ||
20519 | |||
20520 | if (null == value && null != type) | ||
20521 | { | ||
20522 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, "Variable", "Value", "Type")); | ||
20523 | } | ||
20524 | |||
20525 | this.core.ParseForExtensionElements(node); | ||
20526 | |||
20527 | if (!this.core.EncounteredError) | ||
20528 | { | ||
20529 | WixBundleVariableRow row = (WixBundleVariableRow)this.core.CreateRow(sourceLineNumbers, "WixBundleVariable"); | ||
20530 | row.Id = name; | ||
20531 | row.Value = value; | ||
20532 | row.Type = type; | ||
20533 | row.Hidden = hidden; | ||
20534 | row.Persisted = persisted; | ||
20535 | } | ||
20536 | } | ||
20537 | |||
20538 | |||
20539 | |||
20540 | /// <summary> | ||
20541 | /// Parses a Wix element. | ||
20542 | /// </summary> | ||
20543 | /// <param name="node">Element to parse.</param> | ||
20544 | private void ParseWixElement(XElement node) | ||
20545 | { | ||
20546 | SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
20547 | string requiredVersion = null; | ||
20548 | |||
20549 | foreach (XAttribute attrib in node.Attributes()) | ||
20550 | { | ||
20551 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
20552 | { | ||
20553 | switch (attrib.Name.LocalName) | ||
20554 | { | ||
20555 | case "RequiredVersion": | ||
20556 | requiredVersion = this.core.GetAttributeVersionValue(sourceLineNumbers, attrib); | ||
20557 | break; | ||
20558 | default: | ||
20559 | this.core.UnexpectedAttribute(node, attrib); | ||
20560 | break; | ||
20561 | } | ||
20562 | } | ||
20563 | else | ||
20564 | { | ||
20565 | this.core.ParseExtensionAttribute(node, attrib); | ||
20566 | } | ||
20567 | } | ||
20568 | |||
20569 | if (null != requiredVersion) | ||
20570 | { | ||
20571 | this.core.VerifyRequiredVersion(sourceLineNumbers, requiredVersion); | ||
20572 | } | ||
20573 | |||
20574 | foreach (XElement child in node.Elements()) | ||
20575 | { | ||
20576 | if (CompilerCore.WixNamespace == child.Name.Namespace) | ||
20577 | { | ||
20578 | switch (child.Name.LocalName) | ||
20579 | { | ||
20580 | case "Bundle": | ||
20581 | this.ParseBundleElement(child); | ||
20582 | break; | ||
20583 | case "Fragment": | ||
20584 | this.ParseFragmentElement(child); | ||
20585 | break; | ||
20586 | case "Module": | ||
20587 | this.ParseModuleElement(child); | ||
20588 | break; | ||
20589 | case "PatchCreation": | ||
20590 | this.ParsePatchCreationElement(child); | ||
20591 | break; | ||
20592 | case "Product": | ||
20593 | this.ParseProductElement(child); | ||
20594 | break; | ||
20595 | case "Patch": | ||
20596 | this.ParsePatchElement(child); | ||
20597 | break; | ||
20598 | default: | ||
20599 | this.core.UnexpectedElement(node, child); | ||
20600 | break; | ||
20601 | } | ||
20602 | } | ||
20603 | else | ||
20604 | { | ||
20605 | this.core.ParseExtensionElement(node, child); | ||
20606 | } | ||
20607 | } | ||
20608 | } | ||
20609 | |||
20610 | /// <summary> | ||
20611 | /// Parses a WixVariable element. | ||
20612 | /// </summary> | ||
20613 | /// <param name="node">Element to parse.</param> | ||
20614 | private void ParseWixVariableElement(XElement node) | ||
20615 | { | ||
20616 | SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
20617 | Identifier id = null; | ||
20618 | bool overridable = false; | ||
20619 | string value = null; | ||
20620 | |||
20621 | foreach (XAttribute attrib in node.Attributes()) | ||
20622 | { | ||
20623 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
20624 | { | ||
20625 | switch (attrib.Name.LocalName) | ||
20626 | { | ||
20627 | case "Id": | ||
20628 | id = this.core.GetAttributeIdentifier(sourceLineNumbers, attrib); | ||
20629 | break; | ||
20630 | case "Overridable": | ||
20631 | overridable = (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib)); | ||
20632 | break; | ||
20633 | case "Value": | ||
20634 | value = this.core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty); | ||
20635 | break; | ||
20636 | default: | ||
20637 | this.core.UnexpectedAttribute(node, attrib); | ||
20638 | break; | ||
20639 | } | ||
20640 | } | ||
20641 | else | ||
20642 | { | ||
20643 | this.core.ParseExtensionAttribute(node, attrib); | ||
20644 | } | ||
20645 | } | ||
20646 | |||
20647 | if (null == id) | ||
20648 | { | ||
20649 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); | ||
20650 | } | ||
20651 | |||
20652 | if (null == value) | ||
20653 | { | ||
20654 | this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Value")); | ||
20655 | } | ||
20656 | |||
20657 | this.core.ParseForExtensionElements(node); | ||
20658 | |||
20659 | if (!this.core.EncounteredError) | ||
20660 | { | ||
20661 | WixVariableRow wixVariableRow = (WixVariableRow)this.core.CreateRow(sourceLineNumbers, "WixVariable", id); | ||
20662 | wixVariableRow.Value = value; | ||
20663 | wixVariableRow.Overridable = overridable; | ||
20664 | } | ||
20665 | } | ||
20666 | } | ||
20667 | } | ||
diff --git a/src/WixToolset.Core/CompilerCore.cs b/src/WixToolset.Core/CompilerCore.cs new file mode 100644 index 00000000..8640a2da --- /dev/null +++ b/src/WixToolset.Core/CompilerCore.cs | |||
@@ -0,0 +1,1932 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
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 | } | ||
diff --git a/src/WixToolset.Core/Converter.cs b/src/WixToolset.Core/Converter.cs new file mode 100644 index 00000000..6ae2f984 --- /dev/null +++ b/src/WixToolset.Core/Converter.cs | |||
@@ -0,0 +1,614 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
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 | } | ||
diff --git a/src/WixToolset.Core/Data/messages.xml b/src/WixToolset.Core/Data/messages.xml new file mode 100644 index 00000000..edc98147 --- /dev/null +++ b/src/WixToolset.Core/Data/messages.xml | |||
@@ -0,0 +1,4010 @@ | |||
1 | <?xml version='1.0' encoding='utf-8'?> | ||
2 | <!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. --> | ||
3 | |||
4 | |||
5 | <Messages Namespace="WixToolset" Resources="Core.Data.Messages" xmlns="http://schemas.microsoft.com/genmsgs/2004/07/messages"> | ||
6 | <Class Name="WixErrors" ContainerName="WixErrorEventArgs" BaseContainerName="MessageEventArgs" Level="Error"> | ||
7 | <Message Id="UnexpectedException" Number="1" SourceLineNumbers="no"> | ||
8 | <Instance> | ||
9 | {0} Exception Type: {1} Stack Trace: {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: \ ? | > < : / * " + , ; = [ ] (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: \ ? | > < : / * ". | ||
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: \ ? | > < : / * ". | ||
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<N> 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 <?{1}?> processing instruction without a matching <?{0}?> 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 <?{0}?> processing instruction without a matching <?{1}?> 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 '<?define {0}?>' is not well-formed. Define statements should be in the form <?define variableName = "variable value"?>. | ||
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 <?foreach varName in valueList?>. | ||
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 <?foreach?> statement was found that had no matching <?endforeach?>.</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: <{0} xmlns="{1}">. | ||
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: <{0} xmlns="{2}">. | ||
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: "{0}". | ||
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: "{0}". | ||
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 <N> 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<N> 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: ? | > < : / * ". | ||
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: \ ? | > < : / * " + , ; = [ ] (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 <Media Id='{0}' ...> or <MediaTemplate ...>. | ||
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: \ ? | > < : / * " + , ; = [ ] (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 <{0}>. Expected <{1}> 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 "{0}" for the {1}/@{2} attribute has been deprecated. Please use "{3}" instead. | ||
3554 | <Parameter Type="System.String" Name="attributeValue" /> | ||
3555 | <Parameter Type="System.String" Name="elementName" /> | ||
3556 | <Parameter Type="System.String" Name="attributeName" /> | ||
3557 | <Parameter Type="System.String" Name="newAttributeValue" /> | ||
3558 | </Instance> | ||
3559 | </Message> | ||
3560 | <Message Id="InsufficientPermissionHarvestTypeLib" Number="1112" SourceLineNumbers="no"> | ||
3561 | <Instance> | ||
3562 | Not enough permissions to harvest type library. On Windows Vista, you must either run Heat elevated, or install Windows Vista SP1 (or higher). | ||
3563 | </Instance> | ||
3564 | </Message> | ||
3565 | <Message Id="UnclearShortcut" Number="1113"> | ||
3566 | <Instance> | ||
3567 | Because it is an advertised shortcut, the target of shortcut '{0}' will be the keypath of component '{2}' rather than parent file '{1}'. To eliminate this warning, you can (1) make the Shortcut element a child of the File element that is the keypath of component '{2}', (2) make file '{1}' the keypath of component '{2}', or (3) remove the @Advertise attribute so the shortcut is a non-advertised shortcut. | ||
3568 | <Parameter Type="System.String" Name="shortcutId" /> | ||
3569 | <Parameter Type="System.String" Name="fileId" /> | ||
3570 | <Parameter Type="System.String" Name="componentId" /> | ||
3571 | </Instance> | ||
3572 | </Message> | ||
3573 | <Message Id="TooManyProgIds" Number="1114"> | ||
3574 | <Instance> | ||
3575 | Class '{0}' tried to use ProgId '{1}' which has already been associated with class '{2}'. This information will be left out of the decompiled output. | ||
3576 | <Parameter Type="System.String" Name="clsId" /> | ||
3577 | <Parameter Type="System.String" Name="progId" /> | ||
3578 | <Parameter Type="System.String" Name="otherClsId" /> | ||
3579 | </Instance> | ||
3580 | </Message> | ||
3581 | <Message Id="BadColumnDataIgnored" Number="1115"> | ||
3582 | <Instance> | ||
3583 | The value '{0}' in table '{1}', column '{2}' is invalid according to the column's validation information. The decompiled output includes a best-effort representation of this value. | ||
3584 | <Parameter Type="System.String" Name="value" /> | ||
3585 | <Parameter Type="System.String" Name="tableName" /> | ||
3586 | <Parameter Type="System.String" Name="columnName" /> | ||
3587 | </Instance> | ||
3588 | </Message> | ||
3589 | <Message Id="NullMsiAssemblyNameValue" Number="1116"> | ||
3590 | <Instance> | ||
3591 | The assembly in component '{0}' has a null or empty {1} assembly name value. | ||
3592 | <Parameter Type="System.String" Name="componentName" /> | ||
3593 | <Parameter Type="System.String" Name="name" /> | ||
3594 | </Instance> | ||
3595 | </Message> | ||
3596 | <Message Id="InvalidAttributeCombination" Number="1117"> | ||
3597 | <Instance> | ||
3598 | It is invalid to combine attributes {0} and {1}. The decompiled output will set attribute {2} to {3}. | ||
3599 | <Parameter Type="System.String" Name="attrib1" /> | ||
3600 | <Parameter Type="System.String" Name="attrib2" /> | ||
3601 | <Parameter Type="System.String" Name="name" /> | ||
3602 | <Parameter Type="System.String" Name="value" /> | ||
3603 | </Instance> | ||
3604 | </Message> | ||
3605 | <Message Id="VariableDeclarationCollision" Number="1118"> | ||
3606 | <Instance> | ||
3607 | The variable '{0}' with value '{1}' was previously declared with value '{2}'. | ||
3608 | <Parameter Type="System.String" Name="variableName" /> | ||
3609 | <Parameter Type="System.String" Name="variableValue" /> | ||
3610 | <Parameter Type="System.String" Name="variableCollidingValue" /> | ||
3611 | </Instance> | ||
3612 | </Message> | ||
3613 | <Message Id="DuplicatePrimaryKey" Number="1119"> | ||
3614 | <Instance> | ||
3615 | The primary key '{0}' is duplicated in table '{1}' and will be ignored. Please remove one of the entries or rename a part of the primary key to avoid the collision. | ||
3616 | <Parameter Type="System.String" Name="primaryKey" /> | ||
3617 | <Parameter Type="System.String" Name="tableName" /> | ||
3618 | </Instance> | ||
3619 | </Message> | ||
3620 | <Message Id="RequiresMsi200for64bitPackage" Number="1121"> | ||
3621 | <Instance> | ||
3622 | Package/@InstallerVersion must be 200 or greater for a 64-bit package. The value will be changed to 200. Please specify a value of 200 or greater in order to eliminate this warning. | ||
3623 | </Instance> | ||
3624 | </Message> | ||
3625 | <Message Id="ExternalCabsAreNotSigned" Number="1122" SourceLineNumbers="no"> | ||
3626 | <Instance> | ||
3627 | The installer database '{0}' has external cabs, but at least one of them is not signed. Please ensure that all external cabs are signed, if you mean to sign them. If you don't mean to sign them, there is no need to run the insignia tool as part of your build. | ||
3628 | <Parameter Type="System.String" Name="databaseFile" /> | ||
3629 | </Instance> | ||
3630 | </Message> | ||
3631 | <Message Id="FailedToDeleteTempDir" Number="1123" SourceLineNumbers="no"> | ||
3632 | <Instance> | ||
3633 | Failed to delete temporary directory: {0} | ||
3634 | <Parameter Type="System.String" Name="directory" /> | ||
3635 | </Instance> | ||
3636 | </Message> | ||
3637 | <Message Id="StandardDirectoryConflictInMergeModule" Number="1124"> | ||
3638 | <Instance> | ||
3639 | The Directory '{0}' starts with the same Id as the standard folder in Windows Installer '{1}'. A directory Id that begins with the same Id as a standard folder that is in an MSM may encounter a conflict when merging the MSM into an MSI. This may result in the contents of this merge module being installed to an unexpected location. To eliminate this warning, change your directory Id to not start with the same Id as any standard folders. | ||
3640 | <Parameter Type="System.String" Name="directory" /> | ||
3641 | <Parameter Type="System.String" Name="standardDirectory" /> | ||
3642 | </Instance> | ||
3643 | </Message> | ||
3644 | <Message Id="PreprocessorUnknownPragma" Number="1125"> | ||
3645 | <Instance> | ||
3646 | The pragma '{0}' is unknown. Please ensure you have referenced the extension that defines this pragma. | ||
3647 | <Parameter Type="System.String" Name="pragmaName" /> | ||
3648 | </Instance> | ||
3649 | </Message> | ||
3650 | <Message Id="DeprecatedComponentGroupId" Number="1126"> | ||
3651 | <Instance> | ||
3652 | The {0}/@Id attribute contains invalid characters for an identifier. Being able to use invalid identifier characters for a {0} identifier has been deprecated. | ||
3653 | <Parameter Type="System.String" Name="elementName" /> | ||
3654 | </Instance> | ||
3655 | </Message> | ||
3656 | <Message Id="UxPayloadsOnlySupportEmbedding" Number="1127"> | ||
3657 | <Instance> | ||
3658 | A UX Payload ('{0}') was marked for something other than embedded packaging, possibly because it included a @DownloadUrl attribute. At present, UX Payloads must be embedded in the Bundle, so the requested packaging is being ignored. | ||
3659 | <Parameter Type="System.String" Name="sourceFile" /> | ||
3660 | </Instance> | ||
3661 | </Message> | ||
3662 | <Message Id="DiscardedRollbackBoundary" Number="1129"> | ||
3663 | <Instance> | ||
3664 | The RollbackBoundary '{0}' was discarded because it was not followed by a package. Without a package the rollback boundary doesn't do anything. Verify that the RollbackBoundary element is not followed by another RollbackBoundary and that the element is not at the end of the chain. | ||
3665 | <Parameter Type="System.String" Name="rollbackBoundaryId" /> | ||
3666 | </Instance> | ||
3667 | </Message> | ||
3668 | <Message Id="DeprecatedElement" Number="1130"> | ||
3669 | <Instance> | ||
3670 | The {0} element has been deprecated. | ||
3671 | <Parameter Type="System.String" Name="elementName" /> | ||
3672 | </Instance> | ||
3673 | <Instance> | ||
3674 | The {0} element has been deprecated. Please use the {1} element instead. | ||
3675 | <Parameter Type="System.String" Name="elementName" /> | ||
3676 | <Parameter Type="System.String" Name="newElementName" /> | ||
3677 | </Instance> | ||
3678 | <Instance> | ||
3679 | The {0} element has been deprecated. Please use the {1} or {2} element instead. | ||
3680 | <Parameter Type="System.String" Name="elementName" /> | ||
3681 | <Parameter Type="System.String" Name="newElementName1" /> | ||
3682 | <Parameter Type="System.String" Name="newElementName2" /> | ||
3683 | </Instance> | ||
3684 | </Message> | ||
3685 | <Message Id="CannotUpdateCabCache" Number="1131"> | ||
3686 | <Instance> | ||
3687 | Cannot update the timestamp of cached cabinet: '{0}'. If the timestamp is not updated, the build may rebuild more than is necessary. To fix the issue, ensure that the cabinet file is writable, error: {1} | ||
3688 | <Parameter Type="System.String" Name="cabinetPath" /> | ||
3689 | <Parameter Type="System.String" Name="detail" /> | ||
3690 | </Instance> | ||
3691 | </Message> | ||
3692 | <Message Id="DownloadUrlNotSupportedForEmbeddedPayloads" Number="1132"> | ||
3693 | <Instance> | ||
3694 | The Payload '{0}' is embedded but included a @DownloadUrl attribute. Embedded Payloads cannot be downloaded so the download URL is being ignored. | ||
3695 | <Parameter Type="System.String" Name="payloadId" /> | ||
3696 | </Instance> | ||
3697 | </Message> | ||
3698 | <Message Id="DiscouragedAllUsersValue" Number="1133"> | ||
3699 | <Instance> | ||
3700 | Bundles require a package to be either per-machine or per-user. The MSI '{0}' ALLUSERS Property is set to '2' which may change from per-user to per-machine at install time. The Bundle will assume the package is per-{1} and will not work correctly if that changes. If possible, remove the Property with Id='ALLUSERS' and use Package/@InstallScope attribute instead. | ||
3701 | <Parameter Type="System.String" Name="path" /> | ||
3702 | <Parameter Type="System.String" Name="machineOrUser" /> | ||
3703 | </Instance> | ||
3704 | </Message> | ||
3705 | <Message Id="ImplicitlyPerUser" Number="1134"> | ||
3706 | <Instance> | ||
3707 | The MSI '{0}' does not explicitly indicate that it is a per-user package even though the ALLUSERS Property is blank. This suggests a per-user package so the Bundle will assume the package is per-user. If possible, use the Package/@InstallScope attribute to be explicit instead. | ||
3708 | <Parameter Type="System.String" Name="path" /> | ||
3709 | </Instance> | ||
3710 | </Message> | ||
3711 | <Message Id="PerUserButForcingPerMachine" Number="1135"> | ||
3712 | <Instance> | ||
3713 | The MSI '{0}' is a per-user package being forced to per-machine. Verify that the MsiPackage/@ForcePerMachine attribute is expected and that the per-user package works correctly when forced to install per-machine. | ||
3714 | <Parameter Type="System.String" Name="path" /> | ||
3715 | </Instance> | ||
3716 | </Message> | ||
3717 | <Message Id="AttributeShouldContain" Number="1136"> | ||
3718 | <Instance> | ||
3719 | The {0}/@{1} attribute value '{2}' should contain '{3}' when the {0}/@{4} attribute is set to '{5}'. | ||
3720 | <Parameter Type="System.String" Name="elementName" /> | ||
3721 | <Parameter Type="System.String" Name="attributeName" /> | ||
3722 | <Parameter Type="System.String" Name="attributeValue" /> | ||
3723 | <Parameter Type="System.String" Name="expectedContains" /> | ||
3724 | <Parameter Type="System.String" Name="otherAttributeName" /> | ||
3725 | <Parameter Type="System.String" Name="otherAttributeValue" /> | ||
3726 | </Instance> | ||
3727 | </Message> | ||
3728 | <Message Id="DuplicateComponentGuidsMustHaveMutuallyExclusiveConditions" Number="1137"> | ||
3729 | <Instance> | ||
3730 | Component/@Id='{0}' has a @Guid value '{1}' that duplicates another component in this package. This is not officially supported by Windows Installer but works as long as all components have mutually-exclusive conditions. It is recommended to give each component its own unique GUID. | ||
3731 | <Parameter Type="System.String" Name="componentId" /> | ||
3732 | <Parameter Type="System.String" Name="guid" /> | ||
3733 | </Instance> | ||
3734 | </Message> | ||
3735 | <Message Id="DeprecatedRegistryKeyActionAttribute" Number="1138"> | ||
3736 | <Instance> | ||
3737 | The RegistryKey/@Action attribute has been deprecated. In most cases, you can simply omit @Action. If you need to force Windows Installer to create an empty key or recursively delete the key, use the ForceCreateOnInstall or ForceDeleteOnUninstall attributes instead. | ||
3738 | </Instance> | ||
3739 | </Message> | ||
3740 | <Message Id="NotABinaryWixlib" Number="1139" SourceLineNumbers="no"> | ||
3741 | <Instance> | ||
3742 | '{0}' is not a binary Wixlib and has no embedded files. | ||
3743 | <Parameter Type="System.String" Name="wixlib" /> | ||
3744 | </Instance> | ||
3745 | </Message> | ||
3746 | <Message Id="NoPerMachineDependencies" Number="1140"> | ||
3747 | <Instance> | ||
3748 | Bundle dependencies will not be registered on per-machine package '{0}' for a per-user bundle. Either make sure that all packages are installed per-machine, or author any per-machine dependencies as permanent packages. | ||
3749 | <Parameter Type="System.String" Name="packageId" /> | ||
3750 | </Instance> | ||
3751 | </Message> | ||
3752 | <Message Id="DownloadUrlNotSupportedForAttachedContainers" Number="1141"> | ||
3753 | <Instance> | ||
3754 | The Container '{0}' is attached but included a @DownloadUrl attribute. Attached Containers cannot be downloaded so the download URL is being ignored. | ||
3755 | <Parameter Type="System.String" Name="containerId" /> | ||
3756 | </Instance> | ||
3757 | </Message> | ||
3758 | <Message Id="ReservedAttribute" Number="1142"> | ||
3759 | <Instance> | ||
3760 | The {0}/@{1} attribute is reserved for future use and has no effect in this version of the WiX toolset. | ||
3761 | <Parameter Type="System.String" Name="elementName" /> | ||
3762 | <Parameter Type="System.String" Name="attributeName" /> | ||
3763 | </Instance> | ||
3764 | </Message> | ||
3765 | <Message Id="RequiresMsi500forArmPackage" Number="1143"> | ||
3766 | <Instance> | ||
3767 | Package/@InstallerVersion must be 500 or greater for an Arm package. The value will be changed to 500. Please specify a value of 500 or greater in order to eliminate this warning. | ||
3768 | </Instance> | ||
3769 | </Message> | ||
3770 | <Message Id="RemotePayloadsMustNotAlsoBeCompressed" Number="1144"> | ||
3771 | <Instance> | ||
3772 | The {0}/@Compressed attribute must have value 'no' when a RemotePayload child element is present. RemotePayload indicates that a package will always be downloaded and cannot be compressed into a bundle. To eliminate this warning, explicitly set the {0}/@Compressed attribute to 'no'. | ||
3773 | <Parameter Type="System.String" Name="elementName" /> | ||
3774 | </Instance> | ||
3775 | </Message> | ||
3776 | <Message Id="AllChangesIncludedInPatch" Number="1145"> | ||
3777 | <Instance> | ||
3778 | All changes between the baseline and upgraded packages will be included in the patch except for any change to the ProductCode. The 'All' element is supported primarily for testing purposes and negates the benefits of patch families. | ||
3779 | </Instance> | ||
3780 | </Message> | ||
3781 | <Message Id="RelatedAttributeConditionallyIgnored" Number="1146"> | ||
3782 | <Instance> | ||
3783 | Ignoring attribute {0} because attribute {1} is set to {2}. | ||
3784 | <Parameter Type="System.String" Name="recessiveAttribute" /> | ||
3785 | <Parameter Type="System.String" Name="dominantAttribute" /> | ||
3786 | <Parameter Type="System.String" Name="dominantValue" /> | ||
3787 | </Instance> | ||
3788 | </Message> | ||
3789 | <Message Id="BackslashTerminateInlineDirectorySyntax" Number="1147"> | ||
3790 | <Instance> | ||
3791 | Backslash terminate the {0}/@{1} attribute's inline directory value '{2}'. A backslash ensures a directory name will not be mistaken for a directory reference. | ||
3792 | <Parameter Type="System.String" Name="elementName" /> | ||
3793 | <Parameter Type="System.String" Name="attributeName" /> | ||
3794 | <Parameter Type="System.String" Name="value" /> | ||
3795 | </Instance> | ||
3796 | </Message> | ||
3797 | <Message Id="VersionTruncated" Number="1148"> | ||
3798 | <Instance> | ||
3799 | Product version {0} in package '{1}' is not valid per the MSI SDK and cannot be represented in a bundle. It has been truncated to {2}. | ||
3800 | <Parameter Type="System.String" Name="originalVersion" /> | ||
3801 | <Parameter Type="System.String" Name="package" /> | ||
3802 | <Parameter Type="System.String" Name="truncatedVersion" /> | ||
3803 | </Instance> | ||
3804 | </Message> | ||
3805 | <Message Id="ServiceConfigFamilyNotSupported" Number="1149"> | ||
3806 | <Instance> | ||
3807 | {0} functionality is documented in the Windows Installer SDK to "not [work] as expected." Consider replacing {0} with the WixUtilExtension ServiceConfig element. | ||
3808 | <Parameter Type="System.String" Name="elementName" /> | ||
3809 | </Instance> | ||
3810 | </Message> | ||
3811 | </Class> | ||
3812 | |||
3813 | <Class Name="WixVerboses" ContainerName="WixVerboseEventArgs" BaseContainerName="MessageEventArgs" Level="Verbose"> | ||
3814 | <Message Id="ImportBinaryStream" Number="9000" SourceLineNumbers="no"> | ||
3815 | <Instance> | ||
3816 | Importing binary stream from '{0}'. | ||
3817 | <Parameter Type="System.String" Name="streamSource" /> | ||
3818 | </Instance> | ||
3819 | </Message> | ||
3820 | <Message Id="ImportIconStream" Number="9001" SourceLineNumbers="no"> | ||
3821 | <Instance> | ||
3822 | Importing icon stream from '{0}'. | ||
3823 | <Parameter Type="System.String" Name="streamSource" /> | ||
3824 | </Instance> | ||
3825 | </Message> | ||
3826 | <Message Id="CopyFile" Number="9002" SourceLineNumbers="no"> | ||
3827 | <Instance> | ||
3828 | Copying file '{0}' to '{1}'. | ||
3829 | <Parameter Type="System.String" Name="sourceFile" /> | ||
3830 | <Parameter Type="System.String" Name="destinationFile" /> | ||
3831 | </Instance> | ||
3832 | </Message> | ||
3833 | <Message Id="MoveFile" Number="9003" SourceLineNumbers="no"> | ||
3834 | <Instance> | ||
3835 | Moving file '{0}' to '{1}'. | ||
3836 | <Parameter Type="System.String" Name="sourceFile" /> | ||
3837 | <Parameter Type="System.String" Name="destinationFile" /> | ||
3838 | </Instance> | ||
3839 | </Message> | ||
3840 | <Message Id="CreateDirectory" Number="9004" SourceLineNumbers="no"> | ||
3841 | <Instance> | ||
3842 | The directory '{0}' does not exist, creating it now. | ||
3843 | <Parameter Type="System.String" Name="directory" /> | ||
3844 | </Instance> | ||
3845 | </Message> | ||
3846 | <Message Id="RemoveDestinationFile" Number="9005" SourceLineNumbers="no"> | ||
3847 | <Instance> | ||
3848 | The destination file '{0}' already exists, attempting to remove it. | ||
3849 | <Parameter Type="System.String" Name="destinationFile" /> | ||
3850 | </Instance> | ||
3851 | </Message> | ||
3852 | <Message Id="CabFile" Number="9006" SourceLineNumbers="no"> | ||
3853 | <Instance> | ||
3854 | Cabbing file {0} from '{1}'. | ||
3855 | <Parameter Type="System.String" Name="fileId" /> | ||
3856 | <Parameter Type="System.String" Name="filePath" /> | ||
3857 | </Instance> | ||
3858 | </Message> | ||
3859 | <Message Id="UpdatingFileInformation" Number="9007" SourceLineNumbers="no"> | ||
3860 | <Instance>Updating file information.</Instance> | ||
3861 | </Message> | ||
3862 | <Message Id="GeneratingDatabase" Number="9008" SourceLineNumbers="no"> | ||
3863 | <Instance>Generating database.</Instance> | ||
3864 | </Message> | ||
3865 | <Message Id="MergingModules" Number="9009" SourceLineNumbers="no"> | ||
3866 | <Instance>Merging modules.</Instance> | ||
3867 | </Message> | ||
3868 | <Message Id="CreatingCabinetFiles" Number="9010" SourceLineNumbers="no"> | ||
3869 | <Instance>Creating cabinet files.</Instance> | ||
3870 | </Message> | ||
3871 | <Message Id="ImportingStreams" Number="9011" SourceLineNumbers="no"> | ||
3872 | <Instance>Importing streams.</Instance> | ||
3873 | </Message> | ||
3874 | <Message Id="LayingOutMedia" Number="9012" SourceLineNumbers="no"> | ||
3875 | <Instance>Laying out media.</Instance> | ||
3876 | </Message> | ||
3877 | <Message Id="DecompilingTable" Number="9013" SourceLineNumbers="no"> | ||
3878 | <Instance> | ||
3879 | Decompiling the {0} table. | ||
3880 | <Parameter Type="System.String" Name="tableName" /> | ||
3881 | </Instance> | ||
3882 | </Message> | ||
3883 | <Message Id="ValidationInfo" Number="9014" SourceLineNumbers="no"> | ||
3884 | <Instance> | ||
3885 | {0}: {1} | ||
3886 | <Parameter Type="System.String" Name="ice" /> | ||
3887 | <Parameter Type="System.String" Name="message" /> | ||
3888 | </Instance> | ||
3889 | </Message> | ||
3890 | <Message Id="CreateCabinet" Number="9015" SourceLineNumbers="no"> | ||
3891 | <Instance> | ||
3892 | Creating cabinet '{0}'. | ||
3893 | <Parameter Type="System.String" Name="cabinet" /> | ||
3894 | </Instance> | ||
3895 | </Message> | ||
3896 | <Message Id="ValidatingDatabase" Number="9016" SourceLineNumbers="no"> | ||
3897 | <Instance>Validating database.</Instance> | ||
3898 | </Message> | ||
3899 | <Message Id="OpeningMergeModule" Number="9017" SourceLineNumbers="no"> | ||
3900 | <Instance> | ||
3901 | Opening merge module '{0}' with language '{1}'. | ||
3902 | <Parameter Type="System.String" Name="modulePath" /> | ||
3903 | <Parameter Type="System.Int16" Name="language" /> | ||
3904 | </Instance> | ||
3905 | </Message> | ||
3906 | <Message Id="MergingMergeModule" Number="9018" SourceLineNumbers="no"> | ||
3907 | <Instance> | ||
3908 | Merging merge module '{0}'. | ||
3909 | <Parameter Type="System.String" Name="modulePath" /> | ||
3910 | </Instance> | ||
3911 | </Message> | ||
3912 | <Message Id="ConnectingMergeModule" Number="9019" SourceLineNumbers="no"> | ||
3913 | <Instance> | ||
3914 | Connecting merge module '{0}' to feature '{1}'. | ||
3915 | <Parameter Type="System.String" Name="modulePath" /> | ||
3916 | <Parameter Type="System.String" Name="feature" /> | ||
3917 | </Instance> | ||
3918 | </Message> | ||
3919 | <Message Id="ResequencingMergeModuleFiles" Number="9020" SourceLineNumbers="no"> | ||
3920 | <Instance>Resequencing files from all merge modules.</Instance> | ||
3921 | </Message> | ||
3922 | <Message Id="BinderTempDirLocatedAt" Number="9021" SourceLineNumbers="no"> | ||
3923 | <Instance> | ||
3924 | Binder temporary directory located at '{0}'. | ||
3925 | <Parameter Type="System.String" Name="directory" /> | ||
3926 | </Instance> | ||
3927 | </Message> | ||
3928 | <Message Id="ValidatorTempDirLocatedAt" Number="9022" SourceLineNumbers="no"> | ||
3929 | <Instance> | ||
3930 | Validator temporary directory located at '{0}'. | ||
3931 | <Parameter Type="System.String" Name="directory" /> | ||
3932 | </Instance> | ||
3933 | </Message> | ||
3934 | <Message Id="GeneratingBundle" Number="9023" SourceLineNumbers="no"> | ||
3935 | <Instance> | ||
3936 | Generating Burn bundle '{0}' from stub '{1}'. | ||
3937 | <Parameter Type="System.String" Name="bundleFile" /> | ||
3938 | <Parameter Type="System.String" Name="stubFile" /> | ||
3939 | </Instance> | ||
3940 | </Message> | ||
3941 | <Message Id="ResolvingManifest" Number="9024" SourceLineNumbers="no"> | ||
3942 | <Instance> | ||
3943 | Generating resolved manifest '{0}'. | ||
3944 | <Parameter Type="System.String" Name="manifestFile" /> | ||
3945 | </Instance> | ||
3946 | </Message> | ||
3947 | <Message Id="LoadingPayload" Number="9025" SourceLineNumbers="no"> | ||
3948 | <Instance> | ||
3949 | Loading payload '{0}' into container. | ||
3950 | <Parameter Type="System.String" Name="payload" /> | ||
3951 | </Instance> | ||
3952 | </Message> | ||
3953 | <Message Id="BundleGuid" Number="9026" SourceLineNumbers="no"> | ||
3954 | <Instance> | ||
3955 | Assigning bundle GUID '{0}'. | ||
3956 | <Parameter Type="System.String" Name="bundleGuid" /> | ||
3957 | </Instance> | ||
3958 | </Message> | ||
3959 | <Message Id="CopyingExternalPayload" Number="9027" SourceLineNumbers="no"> | ||
3960 | <Instance> | ||
3961 | Copying external payload from '{0}' to '{1}'. | ||
3962 | <Parameter Type="System.String" Name="payload" /> | ||
3963 | <Parameter Type="System.String" Name="outputDirectory" /> | ||
3964 | </Instance> | ||
3965 | </Message> | ||
3966 | <Message Id="EmbeddingContainer" Number="9028" SourceLineNumbers="no"> | ||
3967 | <Instance> | ||
3968 | Embedding container '{0}' ({1} bytes) with '{2}' compression. | ||
3969 | <Parameter Type="System.String" Name="container" /> | ||
3970 | <Parameter Type="System.Int64" Name="size" /> | ||
3971 | <Parameter Type="System.String" Name="compression" /> | ||
3972 | </Instance> | ||
3973 | </Message> | ||
3974 | <Message Id="SwitchingToPerUserPackage" Number="9029"> | ||
3975 | <Instance> | ||
3976 | Bundle switching from per-machine to per-user due to addition of per-user package '{0}'. | ||
3977 | <Parameter Type="System.String" Name="path" /> | ||
3978 | </Instance> | ||
3979 | </Message> | ||
3980 | <Message Id="SetCabbingThreadCount" Number="9030" SourceLineNumbers="no"> | ||
3981 | <Instance> | ||
3982 | There will be '{0}' threads used to produce CAB files. | ||
3983 | <Parameter Type="System.String" Name="threads" /> | ||
3984 | </Instance> | ||
3985 | </Message> | ||
3986 | <Message Id="ValidationSerialized" Number="9031" SourceLineNumbers="no"> | ||
3987 | <Instance> | ||
3988 | Multiple packages cannot reliably be validated simultaneously. This validation will resume when the other package being validated has completed. | ||
3989 | </Instance> | ||
3990 | </Message> | ||
3991 | <Message Id="ReusingCabCache" Number="9032"> | ||
3992 | <Instance> | ||
3993 | Reusing cabinet '{0}' from cabinet cache path: '{1}'. | ||
3994 | <Parameter Type="System.String" Name="cabinetName" /> | ||
3995 | <Parameter Type="System.String" Name="source" /> | ||
3996 | </Instance> | ||
3997 | </Message> | ||
3998 | <Message Id="CabinetsSplitInParallel" Number="9033" SourceLineNumbers="no"> | ||
3999 | <Instance> | ||
4000 | Multiple Cabinets with Large Files are splitting simultaneously. This current cabinet is waiting on a shared resource and splitting will resume when the other splitting has completed. | ||
4001 | </Instance> | ||
4002 | </Message> | ||
4003 | <Message Id="ValidatedDatabase" Number="9034" SourceLineNumbers="no"> | ||
4004 | <Instance> | ||
4005 | Validation complete: {0:N0}ms elapsed. | ||
4006 | <Parameter Type="System.Int64" Name="size" /> | ||
4007 | </Instance> | ||
4008 | </Message> | ||
4009 | </Class> | ||
4010 | </Messages> | ||
diff --git a/src/WixToolset.Core/Decompiler.cs b/src/WixToolset.Core/Decompiler.cs new file mode 100644 index 00000000..249b5788 --- /dev/null +++ b/src/WixToolset.Core/Decompiler.cs | |||
@@ -0,0 +1,9357 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
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 | |||
5000 | int attributes = Convert.ToInt32(row[3]); | ||
5001 | |||
5002 | if (MsiInterop.MsidbComponentAttributesSourceOnly == (attributes & MsiInterop.MsidbComponentAttributesSourceOnly)) | ||
5003 | { | ||
5004 | component.Location = Wix.Component.LocationType.source; | ||
5005 | } | ||
5006 | else if (MsiInterop.MsidbComponentAttributesOptional == (attributes & MsiInterop.MsidbComponentAttributesOptional)) | ||
5007 | { | ||
5008 | component.Location = Wix.Component.LocationType.either; | ||
5009 | } | ||
5010 | |||
5011 | if (MsiInterop.MsidbComponentAttributesSharedDllRefCount == (attributes & MsiInterop.MsidbComponentAttributesSharedDllRefCount)) | ||
5012 | { | ||
5013 | component.SharedDllRefCount = Wix.YesNoType.yes; | ||
5014 | } | ||
5015 | |||
5016 | if (MsiInterop.MsidbComponentAttributesPermanent == (attributes & MsiInterop.MsidbComponentAttributesPermanent)) | ||
5017 | { | ||
5018 | component.Permanent = Wix.YesNoType.yes; | ||
5019 | } | ||
5020 | |||
5021 | if (MsiInterop.MsidbComponentAttributesTransitive == (attributes & MsiInterop.MsidbComponentAttributesTransitive)) | ||
5022 | { | ||
5023 | component.Transitive = Wix.YesNoType.yes; | ||
5024 | } | ||
5025 | |||
5026 | if (MsiInterop.MsidbComponentAttributesNeverOverwrite == (attributes & MsiInterop.MsidbComponentAttributesNeverOverwrite)) | ||
5027 | { | ||
5028 | component.NeverOverwrite = Wix.YesNoType.yes; | ||
5029 | } | ||
5030 | |||
5031 | if (MsiInterop.MsidbComponentAttributes64bit == (attributes & MsiInterop.MsidbComponentAttributes64bit)) | ||
5032 | { | ||
5033 | component.Win64 = Wix.YesNoType.yes; | ||
5034 | } | ||
5035 | |||
5036 | if (MsiInterop.MsidbComponentAttributesDisableRegistryReflection == (attributes & MsiInterop.MsidbComponentAttributesDisableRegistryReflection)) | ||
5037 | { | ||
5038 | component.DisableRegistryReflection = Wix.YesNoType.yes; | ||
5039 | } | ||
5040 | |||
5041 | if (MsiInterop.MsidbComponentAttributesUninstallOnSupersedence == (attributes & MsiInterop.MsidbComponentAttributesUninstallOnSupersedence)) | ||
5042 | { | ||
5043 | component.UninstallWhenSuperseded = Wix.YesNoType.yes; | ||
5044 | } | ||
5045 | |||
5046 | if (MsiInterop.MsidbComponentAttributesShared == (attributes & MsiInterop.MsidbComponentAttributesShared)) | ||
5047 | { | ||
5048 | component.Shared = Wix.YesNoType.yes; | ||
5049 | } | ||
5050 | |||
5051 | if (null != row[4]) | ||
5052 | { | ||
5053 | Wix.Condition condition = new Wix.Condition(); | ||
5054 | |||
5055 | condition.Content = Convert.ToString(row[4]); | ||
5056 | |||
5057 | component.AddChild(condition); | ||
5058 | } | ||
5059 | |||
5060 | Wix.Directory directory = (Wix.Directory)this.core.GetIndexedElement("Directory", Convert.ToString(row[2])); | ||
5061 | if (null != directory) | ||
5062 | { | ||
5063 | directory.AddChild(component); | ||
5064 | } | ||
5065 | else | ||
5066 | { | ||
5067 | this.core.OnMessage(WixWarnings.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Directory_", Convert.ToString(row[2]), "Directory")); | ||
5068 | } | ||
5069 | this.core.IndexElement(row, component); | ||
5070 | } | ||
5071 | } | ||
5072 | |||
5073 | /// <summary> | ||
5074 | /// Decompile the Condition table. | ||
5075 | /// </summary> | ||
5076 | /// <param name="table">The table to decompile.</param> | ||
5077 | private void DecompileConditionTable(Table table) | ||
5078 | { | ||
5079 | foreach (Row row in table.Rows) | ||
5080 | { | ||
5081 | Wix.Condition condition = new Wix.Condition(); | ||
5082 | |||
5083 | condition.Level = Convert.ToInt32(row[1]); | ||
5084 | |||
5085 | if (null != row[2]) | ||
5086 | { | ||
5087 | condition.Content = Convert.ToString(row[2]); | ||
5088 | } | ||
5089 | |||
5090 | Wix.Feature feature = (Wix.Feature)this.core.GetIndexedElement("Feature", Convert.ToString(row[0])); | ||
5091 | if (null != feature) | ||
5092 | { | ||
5093 | feature.AddChild(condition); | ||
5094 | } | ||
5095 | else | ||
5096 | { | ||
5097 | this.core.OnMessage(WixWarnings.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Feature_", Convert.ToString(row[0]), "Feature")); | ||
5098 | } | ||
5099 | } | ||
5100 | } | ||
5101 | |||
5102 | /// <summary> | ||
5103 | /// Decompile the Dialog table. | ||
5104 | /// </summary> | ||
5105 | /// <param name="table">The table to decompile.</param> | ||
5106 | private void DecompileDialogTable(Table table) | ||
5107 | { | ||
5108 | foreach (Row row in table.Rows) | ||
5109 | { | ||
5110 | Wix.Dialog dialog = new Wix.Dialog(); | ||
5111 | |||
5112 | dialog.Id = Convert.ToString(row[0]); | ||
5113 | |||
5114 | dialog.X = Convert.ToInt32(row[1]); | ||
5115 | |||
5116 | dialog.Y = Convert.ToInt32(row[2]); | ||
5117 | |||
5118 | dialog.Width = Convert.ToInt32(row[3]); | ||
5119 | |||
5120 | dialog.Height = Convert.ToInt32(row[4]); | ||
5121 | |||
5122 | if (null != row[5]) | ||
5123 | { | ||
5124 | int attributes = Convert.ToInt32(row[5]); | ||
5125 | |||
5126 | if (0 == (attributes & MsiInterop.MsidbDialogAttributesVisible)) | ||
5127 | { | ||
5128 | dialog.Hidden = Wix.YesNoType.yes; | ||
5129 | } | ||
5130 | |||
5131 | if (0 == (attributes & MsiInterop.MsidbDialogAttributesModal)) | ||
5132 | { | ||
5133 | dialog.Modeless = Wix.YesNoType.yes; | ||
5134 | } | ||
5135 | |||
5136 | if (0 == (attributes & MsiInterop.MsidbDialogAttributesMinimize)) | ||
5137 | { | ||
5138 | dialog.NoMinimize = Wix.YesNoType.yes; | ||
5139 | } | ||
5140 | |||
5141 | if (MsiInterop.MsidbDialogAttributesSysModal == (attributes & MsiInterop.MsidbDialogAttributesSysModal)) | ||
5142 | { | ||
5143 | dialog.SystemModal = Wix.YesNoType.yes; | ||
5144 | } | ||
5145 | |||
5146 | if (MsiInterop.MsidbDialogAttributesKeepModeless == (attributes & MsiInterop.MsidbDialogAttributesKeepModeless)) | ||
5147 | { | ||
5148 | dialog.KeepModeless = Wix.YesNoType.yes; | ||
5149 | } | ||
5150 | |||
5151 | if (MsiInterop.MsidbDialogAttributesTrackDiskSpace == (attributes & MsiInterop.MsidbDialogAttributesTrackDiskSpace)) | ||
5152 | { | ||
5153 | dialog.TrackDiskSpace = Wix.YesNoType.yes; | ||
5154 | } | ||
5155 | |||
5156 | if (MsiInterop.MsidbDialogAttributesUseCustomPalette == (attributes & MsiInterop.MsidbDialogAttributesUseCustomPalette)) | ||
5157 | { | ||
5158 | dialog.CustomPalette = Wix.YesNoType.yes; | ||
5159 | } | ||
5160 | |||
5161 | if (MsiInterop.MsidbDialogAttributesRTLRO == (attributes & MsiInterop.MsidbDialogAttributesRTLRO)) | ||
5162 | { | ||
5163 | dialog.RightToLeft = Wix.YesNoType.yes; | ||
5164 | } | ||
5165 | |||
5166 | if (MsiInterop.MsidbDialogAttributesRightAligned == (attributes & MsiInterop.MsidbDialogAttributesRightAligned)) | ||
5167 | { | ||
5168 | dialog.RightAligned = Wix.YesNoType.yes; | ||
5169 | } | ||
5170 | |||
5171 | if (MsiInterop.MsidbDialogAttributesLeftScroll == (attributes & MsiInterop.MsidbDialogAttributesLeftScroll)) | ||
5172 | { | ||
5173 | dialog.LeftScroll = Wix.YesNoType.yes; | ||
5174 | } | ||
5175 | |||
5176 | if (MsiInterop.MsidbDialogAttributesError == (attributes & MsiInterop.MsidbDialogAttributesError)) | ||
5177 | { | ||
5178 | dialog.ErrorDialog = Wix.YesNoType.yes; | ||
5179 | } | ||
5180 | } | ||
5181 | |||
5182 | if (null != row[6]) | ||
5183 | { | ||
5184 | dialog.Title = Convert.ToString(row[6]); | ||
5185 | } | ||
5186 | |||
5187 | this.core.UIElement.AddChild(dialog); | ||
5188 | this.core.IndexElement(row, dialog); | ||
5189 | } | ||
5190 | } | ||
5191 | |||
5192 | /// <summary> | ||
5193 | /// Decompile the Directory table. | ||
5194 | /// </summary> | ||
5195 | /// <param name="table">The table to decompile.</param> | ||
5196 | private void DecompileDirectoryTable(Table table) | ||
5197 | { | ||
5198 | foreach (Row row in table.Rows) | ||
5199 | { | ||
5200 | Wix.Directory directory = new Wix.Directory(); | ||
5201 | |||
5202 | directory.Id = Convert.ToString(row[0]); | ||
5203 | |||
5204 | string[] names = Installer.GetNames(Convert.ToString(row[2])); | ||
5205 | |||
5206 | if (String.Equals(directory.Id, "TARGETDIR", StringComparison.Ordinal) && !String.Equals(names[0], "SourceDir", StringComparison.Ordinal)) | ||
5207 | { | ||
5208 | this.core.OnMessage(WixWarnings.TargetDirCorrectedDefaultDir()); | ||
5209 | directory.Name = "SourceDir"; | ||
5210 | } | ||
5211 | else | ||
5212 | { | ||
5213 | if (null != names[0] && "." != names[0]) | ||
5214 | { | ||
5215 | if (null != names[1]) | ||
5216 | { | ||
5217 | directory.ShortName = names[0]; | ||
5218 | } | ||
5219 | else | ||
5220 | { | ||
5221 | directory.Name = names[0]; | ||
5222 | } | ||
5223 | } | ||
5224 | |||
5225 | if (null != names[1]) | ||
5226 | { | ||
5227 | directory.Name = names[1]; | ||
5228 | } | ||
5229 | } | ||
5230 | |||
5231 | if (null != names[2]) | ||
5232 | { | ||
5233 | if (null != names[3]) | ||
5234 | { | ||
5235 | directory.ShortSourceName = names[2]; | ||
5236 | } | ||
5237 | else | ||
5238 | { | ||
5239 | directory.SourceName = names[2]; | ||
5240 | } | ||
5241 | } | ||
5242 | |||
5243 | if (null != names[3]) | ||
5244 | { | ||
5245 | directory.SourceName = names[3]; | ||
5246 | } | ||
5247 | |||
5248 | this.core.IndexElement(row, directory); | ||
5249 | } | ||
5250 | |||
5251 | // nest the directories | ||
5252 | foreach (Row row in table.Rows) | ||
5253 | { | ||
5254 | Wix.Directory directory = (Wix.Directory)this.core.GetIndexedElement(row); | ||
5255 | |||
5256 | if (null == row[1]) | ||
5257 | { | ||
5258 | this.core.RootElement.AddChild(directory); | ||
5259 | } | ||
5260 | else | ||
5261 | { | ||
5262 | Wix.Directory parentDirectory = (Wix.Directory)this.core.GetIndexedElement("Directory", Convert.ToString(row[1])); | ||
5263 | |||
5264 | if (null == parentDirectory) | ||
5265 | { | ||
5266 | this.core.OnMessage(WixWarnings.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Directory_Parent", Convert.ToString(row[1]), "Directory")); | ||
5267 | } | ||
5268 | else if (parentDirectory == directory) // another way to specify a root directory | ||
5269 | { | ||
5270 | this.core.RootElement.AddChild(directory); | ||
5271 | } | ||
5272 | else | ||
5273 | { | ||
5274 | parentDirectory.AddChild(directory); | ||
5275 | } | ||
5276 | } | ||
5277 | } | ||
5278 | } | ||
5279 | |||
5280 | /// <summary> | ||
5281 | /// Decompile the DrLocator table. | ||
5282 | /// </summary> | ||
5283 | /// <param name="table">The table to decompile.</param> | ||
5284 | private void DecompileDrLocatorTable(Table table) | ||
5285 | { | ||
5286 | foreach (Row row in table.Rows) | ||
5287 | { | ||
5288 | Wix.DirectorySearch directorySearch = new Wix.DirectorySearch(); | ||
5289 | |||
5290 | directorySearch.Id = Convert.ToString(row[0]); | ||
5291 | |||
5292 | if (null != row[2]) | ||
5293 | { | ||
5294 | directorySearch.Path = Convert.ToString(row[2]); | ||
5295 | } | ||
5296 | |||
5297 | if (null != row[3]) | ||
5298 | { | ||
5299 | directorySearch.Depth = Convert.ToInt32(row[3]); | ||
5300 | } | ||
5301 | |||
5302 | this.core.IndexElement(row, directorySearch); | ||
5303 | } | ||
5304 | } | ||
5305 | |||
5306 | /// <summary> | ||
5307 | /// Decompile the DuplicateFile table. | ||
5308 | /// </summary> | ||
5309 | /// <param name="table">The table to decompile.</param> | ||
5310 | private void DecompileDuplicateFileTable(Table table) | ||
5311 | { | ||
5312 | foreach (Row row in table.Rows) | ||
5313 | { | ||
5314 | Wix.CopyFile copyFile = new Wix.CopyFile(); | ||
5315 | |||
5316 | copyFile.Id = Convert.ToString(row[0]); | ||
5317 | |||
5318 | copyFile.FileId = Convert.ToString(row[2]); | ||
5319 | |||
5320 | if (null != row[3]) | ||
5321 | { | ||
5322 | string[] names = Installer.GetNames(Convert.ToString(row[3])); | ||
5323 | if (null != names[0] && null != names[1]) | ||
5324 | { | ||
5325 | copyFile.DestinationShortName = names[0]; | ||
5326 | copyFile.DestinationName = names[1]; | ||
5327 | } | ||
5328 | else if (null != names[0]) | ||
5329 | { | ||
5330 | copyFile.DestinationName = names[0]; | ||
5331 | } | ||
5332 | } | ||
5333 | |||
5334 | // destination directory/property is set in FinalizeDuplicateMoveFileTables | ||
5335 | |||
5336 | Wix.Component component = (Wix.Component)this.core.GetIndexedElement("Component", Convert.ToString(row[1])); | ||
5337 | if (null != component) | ||
5338 | { | ||
5339 | component.AddChild(copyFile); | ||
5340 | } | ||
5341 | else | ||
5342 | { | ||
5343 | this.core.OnMessage(WixWarnings.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Component_", Convert.ToString(row[1]), "Component")); | ||
5344 | } | ||
5345 | this.core.IndexElement(row, copyFile); | ||
5346 | } | ||
5347 | } | ||
5348 | |||
5349 | /// <summary> | ||
5350 | /// Decompile the Environment table. | ||
5351 | /// </summary> | ||
5352 | /// <param name="table">The table to decompile.</param> | ||
5353 | private void DecompileEnvironmentTable(Table table) | ||
5354 | { | ||
5355 | foreach (Row row in table.Rows) | ||
5356 | { | ||
5357 | Wix.Environment environment = new Wix.Environment(); | ||
5358 | |||
5359 | environment.Id = Convert.ToString(row[0]); | ||
5360 | |||
5361 | bool done = false; | ||
5362 | bool permanent = true; | ||
5363 | string name = Convert.ToString(row[1]); | ||
5364 | for (int i = 0; i < name.Length && !done; i++) | ||
5365 | { | ||
5366 | switch (name[i]) | ||
5367 | { | ||
5368 | case '=': | ||
5369 | environment.Action = Wix.Environment.ActionType.set; | ||
5370 | break; | ||
5371 | case '+': | ||
5372 | environment.Action = Wix.Environment.ActionType.create; | ||
5373 | break; | ||
5374 | case '-': | ||
5375 | permanent = false; | ||
5376 | break; | ||
5377 | case '!': | ||
5378 | environment.Action = Wix.Environment.ActionType.remove; | ||
5379 | break; | ||
5380 | case '*': | ||
5381 | environment.System = Wix.YesNoType.yes; | ||
5382 | break; | ||
5383 | default: | ||
5384 | environment.Name = name.Substring(i); | ||
5385 | done = true; | ||
5386 | break; | ||
5387 | } | ||
5388 | } | ||
5389 | |||
5390 | if (permanent) | ||
5391 | { | ||
5392 | environment.Permanent = Wix.YesNoType.yes; | ||
5393 | } | ||
5394 | |||
5395 | if (null != row[2]) | ||
5396 | { | ||
5397 | string value = Convert.ToString(row[2]); | ||
5398 | |||
5399 | if (value.StartsWith("[~]", StringComparison.Ordinal)) | ||
5400 | { | ||
5401 | environment.Part = Wix.Environment.PartType.last; | ||
5402 | |||
5403 | if (3 < value.Length) | ||
5404 | { | ||
5405 | environment.Separator = value.Substring(3, 1); | ||
5406 | environment.Value = value.Substring(4); | ||
5407 | } | ||
5408 | } | ||
5409 | else if (value.EndsWith("[~]", StringComparison.Ordinal)) | ||
5410 | { | ||
5411 | environment.Part = Wix.Environment.PartType.first; | ||
5412 | |||
5413 | if (3 < value.Length) | ||
5414 | { | ||
5415 | environment.Separator = value.Substring(value.Length - 4, 1); | ||
5416 | environment.Value = value.Substring(0, value.Length - 4); | ||
5417 | } | ||
5418 | } | ||
5419 | else | ||
5420 | { | ||
5421 | environment.Value = value; | ||
5422 | } | ||
5423 | } | ||
5424 | |||
5425 | Wix.Component component = (Wix.Component)this.core.GetIndexedElement("Component", Convert.ToString(row[3])); | ||
5426 | if (null != component) | ||
5427 | { | ||
5428 | component.AddChild(environment); | ||
5429 | } | ||
5430 | else | ||
5431 | { | ||
5432 | this.core.OnMessage(WixWarnings.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Component_", Convert.ToString(row[3]), "Component")); | ||
5433 | } | ||
5434 | } | ||
5435 | } | ||
5436 | |||
5437 | /// <summary> | ||
5438 | /// Decompile the Error table. | ||
5439 | /// </summary> | ||
5440 | /// <param name="table">The table to decompile.</param> | ||
5441 | private void DecompileErrorTable(Table table) | ||
5442 | { | ||
5443 | foreach (Row row in table.Rows) | ||
5444 | { | ||
5445 | Wix.Error error = new Wix.Error(); | ||
5446 | |||
5447 | error.Id = Convert.ToInt32(row[0]); | ||
5448 | |||
5449 | error.Content = Convert.ToString(row[1]); | ||
5450 | |||
5451 | this.core.UIElement.AddChild(error); | ||
5452 | } | ||
5453 | } | ||
5454 | |||
5455 | /// <summary> | ||
5456 | /// Decompile the EventMapping table. | ||
5457 | /// </summary> | ||
5458 | /// <param name="table">The table to decompile.</param> | ||
5459 | private void DecompileEventMappingTable(Table table) | ||
5460 | { | ||
5461 | foreach (Row row in table.Rows) | ||
5462 | { | ||
5463 | Wix.Subscribe subscribe = new Wix.Subscribe(); | ||
5464 | |||
5465 | subscribe.Event = Convert.ToString(row[2]); | ||
5466 | |||
5467 | subscribe.Attribute = Convert.ToString(row[3]); | ||
5468 | |||
5469 | Wix.Control control = (Wix.Control)this.core.GetIndexedElement("Control", Convert.ToString(row[0]), Convert.ToString(row[1])); | ||
5470 | if (null != control) | ||
5471 | { | ||
5472 | control.AddChild(subscribe); | ||
5473 | } | ||
5474 | else | ||
5475 | { | ||
5476 | this.core.OnMessage(WixWarnings.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Dialog_", Convert.ToString(row[0]), "Control_", Convert.ToString(row[1]), "Control")); | ||
5477 | } | ||
5478 | } | ||
5479 | } | ||
5480 | |||
5481 | /// <summary> | ||
5482 | /// Decompile the Extension table. | ||
5483 | /// </summary> | ||
5484 | /// <param name="table">The table to decompile.</param> | ||
5485 | private void DecompileExtensionTable(Table table) | ||
5486 | { | ||
5487 | foreach (Row row in table.Rows) | ||
5488 | { | ||
5489 | Wix.Extension extension = new Wix.Extension(); | ||
5490 | |||
5491 | extension.Advertise = Wix.YesNoType.yes; | ||
5492 | |||
5493 | extension.Id = Convert.ToString(row[0]); | ||
5494 | |||
5495 | if (null != row[3]) | ||
5496 | { | ||
5497 | Wix.MIME mime = (Wix.MIME)this.core.GetIndexedElement("MIME", Convert.ToString(row[3])); | ||
5498 | |||
5499 | if (null != mime) | ||
5500 | { | ||
5501 | mime.Default = Wix.YesNoType.yes; | ||
5502 | } | ||
5503 | else | ||
5504 | { | ||
5505 | this.core.OnMessage(WixWarnings.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "MIME_", Convert.ToString(row[3]), "MIME")); | ||
5506 | } | ||
5507 | } | ||
5508 | |||
5509 | if (null != row[2]) | ||
5510 | { | ||
5511 | Wix.ProgId progId = (Wix.ProgId)this.core.GetIndexedElement("ProgId", Convert.ToString(row[2])); | ||
5512 | |||
5513 | if (null != progId) | ||
5514 | { | ||
5515 | progId.AddChild(extension); | ||
5516 | } | ||
5517 | else | ||
5518 | { | ||
5519 | this.core.OnMessage(WixWarnings.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "ProgId_", Convert.ToString(row[2]), "ProgId")); | ||
5520 | } | ||
5521 | } | ||
5522 | else | ||
5523 | { | ||
5524 | Wix.Component component = (Wix.Component)this.core.GetIndexedElement("Component", Convert.ToString(row[1])); | ||
5525 | |||
5526 | if (null != component) | ||
5527 | { | ||
5528 | component.AddChild(extension); | ||
5529 | } | ||
5530 | else | ||
5531 | { | ||
5532 | this.core.OnMessage(WixWarnings.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Component_", Convert.ToString(row[1]), "Component")); | ||
5533 | } | ||
5534 | } | ||
5535 | |||
5536 | this.core.IndexElement(row, extension); | ||
5537 | } | ||
5538 | } | ||
5539 | |||
5540 | /// <summary> | ||
5541 | /// Decompile the ExternalFiles table. | ||
5542 | /// </summary> | ||
5543 | /// <param name="table">The table to decompile.</param> | ||
5544 | private void DecompileExternalFilesTable(Table table) | ||
5545 | { | ||
5546 | foreach (Row row in table.Rows) | ||
5547 | { | ||
5548 | Wix.ExternalFile externalFile = new Wix.ExternalFile(); | ||
5549 | |||
5550 | externalFile.File = Convert.ToString(row[1]); | ||
5551 | |||
5552 | externalFile.Source = Convert.ToString(row[2]); | ||
5553 | |||
5554 | if (null != row[3]) | ||
5555 | { | ||
5556 | string[] symbolPaths = (Convert.ToString(row[3])).Split(';'); | ||
5557 | |||
5558 | foreach (string symbolPathString in symbolPaths) | ||
5559 | { | ||
5560 | Wix.SymbolPath symbolPath = new Wix.SymbolPath(); | ||
5561 | |||
5562 | symbolPath.Path = symbolPathString; | ||
5563 | |||
5564 | externalFile.AddChild(symbolPath); | ||
5565 | } | ||
5566 | } | ||
5567 | |||
5568 | if (null != row[4] && null != row[5]) | ||
5569 | { | ||
5570 | string[] ignoreOffsets = (Convert.ToString(row[4])).Split(','); | ||
5571 | string[] ignoreLengths = (Convert.ToString(row[5])).Split(','); | ||
5572 | |||
5573 | if (ignoreOffsets.Length == ignoreLengths.Length) | ||
5574 | { | ||
5575 | for (int i = 0; i < ignoreOffsets.Length; i++) | ||
5576 | { | ||
5577 | Wix.IgnoreRange ignoreRange = new Wix.IgnoreRange(); | ||
5578 | |||
5579 | if (ignoreOffsets[i].StartsWith("0x", StringComparison.Ordinal)) | ||
5580 | { | ||
5581 | ignoreRange.Offset = Convert.ToInt32(ignoreOffsets[i].Substring(2), 16); | ||
5582 | } | ||
5583 | else | ||
5584 | { | ||
5585 | ignoreRange.Offset = Convert.ToInt32(ignoreOffsets[i], CultureInfo.InvariantCulture); | ||
5586 | } | ||
5587 | |||
5588 | if (ignoreLengths[i].StartsWith("0x", StringComparison.Ordinal)) | ||
5589 | { | ||
5590 | ignoreRange.Length = Convert.ToInt32(ignoreLengths[i].Substring(2), 16); | ||
5591 | } | ||
5592 | else | ||
5593 | { | ||
5594 | ignoreRange.Length = Convert.ToInt32(ignoreLengths[i], CultureInfo.InvariantCulture); | ||
5595 | } | ||
5596 | |||
5597 | externalFile.AddChild(ignoreRange); | ||
5598 | } | ||
5599 | } | ||
5600 | else | ||
5601 | { | ||
5602 | // TODO: warn | ||
5603 | } | ||
5604 | } | ||
5605 | else if (null != row[4] || null != row[5]) | ||
5606 | { | ||
5607 | // TODO: warn about mismatch between columns | ||
5608 | } | ||
5609 | |||
5610 | // the RetainOffsets column is handled in FinalizeFamilyFileRangesTable | ||
5611 | |||
5612 | if (null != row[7]) | ||
5613 | { | ||
5614 | externalFile.Order = Convert.ToInt32(row[7]); | ||
5615 | } | ||
5616 | |||
5617 | Wix.Family family = (Wix.Family)this.core.GetIndexedElement("ImageFamilies", Convert.ToString(row[0])); | ||
5618 | if (null != family) | ||
5619 | { | ||
5620 | family.AddChild(externalFile); | ||
5621 | } | ||
5622 | else | ||
5623 | { | ||
5624 | this.core.OnMessage(WixWarnings.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Family", Convert.ToString(row[0]), "ImageFamilies")); | ||
5625 | } | ||
5626 | this.core.IndexElement(row, externalFile); | ||
5627 | } | ||
5628 | } | ||
5629 | |||
5630 | /// <summary> | ||
5631 | /// Decompile the Feature table. | ||
5632 | /// </summary> | ||
5633 | /// <param name="table">The table to decompile.</param> | ||
5634 | private void DecompileFeatureTable(Table table) | ||
5635 | { | ||
5636 | SortedList sortedFeatures = new SortedList(); | ||
5637 | |||
5638 | foreach (Row row in table.Rows) | ||
5639 | { | ||
5640 | Wix.Feature feature = new Wix.Feature(); | ||
5641 | |||
5642 | feature.Id = Convert.ToString(row[0]); | ||
5643 | |||
5644 | if (null != row[2]) | ||
5645 | { | ||
5646 | feature.Title = Convert.ToString(row[2]); | ||
5647 | } | ||
5648 | |||
5649 | if (null != row[3]) | ||
5650 | { | ||
5651 | feature.Description = Convert.ToString(row[3]); | ||
5652 | } | ||
5653 | |||
5654 | if (null == row[4]) | ||
5655 | { | ||
5656 | feature.Display = "hidden"; | ||
5657 | } | ||
5658 | else | ||
5659 | { | ||
5660 | int display = Convert.ToInt32(row[4]); | ||
5661 | |||
5662 | if (0 == display) | ||
5663 | { | ||
5664 | feature.Display = "hidden"; | ||
5665 | } | ||
5666 | else if (1 == display % 2) | ||
5667 | { | ||
5668 | feature.Display = "expand"; | ||
5669 | } | ||
5670 | } | ||
5671 | |||
5672 | feature.Level = Convert.ToInt32(row[5]); | ||
5673 | |||
5674 | if (null != row[6]) | ||
5675 | { | ||
5676 | feature.ConfigurableDirectory = Convert.ToString(row[6]); | ||
5677 | } | ||
5678 | |||
5679 | int attributes = Convert.ToInt32(row[7]); | ||
5680 | |||
5681 | if (MsiInterop.MsidbFeatureAttributesFavorSource == (attributes & MsiInterop.MsidbFeatureAttributesFavorSource) && MsiInterop.MsidbFeatureAttributesFollowParent == (attributes & MsiInterop.MsidbFeatureAttributesFollowParent)) | ||
5682 | { | ||
5683 | // TODO: display a warning for setting favor local and follow parent together | ||
5684 | } | ||
5685 | else if (MsiInterop.MsidbFeatureAttributesFavorSource == (attributes & MsiInterop.MsidbFeatureAttributesFavorSource)) | ||
5686 | { | ||
5687 | feature.InstallDefault = Wix.Feature.InstallDefaultType.source; | ||
5688 | } | ||
5689 | else if (MsiInterop.MsidbFeatureAttributesFollowParent == (attributes & MsiInterop.MsidbFeatureAttributesFollowParent)) | ||
5690 | { | ||
5691 | feature.InstallDefault = Wix.Feature.InstallDefaultType.followParent; | ||
5692 | } | ||
5693 | |||
5694 | if (MsiInterop.MsidbFeatureAttributesFavorAdvertise == (attributes & MsiInterop.MsidbFeatureAttributesFavorAdvertise)) | ||
5695 | { | ||
5696 | feature.TypicalDefault = Wix.Feature.TypicalDefaultType.advertise; | ||
5697 | } | ||
5698 | |||
5699 | if (MsiInterop.MsidbFeatureAttributesDisallowAdvertise == (attributes & MsiInterop.MsidbFeatureAttributesDisallowAdvertise) && | ||
5700 | MsiInterop.MsidbFeatureAttributesNoUnsupportedAdvertise == (attributes & MsiInterop.MsidbFeatureAttributesNoUnsupportedAdvertise)) | ||
5701 | { | ||
5702 | this.core.OnMessage(WixWarnings.InvalidAttributeCombination(row.SourceLineNumbers, "msidbFeatureAttributesDisallowAdvertise", "msidbFeatureAttributesNoUnsupportedAdvertise", "Feature.AllowAdvertiseType", "no")); | ||
5703 | feature.AllowAdvertise = Wix.Feature.AllowAdvertiseType.no; | ||
5704 | } | ||
5705 | else if (MsiInterop.MsidbFeatureAttributesDisallowAdvertise == (attributes & MsiInterop.MsidbFeatureAttributesDisallowAdvertise)) | ||
5706 | { | ||
5707 | feature.AllowAdvertise = Wix.Feature.AllowAdvertiseType.no; | ||
5708 | } | ||
5709 | else if (MsiInterop.MsidbFeatureAttributesNoUnsupportedAdvertise == (attributes & MsiInterop.MsidbFeatureAttributesNoUnsupportedAdvertise)) | ||
5710 | { | ||
5711 | feature.AllowAdvertise = Wix.Feature.AllowAdvertiseType.system; | ||
5712 | } | ||
5713 | |||
5714 | if (MsiInterop.MsidbFeatureAttributesUIDisallowAbsent == (attributes & MsiInterop.MsidbFeatureAttributesUIDisallowAbsent)) | ||
5715 | { | ||
5716 | feature.Absent = Wix.Feature.AbsentType.disallow; | ||
5717 | } | ||
5718 | |||
5719 | this.core.IndexElement(row, feature); | ||
5720 | |||
5721 | // sort the features by their display column (and append the identifier to ensure unique keys) | ||
5722 | sortedFeatures.Add(String.Format(CultureInfo.InvariantCulture, "{0:00000}|{1}", Convert.ToInt32(row[4], CultureInfo.InvariantCulture), row[0]), row); | ||
5723 | } | ||
5724 | |||
5725 | // nest the features | ||
5726 | foreach (Row row in sortedFeatures.Values) | ||
5727 | { | ||
5728 | Wix.Feature feature = (Wix.Feature)this.core.GetIndexedElement("Feature", Convert.ToString(row[0])); | ||
5729 | |||
5730 | if (null == row[1]) | ||
5731 | { | ||
5732 | this.core.RootElement.AddChild(feature); | ||
5733 | } | ||
5734 | else | ||
5735 | { | ||
5736 | Wix.Feature parentFeature = (Wix.Feature)this.core.GetIndexedElement("Feature", Convert.ToString(row[1])); | ||
5737 | |||
5738 | if (null == parentFeature) | ||
5739 | { | ||
5740 | this.core.OnMessage(WixWarnings.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Feature_Parent", Convert.ToString(row[1]), "Feature")); | ||
5741 | } | ||
5742 | else if (parentFeature == feature) | ||
5743 | { | ||
5744 | // TODO: display a warning about self-nesting | ||
5745 | } | ||
5746 | else | ||
5747 | { | ||
5748 | parentFeature.AddChild(feature); | ||
5749 | } | ||
5750 | } | ||
5751 | } | ||
5752 | } | ||
5753 | |||
5754 | /// <summary> | ||
5755 | /// Decompile the FeatureComponents table. | ||
5756 | /// </summary> | ||
5757 | /// <param name="table">The table to decompile.</param> | ||
5758 | private void DecompileFeatureComponentsTable(Table table) | ||
5759 | { | ||
5760 | foreach (Row row in table.Rows) | ||
5761 | { | ||
5762 | Wix.ComponentRef componentRef = new Wix.ComponentRef(); | ||
5763 | |||
5764 | componentRef.Id = Convert.ToString(row[1]); | ||
5765 | |||
5766 | Wix.Feature parentFeature = (Wix.Feature)this.core.GetIndexedElement("Feature", Convert.ToString(row[0])); | ||
5767 | if (null != parentFeature) | ||
5768 | { | ||
5769 | parentFeature.AddChild(componentRef); | ||
5770 | } | ||
5771 | else | ||
5772 | { | ||
5773 | this.core.OnMessage(WixWarnings.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Feature_", Convert.ToString(row[0]), "Feature")); | ||
5774 | } | ||
5775 | this.core.IndexElement(row, componentRef); | ||
5776 | } | ||
5777 | } | ||
5778 | |||
5779 | /// <summary> | ||
5780 | /// Decompile the File table. | ||
5781 | /// </summary> | ||
5782 | /// <param name="table">The table to decompile.</param> | ||
5783 | private void DecompileFileTable(Table table) | ||
5784 | { | ||
5785 | foreach (FileRow fileRow in table.Rows) | ||
5786 | { | ||
5787 | Wix.File file = new Wix.File(); | ||
5788 | |||
5789 | file.Id = fileRow.File; | ||
5790 | |||
5791 | string[] names = Installer.GetNames(fileRow.FileName); | ||
5792 | if (null != names[0] && null != names[1]) | ||
5793 | { | ||
5794 | file.ShortName = names[0]; | ||
5795 | file.Name = names[1]; | ||
5796 | } | ||
5797 | else if (null != names[0]) | ||
5798 | { | ||
5799 | file.Name = names[0]; | ||
5800 | } | ||
5801 | |||
5802 | if (null != fileRow.Version && 0 < fileRow.Version.Length) | ||
5803 | { | ||
5804 | if (!Char.IsDigit(fileRow.Version[0])) | ||
5805 | { | ||
5806 | file.CompanionFile = fileRow.Version; | ||
5807 | } | ||
5808 | } | ||
5809 | |||
5810 | if (MsiInterop.MsidbFileAttributesReadOnly == (fileRow.Attributes & MsiInterop.MsidbFileAttributesReadOnly)) | ||
5811 | { | ||
5812 | file.ReadOnly = Wix.YesNoType.yes; | ||
5813 | } | ||
5814 | |||
5815 | if (MsiInterop.MsidbFileAttributesHidden == (fileRow.Attributes & MsiInterop.MsidbFileAttributesHidden)) | ||
5816 | { | ||
5817 | file.Hidden = Wix.YesNoType.yes; | ||
5818 | } | ||
5819 | |||
5820 | if (MsiInterop.MsidbFileAttributesSystem == (fileRow.Attributes & MsiInterop.MsidbFileAttributesSystem)) | ||
5821 | { | ||
5822 | file.System = Wix.YesNoType.yes; | ||
5823 | } | ||
5824 | |||
5825 | if (MsiInterop.MsidbFileAttributesVital != (fileRow.Attributes & MsiInterop.MsidbFileAttributesVital)) | ||
5826 | { | ||
5827 | file.Vital = Wix.YesNoType.no; | ||
5828 | } | ||
5829 | |||
5830 | if (MsiInterop.MsidbFileAttributesChecksum == (fileRow.Attributes & MsiInterop.MsidbFileAttributesChecksum)) | ||
5831 | { | ||
5832 | file.Checksum = Wix.YesNoType.yes; | ||
5833 | } | ||
5834 | |||
5835 | if (MsiInterop.MsidbFileAttributesNoncompressed == (fileRow.Attributes & MsiInterop.MsidbFileAttributesNoncompressed) && | ||
5836 | MsiInterop.MsidbFileAttributesCompressed == (fileRow.Attributes & MsiInterop.MsidbFileAttributesCompressed)) | ||
5837 | { | ||
5838 | // TODO: error | ||
5839 | } | ||
5840 | else if (MsiInterop.MsidbFileAttributesNoncompressed == (fileRow.Attributes & MsiInterop.MsidbFileAttributesNoncompressed)) | ||
5841 | { | ||
5842 | file.Compressed = Wix.YesNoDefaultType.no; | ||
5843 | } | ||
5844 | else if (MsiInterop.MsidbFileAttributesCompressed == (fileRow.Attributes & MsiInterop.MsidbFileAttributesCompressed)) | ||
5845 | { | ||
5846 | file.Compressed = Wix.YesNoDefaultType.yes; | ||
5847 | } | ||
5848 | |||
5849 | this.core.IndexElement(fileRow, file); | ||
5850 | } | ||
5851 | } | ||
5852 | |||
5853 | /// <summary> | ||
5854 | /// Decompile the FileSFPCatalog table. | ||
5855 | /// </summary> | ||
5856 | /// <param name="table">The table to decompile.</param> | ||
5857 | private void DecompileFileSFPCatalogTable(Table table) | ||
5858 | { | ||
5859 | foreach (Row row in table.Rows) | ||
5860 | { | ||
5861 | Wix.SFPFile sfpFile = new Wix.SFPFile(); | ||
5862 | |||
5863 | sfpFile.Id = Convert.ToString(row[0]); | ||
5864 | |||
5865 | Wix.SFPCatalog sfpCatalog = (Wix.SFPCatalog)this.core.GetIndexedElement("SFPCatalog", Convert.ToString(row[1])); | ||
5866 | if (null != sfpCatalog) | ||
5867 | { | ||
5868 | sfpCatalog.AddChild(sfpFile); | ||
5869 | } | ||
5870 | else | ||
5871 | { | ||
5872 | this.core.OnMessage(WixWarnings.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "SFPCatalog_", Convert.ToString(row[1]), "SFPCatalog")); | ||
5873 | } | ||
5874 | } | ||
5875 | } | ||
5876 | |||
5877 | /// <summary> | ||
5878 | /// Decompile the Font table. | ||
5879 | /// </summary> | ||
5880 | /// <param name="table">The table to decompile.</param> | ||
5881 | private void DecompileFontTable(Table table) | ||
5882 | { | ||
5883 | foreach (Row row in table.Rows) | ||
5884 | { | ||
5885 | Wix.File file = (Wix.File)this.core.GetIndexedElement("File", Convert.ToString(row[0])); | ||
5886 | |||
5887 | if (null != file) | ||
5888 | { | ||
5889 | if (null != row[1]) | ||
5890 | { | ||
5891 | file.FontTitle = Convert.ToString(row[1]); | ||
5892 | } | ||
5893 | else | ||
5894 | { | ||
5895 | file.TrueType = Wix.YesNoType.yes; | ||
5896 | } | ||
5897 | } | ||
5898 | else | ||
5899 | { | ||
5900 | this.core.OnMessage(WixWarnings.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "File_", Convert.ToString(row[0]), "File")); | ||
5901 | } | ||
5902 | } | ||
5903 | } | ||
5904 | |||
5905 | /// <summary> | ||
5906 | /// Decompile the Icon table. | ||
5907 | /// </summary> | ||
5908 | /// <param name="table">The table to decompile.</param> | ||
5909 | private void DecompileIconTable(Table table) | ||
5910 | { | ||
5911 | foreach (Row row in table.Rows) | ||
5912 | { | ||
5913 | Wix.Icon icon = new Wix.Icon(); | ||
5914 | |||
5915 | icon.Id = Convert.ToString(row[0]); | ||
5916 | |||
5917 | icon.SourceFile = Convert.ToString(row[1]); | ||
5918 | |||
5919 | this.core.RootElement.AddChild(icon); | ||
5920 | } | ||
5921 | } | ||
5922 | |||
5923 | /// <summary> | ||
5924 | /// Decompile the ImageFamilies table. | ||
5925 | /// </summary> | ||
5926 | /// <param name="table">The table to decompile.</param> | ||
5927 | private void DecompileImageFamiliesTable(Table table) | ||
5928 | { | ||
5929 | foreach (Row row in table.Rows) | ||
5930 | { | ||
5931 | Wix.Family family = new Wix.Family(); | ||
5932 | |||
5933 | family.Name = Convert.ToString(row[0]); | ||
5934 | |||
5935 | if (null != row[1]) | ||
5936 | { | ||
5937 | family.MediaSrcProp = Convert.ToString(row[1]); | ||
5938 | } | ||
5939 | |||
5940 | if (null != row[2]) | ||
5941 | { | ||
5942 | family.DiskId = Convert.ToString(Convert.ToInt32(row[2])); | ||
5943 | } | ||
5944 | |||
5945 | if (null != row[3]) | ||
5946 | { | ||
5947 | family.SequenceStart = Convert.ToInt32(row[3]); | ||
5948 | } | ||
5949 | |||
5950 | if (null != row[4]) | ||
5951 | { | ||
5952 | family.DiskPrompt = Convert.ToString(row[4]); | ||
5953 | } | ||
5954 | |||
5955 | if (null != row[5]) | ||
5956 | { | ||
5957 | family.VolumeLabel = Convert.ToString(row[5]); | ||
5958 | } | ||
5959 | |||
5960 | this.core.RootElement.AddChild(family); | ||
5961 | this.core.IndexElement(row, family); | ||
5962 | } | ||
5963 | } | ||
5964 | |||
5965 | /// <summary> | ||
5966 | /// Decompile the IniFile table. | ||
5967 | /// </summary> | ||
5968 | /// <param name="table">The table to decompile.</param> | ||
5969 | private void DecompileIniFileTable(Table table) | ||
5970 | { | ||
5971 | foreach (Row row in table.Rows) | ||
5972 | { | ||
5973 | Wix.IniFile iniFile = new Wix.IniFile(); | ||
5974 | |||
5975 | iniFile.Id = Convert.ToString(row[0]); | ||
5976 | |||
5977 | string[] names = Installer.GetNames(Convert.ToString(row[1])); | ||
5978 | |||
5979 | if (null != names[0]) | ||
5980 | { | ||
5981 | if (null == names[1]) | ||
5982 | { | ||
5983 | iniFile.Name = names[0]; | ||
5984 | } | ||
5985 | else | ||
5986 | { | ||
5987 | iniFile.ShortName = names[0]; | ||
5988 | } | ||
5989 | } | ||
5990 | |||
5991 | if (null != names[1]) | ||
5992 | { | ||
5993 | iniFile.Name = names[1]; | ||
5994 | } | ||
5995 | |||
5996 | if (null != row[2]) | ||
5997 | { | ||
5998 | iniFile.Directory = Convert.ToString(row[2]); | ||
5999 | } | ||
6000 | |||
6001 | iniFile.Section = Convert.ToString(row[3]); | ||
6002 | |||
6003 | iniFile.Key = Convert.ToString(row[4]); | ||
6004 | |||
6005 | iniFile.Value = Convert.ToString(row[5]); | ||
6006 | |||
6007 | switch (Convert.ToInt32(row[6])) | ||
6008 | { | ||
6009 | case MsiInterop.MsidbIniFileActionAddLine: | ||
6010 | iniFile.Action = Wix.IniFile.ActionType.addLine; | ||
6011 | break; | ||
6012 | case MsiInterop.MsidbIniFileActionCreateLine: | ||
6013 | iniFile.Action = Wix.IniFile.ActionType.createLine; | ||
6014 | break; | ||
6015 | case MsiInterop.MsidbIniFileActionAddTag: | ||
6016 | iniFile.Action = Wix.IniFile.ActionType.addTag; | ||
6017 | break; | ||
6018 | default: | ||
6019 | this.core.OnMessage(WixWarnings.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[6].Column.Name, row[6])); | ||
6020 | break; | ||
6021 | } | ||
6022 | |||
6023 | Wix.Component component = (Wix.Component)this.core.GetIndexedElement("Component", Convert.ToString(row[7])); | ||
6024 | if (null != component) | ||
6025 | { | ||
6026 | component.AddChild(iniFile); | ||
6027 | } | ||
6028 | else | ||
6029 | { | ||
6030 | this.core.OnMessage(WixWarnings.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Component_", Convert.ToString(row[7]), "Component")); | ||
6031 | } | ||
6032 | } | ||
6033 | } | ||
6034 | |||
6035 | /// <summary> | ||
6036 | /// Decompile the IniLocator table. | ||
6037 | /// </summary> | ||
6038 | /// <param name="table">The table to decompile.</param> | ||
6039 | private void DecompileIniLocatorTable(Table table) | ||
6040 | { | ||
6041 | foreach (Row row in table.Rows) | ||
6042 | { | ||
6043 | Wix.IniFileSearch iniFileSearch = new Wix.IniFileSearch(); | ||
6044 | |||
6045 | iniFileSearch.Id = Convert.ToString(row[0]); | ||
6046 | |||
6047 | string[] names = Installer.GetNames(Convert.ToString(row[1])); | ||
6048 | if (null != names[0] && null != names[1]) | ||
6049 | { | ||
6050 | iniFileSearch.ShortName = names[0]; | ||
6051 | iniFileSearch.Name = names[1]; | ||
6052 | } | ||
6053 | else if (null != names[0]) | ||
6054 | { | ||
6055 | iniFileSearch.Name = names[0]; | ||
6056 | } | ||
6057 | |||
6058 | iniFileSearch.Section = Convert.ToString(row[2]); | ||
6059 | |||
6060 | iniFileSearch.Key = Convert.ToString(row[3]); | ||
6061 | |||
6062 | if (null != row[4]) | ||
6063 | { | ||
6064 | int field = Convert.ToInt32(row[4]); | ||
6065 | |||
6066 | if (0 != field) | ||
6067 | { | ||
6068 | iniFileSearch.Field = field; | ||
6069 | } | ||
6070 | } | ||
6071 | |||
6072 | if (null != row[5]) | ||
6073 | { | ||
6074 | switch (Convert.ToInt32(row[5])) | ||
6075 | { | ||
6076 | case MsiInterop.MsidbLocatorTypeDirectory: | ||
6077 | iniFileSearch.Type = Wix.IniFileSearch.TypeType.directory; | ||
6078 | break; | ||
6079 | case MsiInterop.MsidbLocatorTypeFileName: | ||
6080 | // this is the default value | ||
6081 | break; | ||
6082 | case MsiInterop.MsidbLocatorTypeRawValue: | ||
6083 | iniFileSearch.Type = Wix.IniFileSearch.TypeType.raw; | ||
6084 | break; | ||
6085 | default: | ||
6086 | this.core.OnMessage(WixWarnings.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[5].Column.Name, row[5])); | ||
6087 | break; | ||
6088 | } | ||
6089 | } | ||
6090 | |||
6091 | this.core.IndexElement(row, iniFileSearch); | ||
6092 | } | ||
6093 | } | ||
6094 | |||
6095 | /// <summary> | ||
6096 | /// Decompile the IsolatedComponent table. | ||
6097 | /// </summary> | ||
6098 | /// <param name="table">The table to decompile.</param> | ||
6099 | private void DecompileIsolatedComponentTable(Table table) | ||
6100 | { | ||
6101 | foreach (Row row in table.Rows) | ||
6102 | { | ||
6103 | Wix.IsolateComponent isolateComponent = new Wix.IsolateComponent(); | ||
6104 | |||
6105 | isolateComponent.Shared = Convert.ToString(row[0]); | ||
6106 | |||
6107 | Wix.Component component = (Wix.Component)this.core.GetIndexedElement("Component", Convert.ToString(row[1])); | ||
6108 | if (null != component) | ||
6109 | { | ||
6110 | component.AddChild(isolateComponent); | ||
6111 | } | ||
6112 | else | ||
6113 | { | ||
6114 | this.core.OnMessage(WixWarnings.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Component_", Convert.ToString(row[1]), "Component")); | ||
6115 | } | ||
6116 | } | ||
6117 | } | ||
6118 | |||
6119 | /// <summary> | ||
6120 | /// Decompile the LaunchCondition table. | ||
6121 | /// </summary> | ||
6122 | /// <param name="table">The table to decompile.</param> | ||
6123 | private void DecompileLaunchConditionTable(Table table) | ||
6124 | { | ||
6125 | foreach (Row row in table.Rows) | ||
6126 | { | ||
6127 | if (Compiler.DowngradePreventedCondition == Convert.ToString(row[0]) || Compiler.UpgradePreventedCondition == Convert.ToString(row[0])) | ||
6128 | { | ||
6129 | continue; // MajorUpgrade rows processed in FinalizeUpgradeTable | ||
6130 | } | ||
6131 | |||
6132 | Wix.Condition condition = new Wix.Condition(); | ||
6133 | |||
6134 | condition.Content = Convert.ToString(row[0]); | ||
6135 | |||
6136 | condition.Message = Convert.ToString(row[1]); | ||
6137 | |||
6138 | this.core.RootElement.AddChild(condition); | ||
6139 | } | ||
6140 | } | ||
6141 | |||
6142 | /// <summary> | ||
6143 | /// Decompile the ListBox table. | ||
6144 | /// </summary> | ||
6145 | /// <param name="table">The table to decompile.</param> | ||
6146 | private void DecompileListBoxTable(Table table) | ||
6147 | { | ||
6148 | Wix.ListBox listBox = null; | ||
6149 | SortedList listBoxRows = new SortedList(); | ||
6150 | |||
6151 | // sort the list boxes by their property and order | ||
6152 | foreach (Row row in table.Rows) | ||
6153 | { | ||
6154 | listBoxRows.Add(String.Concat("{0}|{1:0000000000}", row[0], row[1]), row); | ||
6155 | } | ||
6156 | |||
6157 | foreach (Row row in listBoxRows.Values) | ||
6158 | { | ||
6159 | if (null == listBox || Convert.ToString(row[0]) != listBox.Property) | ||
6160 | { | ||
6161 | listBox = new Wix.ListBox(); | ||
6162 | |||
6163 | listBox.Property = Convert.ToString(row[0]); | ||
6164 | |||
6165 | this.core.UIElement.AddChild(listBox); | ||
6166 | } | ||
6167 | |||
6168 | Wix.ListItem listItem = new Wix.ListItem(); | ||
6169 | |||
6170 | listItem.Value = Convert.ToString(row[2]); | ||
6171 | |||
6172 | if (null != row[3]) | ||
6173 | { | ||
6174 | listItem.Text = Convert.ToString(row[3]); | ||
6175 | } | ||
6176 | |||
6177 | listBox.AddChild(listItem); | ||
6178 | } | ||
6179 | } | ||
6180 | |||
6181 | /// <summary> | ||
6182 | /// Decompile the ListView table. | ||
6183 | /// </summary> | ||
6184 | /// <param name="table">The table to decompile.</param> | ||
6185 | private void DecompileListViewTable(Table table) | ||
6186 | { | ||
6187 | Wix.ListView listView = null; | ||
6188 | SortedList listViewRows = new SortedList(); | ||
6189 | |||
6190 | // sort the list views by their property and order | ||
6191 | foreach (Row row in table.Rows) | ||
6192 | { | ||
6193 | listViewRows.Add(String.Concat("{0}|{1:0000000000}", row[0], row[1]), row); | ||
6194 | } | ||
6195 | |||
6196 | foreach (Row row in listViewRows.Values) | ||
6197 | { | ||
6198 | if (null == listView || Convert.ToString(row[0]) != listView.Property) | ||
6199 | { | ||
6200 | listView = new Wix.ListView(); | ||
6201 | |||
6202 | listView.Property = Convert.ToString(row[0]); | ||
6203 | |||
6204 | this.core.UIElement.AddChild(listView); | ||
6205 | } | ||
6206 | |||
6207 | Wix.ListItem listItem = new Wix.ListItem(); | ||
6208 | |||
6209 | listItem.Value = Convert.ToString(row[2]); | ||
6210 | |||
6211 | if (null != row[3]) | ||
6212 | { | ||
6213 | listItem.Text = Convert.ToString(row[3]); | ||
6214 | } | ||
6215 | |||
6216 | if (null != row[4]) | ||
6217 | { | ||
6218 | listItem.Icon = Convert.ToString(row[4]); | ||
6219 | } | ||
6220 | |||
6221 | listView.AddChild(listItem); | ||
6222 | } | ||
6223 | } | ||
6224 | |||
6225 | /// <summary> | ||
6226 | /// Decompile the LockPermissions table. | ||
6227 | /// </summary> | ||
6228 | /// <param name="table">The table to decompile.</param> | ||
6229 | private void DecompileLockPermissionsTable(Table table) | ||
6230 | { | ||
6231 | foreach (Row row in table.Rows) | ||
6232 | { | ||
6233 | Wix.Permission permission = new Wix.Permission(); | ||
6234 | string[] specialPermissions; | ||
6235 | |||
6236 | switch (Convert.ToString(row[1])) | ||
6237 | { | ||
6238 | case "CreateFolder": | ||
6239 | specialPermissions = Common.FolderPermissions; | ||
6240 | break; | ||
6241 | case "File": | ||
6242 | specialPermissions = Common.FilePermissions; | ||
6243 | break; | ||
6244 | case "Registry": | ||
6245 | specialPermissions = Common.RegistryPermissions; | ||
6246 | break; | ||
6247 | default: | ||
6248 | this.core.OnMessage(WixWarnings.IllegalColumnValue(row.SourceLineNumbers, row.Table.Name, row.Fields[1].Column.Name, row[1])); | ||
6249 | return; | ||
6250 | } | ||
6251 | |||
6252 | int permissionBits = Convert.ToInt32(row[4]); | ||
6253 | for (int i = 0; i < 32; i++) | ||
6254 | { | ||
6255 | if (0 != ((permissionBits >> i) & 1)) | ||
6256 | { | ||
6257 | string name = null; | ||
6258 | |||
6259 | if (specialPermissions.Length > i) | ||
6260 | { | ||
6261 | name = specialPermissions[i]; | ||
6262 | } | ||
6263 | else if (16 > i && specialPermissions.Length <= i) | ||
6264 | { | ||
6265 | name = "SpecificRightsAll"; | ||
6266 | } | ||
6267 | else if (28 > i && Common.StandardPermissions.Length > (i - 16)) | ||
6268 | { | ||
6269 | name = Common.StandardPermissions[i - 16]; | ||
6270 | } | ||
6271 | else if (0 <= (i - 28) && Common.GenericPermissions.Length > (i - 28)) | ||
6272 | { | ||
6273 | name = Common.GenericPermissions[i - 28]; | ||
6274 | } | ||
6275 | |||
6276 | if (null == name) | ||
6277 | { | ||
6278 | this.core.OnMessage(WixWarnings.UnknownPermission(row.SourceLineNumbers, row.Table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), i)); | ||
6279 | } | ||
6280 | else | ||
6281 | { | ||
6282 | switch (name) | ||
6283 | { | ||
6284 | case "Append": | ||
6285 | permission.Append = Wix.YesNoType.yes; | ||
6286 | break; | ||
6287 | case "ChangePermission": | ||
6288 | permission.ChangePermission = Wix.YesNoType.yes; | ||
6289 | break; | ||
6290 | case "CreateChild": | ||
6291 | permission.CreateChild = Wix.YesNoType.yes; | ||
6292 | break; | ||
6293 | case "CreateFile": | ||
6294 | permission.CreateFile = Wix.YesNoType.yes; | ||
6295 | break; | ||
6296 | case "CreateLink": | ||
6297 | permission.CreateLink = Wix.YesNoType.yes; | ||
6298 | break; | ||
6299 | case "CreateSubkeys": | ||
6300 | permission.CreateSubkeys = Wix.YesNoType.yes; | ||
6301 | break; | ||
6302 | case "Delete": | ||
6303 | permission.Delete = Wix.YesNoType.yes; | ||
6304 | break; | ||
6305 | case "DeleteChild": | ||
6306 | permission.DeleteChild = Wix.YesNoType.yes; | ||
6307 | break; | ||
6308 | case "EnumerateSubkeys": | ||
6309 | permission.EnumerateSubkeys = Wix.YesNoType.yes; | ||
6310 | break; | ||
6311 | case "Execute": | ||
6312 | permission.Execute = Wix.YesNoType.yes; | ||
6313 | break; | ||
6314 | case "FileAllRights": | ||
6315 | permission.FileAllRights = Wix.YesNoType.yes; | ||
6316 | break; | ||
6317 | case "GenericAll": | ||
6318 | permission.GenericAll = Wix.YesNoType.yes; | ||
6319 | break; | ||
6320 | case "GenericExecute": | ||
6321 | permission.GenericExecute = Wix.YesNoType.yes; | ||
6322 | break; | ||
6323 | case "GenericRead": | ||
6324 | permission.GenericRead = Wix.YesNoType.yes; | ||
6325 | break; | ||
6326 | case "GenericWrite": | ||
6327 | permission.GenericWrite = Wix.YesNoType.yes; | ||
6328 | break; | ||
6329 | case "Notify": | ||
6330 | permission.Notify = Wix.YesNoType.yes; | ||
6331 | break; | ||
6332 | case "Read": | ||
6333 | permission.Read = Wix.YesNoType.yes; | ||
6334 | break; | ||
6335 | case "ReadAttributes": | ||
6336 | permission.ReadAttributes = Wix.YesNoType.yes; | ||
6337 | break; | ||
6338 | case "ReadExtendedAttributes": | ||
6339 | permission.ReadExtendedAttributes = Wix.YesNoType.yes; | ||
6340 | break; | ||
6341 | case "ReadPermission": | ||
6342 | permission.ReadPermission = Wix.YesNoType.yes; | ||
6343 | break; | ||
6344 | case "SpecificRightsAll": | ||
6345 | permission.SpecificRightsAll = Wix.YesNoType.yes; | ||
6346 | break; | ||
6347 | case "Synchronize": | ||
6348 | permission.Synchronize = Wix.YesNoType.yes; | ||
6349 | break; | ||
6350 | case "TakeOwnership": | ||
6351 | permission.TakeOwnership = Wix.YesNoType.yes; | ||
6352 | break; | ||
6353 | case "Traverse": | ||
6354 | permission.Traverse = Wix.YesNoType.yes; | ||
6355 | break; | ||
6356 | case "Write": | ||
6357 | permission.Write = Wix.YesNoType.yes; | ||
6358 | break; | ||
6359 | case "WriteAttributes": | ||
6360 | permission.WriteAttributes = Wix.YesNoType.yes; | ||
6361 | break; | ||
6362 | case "WriteExtendedAttributes": | ||
6363 | permission.WriteExtendedAttributes = Wix.YesNoType.yes; | ||
6364 | break; | ||
6365 | default: | ||
6366 | throw new InvalidOperationException(String.Format(CultureInfo.CurrentUICulture, WixStrings.EXP_UnknownPermissionAttribute, name)); | ||
6367 | } | ||
6368 | } | ||
6369 | } | ||
6370 | } | ||
6371 | |||
6372 | if (null != row[2]) | ||
6373 | { | ||
6374 | permission.Domain = Convert.ToString(row[2]); | ||
6375 | } | ||
6376 | |||
6377 | permission.User = Convert.ToString(row[3]); | ||
6378 | |||
6379 | this.core.IndexElement(row, permission); | ||
6380 | } | ||
6381 | } | ||
6382 | |||
6383 | /// <summary> | ||
6384 | /// Decompile the Media table. | ||
6385 | /// </summary> | ||
6386 | /// <param name="table">The table to decompile.</param> | ||
6387 | private void DecompileMediaTable(Table table) | ||
6388 | { | ||
6389 | foreach (MediaRow mediaRow in table.Rows) | ||
6390 | { | ||
6391 | Wix.Media media = new Wix.Media(); | ||
6392 | |||
6393 | media.Id = Convert.ToString(mediaRow.DiskId); | ||
6394 | |||
6395 | if (null != mediaRow.DiskPrompt) | ||
6396 | { | ||
6397 | media.DiskPrompt = mediaRow.DiskPrompt; | ||
6398 | } | ||
6399 | |||
6400 | if (null != mediaRow.Cabinet) | ||
6401 | { | ||
6402 | string cabinet = mediaRow.Cabinet; | ||
6403 | |||
6404 | if (cabinet.StartsWith("#", StringComparison.Ordinal)) | ||
6405 | { | ||
6406 | media.EmbedCab = Wix.YesNoType.yes; | ||
6407 | cabinet = cabinet.Substring(1); | ||
6408 | } | ||
6409 | |||
6410 | media.Cabinet = cabinet; | ||
6411 | } | ||
6412 | |||
6413 | if (null != mediaRow.VolumeLabel) | ||
6414 | { | ||
6415 | media.VolumeLabel = mediaRow.VolumeLabel; | ||
6416 | } | ||
6417 | |||
6418 | this.core.RootElement.AddChild(media); | ||
6419 | this.core.IndexElement(mediaRow, media); | ||
6420 | } | ||
6421 | } | ||
6422 | |||
6423 | /// <summary> | ||
6424 | /// Decompile the MIME table. | ||
6425 | /// </summary> | ||
6426 | /// <param name="table">The table to decompile.</param> | ||
6427 | private void DecompileMIMETable(Table table) | ||
6428 | { | ||
6429 | foreach (Row row in table.Rows) | ||
6430 | { | ||
6431 | Wix.MIME mime = new Wix.MIME(); | ||
6432 | |||
6433 | mime.ContentType = Convert.ToString(row[0]); | ||
6434 | |||
6435 | if (null != row[2]) | ||
6436 | { | ||
6437 | mime.Class = Convert.ToString(row[2]); | ||
6438 | } | ||
6439 | |||
6440 | this.core.IndexElement(row, mime); | ||
6441 | } | ||
6442 | } | ||
6443 | |||
6444 | /// <summary> | ||
6445 | /// Decompile the ModuleConfiguration table. | ||
6446 | /// </summary> | ||
6447 | /// <param name="table">The table to decompile.</param> | ||
6448 | private void DecompileModuleConfigurationTable(Table table) | ||
6449 | { | ||
6450 | foreach (Row row in table.Rows) | ||
6451 | { | ||
6452 | Wix.Configuration configuration = new Wix.Configuration(); | ||
6453 | |||
6454 | configuration.Name = Convert.ToString(row[0]); | ||
6455 | |||
6456 | switch (Convert.ToInt32(row[1])) | ||
6457 | { | ||
6458 | case 0: | ||
6459 | configuration.Format = Wix.Configuration.FormatType.Text; | ||
6460 | break; | ||
6461 | case 1: | ||
6462 | configuration.Format = Wix.Configuration.FormatType.Key; | ||
6463 | break; | ||
6464 | case 2: | ||
6465 | configuration.Format = Wix.Configuration.FormatType.Integer; | ||
6466 | break; | ||
6467 | case 3: | ||
6468 | configuration.Format = Wix.Configuration.FormatType.Bitfield; | ||
6469 | break; | ||
6470 | default: | ||
6471 | this.core.OnMessage(WixWarnings.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[1].Column.Name, row[1])); | ||
6472 | break; | ||
6473 | } | ||
6474 | |||
6475 | if (null != row[2]) | ||
6476 | { | ||
6477 | configuration.Type = Convert.ToString(row[2]); | ||
6478 | } | ||
6479 | |||
6480 | if (null != row[3]) | ||
6481 | { | ||
6482 | configuration.ContextData = Convert.ToString(row[3]); | ||
6483 | } | ||
6484 | |||
6485 | if (null != row[4]) | ||
6486 | { | ||
6487 | configuration.DefaultValue = Convert.ToString(row[4]); | ||
6488 | } | ||
6489 | |||
6490 | if (null != row[5]) | ||
6491 | { | ||
6492 | int attributes = Convert.ToInt32(row[5]); | ||
6493 | |||
6494 | if (MsiInterop.MsidbMsmConfigurableOptionKeyNoOrphan == (attributes & MsiInterop.MsidbMsmConfigurableOptionKeyNoOrphan)) | ||
6495 | { | ||
6496 | configuration.KeyNoOrphan = Wix.YesNoType.yes; | ||
6497 | } | ||
6498 | |||
6499 | if (MsiInterop.MsidbMsmConfigurableOptionNonNullable == (attributes & MsiInterop.MsidbMsmConfigurableOptionNonNullable)) | ||
6500 | { | ||
6501 | configuration.NonNullable = Wix.YesNoType.yes; | ||
6502 | } | ||
6503 | |||
6504 | if (3 < attributes) | ||
6505 | { | ||
6506 | this.core.OnMessage(WixWarnings.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[5].Column.Name, row[5])); | ||
6507 | } | ||
6508 | } | ||
6509 | |||
6510 | if (null != row[6]) | ||
6511 | { | ||
6512 | configuration.DisplayName = Convert.ToString(row[6]); | ||
6513 | } | ||
6514 | |||
6515 | if (null != row[7]) | ||
6516 | { | ||
6517 | configuration.Description = Convert.ToString(row[7]); | ||
6518 | } | ||
6519 | |||
6520 | if (null != row[8]) | ||
6521 | { | ||
6522 | configuration.HelpLocation = Convert.ToString(row[8]); | ||
6523 | } | ||
6524 | |||
6525 | if (null != row[9]) | ||
6526 | { | ||
6527 | configuration.HelpKeyword = Convert.ToString(row[9]); | ||
6528 | } | ||
6529 | |||
6530 | this.core.RootElement.AddChild(configuration); | ||
6531 | } | ||
6532 | } | ||
6533 | |||
6534 | /// <summary> | ||
6535 | /// Decompile the ModuleDependency table. | ||
6536 | /// </summary> | ||
6537 | /// <param name="table">The table to decompile.</param> | ||
6538 | private void DecompileModuleDependencyTable(Table table) | ||
6539 | { | ||
6540 | foreach (Row row in table.Rows) | ||
6541 | { | ||
6542 | Wix.Dependency dependency = new Wix.Dependency(); | ||
6543 | |||
6544 | dependency.RequiredId = Convert.ToString(row[2]); | ||
6545 | |||
6546 | dependency.RequiredLanguage = Convert.ToInt32(row[3], CultureInfo.InvariantCulture); | ||
6547 | |||
6548 | if (null != row[4]) | ||
6549 | { | ||
6550 | dependency.RequiredVersion = Convert.ToString(row[4]); | ||
6551 | } | ||
6552 | |||
6553 | this.core.RootElement.AddChild(dependency); | ||
6554 | } | ||
6555 | } | ||
6556 | |||
6557 | /// <summary> | ||
6558 | /// Decompile the ModuleExclusion table. | ||
6559 | /// </summary> | ||
6560 | /// <param name="table">The table to decompile.</param> | ||
6561 | private void DecompileModuleExclusionTable(Table table) | ||
6562 | { | ||
6563 | foreach (Row row in table.Rows) | ||
6564 | { | ||
6565 | Wix.Exclusion exclusion = new Wix.Exclusion(); | ||
6566 | |||
6567 | exclusion.ExcludedId = Convert.ToString(row[2]); | ||
6568 | |||
6569 | int excludedLanguage = Convert.ToInt32(Convert.ToString(row[3]), CultureInfo.InvariantCulture); | ||
6570 | if (0 < excludedLanguage) | ||
6571 | { | ||
6572 | exclusion.ExcludeLanguage = excludedLanguage; | ||
6573 | } | ||
6574 | else if (0 > excludedLanguage) | ||
6575 | { | ||
6576 | exclusion.ExcludeExceptLanguage = -excludedLanguage; | ||
6577 | } | ||
6578 | |||
6579 | if (null != row[4]) | ||
6580 | { | ||
6581 | exclusion.ExcludedMinVersion = Convert.ToString(row[4]); | ||
6582 | } | ||
6583 | |||
6584 | if (null != row[5]) | ||
6585 | { | ||
6586 | exclusion.ExcludedMinVersion = Convert.ToString(row[5]); | ||
6587 | } | ||
6588 | |||
6589 | this.core.RootElement.AddChild(exclusion); | ||
6590 | } | ||
6591 | } | ||
6592 | |||
6593 | /// <summary> | ||
6594 | /// Decompile the ModuleIgnoreTable table. | ||
6595 | /// </summary> | ||
6596 | /// <param name="table">The table to decompile.</param> | ||
6597 | private void DecompileModuleIgnoreTableTable(Table table) | ||
6598 | { | ||
6599 | foreach (Row row in table.Rows) | ||
6600 | { | ||
6601 | string tableName = Convert.ToString(row[0]); | ||
6602 | |||
6603 | // the linker automatically adds a ModuleIgnoreTable row for some tables | ||
6604 | if ("ModuleConfiguration" != tableName && "ModuleSubstitution" != tableName) | ||
6605 | { | ||
6606 | Wix.IgnoreTable ignoreTable = new Wix.IgnoreTable(); | ||
6607 | |||
6608 | ignoreTable.Id = tableName; | ||
6609 | |||
6610 | this.core.RootElement.AddChild(ignoreTable); | ||
6611 | } | ||
6612 | } | ||
6613 | } | ||
6614 | |||
6615 | /// <summary> | ||
6616 | /// Decompile the ModuleSignature table. | ||
6617 | /// </summary> | ||
6618 | /// <param name="table">The table to decompile.</param> | ||
6619 | private void DecompileModuleSignatureTable(Table table) | ||
6620 | { | ||
6621 | if (1 == table.Rows.Count) | ||
6622 | { | ||
6623 | Row row = table.Rows[0]; | ||
6624 | |||
6625 | Wix.Module module = (Wix.Module)this.core.RootElement; | ||
6626 | |||
6627 | module.Id = Convert.ToString(row[0]); | ||
6628 | |||
6629 | // support Language columns that are treated as integers as well as strings (the WiX default, to support localizability) | ||
6630 | module.Language = Convert.ToString(row[1], CultureInfo.InvariantCulture); | ||
6631 | |||
6632 | module.Version = Convert.ToString(row[2]); | ||
6633 | } | ||
6634 | else | ||
6635 | { | ||
6636 | // TODO: warn | ||
6637 | } | ||
6638 | } | ||
6639 | |||
6640 | /// <summary> | ||
6641 | /// Decompile the ModuleSubstitution table. | ||
6642 | /// </summary> | ||
6643 | /// <param name="table">The table to decompile.</param> | ||
6644 | private void DecompileModuleSubstitutionTable(Table table) | ||
6645 | { | ||
6646 | foreach (Row row in table.Rows) | ||
6647 | { | ||
6648 | Wix.Substitution substitution = new Wix.Substitution(); | ||
6649 | |||
6650 | substitution.Table = Convert.ToString(row[0]); | ||
6651 | |||
6652 | substitution.Row = Convert.ToString(row[1]); | ||
6653 | |||
6654 | substitution.Column = Convert.ToString(row[2]); | ||
6655 | |||
6656 | if (null != row[3]) | ||
6657 | { | ||
6658 | substitution.Value = Convert.ToString(row[3]); | ||
6659 | } | ||
6660 | |||
6661 | this.core.RootElement.AddChild(substitution); | ||
6662 | } | ||
6663 | } | ||
6664 | |||
6665 | /// <summary> | ||
6666 | /// Decompile the MoveFile table. | ||
6667 | /// </summary> | ||
6668 | /// <param name="table">The table to decompile.</param> | ||
6669 | private void DecompileMoveFileTable(Table table) | ||
6670 | { | ||
6671 | foreach (Row row in table.Rows) | ||
6672 | { | ||
6673 | Wix.CopyFile copyFile = new Wix.CopyFile(); | ||
6674 | |||
6675 | copyFile.Id = Convert.ToString(row[0]); | ||
6676 | |||
6677 | if (null != row[2]) | ||
6678 | { | ||
6679 | copyFile.SourceName = Convert.ToString(row[2]); | ||
6680 | } | ||
6681 | |||
6682 | if (null != row[3]) | ||
6683 | { | ||
6684 | string[] names = Installer.GetNames(Convert.ToString(row[3])); | ||
6685 | if (null != names[0] && null != names[1]) | ||
6686 | { | ||
6687 | copyFile.DestinationShortName = names[0]; | ||
6688 | copyFile.DestinationName = names[1]; | ||
6689 | } | ||
6690 | else if (null != names[0]) | ||
6691 | { | ||
6692 | copyFile.DestinationName = names[0]; | ||
6693 | } | ||
6694 | } | ||
6695 | |||
6696 | // source/destination directory/property is set in FinalizeDuplicateMoveFileTables | ||
6697 | |||
6698 | switch (Convert.ToInt32(row[6])) | ||
6699 | { | ||
6700 | case 0: | ||
6701 | break; | ||
6702 | case MsiInterop.MsidbMoveFileOptionsMove: | ||
6703 | copyFile.Delete = Wix.YesNoType.yes; | ||
6704 | break; | ||
6705 | default: | ||
6706 | this.core.OnMessage(WixWarnings.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[6].Column.Name, row[6])); | ||
6707 | break; | ||
6708 | } | ||
6709 | |||
6710 | Wix.Component component = (Wix.Component)this.core.GetIndexedElement("Component", Convert.ToString(row[1])); | ||
6711 | if (null != component) | ||
6712 | { | ||
6713 | component.AddChild(copyFile); | ||
6714 | } | ||
6715 | else | ||
6716 | { | ||
6717 | this.core.OnMessage(WixWarnings.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Component_", Convert.ToString(row[1]), "Component")); | ||
6718 | } | ||
6719 | this.core.IndexElement(row, copyFile); | ||
6720 | } | ||
6721 | } | ||
6722 | |||
6723 | /// <summary> | ||
6724 | /// Decompile the MsiDigitalCertificate table. | ||
6725 | /// </summary> | ||
6726 | /// <param name="table">The table to decompile.</param> | ||
6727 | private void DecompileMsiDigitalCertificateTable(Table table) | ||
6728 | { | ||
6729 | foreach (Row row in table.Rows) | ||
6730 | { | ||
6731 | Wix.DigitalCertificate digitalCertificate = new Wix.DigitalCertificate(); | ||
6732 | |||
6733 | digitalCertificate.Id = Convert.ToString(row[0]); | ||
6734 | |||
6735 | digitalCertificate.SourceFile = Convert.ToString(row[1]); | ||
6736 | |||
6737 | this.core.IndexElement(row, digitalCertificate); | ||
6738 | } | ||
6739 | } | ||
6740 | |||
6741 | /// <summary> | ||
6742 | /// Decompile the MsiDigitalSignature table. | ||
6743 | /// </summary> | ||
6744 | /// <param name="table">The table to decompile.</param> | ||
6745 | private void DecompileMsiDigitalSignatureTable(Table table) | ||
6746 | { | ||
6747 | foreach (Row row in table.Rows) | ||
6748 | { | ||
6749 | Wix.DigitalSignature digitalSignature = new Wix.DigitalSignature(); | ||
6750 | |||
6751 | if (null != row[3]) | ||
6752 | { | ||
6753 | digitalSignature.SourceFile = Convert.ToString(row[3]); | ||
6754 | } | ||
6755 | |||
6756 | Wix.DigitalCertificate digitalCertificate = (Wix.DigitalCertificate)this.core.GetIndexedElement("MsiDigitalCertificate", Convert.ToString(row[2])); | ||
6757 | if (null != digitalCertificate) | ||
6758 | { | ||
6759 | digitalSignature.AddChild(digitalCertificate); | ||
6760 | } | ||
6761 | else | ||
6762 | { | ||
6763 | this.core.OnMessage(WixWarnings.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "DigitalCertificate_", Convert.ToString(row[2]), "MsiDigitalCertificate")); | ||
6764 | } | ||
6765 | |||
6766 | Wix.IParentElement parentElement = (Wix.IParentElement)this.core.GetIndexedElement(Convert.ToString(row[0]), Convert.ToString(row[1])); | ||
6767 | if (null != parentElement) | ||
6768 | { | ||
6769 | parentElement.AddChild(digitalSignature); | ||
6770 | } | ||
6771 | else | ||
6772 | { | ||
6773 | this.core.OnMessage(WixWarnings.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "SignObject", Convert.ToString(row[1]), Convert.ToString(row[0]))); | ||
6774 | } | ||
6775 | } | ||
6776 | } | ||
6777 | |||
6778 | /// <summary> | ||
6779 | /// Decompile the MsiEmbeddedChainer table. | ||
6780 | /// </summary> | ||
6781 | /// <param name="table">The table to decompile.</param> | ||
6782 | private void DecompileMsiEmbeddedChainerTable(Table table) | ||
6783 | { | ||
6784 | foreach (Row row in table.Rows) | ||
6785 | { | ||
6786 | Wix.EmbeddedChainer embeddedChainer = new Wix.EmbeddedChainer(); | ||
6787 | |||
6788 | embeddedChainer.Id = Convert.ToString(row[0]); | ||
6789 | |||
6790 | embeddedChainer.Content = Convert.ToString(row[1]); | ||
6791 | |||
6792 | if (null != row[2]) | ||
6793 | { | ||
6794 | embeddedChainer.CommandLine = Convert.ToString(row[2]); | ||
6795 | } | ||
6796 | |||
6797 | switch (Convert.ToInt32(row[4])) | ||
6798 | { | ||
6799 | case MsiInterop.MsidbCustomActionTypeExe + MsiInterop.MsidbCustomActionTypeBinaryData: | ||
6800 | embeddedChainer.BinarySource = Convert.ToString(row[3]); | ||
6801 | break; | ||
6802 | case MsiInterop.MsidbCustomActionTypeExe + MsiInterop.MsidbCustomActionTypeSourceFile: | ||
6803 | embeddedChainer.FileSource = Convert.ToString(row[3]); | ||
6804 | break; | ||
6805 | case MsiInterop.MsidbCustomActionTypeExe + MsiInterop.MsidbCustomActionTypeProperty: | ||
6806 | embeddedChainer.PropertySource = Convert.ToString(row[3]); | ||
6807 | break; | ||
6808 | default: | ||
6809 | this.core.OnMessage(WixWarnings.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[4].Column.Name, row[4])); | ||
6810 | break; | ||
6811 | } | ||
6812 | |||
6813 | this.core.RootElement.AddChild(embeddedChainer); | ||
6814 | } | ||
6815 | } | ||
6816 | |||
6817 | /// <summary> | ||
6818 | /// Decompile the MsiEmbeddedUI table. | ||
6819 | /// </summary> | ||
6820 | /// <param name="table">The table to decompile.</param> | ||
6821 | private void DecompileMsiEmbeddedUITable(Table table) | ||
6822 | { | ||
6823 | Wix.EmbeddedUI embeddedUI = new Wix.EmbeddedUI(); | ||
6824 | bool foundEmbeddedUI = false; | ||
6825 | bool foundEmbeddedResources = false; | ||
6826 | |||
6827 | foreach (Row row in table.Rows) | ||
6828 | { | ||
6829 | int attributes = Convert.ToInt32(row[2]); | ||
6830 | |||
6831 | if (MsiInterop.MsidbEmbeddedUI == (attributes & MsiInterop.MsidbEmbeddedUI)) | ||
6832 | { | ||
6833 | if (foundEmbeddedUI) | ||
6834 | { | ||
6835 | this.core.OnMessage(WixWarnings.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[2].Column.Name, row[2])); | ||
6836 | } | ||
6837 | else | ||
6838 | { | ||
6839 | embeddedUI.Id = Convert.ToString(row[0]); | ||
6840 | embeddedUI.Name = Convert.ToString(row[1]); | ||
6841 | |||
6842 | int messageFilter = Convert.ToInt32(row[3]); | ||
6843 | if (0 == (messageFilter & MsiInterop.INSTALLLOGMODE_FATALEXIT)) | ||
6844 | { | ||
6845 | embeddedUI.IgnoreFatalExit = Wix.YesNoType.yes; | ||
6846 | } | ||
6847 | |||
6848 | if (0 == (messageFilter & MsiInterop.INSTALLLOGMODE_ERROR)) | ||
6849 | { | ||
6850 | embeddedUI.IgnoreError = Wix.YesNoType.yes; | ||
6851 | } | ||
6852 | |||
6853 | if (0 == (messageFilter & MsiInterop.INSTALLLOGMODE_WARNING)) | ||
6854 | { | ||
6855 | embeddedUI.IgnoreWarning = Wix.YesNoType.yes; | ||
6856 | } | ||
6857 | |||
6858 | if (0 == (messageFilter & MsiInterop.INSTALLLOGMODE_USER)) | ||
6859 | { | ||
6860 | embeddedUI.IgnoreUser = Wix.YesNoType.yes; | ||
6861 | } | ||
6862 | |||
6863 | if (0 == (messageFilter & MsiInterop.INSTALLLOGMODE_INFO)) | ||
6864 | { | ||
6865 | embeddedUI.IgnoreInfo = Wix.YesNoType.yes; | ||
6866 | } | ||
6867 | |||
6868 | if (0 == (messageFilter & MsiInterop.INSTALLLOGMODE_FILESINUSE)) | ||
6869 | { | ||
6870 | embeddedUI.IgnoreFilesInUse = Wix.YesNoType.yes; | ||
6871 | } | ||
6872 | |||
6873 | if (0 == (messageFilter & MsiInterop.INSTALLLOGMODE_RESOLVESOURCE)) | ||
6874 | { | ||
6875 | embeddedUI.IgnoreResolveSource = Wix.YesNoType.yes; | ||
6876 | } | ||
6877 | |||
6878 | if (0 == (messageFilter & MsiInterop.INSTALLLOGMODE_OUTOFDISKSPACE)) | ||
6879 | { | ||
6880 | embeddedUI.IgnoreOutOfDiskSpace = Wix.YesNoType.yes; | ||
6881 | } | ||
6882 | |||
6883 | if (0 == (messageFilter & MsiInterop.INSTALLLOGMODE_ACTIONSTART)) | ||
6884 | { | ||
6885 | embeddedUI.IgnoreActionStart = Wix.YesNoType.yes; | ||
6886 | } | ||
6887 | |||
6888 | if (0 == (messageFilter & MsiInterop.INSTALLLOGMODE_ACTIONDATA)) | ||
6889 | { | ||
6890 | embeddedUI.IgnoreActionData = Wix.YesNoType.yes; | ||
6891 | } | ||
6892 | |||
6893 | if (0 == (messageFilter & MsiInterop.INSTALLLOGMODE_PROGRESS)) | ||
6894 | { | ||
6895 | embeddedUI.IgnoreProgress = Wix.YesNoType.yes; | ||
6896 | } | ||
6897 | |||
6898 | if (0 == (messageFilter & MsiInterop.INSTALLLOGMODE_COMMONDATA)) | ||
6899 | { | ||
6900 | embeddedUI.IgnoreCommonData = Wix.YesNoType.yes; | ||
6901 | } | ||
6902 | |||
6903 | if (0 == (messageFilter & MsiInterop.INSTALLLOGMODE_INITIALIZE)) | ||
6904 | { | ||
6905 | embeddedUI.IgnoreInitialize = Wix.YesNoType.yes; | ||
6906 | } | ||
6907 | |||
6908 | if (0 == (messageFilter & MsiInterop.INSTALLLOGMODE_TERMINATE)) | ||
6909 | { | ||
6910 | embeddedUI.IgnoreTerminate = Wix.YesNoType.yes; | ||
6911 | } | ||
6912 | |||
6913 | if (0 == (messageFilter & MsiInterop.INSTALLLOGMODE_SHOWDIALOG)) | ||
6914 | { | ||
6915 | embeddedUI.IgnoreShowDialog = Wix.YesNoType.yes; | ||
6916 | } | ||
6917 | |||
6918 | if (0 == (messageFilter & MsiInterop.INSTALLLOGMODE_RMFILESINUSE)) | ||
6919 | { | ||
6920 | embeddedUI.IgnoreRMFilesInUse = Wix.YesNoType.yes; | ||
6921 | } | ||
6922 | |||
6923 | if (0 == (messageFilter & MsiInterop.INSTALLLOGMODE_INSTALLSTART)) | ||
6924 | { | ||
6925 | embeddedUI.IgnoreInstallStart = Wix.YesNoType.yes; | ||
6926 | } | ||
6927 | |||
6928 | if (0 == (messageFilter & MsiInterop.INSTALLLOGMODE_INSTALLEND)) | ||
6929 | { | ||
6930 | embeddedUI.IgnoreInstallEnd = Wix.YesNoType.yes; | ||
6931 | } | ||
6932 | |||
6933 | if (MsiInterop.MsidbEmbeddedHandlesBasic == (attributes & MsiInterop.MsidbEmbeddedHandlesBasic)) | ||
6934 | { | ||
6935 | embeddedUI.SupportBasicUI = Wix.YesNoType.yes; | ||
6936 | } | ||
6937 | |||
6938 | embeddedUI.SourceFile = Convert.ToString(row[4]); | ||
6939 | |||
6940 | this.core.UIElement.AddChild(embeddedUI); | ||
6941 | foundEmbeddedUI = true; | ||
6942 | } | ||
6943 | } | ||
6944 | else | ||
6945 | { | ||
6946 | Wix.EmbeddedUIResource embeddedResource = new Wix.EmbeddedUIResource(); | ||
6947 | |||
6948 | embeddedResource.Id = Convert.ToString(row[0]); | ||
6949 | embeddedResource.Name = Convert.ToString(row[1]); | ||
6950 | embeddedResource.SourceFile = Convert.ToString(row[4]); | ||
6951 | |||
6952 | embeddedUI.AddChild(embeddedResource); | ||
6953 | foundEmbeddedResources = true; | ||
6954 | } | ||
6955 | } | ||
6956 | |||
6957 | if (!foundEmbeddedUI && foundEmbeddedResources) | ||
6958 | { | ||
6959 | // TODO: warn | ||
6960 | } | ||
6961 | } | ||
6962 | |||
6963 | /// <summary> | ||
6964 | /// Decompile the MsiLockPermissionsEx table. | ||
6965 | /// </summary> | ||
6966 | /// <param name="table">The table to decompile.</param> | ||
6967 | private void DecompileMsiLockPermissionsExTable(Table table) | ||
6968 | { | ||
6969 | foreach (Row row in table.Rows) | ||
6970 | { | ||
6971 | Wix.PermissionEx permissionEx = new Wix.PermissionEx(); | ||
6972 | permissionEx.Id = Convert.ToString(row[0]); | ||
6973 | permissionEx.Sddl = Convert.ToString(row[3]); | ||
6974 | |||
6975 | if (null != row[4]) | ||
6976 | { | ||
6977 | Wix.Condition condition = new Wix.Condition(); | ||
6978 | condition.Content = Convert.ToString(row[4]); | ||
6979 | permissionEx.AddChild(condition); | ||
6980 | } | ||
6981 | |||
6982 | switch (Convert.ToString(row[2])) | ||
6983 | { | ||
6984 | case "CreateFolder": | ||
6985 | case "File": | ||
6986 | case "Registry": | ||
6987 | case "ServiceInstall": | ||
6988 | break; | ||
6989 | default: | ||
6990 | this.core.OnMessage(WixWarnings.IllegalColumnValue(row.SourceLineNumbers, row.Table.Name, row.Fields[1].Column.Name, row[1])); | ||
6991 | return; | ||
6992 | } | ||
6993 | |||
6994 | this.core.IndexElement(row, permissionEx); | ||
6995 | } | ||
6996 | } | ||
6997 | |||
6998 | /// <summary> | ||
6999 | /// Decompile the MsiPackageCertificate table. | ||
7000 | /// </summary> | ||
7001 | /// <param name="table">The table to decompile.</param> | ||
7002 | private void DecompileMsiPackageCertificateTable(Table table) | ||
7003 | { | ||
7004 | if (0 < table.Rows.Count) | ||
7005 | { | ||
7006 | Wix.PackageCertificates packageCertificates = new Wix.PackageCertificates(); | ||
7007 | this.core.RootElement.AddChild(packageCertificates); | ||
7008 | AddCertificates(table, packageCertificates); | ||
7009 | } | ||
7010 | } | ||
7011 | |||
7012 | /// <summary> | ||
7013 | /// Decompile the MsiPatchCertificate table. | ||
7014 | /// </summary> | ||
7015 | /// <param name="table">The table to decompile.</param> | ||
7016 | private void DecompileMsiPatchCertificateTable(Table table) | ||
7017 | { | ||
7018 | if (0 < table.Rows.Count) | ||
7019 | { | ||
7020 | Wix.PatchCertificates patchCertificates = new Wix.PatchCertificates(); | ||
7021 | this.core.RootElement.AddChild(patchCertificates); | ||
7022 | AddCertificates(table, patchCertificates); | ||
7023 | } | ||
7024 | } | ||
7025 | |||
7026 | /// <summary> | ||
7027 | /// Insert DigitalCertificate records associated with passed msiPackageCertificate or msiPatchCertificate table. | ||
7028 | /// </summary> | ||
7029 | /// <param name="table">The table being decompiled.</param> | ||
7030 | /// <param name="parent">DigitalCertificate parent</param> | ||
7031 | private void AddCertificates(Table table, Wix.IParentElement parent) | ||
7032 | { | ||
7033 | foreach (Row row in table.Rows) | ||
7034 | { | ||
7035 | Wix.DigitalCertificate digitalCertificate = (Wix.DigitalCertificate)this.core.GetIndexedElement("MsiDigitalCertificate", Convert.ToString(row[1])); | ||
7036 | |||
7037 | if (null != digitalCertificate) | ||
7038 | { | ||
7039 | parent.AddChild(digitalCertificate); | ||
7040 | } | ||
7041 | else | ||
7042 | { | ||
7043 | this.core.OnMessage(WixWarnings.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "DigitalCertificate_", Convert.ToString(row[1]), "MsiDigitalCertificate")); | ||
7044 | } | ||
7045 | } | ||
7046 | } | ||
7047 | |||
7048 | /// <summary> | ||
7049 | /// Decompile the MsiShortcutProperty table. | ||
7050 | /// </summary> | ||
7051 | /// <param name="table">The table to decompile.</param> | ||
7052 | private void DecompileMsiShortcutPropertyTable(Table table) | ||
7053 | { | ||
7054 | foreach (Row row in table.Rows) | ||
7055 | { | ||
7056 | Wix.ShortcutProperty property = new Wix.ShortcutProperty(); | ||
7057 | property.Id = Convert.ToString(row[0]); | ||
7058 | property.Key = Convert.ToString(row[2]); | ||
7059 | property.Value = Convert.ToString(row[3]); | ||
7060 | |||
7061 | Wix.Shortcut shortcut = (Wix.Shortcut)this.core.GetIndexedElement("Shortcut", Convert.ToString(row[1])); | ||
7062 | if (null != shortcut) | ||
7063 | { | ||
7064 | shortcut.AddChild(property); | ||
7065 | } | ||
7066 | else | ||
7067 | { | ||
7068 | this.core.OnMessage(WixWarnings.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Shortcut_", Convert.ToString(row[1]), "Shortcut")); | ||
7069 | } | ||
7070 | } | ||
7071 | } | ||
7072 | |||
7073 | /// <summary> | ||
7074 | /// Decompile the ODBCAttribute table. | ||
7075 | /// </summary> | ||
7076 | /// <param name="table">The table to decompile.</param> | ||
7077 | private void DecompileODBCAttributeTable(Table table) | ||
7078 | { | ||
7079 | foreach (Row row in table.Rows) | ||
7080 | { | ||
7081 | Wix.Property property = new Wix.Property(); | ||
7082 | |||
7083 | property.Id = Convert.ToString(row[1]); | ||
7084 | |||
7085 | if (null != row[2]) | ||
7086 | { | ||
7087 | property.Value = Convert.ToString(row[2]); | ||
7088 | } | ||
7089 | |||
7090 | Wix.ODBCDriver odbcDriver = (Wix.ODBCDriver)this.core.GetIndexedElement("ODBCDriver", Convert.ToString(row[0])); | ||
7091 | if (null != odbcDriver) | ||
7092 | { | ||
7093 | odbcDriver.AddChild(property); | ||
7094 | } | ||
7095 | else | ||
7096 | { | ||
7097 | this.core.OnMessage(WixWarnings.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Driver_", Convert.ToString(row[0]), "ODBCDriver")); | ||
7098 | } | ||
7099 | } | ||
7100 | } | ||
7101 | |||
7102 | /// <summary> | ||
7103 | /// Decompile the ODBCDataSource table. | ||
7104 | /// </summary> | ||
7105 | /// <param name="table">The table to decompile.</param> | ||
7106 | private void DecompileODBCDataSourceTable(Table table) | ||
7107 | { | ||
7108 | foreach (Row row in table.Rows) | ||
7109 | { | ||
7110 | Wix.ODBCDataSource odbcDataSource = new Wix.ODBCDataSource(); | ||
7111 | |||
7112 | odbcDataSource.Id = Convert.ToString(row[0]); | ||
7113 | |||
7114 | odbcDataSource.Name = Convert.ToString(row[2]); | ||
7115 | |||
7116 | odbcDataSource.DriverName = Convert.ToString(row[3]); | ||
7117 | |||
7118 | switch (Convert.ToInt32(row[4])) | ||
7119 | { | ||
7120 | case MsiInterop.MsidbODBCDataSourceRegistrationPerMachine: | ||
7121 | odbcDataSource.Registration = Wix.ODBCDataSource.RegistrationType.machine; | ||
7122 | break; | ||
7123 | case MsiInterop.MsidbODBCDataSourceRegistrationPerUser: | ||
7124 | odbcDataSource.Registration = Wix.ODBCDataSource.RegistrationType.user; | ||
7125 | break; | ||
7126 | default: | ||
7127 | this.core.OnMessage(WixWarnings.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[4].Column.Name, row[4])); | ||
7128 | break; | ||
7129 | } | ||
7130 | |||
7131 | this.core.IndexElement(row, odbcDataSource); | ||
7132 | } | ||
7133 | } | ||
7134 | |||
7135 | /// <summary> | ||
7136 | /// Decompile the ODBCDriver table. | ||
7137 | /// </summary> | ||
7138 | /// <param name="table">The table to decompile.</param> | ||
7139 | private void DecompileODBCDriverTable(Table table) | ||
7140 | { | ||
7141 | foreach (Row row in table.Rows) | ||
7142 | { | ||
7143 | Wix.ODBCDriver odbcDriver = new Wix.ODBCDriver(); | ||
7144 | |||
7145 | odbcDriver.Id = Convert.ToString(row[0]); | ||
7146 | |||
7147 | odbcDriver.Name = Convert.ToString(row[2]); | ||
7148 | |||
7149 | odbcDriver.File = Convert.ToString(row[3]); | ||
7150 | |||
7151 | if (null != row[4]) | ||
7152 | { | ||
7153 | odbcDriver.SetupFile = Convert.ToString(row[4]); | ||
7154 | } | ||
7155 | |||
7156 | Wix.Component component = (Wix.Component)this.core.GetIndexedElement("Component", Convert.ToString(row[1])); | ||
7157 | if (null != component) | ||
7158 | { | ||
7159 | component.AddChild(odbcDriver); | ||
7160 | } | ||
7161 | else | ||
7162 | { | ||
7163 | this.core.OnMessage(WixWarnings.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Component_", Convert.ToString(row[1]), "Component")); | ||
7164 | } | ||
7165 | this.core.IndexElement(row, odbcDriver); | ||
7166 | } | ||
7167 | } | ||
7168 | |||
7169 | /// <summary> | ||
7170 | /// Decompile the ODBCSourceAttribute table. | ||
7171 | /// </summary> | ||
7172 | /// <param name="table">The table to decompile.</param> | ||
7173 | private void DecompileODBCSourceAttributeTable(Table table) | ||
7174 | { | ||
7175 | foreach (Row row in table.Rows) | ||
7176 | { | ||
7177 | Wix.Property property = new Wix.Property(); | ||
7178 | |||
7179 | property.Id = Convert.ToString(row[1]); | ||
7180 | |||
7181 | if (null != row[2]) | ||
7182 | { | ||
7183 | property.Value = Convert.ToString(row[2]); | ||
7184 | } | ||
7185 | |||
7186 | Wix.ODBCDataSource odbcDataSource = (Wix.ODBCDataSource)this.core.GetIndexedElement("ODBCDataSource", Convert.ToString(row[0])); | ||
7187 | if (null != odbcDataSource) | ||
7188 | { | ||
7189 | odbcDataSource.AddChild(property); | ||
7190 | } | ||
7191 | else | ||
7192 | { | ||
7193 | this.core.OnMessage(WixWarnings.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "DataSource_", Convert.ToString(row[0]), "ODBCDataSource")); | ||
7194 | } | ||
7195 | } | ||
7196 | } | ||
7197 | |||
7198 | /// <summary> | ||
7199 | /// Decompile the ODBCTranslator table. | ||
7200 | /// </summary> | ||
7201 | /// <param name="table">The table to decompile.</param> | ||
7202 | private void DecompileODBCTranslatorTable(Table table) | ||
7203 | { | ||
7204 | foreach (Row row in table.Rows) | ||
7205 | { | ||
7206 | Wix.ODBCTranslator odbcTranslator = new Wix.ODBCTranslator(); | ||
7207 | |||
7208 | odbcTranslator.Id = Convert.ToString(row[0]); | ||
7209 | |||
7210 | odbcTranslator.Name = Convert.ToString(row[2]); | ||
7211 | |||
7212 | odbcTranslator.File = Convert.ToString(row[3]); | ||
7213 | |||
7214 | if (null != row[4]) | ||
7215 | { | ||
7216 | odbcTranslator.SetupFile = Convert.ToString(row[4]); | ||
7217 | } | ||
7218 | |||
7219 | Wix.Component component = (Wix.Component)this.core.GetIndexedElement("Component", Convert.ToString(row[1])); | ||
7220 | if (null != component) | ||
7221 | { | ||
7222 | component.AddChild(odbcTranslator); | ||
7223 | } | ||
7224 | else | ||
7225 | { | ||
7226 | this.core.OnMessage(WixWarnings.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Component_", Convert.ToString(row[1]), "Component")); | ||
7227 | } | ||
7228 | } | ||
7229 | } | ||
7230 | |||
7231 | /// <summary> | ||
7232 | /// Decompile the PatchMetadata table. | ||
7233 | /// </summary> | ||
7234 | /// <param name="table">The table to decompile.</param> | ||
7235 | private void DecompilePatchMetadataTable(Table table) | ||
7236 | { | ||
7237 | if (0 < table.Rows.Count) | ||
7238 | { | ||
7239 | Wix.PatchMetadata patchMetadata = new Wix.PatchMetadata(); | ||
7240 | |||
7241 | foreach (Row row in table.Rows) | ||
7242 | { | ||
7243 | string value = Convert.ToString(row[2]); | ||
7244 | |||
7245 | switch (Convert.ToString(row[1])) | ||
7246 | { | ||
7247 | case "AllowRemoval": | ||
7248 | if ("1" == value) | ||
7249 | { | ||
7250 | patchMetadata.AllowRemoval = Wix.YesNoType.yes; | ||
7251 | } | ||
7252 | break; | ||
7253 | case "Classification": | ||
7254 | if (null != value) | ||
7255 | { | ||
7256 | patchMetadata.Classification = value; | ||
7257 | } | ||
7258 | break; | ||
7259 | case "CreationTimeUTC": | ||
7260 | if (null != value) | ||
7261 | { | ||
7262 | patchMetadata.CreationTimeUTC = value; | ||
7263 | } | ||
7264 | break; | ||
7265 | case "Description": | ||
7266 | if (null != value) | ||
7267 | { | ||
7268 | patchMetadata.Description = value; | ||
7269 | } | ||
7270 | break; | ||
7271 | case "DisplayName": | ||
7272 | if (null != value) | ||
7273 | { | ||
7274 | patchMetadata.DisplayName = value; | ||
7275 | } | ||
7276 | break; | ||
7277 | case "ManufacturerName": | ||
7278 | if (null != value) | ||
7279 | { | ||
7280 | patchMetadata.ManufacturerName = value; | ||
7281 | } | ||
7282 | break; | ||
7283 | case "MinorUpdateTargetRTM": | ||
7284 | if (null != value) | ||
7285 | { | ||
7286 | patchMetadata.MinorUpdateTargetRTM = value; | ||
7287 | } | ||
7288 | break; | ||
7289 | case "MoreInfoURL": | ||
7290 | if (null != value) | ||
7291 | { | ||
7292 | patchMetadata.MoreInfoURL = value; | ||
7293 | } | ||
7294 | break; | ||
7295 | case "OptimizeCA": | ||
7296 | Wix.OptimizeCustomActions optimizeCustomActions = new Wix.OptimizeCustomActions(); | ||
7297 | int optimizeCA = Int32.Parse(value, CultureInfo.InvariantCulture); | ||
7298 | if (0 != (Convert.ToInt32(OptimizeCA.SkipAssignment) & optimizeCA)) | ||
7299 | { | ||
7300 | optimizeCustomActions.SkipAssignment = Wix.YesNoType.yes; | ||
7301 | } | ||
7302 | |||
7303 | if (0 != (Convert.ToInt32(OptimizeCA.SkipImmediate) & optimizeCA)) | ||
7304 | { | ||
7305 | optimizeCustomActions.SkipImmediate = Wix.YesNoType.yes; | ||
7306 | } | ||
7307 | |||
7308 | if (0 != (Convert.ToInt32(OptimizeCA.SkipDeferred) & optimizeCA)) | ||
7309 | { | ||
7310 | optimizeCustomActions.SkipDeferred = Wix.YesNoType.yes; | ||
7311 | } | ||
7312 | |||
7313 | patchMetadata.AddChild(optimizeCustomActions); | ||
7314 | break; | ||
7315 | case "OptimizedInstallMode": | ||
7316 | if ("1" == value) | ||
7317 | { | ||
7318 | patchMetadata.OptimizedInstallMode = Wix.YesNoType.yes; | ||
7319 | } | ||
7320 | break; | ||
7321 | case "TargetProductName": | ||
7322 | if (null != value) | ||
7323 | { | ||
7324 | patchMetadata.TargetProductName = value; | ||
7325 | } | ||
7326 | break; | ||
7327 | default: | ||
7328 | Wix.CustomProperty customProperty = new Wix.CustomProperty(); | ||
7329 | |||
7330 | if (null != row[0]) | ||
7331 | { | ||
7332 | customProperty.Company = Convert.ToString(row[0]); | ||
7333 | } | ||
7334 | |||
7335 | customProperty.Property = Convert.ToString(row[1]); | ||
7336 | |||
7337 | if (null != row[2]) | ||
7338 | { | ||
7339 | customProperty.Value = Convert.ToString(row[2]); | ||
7340 | } | ||
7341 | |||
7342 | patchMetadata.AddChild(customProperty); | ||
7343 | break; | ||
7344 | } | ||
7345 | } | ||
7346 | |||
7347 | this.core.RootElement.AddChild(patchMetadata); | ||
7348 | } | ||
7349 | } | ||
7350 | |||
7351 | /// <summary> | ||
7352 | /// Decompile the PatchSequence table. | ||
7353 | /// </summary> | ||
7354 | /// <param name="table">The table to decompile.</param> | ||
7355 | private void DecompilePatchSequenceTable(Table table) | ||
7356 | { | ||
7357 | foreach (Row row in table.Rows) | ||
7358 | { | ||
7359 | Wix.PatchSequence patchSequence = new Wix.PatchSequence(); | ||
7360 | |||
7361 | patchSequence.PatchFamily = Convert.ToString(row[0]); | ||
7362 | |||
7363 | if (null != row[1]) | ||
7364 | { | ||
7365 | try | ||
7366 | { | ||
7367 | Guid guid = new Guid(Convert.ToString(row[1])); | ||
7368 | |||
7369 | patchSequence.ProductCode = Convert.ToString(row[1]); | ||
7370 | } | ||
7371 | catch // non-guid value | ||
7372 | { | ||
7373 | patchSequence.TargetImage = Convert.ToString(row[1]); | ||
7374 | } | ||
7375 | } | ||
7376 | |||
7377 | if (null != row[2]) | ||
7378 | { | ||
7379 | patchSequence.Sequence = Convert.ToString(row[2]); | ||
7380 | } | ||
7381 | |||
7382 | if (null != row[3] && 0x1 == Convert.ToInt32(row[3])) | ||
7383 | { | ||
7384 | patchSequence.Supersede = Wix.YesNoType.yes; | ||
7385 | } | ||
7386 | |||
7387 | this.core.RootElement.AddChild(patchSequence); | ||
7388 | } | ||
7389 | } | ||
7390 | |||
7391 | /// <summary> | ||
7392 | /// Decompile the ProgId table. | ||
7393 | /// </summary> | ||
7394 | /// <param name="table">The table to decompile.</param> | ||
7395 | private void DecompileProgIdTable(Table table) | ||
7396 | { | ||
7397 | foreach (Row row in table.Rows) | ||
7398 | { | ||
7399 | Wix.ProgId progId = new Wix.ProgId(); | ||
7400 | |||
7401 | progId.Advertise = Wix.YesNoType.yes; | ||
7402 | |||
7403 | progId.Id = Convert.ToString(row[0]); | ||
7404 | |||
7405 | if (null != row[3]) | ||
7406 | { | ||
7407 | progId.Description = Convert.ToString(row[3]); | ||
7408 | } | ||
7409 | |||
7410 | if (null != row[4]) | ||
7411 | { | ||
7412 | progId.Icon = Convert.ToString(row[4]); | ||
7413 | } | ||
7414 | |||
7415 | if (null != row[5]) | ||
7416 | { | ||
7417 | progId.IconIndex = Convert.ToInt32(row[5]); | ||
7418 | } | ||
7419 | |||
7420 | this.core.IndexElement(row, progId); | ||
7421 | } | ||
7422 | |||
7423 | // nest the ProgIds | ||
7424 | foreach (Row row in table.Rows) | ||
7425 | { | ||
7426 | Wix.ProgId progId = (Wix.ProgId)this.core.GetIndexedElement(row); | ||
7427 | |||
7428 | if (null != row[1]) | ||
7429 | { | ||
7430 | Wix.ProgId parentProgId = (Wix.ProgId)this.core.GetIndexedElement("ProgId", Convert.ToString(row[1])); | ||
7431 | |||
7432 | if (null != parentProgId) | ||
7433 | { | ||
7434 | parentProgId.AddChild(progId); | ||
7435 | } | ||
7436 | else | ||
7437 | { | ||
7438 | this.core.OnMessage(WixWarnings.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "ProgId_Parent", Convert.ToString(row[1]), "ProgId")); | ||
7439 | } | ||
7440 | } | ||
7441 | else if (null != row[2]) | ||
7442 | { | ||
7443 | // nesting is handled in FinalizeProgIdTable | ||
7444 | } | ||
7445 | else | ||
7446 | { | ||
7447 | // TODO: warn for orphaned ProgId | ||
7448 | } | ||
7449 | } | ||
7450 | } | ||
7451 | |||
7452 | /// <summary> | ||
7453 | /// Decompile the Properties table. | ||
7454 | /// </summary> | ||
7455 | /// <param name="table">The table to decompile.</param> | ||
7456 | private void DecompilePropertiesTable(Table table) | ||
7457 | { | ||
7458 | Wix.PatchCreation patchCreation = (Wix.PatchCreation)this.core.RootElement; | ||
7459 | |||
7460 | foreach (Row row in table.Rows) | ||
7461 | { | ||
7462 | string name = Convert.ToString(row[0]); | ||
7463 | string value = Convert.ToString(row[1]); | ||
7464 | |||
7465 | switch (name) | ||
7466 | { | ||
7467 | case "AllowProductCodeMismatches": | ||
7468 | if ("1" == value) | ||
7469 | { | ||
7470 | patchCreation.AllowProductCodeMismatches = Wix.YesNoType.yes; | ||
7471 | } | ||
7472 | break; | ||
7473 | case "AllowProductVersionMajorMismatches": | ||
7474 | if ("1" == value) | ||
7475 | { | ||
7476 | patchCreation.AllowMajorVersionMismatches = Wix.YesNoType.yes; | ||
7477 | } | ||
7478 | break; | ||
7479 | case "ApiPatchingSymbolFlags": | ||
7480 | if (null != value) | ||
7481 | { | ||
7482 | try | ||
7483 | { | ||
7484 | // remove the leading "0x" if its present | ||
7485 | if (value.StartsWith("0x", StringComparison.Ordinal)) | ||
7486 | { | ||
7487 | value = value.Substring(2); | ||
7488 | } | ||
7489 | |||
7490 | patchCreation.SymbolFlags = Convert.ToInt32(value, 16); | ||
7491 | } | ||
7492 | catch | ||
7493 | { | ||
7494 | this.core.OnMessage(WixWarnings.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[1].Column.Name, row[1])); | ||
7495 | } | ||
7496 | } | ||
7497 | break; | ||
7498 | case "DontRemoveTempFolderWhenFinished": | ||
7499 | if ("1" == value) | ||
7500 | { | ||
7501 | patchCreation.CleanWorkingFolder = Wix.YesNoType.no; | ||
7502 | } | ||
7503 | break; | ||
7504 | case "IncludeWholeFilesOnly": | ||
7505 | if ("1" == value) | ||
7506 | { | ||
7507 | patchCreation.WholeFilesOnly = Wix.YesNoType.yes; | ||
7508 | } | ||
7509 | break; | ||
7510 | case "ListOfPatchGUIDsToReplace": | ||
7511 | if (null != value) | ||
7512 | { | ||
7513 | Regex guidRegex = new Regex(@"\{[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}\}"); | ||
7514 | MatchCollection guidMatches = guidRegex.Matches(value); | ||
7515 | |||
7516 | foreach (Match guidMatch in guidMatches) | ||
7517 | { | ||
7518 | Wix.ReplacePatch replacePatch = new Wix.ReplacePatch(); | ||
7519 | |||
7520 | replacePatch.Id = guidMatch.Value; | ||
7521 | |||
7522 | this.core.RootElement.AddChild(replacePatch); | ||
7523 | } | ||
7524 | } | ||
7525 | break; | ||
7526 | case "ListOfTargetProductCodes": | ||
7527 | if (null != value) | ||
7528 | { | ||
7529 | string[] targetProductCodes = value.Split(';'); | ||
7530 | |||
7531 | foreach (string targetProductCodeString in targetProductCodes) | ||
7532 | { | ||
7533 | Wix.TargetProductCode targetProductCode = new Wix.TargetProductCode(); | ||
7534 | |||
7535 | targetProductCode.Id = targetProductCodeString; | ||
7536 | |||
7537 | this.core.RootElement.AddChild(targetProductCode); | ||
7538 | } | ||
7539 | } | ||
7540 | break; | ||
7541 | case "PatchGUID": | ||
7542 | patchCreation.Id = value; | ||
7543 | break; | ||
7544 | case "PatchSourceList": | ||
7545 | patchCreation.SourceList = value; | ||
7546 | break; | ||
7547 | case "PatchOutputPath": | ||
7548 | patchCreation.OutputPath = value; | ||
7549 | break; | ||
7550 | default: | ||
7551 | Wix.PatchProperty patchProperty = new Wix.PatchProperty(); | ||
7552 | |||
7553 | patchProperty.Name = name; | ||
7554 | |||
7555 | patchProperty.Value = value; | ||
7556 | |||
7557 | this.core.RootElement.AddChild(patchProperty); | ||
7558 | break; | ||
7559 | } | ||
7560 | } | ||
7561 | } | ||
7562 | |||
7563 | /// <summary> | ||
7564 | /// Decompile the Property table. | ||
7565 | /// </summary> | ||
7566 | /// <param name="table">The table to decompile.</param> | ||
7567 | private void DecompilePropertyTable(Table table) | ||
7568 | { | ||
7569 | foreach (Row row in table.Rows) | ||
7570 | { | ||
7571 | string id = Convert.ToString(row[0]); | ||
7572 | string value = Convert.ToString(row[1]); | ||
7573 | |||
7574 | if ("AdminProperties" == id || "MsiHiddenProperties" == id || "SecureCustomProperties" == id) | ||
7575 | { | ||
7576 | if (0 < value.Length) | ||
7577 | { | ||
7578 | foreach (string propertyId in value.Split(';')) | ||
7579 | { | ||
7580 | string property = propertyId; | ||
7581 | bool suppressModulularization = false; | ||
7582 | if (OutputType.Module == this.outputType) | ||
7583 | { | ||
7584 | if (propertyId.EndsWith(this.modularizationGuid.Substring(1, 36).Replace('-', '_'), StringComparison.Ordinal)) | ||
7585 | { | ||
7586 | property = propertyId.Substring(0, propertyId.Length - this.modularizationGuid.Length + 1); | ||
7587 | } | ||
7588 | else | ||
7589 | { | ||
7590 | suppressModulularization = true; | ||
7591 | } | ||
7592 | } | ||
7593 | |||
7594 | Wix.Property specialProperty = this.EnsureProperty(property); | ||
7595 | if (suppressModulularization) | ||
7596 | { | ||
7597 | specialProperty.SuppressModularization = Wix.YesNoType.yes; | ||
7598 | } | ||
7599 | |||
7600 | switch (id) | ||
7601 | { | ||
7602 | case "AdminProperties": | ||
7603 | specialProperty.Admin = Wix.YesNoType.yes; | ||
7604 | break; | ||
7605 | case "MsiHiddenProperties": | ||
7606 | specialProperty.Hidden = Wix.YesNoType.yes; | ||
7607 | break; | ||
7608 | case "SecureCustomProperties": | ||
7609 | specialProperty.Secure = Wix.YesNoType.yes; | ||
7610 | break; | ||
7611 | } | ||
7612 | } | ||
7613 | } | ||
7614 | |||
7615 | continue; | ||
7616 | } | ||
7617 | else if (OutputType.Product == this.outputType) | ||
7618 | { | ||
7619 | Wix.Product product = (Wix.Product)this.core.RootElement; | ||
7620 | |||
7621 | switch (id) | ||
7622 | { | ||
7623 | case "Manufacturer": | ||
7624 | product.Manufacturer = value; | ||
7625 | continue; | ||
7626 | case "ProductCode": | ||
7627 | product.Id = value.ToUpper(CultureInfo.InvariantCulture); | ||
7628 | continue; | ||
7629 | case "ProductLanguage": | ||
7630 | product.Language = value; | ||
7631 | continue; | ||
7632 | case "ProductName": | ||
7633 | product.Name = value; | ||
7634 | continue; | ||
7635 | case "ProductVersion": | ||
7636 | product.Version = value; | ||
7637 | continue; | ||
7638 | case "UpgradeCode": | ||
7639 | product.UpgradeCode = value; | ||
7640 | continue; | ||
7641 | } | ||
7642 | } | ||
7643 | |||
7644 | if (!this.suppressUI || "ErrorDialog" != id) | ||
7645 | { | ||
7646 | Wix.Property property = this.EnsureProperty(id); | ||
7647 | |||
7648 | property.Value = value; | ||
7649 | } | ||
7650 | } | ||
7651 | } | ||
7652 | |||
7653 | /// <summary> | ||
7654 | /// Decompile the PublishComponent table. | ||
7655 | /// </summary> | ||
7656 | /// <param name="table">The table to decompile.</param> | ||
7657 | private void DecompilePublishComponentTable(Table table) | ||
7658 | { | ||
7659 | foreach (Row row in table.Rows) | ||
7660 | { | ||
7661 | Wix.Category category = new Wix.Category(); | ||
7662 | |||
7663 | category.Id = Convert.ToString(row[0]); | ||
7664 | |||
7665 | category.Qualifier = Convert.ToString(row[1]); | ||
7666 | |||
7667 | if (null != row[3]) | ||
7668 | { | ||
7669 | category.AppData = Convert.ToString(row[3]); | ||
7670 | } | ||
7671 | |||
7672 | Wix.Component component = (Wix.Component)this.core.GetIndexedElement("Component", Convert.ToString(row[2])); | ||
7673 | if (null != component) | ||
7674 | { | ||
7675 | component.AddChild(category); | ||
7676 | } | ||
7677 | else | ||
7678 | { | ||
7679 | this.core.OnMessage(WixWarnings.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Component_", Convert.ToString(row[2]), "Component")); | ||
7680 | } | ||
7681 | } | ||
7682 | } | ||
7683 | |||
7684 | /// <summary> | ||
7685 | /// Decompile the RadioButton table. | ||
7686 | /// </summary> | ||
7687 | /// <param name="table">The table to decompile.</param> | ||
7688 | private void DecompileRadioButtonTable(Table table) | ||
7689 | { | ||
7690 | SortedList radioButtons = new SortedList(); | ||
7691 | Hashtable radioButtonGroups = new Hashtable(); | ||
7692 | |||
7693 | foreach (Row row in table.Rows) | ||
7694 | { | ||
7695 | Wix.RadioButton radioButton = new Wix.RadioButton(); | ||
7696 | |||
7697 | radioButton.Value = Convert.ToString(row[2]); | ||
7698 | |||
7699 | radioButton.X = Convert.ToString(row[3], CultureInfo.InvariantCulture); | ||
7700 | |||
7701 | radioButton.Y = Convert.ToString(row[4], CultureInfo.InvariantCulture); | ||
7702 | |||
7703 | radioButton.Width = Convert.ToString(row[5], CultureInfo.InvariantCulture); | ||
7704 | |||
7705 | radioButton.Height = Convert.ToString(row[6], CultureInfo.InvariantCulture); | ||
7706 | |||
7707 | if (null != row[7]) | ||
7708 | { | ||
7709 | radioButton.Text = Convert.ToString(row[7]); | ||
7710 | } | ||
7711 | |||
7712 | if (null != row[8]) | ||
7713 | { | ||
7714 | string[] help = (Convert.ToString(row[8])).Split('|'); | ||
7715 | |||
7716 | if (2 == help.Length) | ||
7717 | { | ||
7718 | if (0 < help[0].Length) | ||
7719 | { | ||
7720 | radioButton.ToolTip = help[0]; | ||
7721 | } | ||
7722 | |||
7723 | if (0 < help[1].Length) | ||
7724 | { | ||
7725 | radioButton.Help = help[1]; | ||
7726 | } | ||
7727 | } | ||
7728 | } | ||
7729 | |||
7730 | radioButtons.Add(String.Format(CultureInfo.InvariantCulture, "{0}|{1:0000000000}", row[0], row[1]), row); | ||
7731 | this.core.IndexElement(row, radioButton); | ||
7732 | } | ||
7733 | |||
7734 | // nest the radio buttons | ||
7735 | foreach (Row row in radioButtons.Values) | ||
7736 | { | ||
7737 | Wix.RadioButton radioButton = (Wix.RadioButton)this.core.GetIndexedElement(row); | ||
7738 | Wix.RadioButtonGroup radioButtonGroup = (Wix.RadioButtonGroup)radioButtonGroups[Convert.ToString(row[0])]; | ||
7739 | |||
7740 | if (null == radioButtonGroup) | ||
7741 | { | ||
7742 | radioButtonGroup = new Wix.RadioButtonGroup(); | ||
7743 | |||
7744 | radioButtonGroup.Property = Convert.ToString(row[0]); | ||
7745 | |||
7746 | this.core.UIElement.AddChild(radioButtonGroup); | ||
7747 | radioButtonGroups.Add(Convert.ToString(row[0]), radioButtonGroup); | ||
7748 | } | ||
7749 | |||
7750 | radioButtonGroup.AddChild(radioButton); | ||
7751 | } | ||
7752 | } | ||
7753 | |||
7754 | /// <summary> | ||
7755 | /// Decompile the Registry table. | ||
7756 | /// </summary> | ||
7757 | /// <param name="table">The table to decompile.</param> | ||
7758 | private void DecompileRegistryTable(Table table) | ||
7759 | { | ||
7760 | foreach (Row row in table.Rows) | ||
7761 | { | ||
7762 | if (("-" == Convert.ToString(row[3]) || "+" == Convert.ToString(row[3]) || "*" == Convert.ToString(row[3])) && null == row[4]) | ||
7763 | { | ||
7764 | Wix.RegistryKey registryKey = new Wix.RegistryKey(); | ||
7765 | |||
7766 | registryKey.Id = Convert.ToString(row[0]); | ||
7767 | |||
7768 | Wix.RegistryRootType registryRootType; | ||
7769 | if (this.GetRegistryRootType(row.SourceLineNumbers, table.Name, row.Fields[1], out registryRootType)) | ||
7770 | { | ||
7771 | registryKey.Root = registryRootType; | ||
7772 | } | ||
7773 | |||
7774 | registryKey.Key = Convert.ToString(row[2]); | ||
7775 | |||
7776 | switch (Convert.ToString(row[3])) | ||
7777 | { | ||
7778 | case "+": | ||
7779 | registryKey.ForceCreateOnInstall = Wix.YesNoType.yes; | ||
7780 | break; | ||
7781 | case "-": | ||
7782 | registryKey.ForceDeleteOnUninstall = Wix.YesNoType.yes; | ||
7783 | break; | ||
7784 | case "*": | ||
7785 | registryKey.ForceDeleteOnUninstall = Wix.YesNoType.yes; | ||
7786 | registryKey.ForceCreateOnInstall = Wix.YesNoType.yes; | ||
7787 | break; | ||
7788 | } | ||
7789 | |||
7790 | this.core.IndexElement(row, registryKey); | ||
7791 | } | ||
7792 | else | ||
7793 | { | ||
7794 | Wix.RegistryValue registryValue = new Wix.RegistryValue(); | ||
7795 | |||
7796 | registryValue.Id = Convert.ToString(row[0]); | ||
7797 | |||
7798 | Wix.RegistryRootType registryRootType; | ||
7799 | if (this.GetRegistryRootType(row.SourceLineNumbers, table.Name, row.Fields[1], out registryRootType)) | ||
7800 | { | ||
7801 | registryValue.Root = registryRootType; | ||
7802 | } | ||
7803 | |||
7804 | registryValue.Key = Convert.ToString(row[2]); | ||
7805 | |||
7806 | if (null != row[3]) | ||
7807 | { | ||
7808 | registryValue.Name = Convert.ToString(row[3]); | ||
7809 | } | ||
7810 | |||
7811 | if (null != row[4]) | ||
7812 | { | ||
7813 | string value = Convert.ToString(row[4]); | ||
7814 | |||
7815 | if (value.StartsWith("#x", StringComparison.Ordinal)) | ||
7816 | { | ||
7817 | registryValue.Type = Wix.RegistryValue.TypeType.binary; | ||
7818 | registryValue.Value = value.Substring(2); | ||
7819 | } | ||
7820 | else if (value.StartsWith("#%", StringComparison.Ordinal)) | ||
7821 | { | ||
7822 | registryValue.Type = Wix.RegistryValue.TypeType.expandable; | ||
7823 | registryValue.Value = value.Substring(2); | ||
7824 | } | ||
7825 | else if (value.StartsWith("#", StringComparison.Ordinal) && !value.StartsWith("##", StringComparison.Ordinal)) | ||
7826 | { | ||
7827 | registryValue.Type = Wix.RegistryValue.TypeType.integer; | ||
7828 | registryValue.Value = value.Substring(1); | ||
7829 | } | ||
7830 | else | ||
7831 | { | ||
7832 | if (value.StartsWith("##", StringComparison.Ordinal)) | ||
7833 | { | ||
7834 | value = value.Substring(1); | ||
7835 | } | ||
7836 | |||
7837 | if (0 <= value.IndexOf("[~]", StringComparison.Ordinal)) | ||
7838 | { | ||
7839 | registryValue.Type = Wix.RegistryValue.TypeType.multiString; | ||
7840 | |||
7841 | if ("[~]" == value) | ||
7842 | { | ||
7843 | value = string.Empty; | ||
7844 | } | ||
7845 | else if (value.StartsWith("[~]", StringComparison.Ordinal) && value.EndsWith("[~]", StringComparison.Ordinal)) | ||
7846 | { | ||
7847 | value = value.Substring(3, value.Length - 6); | ||
7848 | } | ||
7849 | else if (value.StartsWith("[~]", StringComparison.Ordinal)) | ||
7850 | { | ||
7851 | registryValue.Action = Wix.RegistryValue.ActionType.append; | ||
7852 | value = value.Substring(3); | ||
7853 | } | ||
7854 | else if (value.EndsWith("[~]", StringComparison.Ordinal)) | ||
7855 | { | ||
7856 | registryValue.Action = Wix.RegistryValue.ActionType.prepend; | ||
7857 | value = value.Substring(0, value.Length - 3); | ||
7858 | } | ||
7859 | |||
7860 | string[] multiValues = NullSplitter.Split(value); | ||
7861 | foreach (string multiValue in multiValues) | ||
7862 | { | ||
7863 | Wix.MultiStringValue multiStringValue = new Wix.MultiStringValue(); | ||
7864 | |||
7865 | multiStringValue.Content = multiValue; | ||
7866 | |||
7867 | registryValue.AddChild(multiStringValue); | ||
7868 | } | ||
7869 | } | ||
7870 | else | ||
7871 | { | ||
7872 | registryValue.Type = Wix.RegistryValue.TypeType.@string; | ||
7873 | registryValue.Value = value; | ||
7874 | } | ||
7875 | } | ||
7876 | } | ||
7877 | else | ||
7878 | { | ||
7879 | registryValue.Type = Wix.RegistryValue.TypeType.@string; | ||
7880 | registryValue.Value = String.Empty; | ||
7881 | } | ||
7882 | |||
7883 | this.core.IndexElement(row, registryValue); | ||
7884 | } | ||
7885 | } | ||
7886 | } | ||
7887 | |||
7888 | /// <summary> | ||
7889 | /// Decompile the RegLocator table. | ||
7890 | /// </summary> | ||
7891 | /// <param name="table">The table to decompile.</param> | ||
7892 | private void DecompileRegLocatorTable(Table table) | ||
7893 | { | ||
7894 | foreach (Row row in table.Rows) | ||
7895 | { | ||
7896 | Wix.RegistrySearch registrySearch = new Wix.RegistrySearch(); | ||
7897 | |||
7898 | registrySearch.Id = Convert.ToString(row[0]); | ||
7899 | |||
7900 | switch (Convert.ToInt32(row[1])) | ||
7901 | { | ||
7902 | case MsiInterop.MsidbRegistryRootClassesRoot: | ||
7903 | registrySearch.Root = Wix.RegistrySearch.RootType.HKCR; | ||
7904 | break; | ||
7905 | case MsiInterop.MsidbRegistryRootCurrentUser: | ||
7906 | registrySearch.Root = Wix.RegistrySearch.RootType.HKCU; | ||
7907 | break; | ||
7908 | case MsiInterop.MsidbRegistryRootLocalMachine: | ||
7909 | registrySearch.Root = Wix.RegistrySearch.RootType.HKLM; | ||
7910 | break; | ||
7911 | case MsiInterop.MsidbRegistryRootUsers: | ||
7912 | registrySearch.Root = Wix.RegistrySearch.RootType.HKU; | ||
7913 | break; | ||
7914 | default: | ||
7915 | this.core.OnMessage(WixWarnings.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[1].Column.Name, row[1])); | ||
7916 | break; | ||
7917 | } | ||
7918 | |||
7919 | registrySearch.Key = Convert.ToString(row[2]); | ||
7920 | |||
7921 | if (null != row[3]) | ||
7922 | { | ||
7923 | registrySearch.Name = Convert.ToString(row[3]); | ||
7924 | } | ||
7925 | |||
7926 | if (null == row[4]) | ||
7927 | { | ||
7928 | registrySearch.Type = Wix.RegistrySearch.TypeType.file; | ||
7929 | } | ||
7930 | else | ||
7931 | { | ||
7932 | int type = Convert.ToInt32(row[4]); | ||
7933 | |||
7934 | if (MsiInterop.MsidbLocatorType64bit == (type & MsiInterop.MsidbLocatorType64bit)) | ||
7935 | { | ||
7936 | registrySearch.Win64 = Wix.YesNoType.yes; | ||
7937 | type &= ~MsiInterop.MsidbLocatorType64bit; | ||
7938 | } | ||
7939 | |||
7940 | switch (type) | ||
7941 | { | ||
7942 | case MsiInterop.MsidbLocatorTypeDirectory: | ||
7943 | registrySearch.Type = Wix.RegistrySearch.TypeType.directory; | ||
7944 | break; | ||
7945 | case MsiInterop.MsidbLocatorTypeFileName: | ||
7946 | registrySearch.Type = Wix.RegistrySearch.TypeType.file; | ||
7947 | break; | ||
7948 | case MsiInterop.MsidbLocatorTypeRawValue: | ||
7949 | registrySearch.Type = Wix.RegistrySearch.TypeType.raw; | ||
7950 | break; | ||
7951 | default: | ||
7952 | this.core.OnMessage(WixWarnings.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[4].Column.Name, row[4])); | ||
7953 | break; | ||
7954 | } | ||
7955 | } | ||
7956 | |||
7957 | this.core.IndexElement(row, registrySearch); | ||
7958 | } | ||
7959 | } | ||
7960 | |||
7961 | /// <summary> | ||
7962 | /// Decompile the RemoveFile table. | ||
7963 | /// </summary> | ||
7964 | /// <param name="table">The table to decompile.</param> | ||
7965 | private void DecompileRemoveFileTable(Table table) | ||
7966 | { | ||
7967 | foreach (Row row in table.Rows) | ||
7968 | { | ||
7969 | if (null == row[2]) | ||
7970 | { | ||
7971 | Wix.RemoveFolder removeFolder = new Wix.RemoveFolder(); | ||
7972 | |||
7973 | removeFolder.Id = Convert.ToString(row[0]); | ||
7974 | |||
7975 | // directory/property is set in FinalizeDecompile | ||
7976 | |||
7977 | switch (Convert.ToInt32(row[4])) | ||
7978 | { | ||
7979 | case MsiInterop.MsidbRemoveFileInstallModeOnInstall: | ||
7980 | removeFolder.On = Wix.InstallUninstallType.install; | ||
7981 | break; | ||
7982 | case MsiInterop.MsidbRemoveFileInstallModeOnRemove: | ||
7983 | removeFolder.On = Wix.InstallUninstallType.uninstall; | ||
7984 | break; | ||
7985 | case MsiInterop.MsidbRemoveFileInstallModeOnBoth: | ||
7986 | removeFolder.On = Wix.InstallUninstallType.both; | ||
7987 | break; | ||
7988 | default: | ||
7989 | this.core.OnMessage(WixWarnings.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[4].Column.Name, row[4])); | ||
7990 | break; | ||
7991 | } | ||
7992 | |||
7993 | Wix.Component component = (Wix.Component)this.core.GetIndexedElement("Component", Convert.ToString(row[1])); | ||
7994 | if (null != component) | ||
7995 | { | ||
7996 | component.AddChild(removeFolder); | ||
7997 | } | ||
7998 | else | ||
7999 | { | ||
8000 | this.core.OnMessage(WixWarnings.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Component_", Convert.ToString(row[1]), "Component")); | ||
8001 | } | ||
8002 | this.core.IndexElement(row, removeFolder); | ||
8003 | } | ||
8004 | else | ||
8005 | { | ||
8006 | Wix.RemoveFile removeFile = new Wix.RemoveFile(); | ||
8007 | |||
8008 | removeFile.Id = Convert.ToString(row[0]); | ||
8009 | |||
8010 | string[] names = Installer.GetNames(Convert.ToString(row[2])); | ||
8011 | if (null != names[0] && null != names[1]) | ||
8012 | { | ||
8013 | removeFile.ShortName = names[0]; | ||
8014 | removeFile.Name = names[1]; | ||
8015 | } | ||
8016 | else if (null != names[0]) | ||
8017 | { | ||
8018 | removeFile.Name = names[0]; | ||
8019 | } | ||
8020 | |||
8021 | // directory/property is set in FinalizeDecompile | ||
8022 | |||
8023 | switch (Convert.ToInt32(row[4])) | ||
8024 | { | ||
8025 | case MsiInterop.MsidbRemoveFileInstallModeOnInstall: | ||
8026 | removeFile.On = Wix.InstallUninstallType.install; | ||
8027 | break; | ||
8028 | case MsiInterop.MsidbRemoveFileInstallModeOnRemove: | ||
8029 | removeFile.On = Wix.InstallUninstallType.uninstall; | ||
8030 | break; | ||
8031 | case MsiInterop.MsidbRemoveFileInstallModeOnBoth: | ||
8032 | removeFile.On = Wix.InstallUninstallType.both; | ||
8033 | break; | ||
8034 | default: | ||
8035 | this.core.OnMessage(WixWarnings.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[4].Column.Name, row[4])); | ||
8036 | break; | ||
8037 | } | ||
8038 | |||
8039 | Wix.Component component = (Wix.Component)this.core.GetIndexedElement("Component", Convert.ToString(row[1])); | ||
8040 | if (null != component) | ||
8041 | { | ||
8042 | component.AddChild(removeFile); | ||
8043 | } | ||
8044 | else | ||
8045 | { | ||
8046 | this.core.OnMessage(WixWarnings.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Component_", Convert.ToString(row[1]), "Component")); | ||
8047 | } | ||
8048 | this.core.IndexElement(row, removeFile); | ||
8049 | } | ||
8050 | } | ||
8051 | } | ||
8052 | |||
8053 | /// <summary> | ||
8054 | /// Decompile the RemoveIniFile table. | ||
8055 | /// </summary> | ||
8056 | /// <param name="table">The table to decompile.</param> | ||
8057 | private void DecompileRemoveIniFileTable(Table table) | ||
8058 | { | ||
8059 | foreach (Row row in table.Rows) | ||
8060 | { | ||
8061 | Wix.IniFile iniFile = new Wix.IniFile(); | ||
8062 | |||
8063 | iniFile.Id = Convert.ToString(row[0]); | ||
8064 | |||
8065 | string[] names = Installer.GetNames(Convert.ToString(row[1])); | ||
8066 | if (null != names[0] && null != names[1]) | ||
8067 | { | ||
8068 | iniFile.ShortName = names[0]; | ||
8069 | iniFile.Name = names[1]; | ||
8070 | } | ||
8071 | else if (null != names[0]) | ||
8072 | { | ||
8073 | iniFile.Name = names[0]; | ||
8074 | } | ||
8075 | |||
8076 | if (null != row[2]) | ||
8077 | { | ||
8078 | iniFile.Directory = Convert.ToString(row[2]); | ||
8079 | } | ||
8080 | |||
8081 | iniFile.Section = Convert.ToString(row[3]); | ||
8082 | |||
8083 | iniFile.Key = Convert.ToString(row[4]); | ||
8084 | |||
8085 | if (null != row[5]) | ||
8086 | { | ||
8087 | iniFile.Value = Convert.ToString(row[5]); | ||
8088 | } | ||
8089 | |||
8090 | switch (Convert.ToInt32(row[6])) | ||
8091 | { | ||
8092 | case MsiInterop.MsidbIniFileActionRemoveLine: | ||
8093 | iniFile.Action = Wix.IniFile.ActionType.removeLine; | ||
8094 | break; | ||
8095 | case MsiInterop.MsidbIniFileActionRemoveTag: | ||
8096 | iniFile.Action = Wix.IniFile.ActionType.removeTag; | ||
8097 | break; | ||
8098 | default: | ||
8099 | this.core.OnMessage(WixWarnings.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[6].Column.Name, row[6])); | ||
8100 | break; | ||
8101 | } | ||
8102 | |||
8103 | Wix.Component component = (Wix.Component)this.core.GetIndexedElement("Component", Convert.ToString(row[7])); | ||
8104 | if (null != component) | ||
8105 | { | ||
8106 | component.AddChild(iniFile); | ||
8107 | } | ||
8108 | else | ||
8109 | { | ||
8110 | this.core.OnMessage(WixWarnings.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Component_", Convert.ToString(row[7]), "Component")); | ||
8111 | } | ||
8112 | } | ||
8113 | } | ||
8114 | |||
8115 | /// <summary> | ||
8116 | /// Decompile the RemoveRegistry table. | ||
8117 | /// </summary> | ||
8118 | /// <param name="table">The table to decompile.</param> | ||
8119 | private void DecompileRemoveRegistryTable(Table table) | ||
8120 | { | ||
8121 | foreach (Row row in table.Rows) | ||
8122 | { | ||
8123 | if ("-" == Convert.ToString(row[3])) | ||
8124 | { | ||
8125 | Wix.RemoveRegistryKey removeRegistryKey = new Wix.RemoveRegistryKey(); | ||
8126 | |||
8127 | removeRegistryKey.Id = Convert.ToString(row[0]); | ||
8128 | |||
8129 | Wix.RegistryRootType registryRootType; | ||
8130 | if (this.GetRegistryRootType(row.SourceLineNumbers, table.Name, row.Fields[1], out registryRootType)) | ||
8131 | { | ||
8132 | removeRegistryKey.Root = registryRootType; | ||
8133 | } | ||
8134 | |||
8135 | removeRegistryKey.Key = Convert.ToString(row[2]); | ||
8136 | |||
8137 | removeRegistryKey.Action = Wix.RemoveRegistryKey.ActionType.removeOnInstall; | ||
8138 | |||
8139 | Wix.Component component = (Wix.Component)this.core.GetIndexedElement("Component", Convert.ToString(row[4])); | ||
8140 | if (null != component) | ||
8141 | { | ||
8142 | component.AddChild(removeRegistryKey); | ||
8143 | } | ||
8144 | else | ||
8145 | { | ||
8146 | this.core.OnMessage(WixWarnings.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Component_", Convert.ToString(row[4]), "Component")); | ||
8147 | } | ||
8148 | } | ||
8149 | else | ||
8150 | { | ||
8151 | Wix.RemoveRegistryValue removeRegistryValue = new Wix.RemoveRegistryValue(); | ||
8152 | |||
8153 | removeRegistryValue.Id = Convert.ToString(row[0]); | ||
8154 | |||
8155 | Wix.RegistryRootType registryRootType; | ||
8156 | if (this.GetRegistryRootType(row.SourceLineNumbers, table.Name, row.Fields[1], out registryRootType)) | ||
8157 | { | ||
8158 | removeRegistryValue.Root = registryRootType; | ||
8159 | } | ||
8160 | |||
8161 | removeRegistryValue.Key = Convert.ToString(row[2]); | ||
8162 | |||
8163 | if (null != row[3]) | ||
8164 | { | ||
8165 | removeRegistryValue.Name = Convert.ToString(row[3]); | ||
8166 | } | ||
8167 | |||
8168 | Wix.Component component = (Wix.Component)this.core.GetIndexedElement("Component", Convert.ToString(row[4])); | ||
8169 | if (null != component) | ||
8170 | { | ||
8171 | component.AddChild(removeRegistryValue); | ||
8172 | } | ||
8173 | else | ||
8174 | { | ||
8175 | this.core.OnMessage(WixWarnings.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Component_", Convert.ToString(row[4]), "Component")); | ||
8176 | } | ||
8177 | } | ||
8178 | } | ||
8179 | } | ||
8180 | |||
8181 | /// <summary> | ||
8182 | /// Decompile the ReserveCost table. | ||
8183 | /// </summary> | ||
8184 | /// <param name="table">The table to decompile.</param> | ||
8185 | private void DecompileReserveCostTable(Table table) | ||
8186 | { | ||
8187 | foreach (Row row in table.Rows) | ||
8188 | { | ||
8189 | Wix.ReserveCost reserveCost = new Wix.ReserveCost(); | ||
8190 | |||
8191 | reserveCost.Id = Convert.ToString(row[0]); | ||
8192 | |||
8193 | if (null != row[2]) | ||
8194 | { | ||
8195 | reserveCost.Directory = Convert.ToString(row[2]); | ||
8196 | } | ||
8197 | |||
8198 | reserveCost.RunLocal = Convert.ToInt32(row[3]); | ||
8199 | |||
8200 | reserveCost.RunFromSource = Convert.ToInt32(row[4]); | ||
8201 | |||
8202 | Wix.Component component = (Wix.Component)this.core.GetIndexedElement("Component", Convert.ToString(row[1])); | ||
8203 | if (null != component) | ||
8204 | { | ||
8205 | component.AddChild(reserveCost); | ||
8206 | } | ||
8207 | else | ||
8208 | { | ||
8209 | this.core.OnMessage(WixWarnings.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Component_", Convert.ToString(row[1]), "Component")); | ||
8210 | } | ||
8211 | } | ||
8212 | } | ||
8213 | |||
8214 | /// <summary> | ||
8215 | /// Decompile the SelfReg table. | ||
8216 | /// </summary> | ||
8217 | /// <param name="table">The table to decompile.</param> | ||
8218 | private void DecompileSelfRegTable(Table table) | ||
8219 | { | ||
8220 | foreach (Row row in table.Rows) | ||
8221 | { | ||
8222 | Wix.File file = (Wix.File)this.core.GetIndexedElement("File", Convert.ToString(row[0])); | ||
8223 | |||
8224 | if (null != file) | ||
8225 | { | ||
8226 | if (null != row[1]) | ||
8227 | { | ||
8228 | file.SelfRegCost = Convert.ToInt32(row[1]); | ||
8229 | } | ||
8230 | else | ||
8231 | { | ||
8232 | file.SelfRegCost = 0; | ||
8233 | } | ||
8234 | } | ||
8235 | else | ||
8236 | { | ||
8237 | this.core.OnMessage(WixWarnings.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "File_", Convert.ToString(row[0]), "File")); | ||
8238 | } | ||
8239 | } | ||
8240 | } | ||
8241 | |||
8242 | /// <summary> | ||
8243 | /// Decompile the ServiceControl table. | ||
8244 | /// </summary> | ||
8245 | /// <param name="table">The table to decompile.</param> | ||
8246 | private void DecompileServiceControlTable(Table table) | ||
8247 | { | ||
8248 | foreach (Row row in table.Rows) | ||
8249 | { | ||
8250 | Wix.ServiceControl serviceControl = new Wix.ServiceControl(); | ||
8251 | |||
8252 | serviceControl.Id = Convert.ToString(row[0]); | ||
8253 | |||
8254 | serviceControl.Name = Convert.ToString(row[1]); | ||
8255 | |||
8256 | int eventValue = Convert.ToInt32(row[2]); | ||
8257 | if (MsiInterop.MsidbServiceControlEventStart == (eventValue & MsiInterop.MsidbServiceControlEventStart) && | ||
8258 | MsiInterop.MsidbServiceControlEventUninstallStart == (eventValue & MsiInterop.MsidbServiceControlEventUninstallStart)) | ||
8259 | { | ||
8260 | serviceControl.Start = Wix.InstallUninstallType.both; | ||
8261 | } | ||
8262 | else if (MsiInterop.MsidbServiceControlEventStart == (eventValue & MsiInterop.MsidbServiceControlEventStart)) | ||
8263 | { | ||
8264 | serviceControl.Start = Wix.InstallUninstallType.install; | ||
8265 | } | ||
8266 | else if (MsiInterop.MsidbServiceControlEventUninstallStart == (eventValue & MsiInterop.MsidbServiceControlEventUninstallStart)) | ||
8267 | { | ||
8268 | serviceControl.Start = Wix.InstallUninstallType.uninstall; | ||
8269 | } | ||
8270 | |||
8271 | if (MsiInterop.MsidbServiceControlEventStop == (eventValue & MsiInterop.MsidbServiceControlEventStop) && | ||
8272 | MsiInterop.MsidbServiceControlEventUninstallStop == (eventValue & MsiInterop.MsidbServiceControlEventUninstallStop)) | ||
8273 | { | ||
8274 | serviceControl.Stop = Wix.InstallUninstallType.both; | ||
8275 | } | ||
8276 | else if (MsiInterop.MsidbServiceControlEventStop == (eventValue & MsiInterop.MsidbServiceControlEventStop)) | ||
8277 | { | ||
8278 | serviceControl.Stop = Wix.InstallUninstallType.install; | ||
8279 | } | ||
8280 | else if (MsiInterop.MsidbServiceControlEventUninstallStop == (eventValue & MsiInterop.MsidbServiceControlEventUninstallStop)) | ||
8281 | { | ||
8282 | serviceControl.Stop = Wix.InstallUninstallType.uninstall; | ||
8283 | } | ||
8284 | |||
8285 | if (MsiInterop.MsidbServiceControlEventDelete == (eventValue & MsiInterop.MsidbServiceControlEventDelete) && | ||
8286 | MsiInterop.MsidbServiceControlEventUninstallDelete == (eventValue & MsiInterop.MsidbServiceControlEventUninstallDelete)) | ||
8287 | { | ||
8288 | serviceControl.Remove = Wix.InstallUninstallType.both; | ||
8289 | } | ||
8290 | else if (MsiInterop.MsidbServiceControlEventDelete == (eventValue & MsiInterop.MsidbServiceControlEventDelete)) | ||
8291 | { | ||
8292 | serviceControl.Remove = Wix.InstallUninstallType.install; | ||
8293 | } | ||
8294 | else if (MsiInterop.MsidbServiceControlEventUninstallDelete == (eventValue & MsiInterop.MsidbServiceControlEventUninstallDelete)) | ||
8295 | { | ||
8296 | serviceControl.Remove = Wix.InstallUninstallType.uninstall; | ||
8297 | } | ||
8298 | |||
8299 | if (null != row[3]) | ||
8300 | { | ||
8301 | string[] arguments = NullSplitter.Split(Convert.ToString(row[3])); | ||
8302 | |||
8303 | foreach (string argument in arguments) | ||
8304 | { | ||
8305 | Wix.ServiceArgument serviceArgument = new Wix.ServiceArgument(); | ||
8306 | |||
8307 | serviceArgument.Content = argument; | ||
8308 | |||
8309 | serviceControl.AddChild(serviceArgument); | ||
8310 | } | ||
8311 | } | ||
8312 | |||
8313 | if (null != row[4]) | ||
8314 | { | ||
8315 | if (0 == Convert.ToInt32(row[4])) | ||
8316 | { | ||
8317 | serviceControl.Wait = Wix.YesNoType.no; | ||
8318 | } | ||
8319 | else | ||
8320 | { | ||
8321 | serviceControl.Wait = Wix.YesNoType.yes; | ||
8322 | } | ||
8323 | } | ||
8324 | |||
8325 | Wix.Component component = (Wix.Component)this.core.GetIndexedElement("Component", Convert.ToString(row[5])); | ||
8326 | if (null != component) | ||
8327 | { | ||
8328 | component.AddChild(serviceControl); | ||
8329 | } | ||
8330 | else | ||
8331 | { | ||
8332 | this.core.OnMessage(WixWarnings.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Component_", Convert.ToString(row[5]), "Component")); | ||
8333 | } | ||
8334 | } | ||
8335 | } | ||
8336 | |||
8337 | /// <summary> | ||
8338 | /// Decompile the ServiceInstall table. | ||
8339 | /// </summary> | ||
8340 | /// <param name="table">The table to decompile.</param> | ||
8341 | private void DecompileServiceInstallTable(Table table) | ||
8342 | { | ||
8343 | foreach (Row row in table.Rows) | ||
8344 | { | ||
8345 | Wix.ServiceInstall serviceInstall = new Wix.ServiceInstall(); | ||
8346 | |||
8347 | serviceInstall.Id = Convert.ToString(row[0]); | ||
8348 | |||
8349 | serviceInstall.Name = Convert.ToString(row[1]); | ||
8350 | |||
8351 | if (null != row[2]) | ||
8352 | { | ||
8353 | serviceInstall.DisplayName = Convert.ToString(row[2]); | ||
8354 | } | ||
8355 | |||
8356 | int serviceType = Convert.ToInt32(row[3]); | ||
8357 | if (MsiInterop.MsidbServiceInstallInteractive == (serviceType & MsiInterop.MsidbServiceInstallInteractive)) | ||
8358 | { | ||
8359 | serviceInstall.Interactive = Wix.YesNoType.yes; | ||
8360 | } | ||
8361 | |||
8362 | if (MsiInterop.MsidbServiceInstallOwnProcess == (serviceType & MsiInterop.MsidbServiceInstallOwnProcess) && | ||
8363 | MsiInterop.MsidbServiceInstallShareProcess == (serviceType & MsiInterop.MsidbServiceInstallShareProcess)) | ||
8364 | { | ||
8365 | // TODO: warn | ||
8366 | } | ||
8367 | else if (MsiInterop.MsidbServiceInstallOwnProcess == (serviceType & MsiInterop.MsidbServiceInstallOwnProcess)) | ||
8368 | { | ||
8369 | serviceInstall.Type = Wix.ServiceInstall.TypeType.ownProcess; | ||
8370 | } | ||
8371 | else if (MsiInterop.MsidbServiceInstallShareProcess == (serviceType & MsiInterop.MsidbServiceInstallShareProcess)) | ||
8372 | { | ||
8373 | serviceInstall.Type = Wix.ServiceInstall.TypeType.shareProcess; | ||
8374 | } | ||
8375 | |||
8376 | int startType = Convert.ToInt32(row[4]); | ||
8377 | if (MsiInterop.MsidbServiceInstallDisabled == startType) | ||
8378 | { | ||
8379 | serviceInstall.Start = Wix.ServiceInstall.StartType.disabled; | ||
8380 | } | ||
8381 | else if (MsiInterop.MsidbServiceInstallDemandStart == startType) | ||
8382 | { | ||
8383 | serviceInstall.Start = Wix.ServiceInstall.StartType.demand; | ||
8384 | } | ||
8385 | else if (MsiInterop.MsidbServiceInstallAutoStart == startType) | ||
8386 | { | ||
8387 | serviceInstall.Start = Wix.ServiceInstall.StartType.auto; | ||
8388 | } | ||
8389 | else | ||
8390 | { | ||
8391 | this.core.OnMessage(WixWarnings.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[4].Column.Name, row[4])); | ||
8392 | } | ||
8393 | |||
8394 | int errorControl = Convert.ToInt32(row[5]); | ||
8395 | if (MsiInterop.MsidbServiceInstallErrorCritical == (errorControl & MsiInterop.MsidbServiceInstallErrorCritical)) | ||
8396 | { | ||
8397 | serviceInstall.ErrorControl = Wix.ServiceInstall.ErrorControlType.critical; | ||
8398 | } | ||
8399 | else if (MsiInterop.MsidbServiceInstallErrorNormal == (errorControl & MsiInterop.MsidbServiceInstallErrorNormal)) | ||
8400 | { | ||
8401 | serviceInstall.ErrorControl = Wix.ServiceInstall.ErrorControlType.normal; | ||
8402 | } | ||
8403 | else | ||
8404 | { | ||
8405 | serviceInstall.ErrorControl = Wix.ServiceInstall.ErrorControlType.ignore; | ||
8406 | } | ||
8407 | |||
8408 | if (MsiInterop.MsidbServiceInstallErrorControlVital == (errorControl & MsiInterop.MsidbServiceInstallErrorControlVital)) | ||
8409 | { | ||
8410 | serviceInstall.Vital = Wix.YesNoType.yes; | ||
8411 | } | ||
8412 | |||
8413 | if (null != row[6]) | ||
8414 | { | ||
8415 | serviceInstall.LoadOrderGroup = Convert.ToString(row[6]); | ||
8416 | } | ||
8417 | |||
8418 | if (null != row[7]) | ||
8419 | { | ||
8420 | string[] dependencies = NullSplitter.Split(Convert.ToString(row[7])); | ||
8421 | |||
8422 | foreach (string dependency in dependencies) | ||
8423 | { | ||
8424 | if (0 < dependency.Length) | ||
8425 | { | ||
8426 | Wix.ServiceDependency serviceDependency = new Wix.ServiceDependency(); | ||
8427 | |||
8428 | if (dependency.StartsWith("+", StringComparison.Ordinal)) | ||
8429 | { | ||
8430 | serviceDependency.Group = Wix.YesNoType.yes; | ||
8431 | serviceDependency.Id = dependency.Substring(1); | ||
8432 | } | ||
8433 | else | ||
8434 | { | ||
8435 | serviceDependency.Id = dependency; | ||
8436 | } | ||
8437 | |||
8438 | serviceInstall.AddChild(serviceDependency); | ||
8439 | } | ||
8440 | } | ||
8441 | } | ||
8442 | |||
8443 | if (null != row[8]) | ||
8444 | { | ||
8445 | serviceInstall.Account = Convert.ToString(row[8]); | ||
8446 | } | ||
8447 | |||
8448 | if (null != row[9]) | ||
8449 | { | ||
8450 | serviceInstall.Password = Convert.ToString(row[9]); | ||
8451 | } | ||
8452 | |||
8453 | if (null != row[10]) | ||
8454 | { | ||
8455 | serviceInstall.Arguments = Convert.ToString(row[10]); | ||
8456 | } | ||
8457 | |||
8458 | if (null != row[12]) | ||
8459 | { | ||
8460 | serviceInstall.Description = Convert.ToString(row[12]); | ||
8461 | } | ||
8462 | |||
8463 | Wix.Component component = (Wix.Component)this.core.GetIndexedElement("Component", Convert.ToString(row[11])); | ||
8464 | if (null != component) | ||
8465 | { | ||
8466 | component.AddChild(serviceInstall); | ||
8467 | } | ||
8468 | else | ||
8469 | { | ||
8470 | this.core.OnMessage(WixWarnings.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Component_", Convert.ToString(row[11]), "Component")); | ||
8471 | } | ||
8472 | this.core.IndexElement(row, serviceInstall); | ||
8473 | } | ||
8474 | } | ||
8475 | |||
8476 | /// <summary> | ||
8477 | /// Decompile the SFPCatalog table. | ||
8478 | /// </summary> | ||
8479 | /// <param name="table">The table to decompile.</param> | ||
8480 | private void DecompileSFPCatalogTable(Table table) | ||
8481 | { | ||
8482 | foreach (Row row in table.Rows) | ||
8483 | { | ||
8484 | Wix.SFPCatalog sfpCatalog = new Wix.SFPCatalog(); | ||
8485 | |||
8486 | sfpCatalog.Name = Convert.ToString(row[0]); | ||
8487 | |||
8488 | sfpCatalog.SourceFile = Convert.ToString(row[1]); | ||
8489 | |||
8490 | this.core.IndexElement(row, sfpCatalog); | ||
8491 | } | ||
8492 | |||
8493 | // nest the SFPCatalog elements | ||
8494 | foreach (Row row in table.Rows) | ||
8495 | { | ||
8496 | Wix.SFPCatalog sfpCatalog = (Wix.SFPCatalog)this.core.GetIndexedElement(row); | ||
8497 | |||
8498 | if (null != row[2]) | ||
8499 | { | ||
8500 | Wix.SFPCatalog parentSFPCatalog = (Wix.SFPCatalog)this.core.GetIndexedElement("SFPCatalog", Convert.ToString(row[2])); | ||
8501 | |||
8502 | if (null != parentSFPCatalog) | ||
8503 | { | ||
8504 | parentSFPCatalog.AddChild(sfpCatalog); | ||
8505 | } | ||
8506 | else | ||
8507 | { | ||
8508 | sfpCatalog.Dependency = Convert.ToString(row[2]); | ||
8509 | |||
8510 | this.core.RootElement.AddChild(sfpCatalog); | ||
8511 | } | ||
8512 | } | ||
8513 | else | ||
8514 | { | ||
8515 | this.core.RootElement.AddChild(sfpCatalog); | ||
8516 | } | ||
8517 | } | ||
8518 | } | ||
8519 | |||
8520 | /// <summary> | ||
8521 | /// Decompile the Shortcut table. | ||
8522 | /// </summary> | ||
8523 | /// <param name="table">The table to decompile.</param> | ||
8524 | private void DecompileShortcutTable(Table table) | ||
8525 | { | ||
8526 | foreach (Row row in table.Rows) | ||
8527 | { | ||
8528 | Wix.Shortcut shortcut = new Wix.Shortcut(); | ||
8529 | |||
8530 | shortcut.Id = Convert.ToString(row[0]); | ||
8531 | |||
8532 | shortcut.Directory = Convert.ToString(row[1]); | ||
8533 | |||
8534 | string[] names = Installer.GetNames(Convert.ToString(row[2])); | ||
8535 | if (null != names[0] && null != names[1]) | ||
8536 | { | ||
8537 | shortcut.ShortName = names[0]; | ||
8538 | shortcut.Name = names[1]; | ||
8539 | } | ||
8540 | else if (null != names[0]) | ||
8541 | { | ||
8542 | shortcut.Name = names[0]; | ||
8543 | } | ||
8544 | |||
8545 | string target = Convert.ToString(row[4]); | ||
8546 | if (target.StartsWith("[", StringComparison.Ordinal) && target.EndsWith("]", StringComparison.Ordinal)) | ||
8547 | { | ||
8548 | // TODO: use this value to do a "more-correct" nesting under the indicated File or CreateDirectory element | ||
8549 | shortcut.Target = target; | ||
8550 | } | ||
8551 | else | ||
8552 | { | ||
8553 | shortcut.Advertise = Wix.YesNoType.yes; | ||
8554 | |||
8555 | // primary feature is set in FinalizeFeatureComponentsTable | ||
8556 | } | ||
8557 | |||
8558 | if (null != row[5]) | ||
8559 | { | ||
8560 | shortcut.Arguments = Convert.ToString(row[5]); | ||
8561 | } | ||
8562 | |||
8563 | if (null != row[6]) | ||
8564 | { | ||
8565 | shortcut.Description = Convert.ToString(row[6]); | ||
8566 | } | ||
8567 | |||
8568 | if (null != row[7]) | ||
8569 | { | ||
8570 | shortcut.Hotkey = Convert.ToInt32(row[7]); | ||
8571 | } | ||
8572 | |||
8573 | if (null != row[8]) | ||
8574 | { | ||
8575 | shortcut.Icon = Convert.ToString(row[8]); | ||
8576 | } | ||
8577 | |||
8578 | if (null != row[9]) | ||
8579 | { | ||
8580 | shortcut.IconIndex = Convert.ToInt32(row[9]); | ||
8581 | } | ||
8582 | |||
8583 | if (null != row[10]) | ||
8584 | { | ||
8585 | switch (Convert.ToInt32(row[10])) | ||
8586 | { | ||
8587 | case 1: | ||
8588 | shortcut.Show = Wix.Shortcut.ShowType.normal; | ||
8589 | break; | ||
8590 | case 3: | ||
8591 | shortcut.Show = Wix.Shortcut.ShowType.maximized; | ||
8592 | break; | ||
8593 | case 7: | ||
8594 | shortcut.Show = Wix.Shortcut.ShowType.minimized; | ||
8595 | break; | ||
8596 | default: | ||
8597 | this.core.OnMessage(WixWarnings.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[10].Column.Name, row[10])); | ||
8598 | break; | ||
8599 | } | ||
8600 | } | ||
8601 | |||
8602 | if (null != row[11]) | ||
8603 | { | ||
8604 | shortcut.WorkingDirectory = Convert.ToString(row[11]); | ||
8605 | } | ||
8606 | |||
8607 | // Only try to read the MSI 4.0-specific columns if they actually exist | ||
8608 | if (15 < row.Fields.Length) | ||
8609 | { | ||
8610 | if (null != row[12]) | ||
8611 | { | ||
8612 | shortcut.DisplayResourceDll = Convert.ToString(row[12]); | ||
8613 | } | ||
8614 | |||
8615 | if (null != row[13]) | ||
8616 | { | ||
8617 | shortcut.DisplayResourceId = Convert.ToInt32(row[13]); | ||
8618 | } | ||
8619 | |||
8620 | if (null != row[14]) | ||
8621 | { | ||
8622 | shortcut.DescriptionResourceDll = Convert.ToString(row[14]); | ||
8623 | } | ||
8624 | |||
8625 | if (null != row[15]) | ||
8626 | { | ||
8627 | shortcut.DescriptionResourceId = Convert.ToInt32(row[15]); | ||
8628 | } | ||
8629 | } | ||
8630 | |||
8631 | Wix.Component component = (Wix.Component)this.core.GetIndexedElement("Component", Convert.ToString(row[3])); | ||
8632 | if (null != component) | ||
8633 | { | ||
8634 | component.AddChild(shortcut); | ||
8635 | } | ||
8636 | else | ||
8637 | { | ||
8638 | this.core.OnMessage(WixWarnings.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Component_", Convert.ToString(row[3]), "Component")); | ||
8639 | } | ||
8640 | |||
8641 | this.core.IndexElement(row, shortcut); | ||
8642 | } | ||
8643 | } | ||
8644 | |||
8645 | /// <summary> | ||
8646 | /// Decompile the Signature table. | ||
8647 | /// </summary> | ||
8648 | /// <param name="table">The table to decompile.</param> | ||
8649 | private void DecompileSignatureTable(Table table) | ||
8650 | { | ||
8651 | foreach (Row row in table.Rows) | ||
8652 | { | ||
8653 | Wix.FileSearch fileSearch = new Wix.FileSearch(); | ||
8654 | |||
8655 | fileSearch.Id = Convert.ToString(row[0]); | ||
8656 | |||
8657 | string[] names = Installer.GetNames(Convert.ToString(row[1])); | ||
8658 | if (null != names[0]) | ||
8659 | { | ||
8660 | // it is permissable to just have a long name | ||
8661 | if (!this.core.IsValidShortFilename(names[0], false) && null == names[1]) | ||
8662 | { | ||
8663 | fileSearch.Name = names[0]; | ||
8664 | } | ||
8665 | else | ||
8666 | { | ||
8667 | fileSearch.ShortName = names[0]; | ||
8668 | } | ||
8669 | } | ||
8670 | |||
8671 | if (null != names[1]) | ||
8672 | { | ||
8673 | fileSearch.Name = names[1]; | ||
8674 | } | ||
8675 | |||
8676 | if (null != row[2]) | ||
8677 | { | ||
8678 | fileSearch.MinVersion = Convert.ToString(row[2]); | ||
8679 | } | ||
8680 | |||
8681 | if (null != row[3]) | ||
8682 | { | ||
8683 | fileSearch.MaxVersion = Convert.ToString(row[3]); | ||
8684 | } | ||
8685 | |||
8686 | if (null != row[4]) | ||
8687 | { | ||
8688 | fileSearch.MinSize = Convert.ToInt32(row[4]); | ||
8689 | } | ||
8690 | |||
8691 | if (null != row[5]) | ||
8692 | { | ||
8693 | fileSearch.MaxSize = Convert.ToInt32(row[5]); | ||
8694 | } | ||
8695 | |||
8696 | if (null != row[6]) | ||
8697 | { | ||
8698 | fileSearch.MinDate = this.core.ConvertIntegerToDateTime(Convert.ToInt32(row[6])); | ||
8699 | } | ||
8700 | |||
8701 | if (null != row[7]) | ||
8702 | { | ||
8703 | fileSearch.MaxDate = this.core.ConvertIntegerToDateTime(Convert.ToInt32(row[7])); | ||
8704 | } | ||
8705 | |||
8706 | if (null != row[8]) | ||
8707 | { | ||
8708 | fileSearch.Languages = Convert.ToString(row[8]); | ||
8709 | } | ||
8710 | |||
8711 | this.core.IndexElement(row, fileSearch); | ||
8712 | } | ||
8713 | } | ||
8714 | |||
8715 | /// <summary> | ||
8716 | /// Decompile the TargetFiles_OptionalData table. | ||
8717 | /// </summary> | ||
8718 | /// <param name="table">The table to decompile.</param> | ||
8719 | private void DecompileTargetFiles_OptionalDataTable(Table table) | ||
8720 | { | ||
8721 | foreach (Row row in table.Rows) | ||
8722 | { | ||
8723 | Wix.TargetFile targetFile = (Wix.TargetFile)this.patchTargetFiles[row[0]]; | ||
8724 | if (null == targetFile) | ||
8725 | { | ||
8726 | targetFile = new Wix.TargetFile(); | ||
8727 | |||
8728 | targetFile.Id = Convert.ToString(row[1]); | ||
8729 | |||
8730 | Wix.TargetImage targetImage = (Wix.TargetImage)this.core.GetIndexedElement("TargetImages", Convert.ToString(row[0])); | ||
8731 | if (null != targetImage) | ||
8732 | { | ||
8733 | targetImage.AddChild(targetFile); | ||
8734 | } | ||
8735 | else | ||
8736 | { | ||
8737 | this.core.OnMessage(WixWarnings.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Target", Convert.ToString(row[0]), "TargetImages")); | ||
8738 | } | ||
8739 | this.patchTargetFiles.Add(row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), targetFile); | ||
8740 | } | ||
8741 | |||
8742 | if (null != row[2]) | ||
8743 | { | ||
8744 | string[] symbolPaths = (Convert.ToString(row[2])).Split(';'); | ||
8745 | |||
8746 | foreach (string symbolPathString in symbolPaths) | ||
8747 | { | ||
8748 | Wix.SymbolPath symbolPath = new Wix.SymbolPath(); | ||
8749 | |||
8750 | symbolPath.Path = symbolPathString; | ||
8751 | |||
8752 | targetFile.AddChild(symbolPath); | ||
8753 | } | ||
8754 | } | ||
8755 | |||
8756 | if (null != row[3] && null != row[4]) | ||
8757 | { | ||
8758 | string[] ignoreOffsets = (Convert.ToString(row[3])).Split(','); | ||
8759 | string[] ignoreLengths = (Convert.ToString(row[4])).Split(','); | ||
8760 | |||
8761 | if (ignoreOffsets.Length == ignoreLengths.Length) | ||
8762 | { | ||
8763 | for (int i = 0; i < ignoreOffsets.Length; i++) | ||
8764 | { | ||
8765 | Wix.IgnoreRange ignoreRange = new Wix.IgnoreRange(); | ||
8766 | |||
8767 | if (ignoreOffsets[i].StartsWith("0x", StringComparison.Ordinal)) | ||
8768 | { | ||
8769 | ignoreRange.Offset = Convert.ToInt32(ignoreOffsets[i].Substring(2), 16); | ||
8770 | } | ||
8771 | else | ||
8772 | { | ||
8773 | ignoreRange.Offset = Convert.ToInt32(ignoreOffsets[i], CultureInfo.InvariantCulture); | ||
8774 | } | ||
8775 | |||
8776 | if (ignoreLengths[i].StartsWith("0x", StringComparison.Ordinal)) | ||
8777 | { | ||
8778 | ignoreRange.Length = Convert.ToInt32(ignoreLengths[i].Substring(2), 16); | ||
8779 | } | ||
8780 | else | ||
8781 | { | ||
8782 | ignoreRange.Length = Convert.ToInt32(ignoreLengths[i], CultureInfo.InvariantCulture); | ||
8783 | } | ||
8784 | |||
8785 | targetFile.AddChild(ignoreRange); | ||
8786 | } | ||
8787 | } | ||
8788 | else | ||
8789 | { | ||
8790 | // TODO: warn | ||
8791 | } | ||
8792 | } | ||
8793 | else if (null != row[3] || null != row[4]) | ||
8794 | { | ||
8795 | // TODO: warn about mismatch between columns | ||
8796 | } | ||
8797 | |||
8798 | // the RetainOffsets column is handled in FinalizeFamilyFileRangesTable | ||
8799 | } | ||
8800 | } | ||
8801 | |||
8802 | /// <summary> | ||
8803 | /// Decompile the TargetImages table. | ||
8804 | /// </summary> | ||
8805 | /// <param name="table">The table to decompile.</param> | ||
8806 | private void DecompileTargetImagesTable(Table table) | ||
8807 | { | ||
8808 | foreach (Row row in table.Rows) | ||
8809 | { | ||
8810 | Wix.TargetImage targetImage = new Wix.TargetImage(); | ||
8811 | |||
8812 | targetImage.Id = Convert.ToString(row[0]); | ||
8813 | |||
8814 | targetImage.SourceFile = Convert.ToString(row[1]); | ||
8815 | |||
8816 | if (null != row[2]) | ||
8817 | { | ||
8818 | string[] symbolPaths = (Convert.ToString(row[3])).Split(';'); | ||
8819 | |||
8820 | foreach (string symbolPathString in symbolPaths) | ||
8821 | { | ||
8822 | Wix.SymbolPath symbolPath = new Wix.SymbolPath(); | ||
8823 | |||
8824 | symbolPath.Path = symbolPathString; | ||
8825 | |||
8826 | targetImage.AddChild(symbolPath); | ||
8827 | } | ||
8828 | } | ||
8829 | |||
8830 | targetImage.Order = Convert.ToInt32(row[4]); | ||
8831 | |||
8832 | if (null != row[5]) | ||
8833 | { | ||
8834 | targetImage.Validation = Convert.ToString(row[5]); | ||
8835 | } | ||
8836 | |||
8837 | if (0 != Convert.ToInt32(row[6])) | ||
8838 | { | ||
8839 | targetImage.IgnoreMissingFiles = Wix.YesNoType.yes; | ||
8840 | } | ||
8841 | |||
8842 | Wix.UpgradeImage upgradeImage = (Wix.UpgradeImage)this.core.GetIndexedElement("UpgradedImages", Convert.ToString(row[3])); | ||
8843 | if (null != upgradeImage) | ||
8844 | { | ||
8845 | upgradeImage.AddChild(targetImage); | ||
8846 | } | ||
8847 | else | ||
8848 | { | ||
8849 | this.core.OnMessage(WixWarnings.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Upgraded", Convert.ToString(row[3]), "UpgradedImages")); | ||
8850 | } | ||
8851 | this.core.IndexElement(row, targetImage); | ||
8852 | } | ||
8853 | } | ||
8854 | |||
8855 | /// <summary> | ||
8856 | /// Decompile the TextStyle table. | ||
8857 | /// </summary> | ||
8858 | /// <param name="table">The table to decompile.</param> | ||
8859 | private void DecompileTextStyleTable(Table table) | ||
8860 | { | ||
8861 | foreach (Row row in table.Rows) | ||
8862 | { | ||
8863 | Wix.TextStyle textStyle = new Wix.TextStyle(); | ||
8864 | |||
8865 | textStyle.Id = Convert.ToString(row[0]); | ||
8866 | |||
8867 | textStyle.FaceName = Convert.ToString(row[1]); | ||
8868 | |||
8869 | textStyle.Size = Convert.ToString(row[2]); | ||
8870 | |||
8871 | if (null != row[3]) | ||
8872 | { | ||
8873 | int color = Convert.ToInt32(row[3]); | ||
8874 | |||
8875 | textStyle.Red = color & 0xFF; | ||
8876 | |||
8877 | textStyle.Green = (color & 0xFF00) >> 8; | ||
8878 | |||
8879 | textStyle.Blue = (color & 0xFF0000) >> 16; | ||
8880 | } | ||
8881 | |||
8882 | if (null != row[4]) | ||
8883 | { | ||
8884 | int styleBits = Convert.ToInt32(row[4]); | ||
8885 | |||
8886 | if (MsiInterop.MsidbTextStyleStyleBitsBold == (styleBits & MsiInterop.MsidbTextStyleStyleBitsBold)) | ||
8887 | { | ||
8888 | textStyle.Bold = Wix.YesNoType.yes; | ||
8889 | } | ||
8890 | |||
8891 | if (MsiInterop.MsidbTextStyleStyleBitsItalic == (styleBits & MsiInterop.MsidbTextStyleStyleBitsItalic)) | ||
8892 | { | ||
8893 | textStyle.Italic = Wix.YesNoType.yes; | ||
8894 | } | ||
8895 | |||
8896 | if (MsiInterop.MsidbTextStyleStyleBitsUnderline == (styleBits & MsiInterop.MsidbTextStyleStyleBitsUnderline)) | ||
8897 | { | ||
8898 | textStyle.Underline = Wix.YesNoType.yes; | ||
8899 | } | ||
8900 | |||
8901 | if (MsiInterop.MsidbTextStyleStyleBitsStrike == (styleBits & MsiInterop.MsidbTextStyleStyleBitsStrike)) | ||
8902 | { | ||
8903 | textStyle.Strike = Wix.YesNoType.yes; | ||
8904 | } | ||
8905 | } | ||
8906 | |||
8907 | this.core.UIElement.AddChild(textStyle); | ||
8908 | } | ||
8909 | } | ||
8910 | |||
8911 | /// <summary> | ||
8912 | /// Decompile the TypeLib table. | ||
8913 | /// </summary> | ||
8914 | /// <param name="table">The table to decompile.</param> | ||
8915 | private void DecompileTypeLibTable(Table table) | ||
8916 | { | ||
8917 | foreach (Row row in table.Rows) | ||
8918 | { | ||
8919 | Wix.TypeLib typeLib = new Wix.TypeLib(); | ||
8920 | |||
8921 | typeLib.Id = Convert.ToString(row[0]); | ||
8922 | |||
8923 | typeLib.Advertise = Wix.YesNoType.yes; | ||
8924 | |||
8925 | typeLib.Language = Convert.ToInt32(row[1]); | ||
8926 | |||
8927 | if (null != row[3]) | ||
8928 | { | ||
8929 | int version = Convert.ToInt32(row[3]); | ||
8930 | |||
8931 | if (65536 == version) | ||
8932 | { | ||
8933 | this.core.OnMessage(WixWarnings.PossiblyIncorrectTypelibVersion(row.SourceLineNumbers, typeLib.Id)); | ||
8934 | } | ||
8935 | |||
8936 | typeLib.MajorVersion = ((version & 0xFFFF00) >> 8); | ||
8937 | typeLib.MinorVersion = (version & 0xFF); | ||
8938 | } | ||
8939 | |||
8940 | if (null != row[4]) | ||
8941 | { | ||
8942 | typeLib.Description = Convert.ToString(row[4]); | ||
8943 | } | ||
8944 | |||
8945 | if (null != row[5]) | ||
8946 | { | ||
8947 | typeLib.HelpDirectory = Convert.ToString(row[5]); | ||
8948 | } | ||
8949 | |||
8950 | if (null != row[7]) | ||
8951 | { | ||
8952 | typeLib.Cost = Convert.ToInt32(row[7]); | ||
8953 | } | ||
8954 | |||
8955 | // nested under the appropriate File element in FinalizeFileTable | ||
8956 | this.core.IndexElement(row, typeLib); | ||
8957 | } | ||
8958 | } | ||
8959 | |||
8960 | /// <summary> | ||
8961 | /// Decompile the Upgrade table. | ||
8962 | /// </summary> | ||
8963 | /// <param name="table">The table to decompile.</param> | ||
8964 | private void DecompileUpgradeTable(Table table) | ||
8965 | { | ||
8966 | Hashtable upgradeElements = new Hashtable(); | ||
8967 | |||
8968 | foreach (UpgradeRow upgradeRow in table.Rows) | ||
8969 | { | ||
8970 | if (Compiler.UpgradeDetectedProperty == upgradeRow.ActionProperty || Compiler.DowngradeDetectedProperty == upgradeRow.ActionProperty) | ||
8971 | { | ||
8972 | continue; // MajorUpgrade rows processed in FinalizeUpgradeTable | ||
8973 | } | ||
8974 | |||
8975 | Wix.Upgrade upgrade = (Wix.Upgrade)upgradeElements[upgradeRow.UpgradeCode]; | ||
8976 | |||
8977 | // create the parent Upgrade element if it doesn't already exist | ||
8978 | if (null == upgrade) | ||
8979 | { | ||
8980 | upgrade = new Wix.Upgrade(); | ||
8981 | |||
8982 | upgrade.Id = upgradeRow.UpgradeCode; | ||
8983 | |||
8984 | this.core.RootElement.AddChild(upgrade); | ||
8985 | upgradeElements.Add(upgrade.Id, upgrade); | ||
8986 | } | ||
8987 | |||
8988 | Wix.UpgradeVersion upgradeVersion = new Wix.UpgradeVersion(); | ||
8989 | |||
8990 | if (null != upgradeRow.VersionMin) | ||
8991 | { | ||
8992 | upgradeVersion.Minimum = upgradeRow.VersionMin; | ||
8993 | } | ||
8994 | |||
8995 | if (null != upgradeRow.VersionMax) | ||
8996 | { | ||
8997 | upgradeVersion.Maximum = upgradeRow.VersionMax; | ||
8998 | } | ||
8999 | |||
9000 | if (null != upgradeRow.Language) | ||
9001 | { | ||
9002 | upgradeVersion.Language = upgradeRow.Language; | ||
9003 | } | ||
9004 | |||
9005 | if (MsiInterop.MsidbUpgradeAttributesMigrateFeatures == (upgradeRow.Attributes & MsiInterop.MsidbUpgradeAttributesMigrateFeatures)) | ||
9006 | { | ||
9007 | upgradeVersion.MigrateFeatures = Wix.YesNoType.yes; | ||
9008 | } | ||
9009 | |||
9010 | if (MsiInterop.MsidbUpgradeAttributesOnlyDetect == (upgradeRow.Attributes & MsiInterop.MsidbUpgradeAttributesOnlyDetect)) | ||
9011 | { | ||
9012 | upgradeVersion.OnlyDetect = Wix.YesNoType.yes; | ||
9013 | } | ||
9014 | |||
9015 | if (MsiInterop.MsidbUpgradeAttributesIgnoreRemoveFailure == (upgradeRow.Attributes & MsiInterop.MsidbUpgradeAttributesIgnoreRemoveFailure)) | ||
9016 | { | ||
9017 | upgradeVersion.IgnoreRemoveFailure = Wix.YesNoType.yes; | ||
9018 | } | ||
9019 | |||
9020 | if (MsiInterop.MsidbUpgradeAttributesVersionMinInclusive == (upgradeRow.Attributes & MsiInterop.MsidbUpgradeAttributesVersionMinInclusive)) | ||
9021 | { | ||
9022 | upgradeVersion.IncludeMinimum = Wix.YesNoType.yes; | ||
9023 | } | ||
9024 | |||
9025 | if (MsiInterop.MsidbUpgradeAttributesVersionMaxInclusive == (upgradeRow.Attributes & MsiInterop.MsidbUpgradeAttributesVersionMaxInclusive)) | ||
9026 | { | ||
9027 | upgradeVersion.IncludeMaximum = Wix.YesNoType.yes; | ||
9028 | } | ||
9029 | |||
9030 | if (MsiInterop.MsidbUpgradeAttributesLanguagesExclusive == (upgradeRow.Attributes & MsiInterop.MsidbUpgradeAttributesLanguagesExclusive)) | ||
9031 | { | ||
9032 | upgradeVersion.ExcludeLanguages = Wix.YesNoType.yes; | ||
9033 | } | ||
9034 | |||
9035 | if (null != upgradeRow.Remove) | ||
9036 | { | ||
9037 | upgradeVersion.RemoveFeatures = upgradeRow.Remove; | ||
9038 | } | ||
9039 | |||
9040 | upgradeVersion.Property = upgradeRow.ActionProperty; | ||
9041 | |||
9042 | upgrade.AddChild(upgradeVersion); | ||
9043 | } | ||
9044 | } | ||
9045 | |||
9046 | /// <summary> | ||
9047 | /// Decompile the UpgradedFiles_OptionalData table. | ||
9048 | /// </summary> | ||
9049 | /// <param name="table">The table to decompile.</param> | ||
9050 | private void DecompileUpgradedFiles_OptionalDataTable(Table table) | ||
9051 | { | ||
9052 | foreach (Row row in table.Rows) | ||
9053 | { | ||
9054 | Wix.UpgradeFile upgradeFile = new Wix.UpgradeFile(); | ||
9055 | |||
9056 | upgradeFile.File = Convert.ToString(row[1]); | ||
9057 | |||
9058 | if (null != row[2]) | ||
9059 | { | ||
9060 | string[] symbolPaths = (Convert.ToString(row[2])).Split(';'); | ||
9061 | |||
9062 | foreach (string symbolPathString in symbolPaths) | ||
9063 | { | ||
9064 | Wix.SymbolPath symbolPath = new Wix.SymbolPath(); | ||
9065 | |||
9066 | symbolPath.Path = symbolPathString; | ||
9067 | |||
9068 | upgradeFile.AddChild(symbolPath); | ||
9069 | } | ||
9070 | } | ||
9071 | |||
9072 | if (null != row[3] && 1 == Convert.ToInt32(row[3])) | ||
9073 | { | ||
9074 | upgradeFile.AllowIgnoreOnError = Wix.YesNoType.yes; | ||
9075 | } | ||
9076 | |||
9077 | if (null != row[4] && 0 != Convert.ToInt32(row[4])) | ||
9078 | { | ||
9079 | upgradeFile.WholeFile = Wix.YesNoType.yes; | ||
9080 | } | ||
9081 | |||
9082 | upgradeFile.Ignore = Wix.YesNoType.no; | ||
9083 | |||
9084 | Wix.UpgradeImage upgradeImage = (Wix.UpgradeImage)this.core.GetIndexedElement("UpgradedImages", Convert.ToString(row[0])); | ||
9085 | if (null != upgradeImage) | ||
9086 | { | ||
9087 | upgradeImage.AddChild(upgradeFile); | ||
9088 | } | ||
9089 | else | ||
9090 | { | ||
9091 | this.core.OnMessage(WixWarnings.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Upgraded", Convert.ToString(row[0]), "UpgradedImages")); | ||
9092 | } | ||
9093 | } | ||
9094 | } | ||
9095 | |||
9096 | /// <summary> | ||
9097 | /// Decompile the UpgradedFilesToIgnore table. | ||
9098 | /// </summary> | ||
9099 | /// <param name="table">The table to decompile.</param> | ||
9100 | private void DecompileUpgradedFilesToIgnoreTable(Table table) | ||
9101 | { | ||
9102 | foreach (Row row in table.Rows) | ||
9103 | { | ||
9104 | if ("*" != Convert.ToString(row[0])) | ||
9105 | { | ||
9106 | Wix.UpgradeFile upgradeFile = new Wix.UpgradeFile(); | ||
9107 | |||
9108 | upgradeFile.File = Convert.ToString(row[1]); | ||
9109 | |||
9110 | upgradeFile.Ignore = Wix.YesNoType.yes; | ||
9111 | |||
9112 | Wix.UpgradeImage upgradeImage = (Wix.UpgradeImage)this.core.GetIndexedElement("UpgradedImages", Convert.ToString(row[0])); | ||
9113 | if (null != upgradeImage) | ||
9114 | { | ||
9115 | upgradeImage.AddChild(upgradeFile); | ||
9116 | } | ||
9117 | else | ||
9118 | { | ||
9119 | this.core.OnMessage(WixWarnings.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Upgraded", Convert.ToString(row[0]), "UpgradedImages")); | ||
9120 | } | ||
9121 | } | ||
9122 | else | ||
9123 | { | ||
9124 | this.core.OnMessage(WixWarnings.UnrepresentableColumnValue(row.SourceLineNumbers, table.Name, row.Fields[0].Column.Name, row[0])); | ||
9125 | } | ||
9126 | } | ||
9127 | } | ||
9128 | |||
9129 | /// <summary> | ||
9130 | /// Decompile the UpgradedImages table. | ||
9131 | /// </summary> | ||
9132 | /// <param name="table">The table to decompile.</param> | ||
9133 | private void DecompileUpgradedImagesTable(Table table) | ||
9134 | { | ||
9135 | foreach (Row row in table.Rows) | ||
9136 | { | ||
9137 | Wix.UpgradeImage upgradeImage = new Wix.UpgradeImage(); | ||
9138 | |||
9139 | upgradeImage.Id = Convert.ToString(row[0]); | ||
9140 | |||
9141 | upgradeImage.SourceFile = Convert.ToString(row[1]); | ||
9142 | |||
9143 | if (null != row[2]) | ||
9144 | { | ||
9145 | upgradeImage.SourcePatch = Convert.ToString(row[2]); | ||
9146 | } | ||
9147 | |||
9148 | if (null != row[3]) | ||
9149 | { | ||
9150 | string[] symbolPaths = (Convert.ToString(row[3])).Split(';'); | ||
9151 | |||
9152 | foreach (string symbolPathString in symbolPaths) | ||
9153 | { | ||
9154 | Wix.SymbolPath symbolPath = new Wix.SymbolPath(); | ||
9155 | |||
9156 | symbolPath.Path = symbolPathString; | ||
9157 | |||
9158 | upgradeImage.AddChild(symbolPath); | ||
9159 | } | ||
9160 | } | ||
9161 | |||
9162 | Wix.Family family = (Wix.Family)this.core.GetIndexedElement("ImageFamilies", Convert.ToString(row[4])); | ||
9163 | if (null != family) | ||
9164 | { | ||
9165 | family.AddChild(upgradeImage); | ||
9166 | } | ||
9167 | else | ||
9168 | { | ||
9169 | this.core.OnMessage(WixWarnings.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Family", Convert.ToString(row[4]), "ImageFamilies")); | ||
9170 | } | ||
9171 | this.core.IndexElement(row, upgradeImage); | ||
9172 | } | ||
9173 | } | ||
9174 | |||
9175 | /// <summary> | ||
9176 | /// Decompile the UIText table. | ||
9177 | /// </summary> | ||
9178 | /// <param name="table">The table to decompile.</param> | ||
9179 | private void DecompileUITextTable(Table table) | ||
9180 | { | ||
9181 | foreach (Row row in table.Rows) | ||
9182 | { | ||
9183 | Wix.UIText uiText = new Wix.UIText(); | ||
9184 | |||
9185 | uiText.Id = Convert.ToString(row[0]); | ||
9186 | |||
9187 | uiText.Content = Convert.ToString(row[1]); | ||
9188 | |||
9189 | this.core.UIElement.AddChild(uiText); | ||
9190 | } | ||
9191 | } | ||
9192 | |||
9193 | /// <summary> | ||
9194 | /// Decompile the Verb table. | ||
9195 | /// </summary> | ||
9196 | /// <param name="table">The table to decompile.</param> | ||
9197 | private void DecompileVerbTable(Table table) | ||
9198 | { | ||
9199 | foreach (Row row in table.Rows) | ||
9200 | { | ||
9201 | Wix.Verb verb = new Wix.Verb(); | ||
9202 | |||
9203 | verb.Id = Convert.ToString(row[1]); | ||
9204 | |||
9205 | if (null != row[2]) | ||
9206 | { | ||
9207 | verb.Sequence = Convert.ToInt32(row[2]); | ||
9208 | } | ||
9209 | |||
9210 | if (null != row[3]) | ||
9211 | { | ||
9212 | verb.Command = Convert.ToString(row[3]); | ||
9213 | } | ||
9214 | |||
9215 | if (null != row[4]) | ||
9216 | { | ||
9217 | verb.Argument = Convert.ToString(row[4]); | ||
9218 | } | ||
9219 | |||
9220 | this.core.IndexElement(row, verb); | ||
9221 | } | ||
9222 | } | ||
9223 | |||
9224 | /// <summary> | ||
9225 | /// Gets the RegistryRootType from an integer representation of the root. | ||
9226 | /// </summary> | ||
9227 | /// <param name="sourceLineNumbers">The source line information for the root.</param> | ||
9228 | /// <param name="tableName">The name of the table containing the field.</param> | ||
9229 | /// <param name="field">The field containing the root value.</param> | ||
9230 | /// <param name="registryRootType">The strongly-typed representation of the root.</param> | ||
9231 | /// <returns>true if the value could be converted; false otherwise.</returns> | ||
9232 | private bool GetRegistryRootType(SourceLineNumber sourceLineNumbers, string tableName, Field field, out Wix.RegistryRootType registryRootType) | ||
9233 | { | ||
9234 | switch (Convert.ToInt32(field.Data)) | ||
9235 | { | ||
9236 | case (-1): | ||
9237 | registryRootType = Wix.RegistryRootType.HKMU; | ||
9238 | return true; | ||
9239 | case MsiInterop.MsidbRegistryRootClassesRoot: | ||
9240 | registryRootType = Wix.RegistryRootType.HKCR; | ||
9241 | return true; | ||
9242 | case MsiInterop.MsidbRegistryRootCurrentUser: | ||
9243 | registryRootType = Wix.RegistryRootType.HKCU; | ||
9244 | return true; | ||
9245 | case MsiInterop.MsidbRegistryRootLocalMachine: | ||
9246 | registryRootType = Wix.RegistryRootType.HKLM; | ||
9247 | return true; | ||
9248 | case MsiInterop.MsidbRegistryRootUsers: | ||
9249 | registryRootType = Wix.RegistryRootType.HKU; | ||
9250 | return true; | ||
9251 | default: | ||
9252 | this.core.OnMessage(WixWarnings.IllegalColumnValue(sourceLineNumbers, tableName, field.Column.Name, field.Data)); | ||
9253 | registryRootType = Wix.RegistryRootType.HKCR; // assign anything to satisfy the out parameter | ||
9254 | return false; | ||
9255 | } | ||
9256 | } | ||
9257 | |||
9258 | /// <summary> | ||
9259 | /// Set the primary feature for a component. | ||
9260 | /// </summary> | ||
9261 | /// <param name="row">The row which specifies a primary feature.</param> | ||
9262 | /// <param name="featureColumnIndex">The index of the column contaning the feature identifier.</param> | ||
9263 | /// <param name="componentColumnIndex">The index of the column containing the component identifier.</param> | ||
9264 | private void SetPrimaryFeature(Row row, int featureColumnIndex, int componentColumnIndex) | ||
9265 | { | ||
9266 | // only products contain primary features | ||
9267 | if (OutputType.Product == this.outputType) | ||
9268 | { | ||
9269 | Field featureField = row.Fields[featureColumnIndex]; | ||
9270 | Field componentField = row.Fields[componentColumnIndex]; | ||
9271 | |||
9272 | Wix.ComponentRef componentRef = (Wix.ComponentRef)this.core.GetIndexedElement("FeatureComponents", Convert.ToString(featureField.Data), Convert.ToString(componentField.Data)); | ||
9273 | |||
9274 | if (null != componentRef) | ||
9275 | { | ||
9276 | componentRef.Primary = Wix.YesNoType.yes; | ||
9277 | } | ||
9278 | else | ||
9279 | { | ||
9280 | this.core.OnMessage(WixWarnings.ExpectedForeignRow(row.SourceLineNumbers, row.TableDefinition.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), featureField.Column.Name, Convert.ToString(featureField.Data), componentField.Column.Name, Convert.ToString(componentField.Data), "FeatureComponents")); | ||
9281 | } | ||
9282 | } | ||
9283 | } | ||
9284 | |||
9285 | /// <summary> | ||
9286 | /// Checks the InstallExecuteSequence table to determine where RemoveExistingProducts is scheduled and removes it. | ||
9287 | /// </summary> | ||
9288 | /// <param name="tables">The collection of all tables.</param> | ||
9289 | private static Wix.MajorUpgrade.ScheduleType DetermineMajorUpgradeScheduling(TableIndexedCollection tables) | ||
9290 | { | ||
9291 | int sequenceRemoveExistingProducts = 0; | ||
9292 | int sequenceInstallValidate = 0; | ||
9293 | int sequenceInstallInitialize = 0; | ||
9294 | int sequenceInstallFinalize = 0; | ||
9295 | int sequenceInstallExecute = 0; | ||
9296 | int sequenceInstallExecuteAgain = 0; | ||
9297 | |||
9298 | Table installExecuteSequenceTable = tables["InstallExecuteSequence"]; | ||
9299 | if (null != installExecuteSequenceTable) | ||
9300 | { | ||
9301 | int removeExistingProductsRow = -1; | ||
9302 | for (int i = 0; i < installExecuteSequenceTable.Rows.Count; i++) | ||
9303 | { | ||
9304 | Row row = installExecuteSequenceTable.Rows[i]; | ||
9305 | string action = Convert.ToString(row[0]); | ||
9306 | int sequence = Convert.ToInt32(row[2]); | ||
9307 | |||
9308 | switch (action) | ||
9309 | { | ||
9310 | case "RemoveExistingProducts": | ||
9311 | sequenceRemoveExistingProducts = sequence; | ||
9312 | removeExistingProductsRow = i; | ||
9313 | break; | ||
9314 | case "InstallValidate": | ||
9315 | sequenceInstallValidate = sequence; | ||
9316 | break; | ||
9317 | case "InstallInitialize": | ||
9318 | sequenceInstallInitialize = sequence; | ||
9319 | break; | ||
9320 | case "InstallExecute": | ||
9321 | sequenceInstallExecute = sequence; | ||
9322 | break; | ||
9323 | case "InstallExecuteAgain": | ||
9324 | sequenceInstallExecuteAgain = sequence; | ||
9325 | break; | ||
9326 | case "InstallFinalize": | ||
9327 | sequenceInstallFinalize = sequence; | ||
9328 | break; | ||
9329 | } | ||
9330 | } | ||
9331 | |||
9332 | installExecuteSequenceTable.Rows.RemoveAt(removeExistingProductsRow); | ||
9333 | } | ||
9334 | |||
9335 | if (0 != sequenceInstallValidate && sequenceInstallValidate < sequenceRemoveExistingProducts && sequenceRemoveExistingProducts < sequenceInstallInitialize) | ||
9336 | { | ||
9337 | return Wix.MajorUpgrade.ScheduleType.afterInstallValidate; | ||
9338 | } | ||
9339 | else if (0 != sequenceInstallInitialize && sequenceInstallInitialize < sequenceRemoveExistingProducts && sequenceRemoveExistingProducts < sequenceInstallExecute) | ||
9340 | { | ||
9341 | return Wix.MajorUpgrade.ScheduleType.afterInstallInitialize; | ||
9342 | } | ||
9343 | else if (0 != sequenceInstallExecute && sequenceInstallExecute < sequenceRemoveExistingProducts && sequenceRemoveExistingProducts < sequenceInstallExecuteAgain) | ||
9344 | { | ||
9345 | return Wix.MajorUpgrade.ScheduleType.afterInstallExecute; | ||
9346 | } | ||
9347 | else if (0 != sequenceInstallExecuteAgain && sequenceInstallExecuteAgain < sequenceRemoveExistingProducts && sequenceRemoveExistingProducts < sequenceInstallFinalize) | ||
9348 | { | ||
9349 | return Wix.MajorUpgrade.ScheduleType.afterInstallExecuteAgain; | ||
9350 | } | ||
9351 | else | ||
9352 | { | ||
9353 | return Wix.MajorUpgrade.ScheduleType.afterInstallFinalize; | ||
9354 | } | ||
9355 | } | ||
9356 | } | ||
9357 | } | ||
diff --git a/src/WixToolset.Core/DecompilerCore.cs b/src/WixToolset.Core/DecompilerCore.cs new file mode 100644 index 00000000..203f2bc4 --- /dev/null +++ b/src/WixToolset.Core/DecompilerCore.cs | |||
@@ -0,0 +1,152 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
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 | } | ||
diff --git a/src/WixToolset.Core/Differ.cs b/src/WixToolset.Core/Differ.cs new file mode 100644 index 00000000..71a64327 --- /dev/null +++ b/src/WixToolset.Core/Differ.cs | |||
@@ -0,0 +1,621 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
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 | } | ||
diff --git a/src/WixToolset.Core/Exceptions/WixFileNotFoundException.cs b/src/WixToolset.Core/Exceptions/WixFileNotFoundException.cs new file mode 100644 index 00000000..14169c4c --- /dev/null +++ b/src/WixToolset.Core/Exceptions/WixFileNotFoundException.cs | |||
@@ -0,0 +1,52 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
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 | } | ||
diff --git a/src/WixToolset.Core/Extensibility/AssemblyDefaultHeatExtensionAttribute.cs b/src/WixToolset.Core/Extensibility/AssemblyDefaultHeatExtensionAttribute.cs new file mode 100644 index 00000000..bb53e30c --- /dev/null +++ b/src/WixToolset.Core/Extensibility/AssemblyDefaultHeatExtensionAttribute.cs | |||
@@ -0,0 +1,33 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
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 | } | ||
diff --git a/src/WixToolset.Core/Extensibility/HarvesterExtension.cs b/src/WixToolset.Core/Extensibility/HarvesterExtension.cs new file mode 100644 index 00000000..d8d0ab34 --- /dev/null +++ b/src/WixToolset.Core/Extensibility/HarvesterExtension.cs | |||
@@ -0,0 +1,26 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
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 | } | ||
diff --git a/src/WixToolset.Core/Extensibility/HeatExtension.cs b/src/WixToolset.Core/Extensibility/HeatExtension.cs new file mode 100644 index 00000000..5e292220 --- /dev/null +++ b/src/WixToolset.Core/Extensibility/HeatExtension.cs | |||
@@ -0,0 +1,204 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
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 | } | ||
diff --git a/src/WixToolset.Core/Extensibility/IHarvesterCore.cs b/src/WixToolset.Core/Extensibility/IHarvesterCore.cs new file mode 100644 index 00000000..9a6fd10c --- /dev/null +++ b/src/WixToolset.Core/Extensibility/IHarvesterCore.cs | |||
@@ -0,0 +1,62 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
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 | } | ||
diff --git a/src/WixToolset.Core/Extensibility/IHeatCore.cs b/src/WixToolset.Core/Extensibility/IHeatCore.cs new file mode 100644 index 00000000..bc853b24 --- /dev/null +++ b/src/WixToolset.Core/Extensibility/IHeatCore.cs | |||
@@ -0,0 +1,36 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
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 | } | ||
diff --git a/src/WixToolset.Core/Extensibility/MutatorExtension.cs b/src/WixToolset.Core/Extensibility/MutatorExtension.cs new file mode 100644 index 00000000..9de64180 --- /dev/null +++ b/src/WixToolset.Core/Extensibility/MutatorExtension.cs | |||
@@ -0,0 +1,198 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
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 | } | ||
diff --git a/src/WixToolset.Core/Extensibility/ValidatorExtension.cs b/src/WixToolset.Core/Extensibility/ValidatorExtension.cs new file mode 100644 index 00000000..44ec3106 --- /dev/null +++ b/src/WixToolset.Core/Extensibility/ValidatorExtension.cs | |||
@@ -0,0 +1,299 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
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 | } | ||
diff --git a/src/WixToolset.Core/ExtensionManager.cs b/src/WixToolset.Core/ExtensionManager.cs new file mode 100644 index 00000000..45cb65ec --- /dev/null +++ b/src/WixToolset.Core/ExtensionManager.cs | |||
@@ -0,0 +1,110 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
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 | } | ||
diff --git a/src/WixToolset.Core/GlobalSuppressions.cs b/src/WixToolset.Core/GlobalSuppressions.cs new file mode 100644 index 00000000..4b283c52 --- /dev/null +++ b/src/WixToolset.Core/GlobalSuppressions.cs | |||
@@ -0,0 +1,24 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
3 | [assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1020:AvoidNamespacesWithFewTypes", Scope = "namespace", Target = "WixToolset.Cab")] | ||
4 | [assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1008:EnumsShouldHaveZeroValue", Scope = "type", Target = "WixToolset.Msi.InstallMessage")] | ||
5 | [assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1008:EnumsShouldHaveZeroValue", Scope = "type", Target = "WixToolset.Msi.InstallUILevels")] | ||
6 | [assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1027:MarkEnumsWithFlags", Scope = "type", Target = "WixToolset.Msi.OpenDatabase")] | ||
7 | [assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1032:ImplementStandardExceptionConstructors")] | ||
8 | [assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1036:OverrideMethodsOnComparableTypes")] | ||
9 | |||
10 | [assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity")] | ||
11 | |||
12 | // .NET 2.0 requirement | ||
13 | [assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2000:DisposeObjectsBeforeLosingScope", Scope = "member", Target = "WixToolset.Intermediate.Load(System.String,WixToolset.TableDefinitionCollection,System.Boolean,System.Boolean):WixToolset.Intermediate")] | ||
14 | [assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2000:DisposeObjectsBeforeLosingScope", Scope = "member", Target = "WixToolset.Library.Load(System.IO.Stream,System.Uri,WixToolset.TableDefinitionCollection,System.Boolean,System.Boolean):WixToolset.Library")] | ||
15 | [assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2000:DisposeObjectsBeforeLosingScope", Scope = "member", Target = "WixToolset.Localization.Load(System.IO.Stream,System.Uri,WixToolset.TableDefinitionCollection,System.Boolean):WixToolset.Localization")] | ||
16 | [assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2000:DisposeObjectsBeforeLosingScope", Scope = "member", Target = "WixToolset.Output.Load(System.IO.Stream,System.Uri,System.Boolean,System.Boolean):WixToolset.Output")] | ||
17 | |||
18 | // .NET 2.0 requirement | ||
19 | [assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2006:UseSafeHandleToEncapsulateNativeResources", Scope = "member", Target = "WixToolset.Cab.WixCreateCab.handle")] | ||
20 | |||
21 | [assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Msi", Scope = "namespace", Target = "WixToolset.Msi")] | ||
22 | [assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "wix")] | ||
23 | [assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "wix")] | ||
24 | [assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "wx")] | ||
diff --git a/src/WixToolset.Core/Harvester.cs b/src/WixToolset.Core/Harvester.cs new file mode 100644 index 00000000..0f79a2d6 --- /dev/null +++ b/src/WixToolset.Core/Harvester.cs | |||
@@ -0,0 +1,80 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
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 | } | ||
diff --git a/src/WixToolset.Core/HarvesterCore.cs b/src/WixToolset.Core/HarvesterCore.cs new file mode 100644 index 00000000..66a693f2 --- /dev/null +++ b/src/WixToolset.Core/HarvesterCore.cs | |||
@@ -0,0 +1,103 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
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 | } | ||
diff --git a/src/WixToolset.Core/HeatCore.cs b/src/WixToolset.Core/HeatCore.cs new file mode 100644 index 00000000..5c5defe8 --- /dev/null +++ b/src/WixToolset.Core/HeatCore.cs | |||
@@ -0,0 +1,65 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
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 | } | ||
diff --git a/src/WixToolset.Core/ICommand.cs b/src/WixToolset.Core/ICommand.cs new file mode 100644 index 00000000..957f735b --- /dev/null +++ b/src/WixToolset.Core/ICommand.cs | |||
@@ -0,0 +1,9 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
3 | namespace WixToolset | ||
4 | { | ||
5 | internal interface ICommand | ||
6 | { | ||
7 | void Execute(); | ||
8 | } | ||
9 | } | ||
diff --git a/src/WixToolset.Core/IfDefEventHandler.cs b/src/WixToolset.Core/IfDefEventHandler.cs new file mode 100644 index 00000000..37a7206e --- /dev/null +++ b/src/WixToolset.Core/IfDefEventHandler.cs | |||
@@ -0,0 +1,46 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
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 | } | ||
diff --git a/src/WixToolset.Core/IncludedFileEventHandler.cs b/src/WixToolset.Core/IncludedFileEventHandler.cs new file mode 100644 index 00000000..57527e5c --- /dev/null +++ b/src/WixToolset.Core/IncludedFileEventHandler.cs | |||
@@ -0,0 +1,52 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
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 | } | ||
diff --git a/src/WixToolset.Core/Inscriber.cs b/src/WixToolset.Core/Inscriber.cs new file mode 100644 index 00000000..5b467ec1 --- /dev/null +++ b/src/WixToolset.Core/Inscriber.cs | |||
@@ -0,0 +1,457 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
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 | } | ||
diff --git a/src/WixToolset.Core/InspectorCore.cs b/src/WixToolset.Core/InspectorCore.cs new file mode 100644 index 00000000..63b6af6e --- /dev/null +++ b/src/WixToolset.Core/InspectorCore.cs | |||
@@ -0,0 +1,32 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
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 | } | ||
diff --git a/src/WixToolset.Core/Librarian.cs b/src/WixToolset.Core/Librarian.cs new file mode 100644 index 00000000..daf53478 --- /dev/null +++ b/src/WixToolset.Core/Librarian.cs | |||
@@ -0,0 +1,95 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
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 | } | ||
diff --git a/src/WixToolset.Core/Link/ConnectToFeature.cs b/src/WixToolset.Core/Link/ConnectToFeature.cs new file mode 100644 index 00000000..6e046b89 --- /dev/null +++ b/src/WixToolset.Core/Link/ConnectToFeature.cs | |||
@@ -0,0 +1,95 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
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 | } | ||
diff --git a/src/WixToolset.Core/Link/ConnectToFeatureCollection.cs b/src/WixToolset.Core/Link/ConnectToFeatureCollection.cs new file mode 100644 index 00000000..8dd0d22c --- /dev/null +++ b/src/WixToolset.Core/Link/ConnectToFeatureCollection.cs | |||
@@ -0,0 +1,92 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
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 | } | ||
diff --git a/src/WixToolset.Core/Link/ConnectToModule.cs b/src/WixToolset.Core/Link/ConnectToModule.cs new file mode 100644 index 00000000..d6a8338e --- /dev/null +++ b/src/WixToolset.Core/Link/ConnectToModule.cs | |||
@@ -0,0 +1,54 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
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 | } | ||
diff --git a/src/WixToolset.Core/Link/ConnectToModuleCollection.cs b/src/WixToolset.Core/Link/ConnectToModuleCollection.cs new file mode 100644 index 00000000..6595487f --- /dev/null +++ b/src/WixToolset.Core/Link/ConnectToModuleCollection.cs | |||
@@ -0,0 +1,92 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
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 | } | ||
diff --git a/src/WixToolset.Core/Link/FindEntrySectionAndLoadSymbolsCommand.cs b/src/WixToolset.Core/Link/FindEntrySectionAndLoadSymbolsCommand.cs new file mode 100644 index 00000000..effb06e4 --- /dev/null +++ b/src/WixToolset.Core/Link/FindEntrySectionAndLoadSymbolsCommand.cs | |||
@@ -0,0 +1,109 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
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 | } | ||
diff --git a/src/WixToolset.Core/Link/ReportConflictingSymbolsCommand.cs b/src/WixToolset.Core/Link/ReportConflictingSymbolsCommand.cs new file mode 100644 index 00000000..39c3a5c2 --- /dev/null +++ b/src/WixToolset.Core/Link/ReportConflictingSymbolsCommand.cs | |||
@@ -0,0 +1,49 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
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 | } | ||
diff --git a/src/WixToolset.Core/Link/ResolveReferencesCommand.cs b/src/WixToolset.Core/Link/ResolveReferencesCommand.cs new file mode 100644 index 00000000..5a985f3f --- /dev/null +++ b/src/WixToolset.Core/Link/ResolveReferencesCommand.cs | |||
@@ -0,0 +1,178 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
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 | } | ||
diff --git a/src/WixToolset.Core/Link/WixGroupingOrdering.cs b/src/WixToolset.Core/Link/WixGroupingOrdering.cs new file mode 100644 index 00000000..fc0ce43b --- /dev/null +++ b/src/WixToolset.Core/Link/WixGroupingOrdering.cs | |||
@@ -0,0 +1,726 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
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 | } | ||
diff --git a/src/WixToolset.Core/Linker.cs b/src/WixToolset.Core/Linker.cs new file mode 100644 index 00000000..166894b9 --- /dev/null +++ b/src/WixToolset.Core/Linker.cs | |||
@@ -0,0 +1,2434 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
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 | } | ||
diff --git a/src/WixToolset.Core/Localizer.cs b/src/WixToolset.Core/Localizer.cs new file mode 100644 index 00000000..3c299896 --- /dev/null +++ b/src/WixToolset.Core/Localizer.cs | |||
@@ -0,0 +1,384 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
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 | } | ||
diff --git a/src/WixToolset.Core/Melter.cs b/src/WixToolset.Core/Melter.cs new file mode 100644 index 00000000..ccc0cb6f --- /dev/null +++ b/src/WixToolset.Core/Melter.cs | |||
@@ -0,0 +1,398 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
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 | } | ||
diff --git a/src/WixToolset.Core/MelterCore.cs b/src/WixToolset.Core/MelterCore.cs new file mode 100644 index 00000000..75d43619 --- /dev/null +++ b/src/WixToolset.Core/MelterCore.cs | |||
@@ -0,0 +1,30 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
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 | } | ||
diff --git a/src/WixToolset.Core/MergeMod/NativeMethods.cs b/src/WixToolset.Core/MergeMod/NativeMethods.cs new file mode 100644 index 00000000..daf259b4 --- /dev/null +++ b/src/WixToolset.Core/MergeMod/NativeMethods.cs | |||
@@ -0,0 +1,508 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | #if false | ||
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 | ||
diff --git a/src/WixToolset.Core/Msi/Database.cs b/src/WixToolset.Core/Msi/Database.cs new file mode 100644 index 00000000..7c3a97bb --- /dev/null +++ b/src/WixToolset.Core/Msi/Database.cs | |||
@@ -0,0 +1,303 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
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 | } | ||
diff --git a/src/WixToolset.Core/Msi/Installer.cs b/src/WixToolset.Core/Msi/Installer.cs new file mode 100644 index 00000000..3beb26f4 --- /dev/null +++ b/src/WixToolset.Core/Msi/Installer.cs | |||
@@ -0,0 +1,484 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
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 | } | ||
diff --git a/src/WixToolset.Core/Msi/Interop/MsiInterop.cs b/src/WixToolset.Core/Msi/Interop/MsiInterop.cs new file mode 100644 index 00000000..054289ee --- /dev/null +++ b/src/WixToolset.Core/Msi/Interop/MsiInterop.cs | |||
@@ -0,0 +1,697 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | #if false | ||
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 | ||
diff --git a/src/WixToolset.Core/Msi/MsiException.cs b/src/WixToolset.Core/Msi/MsiException.cs new file mode 100644 index 00000000..b33bf27a --- /dev/null +++ b/src/WixToolset.Core/Msi/MsiException.cs | |||
@@ -0,0 +1,78 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
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 | } | ||
diff --git a/src/WixToolset.Core/Msi/MsiHandle.cs b/src/WixToolset.Core/Msi/MsiHandle.cs new file mode 100644 index 00000000..6d2dc984 --- /dev/null +++ b/src/WixToolset.Core/Msi/MsiHandle.cs | |||
@@ -0,0 +1,116 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
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 | } | ||
diff --git a/src/WixToolset.Core/Msi/Record.cs b/src/WixToolset.Core/Msi/Record.cs new file mode 100644 index 00000000..438aa3b0 --- /dev/null +++ b/src/WixToolset.Core/Msi/Record.cs | |||
@@ -0,0 +1,182 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
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 | } | ||
diff --git a/src/WixToolset.Core/Msi/Session.cs b/src/WixToolset.Core/Msi/Session.cs new file mode 100644 index 00000000..d3a19711 --- /dev/null +++ b/src/WixToolset.Core/Msi/Session.cs | |||
@@ -0,0 +1,45 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
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 | } | ||
diff --git a/src/WixToolset.Core/Msi/SummaryInformation.cs b/src/WixToolset.Core/Msi/SummaryInformation.cs new file mode 100644 index 00000000..39949db6 --- /dev/null +++ b/src/WixToolset.Core/Msi/SummaryInformation.cs | |||
@@ -0,0 +1,323 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
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 | } | ||
diff --git a/src/WixToolset.Core/Msi/View.cs b/src/WixToolset.Core/Msi/View.cs new file mode 100644 index 00000000..d6542824 --- /dev/null +++ b/src/WixToolset.Core/Msi/View.cs | |||
@@ -0,0 +1,189 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
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 | } | ||
diff --git a/src/WixToolset.Core/Mutator.cs b/src/WixToolset.Core/Mutator.cs new file mode 100644 index 00000000..d37815f4 --- /dev/null +++ b/src/WixToolset.Core/Mutator.cs | |||
@@ -0,0 +1,115 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
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 | } | ||
diff --git a/src/WixToolset.Core/Ole32/Storage.cs b/src/WixToolset.Core/Ole32/Storage.cs new file mode 100644 index 00000000..c6a43bc4 --- /dev/null +++ b/src/WixToolset.Core/Ole32/Storage.cs | |||
@@ -0,0 +1,437 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
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 | } | ||
diff --git a/src/WixToolset.Core/Patch.cs b/src/WixToolset.Core/Patch.cs new file mode 100644 index 00000000..e3e6c27f --- /dev/null +++ b/src/WixToolset.Core/Patch.cs | |||
@@ -0,0 +1,1284 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
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 | } | ||
diff --git a/src/WixToolset.Core/PatchAPI/PatchInterop.cs b/src/WixToolset.Core/PatchAPI/PatchInterop.cs new file mode 100644 index 00000000..ce749a33 --- /dev/null +++ b/src/WixToolset.Core/PatchAPI/PatchInterop.cs | |||
@@ -0,0 +1,1002 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
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 | } | ||
diff --git a/src/WixToolset.Core/PatchTransform.cs b/src/WixToolset.Core/PatchTransform.cs new file mode 100644 index 00000000..c87b1a21 --- /dev/null +++ b/src/WixToolset.Core/PatchTransform.cs | |||
@@ -0,0 +1,274 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
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 | } | ||
diff --git a/src/WixToolset.Core/Preprocess/IfContext.cs b/src/WixToolset.Core/Preprocess/IfContext.cs new file mode 100644 index 00000000..64b5bd91 --- /dev/null +++ b/src/WixToolset.Core/Preprocess/IfContext.cs | |||
@@ -0,0 +1,110 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
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 | } | ||
diff --git a/src/WixToolset.Core/Preprocessor.cs b/src/WixToolset.Core/Preprocessor.cs new file mode 100644 index 00000000..a9fbcbb7 --- /dev/null +++ b/src/WixToolset.Core/Preprocessor.cs | |||
@@ -0,0 +1,1598 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
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 | /// <, >, <=, >=, =, != | ||
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) <= 2 | ||
1423 | /// $(var.A) != "2" | ||
1424 | /// </summary> | ||
1425 | /// <param name="originalExpression">The original expression</param> | ||
1426 | /// <param name="expression">The expression currently being evaluated</param> | ||
1427 | /// <param name="prevResultOperation">The operation to apply to this result</param> | ||
1428 | /// <param name="prevResult">The previous result to apply to this result</param> | ||
1429 | /// <returns>Boolean to indicate if the expression is true or false</returns> | ||
1430 | private bool EvaluateExpressionRecurse(string originalExpression, ref string expression, PreprocessorOperation prevResultOperation, bool prevResult) | ||
1431 | { | ||
1432 | bool expressionValue = false; | ||
1433 | expression = expression.Trim(); | ||
1434 | if (expression.Length == 0) | ||
1435 | { | ||
1436 | throw new WixException(WixErrors.UnexpectedEmptySubexpression(this.currentLineNumber, originalExpression)); | ||
1437 | } | ||
1438 | |||
1439 | // If the expression starts with parenthesis, evaluate it | ||
1440 | if (expression.IndexOf('(') == 0) | ||
1441 | { | ||
1442 | int endSubExpressionIndex; | ||
1443 | string subExpression = this.GetParenthesisExpression(originalExpression, expression, out endSubExpressionIndex); | ||
1444 | expressionValue = this.EvaluateExpressionRecurse(originalExpression, ref subExpression, PreprocessorOperation.And, true); | ||
1445 | |||
1446 | // Now get the rest of the expression that hasn't been evaluated | ||
1447 | expression = expression.Substring(endSubExpressionIndex).Trim(); | ||
1448 | } | ||
1449 | else | ||
1450 | { | ||
1451 | // Check for NOT | ||
1452 | if (StartsWithKeyword(expression, PreprocessorOperation.Not)) | ||
1453 | { | ||
1454 | expression = expression.Substring(3).Trim(); | ||
1455 | if (expression.Length == 0) | ||
1456 | { | ||
1457 | throw new WixException(WixErrors.ExpectedExpressionAfterNot(this.currentLineNumber, originalExpression)); | ||
1458 | } | ||
1459 | |||
1460 | expressionValue = this.EvaluateExpressionRecurse(originalExpression, ref expression, PreprocessorOperation.Not, true); | ||
1461 | } | ||
1462 | else // Expect a literal | ||
1463 | { | ||
1464 | expressionValue = this.EvaluateAtomicExpression(originalExpression, ref expression); | ||
1465 | |||
1466 | // Expect the literal that was just evaluated to already be cut off | ||
1467 | } | ||
1468 | } | ||
1469 | this.UpdateExpressionValue(ref expressionValue, prevResultOperation, prevResult); | ||
1470 | |||
1471 | // If there's still an expression left, it must start with AND or OR. | ||
1472 | if (expression.Trim().Length > 0) | ||
1473 | { | ||
1474 | if (StartsWithKeyword(expression, PreprocessorOperation.And)) | ||
1475 | { | ||
1476 | expression = expression.Substring(3); | ||
1477 | return this.EvaluateExpressionRecurse(originalExpression, ref expression, PreprocessorOperation.And, expressionValue); | ||
1478 | } | ||
1479 | else if (StartsWithKeyword(expression, PreprocessorOperation.Or)) | ||
1480 | { | ||
1481 | expression = expression.Substring(2); | ||
1482 | return this.EvaluateExpressionRecurse(originalExpression, ref expression, PreprocessorOperation.Or, expressionValue); | ||
1483 | } | ||
1484 | else | ||
1485 | { | ||
1486 | throw new WixException(WixErrors.InvalidSubExpression(this.currentLineNumber, expression, originalExpression)); | ||
1487 | } | ||
1488 | } | ||
1489 | |||
1490 | return expressionValue; | ||
1491 | } | ||
1492 | |||
1493 | /// <summary> | ||
1494 | /// Update the current line number with the reader's current state. | ||
1495 | /// </summary> | ||
1496 | /// <param name="reader">The xml reader for the preprocessor.</param> | ||
1497 | /// <param name="offset">This is the artificial offset of the line numbers from the reader. Used for the foreach processing.</param> | ||
1498 | private void UpdateCurrentLineNumber(XmlReader reader, int offset) | ||
1499 | { | ||
1500 | IXmlLineInfo lineInfoReader = reader as IXmlLineInfo; | ||
1501 | if (null != lineInfoReader) | ||
1502 | { | ||
1503 | int newLine = lineInfoReader.LineNumber + offset; | ||
1504 | |||
1505 | if (this.currentLineNumber.LineNumber != newLine) | ||
1506 | { | ||
1507 | this.currentLineNumber = new SourceLineNumber(this.currentLineNumber.FileName, newLine); | ||
1508 | } | ||
1509 | } | ||
1510 | } | ||
1511 | |||
1512 | /// <summary> | ||
1513 | /// Pushes a file name on the stack of included files. | ||
1514 | /// </summary> | ||
1515 | /// <param name="fileName">Name to push on to the stack of included files.</param> | ||
1516 | private void PushInclude(string fileName) | ||
1517 | { | ||
1518 | if (1023 < this.currentFileStack.Count) | ||
1519 | { | ||
1520 | throw new WixException(WixErrors.TooDeeplyIncluded(this.currentLineNumber, this.currentFileStack.Count)); | ||
1521 | } | ||
1522 | |||
1523 | this.currentFileStack.Push(fileName); | ||
1524 | this.sourceStack.Push(this.currentLineNumber); | ||
1525 | this.currentLineNumber = new SourceLineNumber(fileName); | ||
1526 | this.includeNextStack.Push(true); | ||
1527 | } | ||
1528 | |||
1529 | /// <summary> | ||
1530 | /// Pops a file name from the stack of included files. | ||
1531 | /// </summary> | ||
1532 | private void PopInclude() | ||
1533 | { | ||
1534 | this.currentLineNumber = this.sourceStack.Pop(); | ||
1535 | |||
1536 | this.currentFileStack.Pop(); | ||
1537 | this.includeNextStack.Pop(); | ||
1538 | } | ||
1539 | |||
1540 | /// <summary> | ||
1541 | /// Go through search paths, looking for a matching include file. | ||
1542 | /// Start the search in the directory of the source file, then go | ||
1543 | /// through the search paths in the order given on the command line | ||
1544 | /// (leftmost first, ...). | ||
1545 | /// </summary> | ||
1546 | /// <param name="includePath">User-specified path to the included file (usually just the file name).</param> | ||
1547 | /// <returns>Returns a FileInfo for the found include file, or null if the file cannot be found.</returns> | ||
1548 | private string GetIncludeFile(string includePath) | ||
1549 | { | ||
1550 | string finalIncludePath = null; | ||
1551 | |||
1552 | includePath = includePath.Trim(); | ||
1553 | |||
1554 | // remove quotes (only if they match) | ||
1555 | if ((includePath.StartsWith("\"", StringComparison.Ordinal) && includePath.EndsWith("\"", StringComparison.Ordinal)) || | ||
1556 | (includePath.StartsWith("'", StringComparison.Ordinal) && includePath.EndsWith("'", StringComparison.Ordinal))) | ||
1557 | { | ||
1558 | includePath = includePath.Substring(1, includePath.Length - 2); | ||
1559 | } | ||
1560 | |||
1561 | // check if the include file is a full path | ||
1562 | if (Path.IsPathRooted(includePath)) | ||
1563 | { | ||
1564 | if (File.Exists(includePath)) | ||
1565 | { | ||
1566 | finalIncludePath = includePath; | ||
1567 | } | ||
1568 | } | ||
1569 | else // relative path | ||
1570 | { | ||
1571 | // build a string to test the directory containing the source file first | ||
1572 | string currentFolder = this.currentFileStack.Peek(); | ||
1573 | string includeTestPath = Path.Combine(Path.GetDirectoryName(currentFolder) ?? String.Empty, includePath); | ||
1574 | |||
1575 | // test the source file directory | ||
1576 | if (File.Exists(includeTestPath)) | ||
1577 | { | ||
1578 | finalIncludePath = includeTestPath; | ||
1579 | } | ||
1580 | else // test all search paths in the order specified on the command line | ||
1581 | { | ||
1582 | foreach (string includeSearchPath in this.IncludeSearchPaths) | ||
1583 | { | ||
1584 | // if the path exists, we have found the final string | ||
1585 | includeTestPath = Path.Combine(includeSearchPath, includePath); | ||
1586 | if (File.Exists(includeTestPath)) | ||
1587 | { | ||
1588 | finalIncludePath = includeTestPath; | ||
1589 | break; | ||
1590 | } | ||
1591 | } | ||
1592 | } | ||
1593 | } | ||
1594 | |||
1595 | return finalIncludePath; | ||
1596 | } | ||
1597 | } | ||
1598 | } | ||
diff --git a/src/WixToolset.Core/PreprocessorCore.cs b/src/WixToolset.Core/PreprocessorCore.cs new file mode 100644 index 00000000..b58fc80c --- /dev/null +++ b/src/WixToolset.Core/PreprocessorCore.cs | |||
@@ -0,0 +1,560 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
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 | } | ||
diff --git a/src/WixToolset.Core/ProcessedStreamEventHandler.cs b/src/WixToolset.Core/ProcessedStreamEventHandler.cs new file mode 100644 index 00000000..de6b5d1f --- /dev/null +++ b/src/WixToolset.Core/ProcessedStreamEventHandler.cs | |||
@@ -0,0 +1,43 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
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 | } | ||
diff --git a/src/WixToolset.Core/ProvidesDependency.cs b/src/WixToolset.Core/ProvidesDependency.cs new file mode 100644 index 00000000..ea96b5c8 --- /dev/null +++ b/src/WixToolset.Core/ProvidesDependency.cs | |||
@@ -0,0 +1,108 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
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 | } | ||
diff --git a/src/WixToolset.Core/ProvidesDependencyCollection.cs b/src/WixToolset.Core/ProvidesDependencyCollection.cs new file mode 100644 index 00000000..a777afb0 --- /dev/null +++ b/src/WixToolset.Core/ProvidesDependencyCollection.cs | |||
@@ -0,0 +1,64 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
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 | } | ||
diff --git a/src/WixToolset.Core/ResolvedVariableEventHandler.cs b/src/WixToolset.Core/ResolvedVariableEventHandler.cs new file mode 100644 index 00000000..232ad9e4 --- /dev/null +++ b/src/WixToolset.Core/ResolvedVariableEventHandler.cs | |||
@@ -0,0 +1,39 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
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 | } | ||
diff --git a/src/WixToolset.Core/SourceFile.cs b/src/WixToolset.Core/SourceFile.cs new file mode 100644 index 00000000..3b1e386a --- /dev/null +++ b/src/WixToolset.Core/SourceFile.cs | |||
@@ -0,0 +1,21 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
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 | } | ||
diff --git a/src/WixToolset.Core/Unbinder.cs b/src/WixToolset.Core/Unbinder.cs new file mode 100644 index 00000000..744d5536 --- /dev/null +++ b/src/WixToolset.Core/Unbinder.cs | |||
@@ -0,0 +1,1491 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
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 | } | ||
diff --git a/src/WixToolset.Core/Util.cs b/src/WixToolset.Core/Util.cs new file mode 100644 index 00000000..5950fe0a --- /dev/null +++ b/src/WixToolset.Core/Util.cs | |||
@@ -0,0 +1,17 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
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 | } | ||
diff --git a/src/WixToolset.Core/Uuid.cs b/src/WixToolset.Core/Uuid.cs new file mode 100644 index 00000000..2e599793 --- /dev/null +++ b/src/WixToolset.Core/Uuid.cs | |||
@@ -0,0 +1,89 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
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 | } | ||
diff --git a/src/WixToolset.Core/Validator.cs b/src/WixToolset.Core/Validator.cs new file mode 100644 index 00000000..6420b9b7 --- /dev/null +++ b/src/WixToolset.Core/Validator.cs | |||
@@ -0,0 +1,401 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
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 | } | ||
diff --git a/src/WixToolset.Core/VerifyInterop.cs b/src/WixToolset.Core/VerifyInterop.cs new file mode 100644 index 00000000..81fbec65 --- /dev/null +++ b/src/WixToolset.Core/VerifyInterop.cs | |||
@@ -0,0 +1,68 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
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 | } | ||
diff --git a/src/WixToolset.Core/WixComponentSearchInfo.cs b/src/WixToolset.Core/WixComponentSearchInfo.cs new file mode 100644 index 00000000..dfd5d8ba --- /dev/null +++ b/src/WixToolset.Core/WixComponentSearchInfo.cs | |||
@@ -0,0 +1,64 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
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 | } | ||
diff --git a/src/WixToolset.Core/WixDistribution.cs b/src/WixToolset.Core/WixDistribution.cs new file mode 100644 index 00000000..4ceb8982 --- /dev/null +++ b/src/WixToolset.Core/WixDistribution.cs | |||
@@ -0,0 +1,109 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
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 | } | ||
diff --git a/src/WixToolset.Core/WixDistributionSpecificStrings.Designer.cs b/src/WixToolset.Core/WixDistributionSpecificStrings.Designer.cs new file mode 100644 index 00000000..841f6ca8 --- /dev/null +++ b/src/WixToolset.Core/WixDistributionSpecificStrings.Designer.cs | |||
@@ -0,0 +1,77 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
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 | } | ||
diff --git a/src/WixToolset.Core/WixDistributionSpecificStrings.resx b/src/WixToolset.Core/WixDistributionSpecificStrings.resx new file mode 100644 index 00000000..7cca2d4e --- /dev/null +++ b/src/WixToolset.Core/WixDistributionSpecificStrings.resx | |||
@@ -0,0 +1,130 @@ | |||
1 | <?xml version="1.0" encoding="utf-8"?> | ||
2 | <root> | ||
3 | <!-- | ||
4 | Microsoft ResX Schema | ||
5 | |||
6 | Version 2.0 | ||
7 | |||
8 | The primary goals of this format is to allow a simple XML format | ||
9 | that is mostly human readable. The generation and parsing of the | ||
10 | various data types are done through the TypeConverter classes | ||
11 | associated with the data types. | ||
12 | |||
13 | Example: | ||
14 | |||
15 | ... ado.net/XML headers & schema ... | ||
16 | <resheader name="resmimetype">text/microsoft-resx</resheader> | ||
17 | <resheader name="version">2.0</resheader> | ||
18 | <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader> | ||
19 | <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader> | ||
20 | <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data> | ||
21 | <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data> | ||
22 | <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64"> | ||
23 | <value>[base64 mime encoded serialized .NET Framework object]</value> | ||
24 | </data> | ||
25 | <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64"> | ||
26 | <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value> | ||
27 | <comment>This is a comment</comment> | ||
28 | </data> | ||
29 | |||
30 | There are any number of "resheader" rows that contain simple | ||
31 | name/value pairs. | ||
32 | |||
33 | Each data row contains a name, and value. The row also contains a | ||
34 | type or mimetype. Type corresponds to a .NET class that support | ||
35 | text/value conversion through the TypeConverter architecture. | ||
36 | Classes that don't support this are serialized and stored with the | ||
37 | mimetype set. | ||
38 | |||
39 | The mimetype is used for serialized objects, and tells the | ||
40 | ResXResourceReader how to depersist the object. This is currently not | ||
41 | extensible. For a given mimetype the value must be set accordingly: | ||
42 | |||
43 | Note - application/x-microsoft.net.object.binary.base64 is the format | ||
44 | that the ResXResourceWriter will generate, however the reader can | ||
45 | read any of the formats listed below. | ||
46 | |||
47 | mimetype: application/x-microsoft.net.object.binary.base64 | ||
48 | value : The object must be serialized with | ||
49 | : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter | ||
50 | : and then encoded with base64 encoding. | ||
51 | |||
52 | mimetype: application/x-microsoft.net.object.soap.base64 | ||
53 | value : The object must be serialized with | ||
54 | : System.Runtime.Serialization.Formatters.Soap.SoapFormatter | ||
55 | : and then encoded with base64 encoding. | ||
56 | |||
57 | mimetype: application/x-microsoft.net.object.bytearray.base64 | ||
58 | value : The object must be serialized into a byte array | ||
59 | : using a System.ComponentModel.TypeConverter | ||
60 | : and then encoded with base64 encoding. | ||
61 | --> | ||
62 | <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata"> | ||
63 | <xsd:import namespace="http://www.w3.org/XML/1998/namespace" /> | ||
64 | <xsd:element name="root" msdata:IsDataSet="true"> | ||
65 | <xsd:complexType> | ||
66 | <xsd:choice maxOccurs="unbounded"> | ||
67 | <xsd:element name="metadata"> | ||
68 | <xsd:complexType> | ||
69 | <xsd:sequence> | ||
70 | <xsd:element name="value" type="xsd:string" minOccurs="0" /> | ||
71 | </xsd:sequence> | ||
72 | <xsd:attribute name="name" use="required" type="xsd:string" /> | ||
73 | <xsd:attribute name="type" type="xsd:string" /> | ||
74 | <xsd:attribute name="mimetype" type="xsd:string" /> | ||
75 | <xsd:attribute ref="xml:space" /> | ||
76 | </xsd:complexType> | ||
77 | </xsd:element> | ||
78 | <xsd:element name="assembly"> | ||
79 | <xsd:complexType> | ||
80 | <xsd:attribute name="alias" type="xsd:string" /> | ||
81 | <xsd:attribute name="name" type="xsd:string" /> | ||
82 | </xsd:complexType> | ||
83 | </xsd:element> | ||
84 | <xsd:element name="data"> | ||
85 | <xsd:complexType> | ||
86 | <xsd:sequence> | ||
87 | <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" /> | ||
88 | <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" /> | ||
89 | </xsd:sequence> | ||
90 | <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" /> | ||
91 | <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" /> | ||
92 | <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" /> | ||
93 | <xsd:attribute ref="xml:space" /> | ||
94 | </xsd:complexType> | ||
95 | </xsd:element> | ||
96 | <xsd:element name="resheader"> | ||
97 | <xsd:complexType> | ||
98 | <xsd:sequence> | ||
99 | <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" /> | ||
100 | </xsd:sequence> | ||
101 | <xsd:attribute name="name" type="xsd:string" use="required" /> | ||
102 | </xsd:complexType> | ||
103 | </xsd:element> | ||
104 | </xsd:choice> | ||
105 | </xsd:complexType> | ||
106 | </xsd:element> | ||
107 | </xsd:schema> | ||
108 | <resheader name="resmimetype"> | ||
109 | <value>text/microsoft-resx</value> | ||
110 | </resheader> | ||
111 | <resheader name="version"> | ||
112 | <value>2.0</value> | ||
113 | </resheader> | ||
114 | <resheader name="reader"> | ||
115 | <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> | ||
116 | </resheader> | ||
117 | <resheader name="writer"> | ||
118 | <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> | ||
119 | </resheader> | ||
120 | <data name="ToolsetHelpFooter" xml:space="preserve"> | ||
121 | <value> | ||
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 | ||
diff --git a/src/WixToolset.Core/WixFileSearchInfo.cs b/src/WixToolset.Core/WixFileSearchInfo.cs new file mode 100644 index 00000000..e53f7bf7 --- /dev/null +++ b/src/WixToolset.Core/WixFileSearchInfo.cs | |||
@@ -0,0 +1,54 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
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 | } | ||
diff --git a/src/WixToolset.Core/WixGenericMessageEventArgs.cs b/src/WixToolset.Core/WixGenericMessageEventArgs.cs new file mode 100644 index 00000000..2c1d4705 --- /dev/null +++ b/src/WixToolset.Core/WixGenericMessageEventArgs.cs | |||
@@ -0,0 +1,45 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
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 | } | ||
diff --git a/src/WixToolset.Core/WixProductSearchInfo.cs b/src/WixToolset.Core/WixProductSearchInfo.cs new file mode 100644 index 00000000..4c57d8be --- /dev/null +++ b/src/WixToolset.Core/WixProductSearchInfo.cs | |||
@@ -0,0 +1,67 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
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 | } | ||
diff --git a/src/WixToolset.Core/WixRegistrySearchInfo.cs b/src/WixToolset.Core/WixRegistrySearchInfo.cs new file mode 100644 index 00000000..e8d7ce9b --- /dev/null +++ b/src/WixToolset.Core/WixRegistrySearchInfo.cs | |||
@@ -0,0 +1,92 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
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 | } | ||
diff --git a/src/WixToolset.Core/WixSearchInfo.cs b/src/WixToolset.Core/WixSearchInfo.cs new file mode 100644 index 00000000..906365a2 --- /dev/null +++ b/src/WixToolset.Core/WixSearchInfo.cs | |||
@@ -0,0 +1,53 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
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 | } | ||
diff --git a/src/WixToolset.Core/WixStrings.Designer.cs b/src/WixToolset.Core/WixStrings.Designer.cs new file mode 100644 index 00000000..4ba9381a --- /dev/null +++ b/src/WixToolset.Core/WixStrings.Designer.cs | |||
@@ -0,0 +1,442 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
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 '{0}' 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 '{0}' 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 '{0}' 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 '{0}'.. | ||
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 '{0}' 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 '{0}' with a value of type '{1}'.. | ||
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 '{0}' with a value of type '{1}'.. | ||
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'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 '{0}'.. | ||
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 '{0}' and '{1}'.. | ||
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 '{0}'. More information about the merge and the failure can be found in the merge log: '{1}'. | ||
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 '{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}'. | ||
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: '{0}'.. | ||
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 '{0}'.. | ||
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 '{0}'.. | ||
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 '{0}'.. | ||
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 '{0}'.. | ||
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 '{0}' encountered.. | ||
345 | /// </summary> | ||
346 | internal static string EXP_UnknownPlatformEnum { | ||
347 | get { | ||
348 | return ResourceManager.GetString("EXP_UnknownPlatformEnum", resourceCulture); | ||
349 | } | ||
350 | } | ||
351 | |||
352 | /// <summary> | ||
353 | /// Looks up a localized string similar to Unknown sequence table.. | ||
354 | /// </summary> | ||
355 | internal static string EXP_UnknowSequenceTable { | ||
356 | get { | ||
357 | return ResourceManager.GetString("EXP_UnknowSequenceTable", resourceCulture); | ||
358 | } | ||
359 | } | ||
360 | |||
361 | /// <summary> | ||
362 | /// Looks up a localized string similar to The table {0} is not supported.. | ||
363 | /// </summary> | ||
364 | internal static string EXP_UnsupportedTable { | ||
365 | get { | ||
366 | return ResourceManager.GetString("EXP_UnsupportedTable", resourceCulture); | ||
367 | } | ||
368 | } | ||
369 | |||
370 | /// <summary> | ||
371 | /// Looks up a localized string similar to {0}({1}). | ||
372 | /// </summary> | ||
373 | internal static string Format_FirstLineNumber { | ||
374 | get { | ||
375 | return ResourceManager.GetString("Format_FirstLineNumber", resourceCulture); | ||
376 | } | ||
377 | } | ||
378 | |||
379 | /// <summary> | ||
380 | /// Looks up a localized string similar to {0}. | ||
381 | /// </summary> | ||
382 | internal static string Format_InfoMessage { | ||
383 | get { | ||
384 | return ResourceManager.GetString("Format_InfoMessage", resourceCulture); | ||
385 | } | ||
386 | } | ||
387 | |||
388 | /// <summary> | ||
389 | /// Looks up a localized string similar to {0}: line {1}. | ||
390 | /// </summary> | ||
391 | internal static string Format_LineNumber { | ||
392 | get { | ||
393 | return ResourceManager.GetString("Format_LineNumber", resourceCulture); | ||
394 | } | ||
395 | } | ||
396 | |||
397 | /// <summary> | ||
398 | /// Looks up a localized string similar to {0} : {1} {2}{3:0000} : {4}. | ||
399 | /// </summary> | ||
400 | internal static string Format_NonInfoMessage { | ||
401 | get { | ||
402 | return ResourceManager.GetString("Format_NonInfoMessage", resourceCulture); | ||
403 | } | ||
404 | } | ||
405 | |||
406 | /// <summary> | ||
407 | /// Looks up a localized string similar to Source trace:{0}. | ||
408 | /// </summary> | ||
409 | internal static string INF_SourceTrace { | ||
410 | get { | ||
411 | return ResourceManager.GetString("INF_SourceTrace", resourceCulture); | ||
412 | } | ||
413 | } | ||
414 | |||
415 | /// <summary> | ||
416 | /// Looks up a localized string similar to at {0}{1}. | ||
417 | /// </summary> | ||
418 | internal static string INF_SourceTraceLocation { | ||
419 | get { | ||
420 | return ResourceManager.GetString("INF_SourceTraceLocation", resourceCulture); | ||
421 | } | ||
422 | } | ||
423 | |||
424 | /// <summary> | ||
425 | /// Looks up a localized string similar to error. | ||
426 | /// </summary> | ||
427 | internal static string MessageType_Error { | ||
428 | get { | ||
429 | return ResourceManager.GetString("MessageType_Error", resourceCulture); | ||
430 | } | ||
431 | } | ||
432 | |||
433 | /// <summary> | ||
434 | /// Looks up a localized string similar to warning. | ||
435 | /// </summary> | ||
436 | internal static string MessageType_Warning { | ||
437 | get { | ||
438 | return ResourceManager.GetString("MessageType_Warning", resourceCulture); | ||
439 | } | ||
440 | } | ||
441 | } | ||
442 | } | ||
diff --git a/src/WixToolset.Core/WixStrings.resx b/src/WixToolset.Core/WixStrings.resx new file mode 100644 index 00000000..3fbf639e --- /dev/null +++ b/src/WixToolset.Core/WixStrings.resx | |||
@@ -0,0 +1,249 @@ | |||
1 | <?xml version="1.0" encoding="utf-8"?> | ||
2 | <root> | ||
3 | <!-- | ||
4 | Microsoft ResX Schema | ||
5 | |||
6 | Version 2.0 | ||
7 | |||
8 | The primary goals of this format is to allow a simple XML format | ||
9 | that is mostly human readable. The generation and parsing of the | ||
10 | various data types are done through the TypeConverter classes | ||
11 | associated with the data types. | ||
12 | |||
13 | Example: | ||
14 | |||
15 | ... ado.net/XML headers & schema ... | ||
16 | <resheader name="resmimetype">text/microsoft-resx</resheader> | ||
17 | <resheader name="version">2.0</resheader> | ||
18 | <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader> | ||
19 | <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader> | ||
20 | <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data> | ||
21 | <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data> | ||
22 | <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64"> | ||
23 | <value>[base64 mime encoded serialized .NET Framework object]</value> | ||
24 | </data> | ||
25 | <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64"> | ||
26 | <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value> | ||
27 | <comment>This is a comment</comment> | ||
28 | </data> | ||
29 | |||
30 | There are any number of "resheader" rows that contain simple | ||
31 | name/value pairs. | ||
32 | |||
33 | Each data row contains a name, and value. The row also contains a | ||
34 | type or mimetype. Type corresponds to a .NET class that support | ||
35 | text/value conversion through the TypeConverter architecture. | ||
36 | Classes that don't support this are serialized and stored with the | ||
37 | mimetype set. | ||
38 | |||
39 | The mimetype is used for serialized objects, and tells the | ||
40 | ResXResourceReader how to depersist the object. This is currently not | ||
41 | extensible. For a given mimetype the value must be set accordingly: | ||
42 | |||
43 | Note - application/x-microsoft.net.object.binary.base64 is the format | ||
44 | that the ResXResourceWriter will generate, however the reader can | ||
45 | read any of the formats listed below. | ||
46 | |||
47 | mimetype: application/x-microsoft.net.object.binary.base64 | ||
48 | value : The object must be serialized with | ||
49 | : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter | ||
50 | : and then encoded with base64 encoding. | ||
51 | |||
52 | mimetype: application/x-microsoft.net.object.soap.base64 | ||
53 | value : The object must be serialized with | ||
54 | : System.Runtime.Serialization.Formatters.Soap.SoapFormatter | ||
55 | : and then encoded with base64 encoding. | ||
56 | |||
57 | mimetype: application/x-microsoft.net.object.bytearray.base64 | ||
58 | value : The object must be serialized into a byte array | ||
59 | : using a System.ComponentModel.TypeConverter | ||
60 | : and then encoded with base64 encoding. | ||
61 | --> | ||
62 | <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata"> | ||
63 | <xsd:import namespace="http://www.w3.org/XML/1998/namespace" /> | ||
64 | <xsd:element name="root" msdata:IsDataSet="true"> | ||
65 | <xsd:complexType> | ||
66 | <xsd:choice maxOccurs="unbounded"> | ||
67 | <xsd:element name="metadata"> | ||
68 | <xsd:complexType> | ||
69 | <xsd:sequence> | ||
70 | <xsd:element name="value" type="xsd:string" minOccurs="0" /> | ||
71 | </xsd:sequence> | ||
72 | <xsd:attribute name="name" use="required" type="xsd:string" /> | ||
73 | <xsd:attribute name="type" type="xsd:string" /> | ||
74 | <xsd:attribute name="mimetype" type="xsd:string" /> | ||
75 | <xsd:attribute ref="xml:space" /> | ||
76 | </xsd:complexType> | ||
77 | </xsd:element> | ||
78 | <xsd:element name="assembly"> | ||
79 | <xsd:complexType> | ||
80 | <xsd:attribute name="alias" type="xsd:string" /> | ||
81 | <xsd:attribute name="name" type="xsd:string" /> | ||
82 | </xsd:complexType> | ||
83 | </xsd:element> | ||
84 | <xsd:element name="data"> | ||
85 | <xsd:complexType> | ||
86 | <xsd:sequence> | ||
87 | <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" /> | ||
88 | <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" /> | ||
89 | </xsd:sequence> | ||
90 | <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" /> | ||
91 | <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" /> | ||
92 | <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" /> | ||
93 | <xsd:attribute ref="xml:space" /> | ||
94 | </xsd:complexType> | ||
95 | </xsd:element> | ||
96 | <xsd:element name="resheader"> | ||
97 | <xsd:complexType> | ||
98 | <xsd:sequence> | ||
99 | <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" /> | ||
100 | </xsd:sequence> | ||
101 | <xsd:attribute name="name" type="xsd:string" use="required" /> | ||
102 | </xsd:complexType> | ||
103 | </xsd:element> | ||
104 | </xsd:choice> | ||
105 | </xsd:complexType> | ||
106 | </xsd:element> | ||
107 | </xsd:schema> | ||
108 | <resheader name="resmimetype"> | ||
109 | <value>text/microsoft-resx</value> | ||
110 | </resheader> | ||
111 | <resheader name="version"> | ||
112 | <value>2.0</value> | ||
113 | </resheader> | ||
114 | <resheader name="reader"> | ||
115 | <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> | ||
116 | </resheader> | ||
117 | <resheader name="writer"> | ||
118 | <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> | ||
119 | </resheader> | ||
120 | <data name="EXP_CannotIndexIntoFileRowCollection" xml:space="preserve"> | ||
121 | <value>Cannot index into a FileRowCollection that allows duplicate FileIds</value> | ||
122 | </data> | ||
123 | <data name="EXP_CannotModularizeIllegalID" xml:space="preserve"> | ||
124 | <value>The value '{0}' is not a legal identifier and therefore cannot be modularized.</value> | ||
125 | </data> | ||
126 | <data name="EXP_CannotSetColumnWithValueGreaterThanMaxValue" xml:space="preserve"> | ||
127 | <value>Cannot set column '{0}' with value {1} because it is greater than the maximum allowed value for this column, {2}.</value> | ||
128 | </data> | ||
129 | <data name="EXP_CannotSetColumnWithValueLessThanMinValue" xml:space="preserve"> | ||
130 | <value>Cannot set column '{0}' with value {1} because it is less than the minimum allowed value for this column, {2}.</value> | ||
131 | </data> | ||
132 | <data name="EXP_CannotSetMergeTableFileCompressionColumnToInvalidValue" xml:space="preserve"> | ||
133 | <value>A Merge table FileCompression column cannot be set to the invalid value '{0}'.</value> | ||
134 | </data> | ||
135 | <data name="EXP_CannotSetNullOnRequiredField" xml:space="preserve"> | ||
136 | <value>Cannot set column '{0}' with a null value because this is a required field.</value> | ||
137 | </data> | ||
138 | <data name="EXP_CannotSetNumberColumnWithValueOfType" xml:space="preserve"> | ||
139 | <value>Cannot set number column '{0}' with a value of type '{1}'.</value> | ||
140 | </data> | ||
141 | <data name="EXP_CannotSetStringColumnWithValueOfType" xml:space="preserve"> | ||
142 | <value>Cannot set string column '{0}' with a value of type '{1}'.</value> | ||
143 | </data> | ||
144 | <data name="EXP_CouldnotDetermineProductCodeFromTransformSummaryInfo" xml:space="preserve"> | ||
145 | <value>Could not determine ProductCode from transform summary information</value> | ||
146 | </data> | ||
147 | <data name="EXP_CouldnotFileUniqueIDForResourceName" xml:space="preserve"> | ||
148 | <value>Could not find a unique identifier for the given resource name.</value> | ||
149 | </data> | ||
150 | <data name="EXP_DidnotFindDuplicateSymbol" xml:space="preserve"> | ||
151 | <value>Didn't find duplicated symbol.</value> | ||
152 | </data> | ||
153 | <data name="EXP_ExpectedComplexReferenceType" xml:space="preserve"> | ||
154 | <value>Expected ComplexReference type.</value> | ||
155 | </data> | ||
156 | <data name="EXP_FoundActionRowWinNonExistentAction" xml:space="preserve"> | ||
157 | <value>Found an ActionRow with a non-existent {0} action: {1}.</value> | ||
158 | </data> | ||
159 | <data name="EXP_FoundActionRowWithNoSequenceBeforeOrAfterColumnSet" xml:space="preserve"> | ||
160 | <value>Found an ActionRow with no Sequence, Before, or After column set.</value> | ||
161 | </data> | ||
162 | <data name="EXP_IllegalArgumentsPassed" xml:space="preserve"> | ||
163 | <value>Illegal arguments passed.</value> | ||
164 | </data> | ||
165 | <data name="EXP_InvalidTableNamePassed" xml:space="preserve"> | ||
166 | <value>Invalid table name passed into GenerateIdentifier.</value> | ||
167 | </data> | ||
168 | <data name="EXP_MergeTableFileCompressionColumnContainsInvalidValue" xml:space="preserve"> | ||
169 | <value>A Merge table FileCompression column contains an invalid value '{0}'.</value> | ||
170 | </data> | ||
171 | <data name="EXP_MultipleHarvesterExtensionsSpecified" xml:space="preserve"> | ||
172 | <value>Multiple harvester extensions specified.</value> | ||
173 | </data> | ||
174 | <data name="EXP_OtherObjectIsNotFileRow" xml:space="preserve"> | ||
175 | <value>The other object is not a FileRow.</value> | ||
176 | </data> | ||
177 | <data name="EXP_TransformAuthoredIntoMultipleMedia" xml:space="preserve"> | ||
178 | <value>Transform authored into multiple Media '{0}' and '{1}'.</value> | ||
179 | </data> | ||
180 | <data name="EXP_UnexpectedComplexReferenceChildType" xml:space="preserve"> | ||
181 | <value>Unexpected complex reference child type: {0}</value> | ||
182 | </data> | ||
183 | <data name="EXP_UnexpectedEntrySectionType" xml:space="preserve"> | ||
184 | <value>Unexpected entry section type: {0}</value> | ||
185 | </data> | ||
186 | <data name="EXP_UnexpectedMergerErrorInSourceFile" xml:space="preserve"> | ||
187 | <value>Encountered an unexpected error while merging '{0}'. More information about the merge and the failure can be found in the merge log: '{1}'</value> | ||
188 | </data> | ||
189 | <data name="EXP_UnexpectedMergerErrorWithType" xml:space="preserve"> | ||
190 | <value>Encountered an unexpected merge error of type '{0}' for which there is currently no error message to display. More information about the merge and the failure can be found in the merge log: '{1}'</value> | ||
191 | </data> | ||
192 | <data name="EXP_UnknowControlAttribute" xml:space="preserve"> | ||
193 | <value>Unknown control attribute: '{0}'.</value> | ||
194 | </data> | ||
195 | <data name="EXP_UnknownColumnType" xml:space="preserve"> | ||
196 | <value>Unknown column type: {0}</value> | ||
197 | </data> | ||
198 | <data name="EXP_UnknownCompressionLevelType" xml:space="preserve"> | ||
199 | <value>Unknown compression level type: {0}</value> | ||
200 | </data> | ||
201 | <data name="EXP_UnknownCustomColumnCategory" xml:space="preserve"> | ||
202 | <value>Unknown custom column category '{0}'.</value> | ||
203 | </data> | ||
204 | <data name="EXP_UnknownCustomColumnModularizationType" xml:space="preserve"> | ||
205 | <value>Unknown custom column modularization type '{0}'.</value> | ||
206 | </data> | ||
207 | <data name="EXP_UnknownCustomColumnType" xml:space="preserve"> | ||
208 | <value>Unknown custom column type '{0}'.</value> | ||
209 | </data> | ||
210 | <data name="EXP_UnknownOutputType" xml:space="preserve"> | ||
211 | <value>Unknown output type.</value> | ||
212 | </data> | ||
213 | <data name="EXP_UnknownPermissionAttribute" xml:space="preserve"> | ||
214 | <value>Unknown permission attribute '{0}'.</value> | ||
215 | </data> | ||
216 | <data name="EXP_UnknowSequenceTable" xml:space="preserve"> | ||
217 | <value>Unknown sequence table.</value> | ||
218 | </data> | ||
219 | <data name="EXP_UnknownPlatformEnum" xml:space="preserve"> | ||
220 | <value>Unknown platform enumeration '{0}' encountered.</value> | ||
221 | </data> | ||
222 | <data name="EXP_UnsupportedTable" xml:space="preserve"> | ||
223 | <value>The table {0} is not supported.</value> | ||
224 | </data> | ||
225 | <data name="Format_FirstLineNumber" xml:space="preserve"> | ||
226 | <value>{0}({1})</value> | ||
227 | </data> | ||
228 | <data name="Format_InfoMessage" xml:space="preserve"> | ||
229 | <value>{0}</value> | ||
230 | </data> | ||
231 | <data name="Format_LineNumber" xml:space="preserve"> | ||
232 | <value>{0}: line {1}</value> | ||
233 | </data> | ||
234 | <data name="Format_NonInfoMessage" xml:space="preserve"> | ||
235 | <value>{0} : {1} {2}{3:0000} : {4}</value> | ||
236 | </data> | ||
237 | <data name="INF_SourceTrace" xml:space="preserve"> | ||
238 | <value>Source trace:{0}</value> | ||
239 | </data> | ||
240 | <data name="INF_SourceTraceLocation" xml:space="preserve"> | ||
241 | <value>at {0}{1}</value> | ||
242 | </data> | ||
243 | <data name="MessageType_Error" xml:space="preserve"> | ||
244 | <value>error</value> | ||
245 | </data> | ||
246 | <data name="MessageType_Warning" xml:space="preserve"> | ||
247 | <value>warning</value> | ||
248 | </data> | ||
249 | </root> \ No newline at end of file | ||
diff --git a/src/WixToolset.Core/WixToolset.Core.csproj b/src/WixToolset.Core/WixToolset.Core.csproj new file mode 100644 index 00000000..3db31e4d --- /dev/null +++ b/src/WixToolset.Core/WixToolset.Core.csproj | |||
@@ -0,0 +1,46 @@ | |||
1 | <?xml version="1.0" encoding="utf-8"?> | ||
2 | <!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. --> | ||
3 | |||
4 | <Project Sdk="Microsoft.NET.Sdk"> | ||
5 | <PropertyGroup> | ||
6 | <TargetFramework>netstandard2.0</TargetFramework> | ||
7 | <Description></Description> | ||
8 | <Title>WiX Toolset Core</Title> | ||
9 | </PropertyGroup> | ||
10 | |||
11 | <PropertyGroup> | ||
12 | <NoWarn>NU1701</NoWarn> | ||
13 | </PropertyGroup> | ||
14 | |||
15 | <ItemGroup> | ||
16 | <MsgGenSource Include="Data\messages.xml"> | ||
17 | <ResourcesLogicalName>WixToolset.Core.Data.messages.resources</ResourcesLogicalName> | ||
18 | </MsgGenSource> | ||
19 | </ItemGroup> | ||
20 | |||
21 | <ItemGroup> | ||
22 | <ProjectReference Include="$(WixToolsetRootFolder)\Data\src\WixToolset.Data\WixToolset.Data.csproj" Condition=" '$(Configuration)' == 'Debug' And Exists('$(WixToolsetRootFolder)\Data\src\WixToolset.Data\WixToolset.Data.csproj') " /> | ||
23 | <PackageReference Include="WixToolset.Data" Version="4.0.*" Condition=" '$(Configuration)' == 'Release' Or !Exists('$(WixToolsetRootFolder)\Data\src\WixToolset.Data\WixToolset.Data.csproj') " /> | ||
24 | |||
25 | <ProjectReference Include="$(WixToolsetRootFolder)\Extensibility\src\WixToolset.Extensibility\WixToolset.Extensibility.csproj" Condition=" '$(Configuration)' == 'Debug' And Exists('$(WixToolsetRootFolder)\Extensibility\src\WixToolset.Extensibility\WixToolset.Extensibility.csproj') " /> | ||
26 | <PackageReference Include="WixToolset.Extensibility" Version="4.0.*" Condition=" '$(Configuration)' == 'Release' Or !Exists('$(WixToolsetRootFolder)\Extensibility\src\WixToolset.Extensibility\WixToolset.Extensibility.csproj') " /> | ||
27 | |||
28 | <ProjectReference Include="$(WixToolsetRootFolder)\Core.Native\src\WixToolset.Core.Native\WixToolset.Core.Native.csproj" Condition=" '$(Configuration)' == 'Debug' And Exists('$(WixToolsetRootFolder)\Core.Native\src\WixToolset.Core.Native\WixToolset.Core.Native.csproj') " /> | ||
29 | <PackageReference Include="WixToolset.Core.Native" Version="4.0.*" Condition=" '$(Configuration)' == 'Release' Or !Exists('$(WixToolsetRootFolder)\Core.Native\src\WixToolset.Core.Native\WixToolset.Core.Native.csproj') " /> | ||
30 | </ItemGroup> | ||
31 | |||
32 | <ItemGroup> | ||
33 | <PackageReference Include="WixToolset.Dtf.Resources" Version="4.0.*" /> | ||
34 | <PackageReference Include="WixToolset.Dtf.WindowsInstaller" Version="4.0.*" /> | ||
35 | </ItemGroup> | ||
36 | |||
37 | <ItemGroup> | ||
38 | <PackageReference Include="System.IO.FileSystem.AccessControl" Version="4.4.0" /> | ||
39 | </ItemGroup> | ||
40 | |||
41 | <ItemGroup> | ||
42 | <PackageReference Include="Nerdbank.GitVersioning" Version="2.0.41" PrivateAssets="all" /> | ||
43 | <PackageReference Include="WixBuildTools.MsgGen" Version="4.0.*" PrivateAssets="all" /> | ||
44 | <PackageReference Include="WixBuildTools.XsdGen" Version="4.0.*" PrivateAssets="all" /> | ||
45 | </ItemGroup> | ||
46 | </Project> | ||
diff --git a/src/WixToolset.Core/WixVariableResolver.cs b/src/WixToolset.Core/WixVariableResolver.cs new file mode 100644 index 00000000..921ff1e3 --- /dev/null +++ b/src/WixToolset.Core/WixVariableResolver.cs | |||
@@ -0,0 +1,337 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
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 | } | ||
diff --git a/src/candle/App.ico b/src/candle/App.ico new file mode 100644 index 00000000..3a5525fd --- /dev/null +++ b/src/candle/App.ico | |||
Binary files differ | |||
diff --git a/src/candle/AssemblyInfo.cs b/src/candle/AssemblyInfo.cs new file mode 100644 index 00000000..7df55940 --- /dev/null +++ b/src/candle/AssemblyInfo.cs | |||
@@ -0,0 +1,11 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
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)] | ||
diff --git a/src/candle/CandleCommandLine.cs b/src/candle/CandleCommandLine.cs new file mode 100644 index 00000000..81fdf7b2 --- /dev/null +++ b/src/candle/CandleCommandLine.cs | |||
@@ -0,0 +1,343 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
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 | } | ||
diff --git a/src/candle/CandleStrings.Designer.cs b/src/candle/CandleStrings.Designer.cs new file mode 100644 index 00000000..aaf7254e --- /dev/null +++ b/src/candle/CandleStrings.Designer.cs | |||
@@ -0,0 +1,81 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
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 '\' 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<name>[=<value>] define a parameter for the preprocessor | ||
70 | /// -ext <extension> extension assembly or "class, assembly" | ||
71 | /// -I<dir> add to include search path | ||
72 | /// -nologo skip printing candle logo information | ||
73 | /// -o[ut] s [rest of string was truncated]";. | ||
74 | /// </summary> | ||
75 | internal static string HelpMessage { | ||
76 | get { | ||
77 | return ResourceManager.GetString("HelpMessage", resourceCulture); | ||
78 | } | ||
79 | } | ||
80 | } | ||
81 | } | ||
diff --git a/src/candle/CandleStrings.resx b/src/candle/CandleStrings.resx new file mode 100644 index 00000000..d77788ca --- /dev/null +++ b/src/candle/CandleStrings.resx | |||
@@ -0,0 +1,142 @@ | |||
1 | <?xml version="1.0" encoding="utf-8"?> | ||
2 | <root> | ||
3 | <!-- | ||
4 | Microsoft ResX Schema | ||
5 | |||
6 | Version 2.0 | ||
7 | |||
8 | The primary goals of this format is to allow a simple XML format | ||
9 | that is mostly human readable. The generation and parsing of the | ||
10 | various data types are done through the TypeConverter classes | ||
11 | associated with the data types. | ||
12 | |||
13 | Example: | ||
14 | |||
15 | ... ado.net/XML headers & schema ... | ||
16 | <resheader name="resmimetype">text/microsoft-resx</resheader> | ||
17 | <resheader name="version">2.0</resheader> | ||
18 | <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader> | ||
19 | <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader> | ||
20 | <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data> | ||
21 | <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data> | ||
22 | <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64"> | ||
23 | <value>[base64 mime encoded serialized .NET Framework object]</value> | ||
24 | </data> | ||
25 | <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64"> | ||
26 | <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value> | ||
27 | <comment>This is a comment</comment> | ||
28 | </data> | ||
29 | |||
30 | There are any number of "resheader" rows that contain simple | ||
31 | name/value pairs. | ||
32 | |||
33 | Each data row contains a name, and value. The row also contains a | ||
34 | type or mimetype. Type corresponds to a .NET class that support | ||
35 | text/value conversion through the TypeConverter architecture. | ||
36 | Classes that don't support this are serialized and stored with the | ||
37 | mimetype set. | ||
38 | |||
39 | The mimetype is used for serialized objects, and tells the | ||
40 | ResXResourceReader how to depersist the object. This is currently not | ||
41 | extensible. For a given mimetype the value must be set accordingly: | ||
42 | |||
43 | Note - application/x-microsoft.net.object.binary.base64 is the format | ||
44 | that the ResXResourceWriter will generate, however the reader can | ||
45 | read any of the formats listed below. | ||
46 | |||
47 | mimetype: application/x-microsoft.net.object.binary.base64 | ||
48 | value : The object must be serialized with | ||
49 | : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter | ||
50 | : and then encoded with base64 encoding. | ||
51 | |||
52 | mimetype: application/x-microsoft.net.object.soap.base64 | ||
53 | value : The object must be serialized with | ||
54 | : System.Runtime.Serialization.Formatters.Soap.SoapFormatter | ||
55 | : and then encoded with base64 encoding. | ||
56 | |||
57 | mimetype: application/x-microsoft.net.object.bytearray.base64 | ||
58 | value : The object must be serialized into a byte array | ||
59 | : using a System.ComponentModel.TypeConverter | ||
60 | : and then encoded with base64 encoding. | ||
61 | --> | ||
62 | <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata"> | ||
63 | <xsd:import namespace="http://www.w3.org/XML/1998/namespace" /> | ||
64 | <xsd:element name="root" msdata:IsDataSet="true"> | ||
65 | <xsd:complexType> | ||
66 | <xsd:choice maxOccurs="unbounded"> | ||
67 | <xsd:element name="metadata"> | ||
68 | <xsd:complexType> | ||
69 | <xsd:sequence> | ||
70 | <xsd:element name="value" type="xsd:string" minOccurs="0" /> | ||
71 | </xsd:sequence> | ||
72 | <xsd:attribute name="name" use="required" type="xsd:string" /> | ||
73 | <xsd:attribute name="type" type="xsd:string" /> | ||
74 | <xsd:attribute name="mimetype" type="xsd:string" /> | ||
75 | <xsd:attribute ref="xml:space" /> | ||
76 | </xsd:complexType> | ||
77 | </xsd:element> | ||
78 | <xsd:element name="assembly"> | ||
79 | <xsd:complexType> | ||
80 | <xsd:attribute name="alias" type="xsd:string" /> | ||
81 | <xsd:attribute name="name" type="xsd:string" /> | ||
82 | </xsd:complexType> | ||
83 | </xsd:element> | ||
84 | <xsd:element name="data"> | ||
85 | <xsd:complexType> | ||
86 | <xsd:sequence> | ||
87 | <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" /> | ||
88 | <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" /> | ||
89 | </xsd:sequence> | ||
90 | <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" /> | ||
91 | <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" /> | ||
92 | <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" /> | ||
93 | <xsd:attribute ref="xml:space" /> | ||
94 | </xsd:complexType> | ||
95 | </xsd:element> | ||
96 | <xsd:element name="resheader"> | ||
97 | <xsd:complexType> | ||
98 | <xsd:sequence> | ||
99 | <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" /> | ||
100 | </xsd:sequence> | ||
101 | <xsd:attribute name="name" type="xsd:string" use="required" /> | ||
102 | </xsd:complexType> | ||
103 | </xsd:element> | ||
104 | </xsd:choice> | ||
105 | </xsd:complexType> | ||
106 | </xsd:element> | ||
107 | </xsd:schema> | ||
108 | <resheader name="resmimetype"> | ||
109 | <value>text/microsoft-resx</value> | ||
110 | </resheader> | ||
111 | <resheader name="version"> | ||
112 | <value>2.0</value> | ||
113 | </resheader> | ||
114 | <resheader name="reader"> | ||
115 | <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> | ||
116 | </resheader> | ||
117 | <resheader name="writer"> | ||
118 | <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> | ||
119 | </resheader> | ||
120 | <data name="CannotSpecifyMoreThanOneSourceFileForSingleTargetFile" xml:space="preserve"> | ||
121 | <value>Cannot specify more than one source file with single output file. Either specify an output directory for the -out argument by ending the argument with a '\' or remove the -out argument to have the source files compiled to the current directory.</value> | ||
122 | </data> | ||
123 | <data name="HelpMessage" xml:space="preserve"> | ||
124 | <value> usage: candle.exe [-?] [-nologo] [-out outputFile] sourceFile [sourceFile ...] [@responseFile] | ||
125 | |||
126 | -arch set architecture defaults for package, components, etc. | ||
127 | values: x86, x64, ia64 or arm (default: x86) | ||
128 | -d<name>[=<value>] define a parameter for the preprocessor | ||
129 | -ext <extension> extension assembly or "class, assembly" | ||
130 | -I<dir> 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<file> preprocess to a file (or stdout if no file supplied) | ||
134 | -pedantic show pedantic messages | ||
135 | -sw[N] suppress all warnings or a specific message ID | ||
136 | (example: -sw1009 -sw1103) | ||
137 | -v verbose output | ||
138 | -wx[N] treat all warnings or a specific message ID as an error | ||
139 | (example: -wx1009 -wx1103) | ||
140 | -? | -help this help information</value> | ||
141 | </data> | ||
142 | </root> \ No newline at end of file | ||
diff --git a/src/candle/CompileFile.cs b/src/candle/CompileFile.cs new file mode 100644 index 00000000..682acc21 --- /dev/null +++ b/src/candle/CompileFile.cs | |||
@@ -0,0 +1,20 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
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 | } | ||
diff --git a/src/candle/app.config b/src/candle/app.config new file mode 100644 index 00000000..71c529fb --- /dev/null +++ b/src/candle/app.config | |||
@@ -0,0 +1,9 @@ | |||
1 | <?xml version="1.0" encoding="utf-8" ?> | ||
2 | <!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. --> | ||
3 | |||
4 | |||
5 | <configuration> | ||
6 | <runtime> | ||
7 | <loadFromRemoteSources enabled="true"/> | ||
8 | </runtime> | ||
9 | </configuration> | ||
diff --git a/src/candle/candle.cs b/src/candle/candle.cs new file mode 100644 index 00000000..f5c65cb1 --- /dev/null +++ b/src/candle/candle.cs | |||
@@ -0,0 +1,200 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
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 | } | ||
diff --git a/src/candle/candle.csproj b/src/candle/candle.csproj new file mode 100644 index 00000000..cea0cf62 --- /dev/null +++ b/src/candle/candle.csproj | |||
@@ -0,0 +1,62 @@ | |||
1 | <?xml version="1.0" encoding="utf-8"?> | ||
2 | <!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. --> | ||
3 | |||
4 | |||
5 | <Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="4.0"> | ||
6 | <PropertyGroup> | ||
7 | <ProjectGuid>{956401A5-3C04-4786-9611-B2AEC6207686}</ProjectGuid> | ||
8 | <AssemblyName>candle</AssemblyName> | ||
9 | <OutputType>Exe</OutputType> | ||
10 | <RootNamespace>WixToolset.Tools</RootNamespace> | ||
11 | <PlatformTarget>x86</PlatformTarget> | ||
12 | </PropertyGroup> | ||
13 | <ItemGroup> | ||
14 | <Compile Include="AssemblyInfo.cs" /> | ||
15 | <Compile Include="candle.cs" /> | ||
16 | <Compile Include="CandleCommandLine.cs" /> | ||
17 | <Compile Include="CompileFile.cs" /> | ||
18 | <Compile Include="CandleStrings.Designer.cs"> | ||
19 | <AutoGen>True</AutoGen> | ||
20 | <DesignTime>True</DesignTime> | ||
21 | <DependentUpon>CandleStrings.resx</DependentUpon> | ||
22 | </Compile> | ||
23 | </ItemGroup> | ||
24 | <ItemGroup> | ||
25 | <EmbeddedNativeResource Include="candle.rc" /> | ||
26 | </ItemGroup> | ||
27 | <ItemGroup> | ||
28 | <None Include="app.config" /> | ||
29 | </ItemGroup> | ||
30 | <ItemGroup> | ||
31 | <EmbeddedResource Include="CandleStrings.resx"> | ||
32 | <SubType>Designer</SubType> | ||
33 | <Generator>ResXFileCodeGenerator</Generator> | ||
34 | <LastGenOutput>CandleStrings.Designer.cs</LastGenOutput> | ||
35 | </EmbeddedResource> | ||
36 | </ItemGroup> | ||
37 | <ItemGroup> | ||
38 | <Service Include="{B4F97281-0DBD-4835-9ED8-7DFB966E87FF}" /> | ||
39 | </ItemGroup> | ||
40 | <ItemGroup> | ||
41 | <Reference Include="System" /> | ||
42 | <Reference Include="System.Xml" /> | ||
43 | <ProjectReference Include="..\..\libs\WixToolset.Data\WixToolset.Data.csproj"> | ||
44 | <Project>{6a98499e-40ec-4335-9c31-96a2511d47c6}</Project> | ||
45 | <Name>WixToolset.Data</Name> | ||
46 | </ProjectReference> | ||
47 | <ProjectReference Include="..\..\libs\WixToolset.Extensibility\WixToolset.Extensibility.csproj"> | ||
48 | <Project>{eee88c2a-45a0-4e48-a40a-431a4ba458d8}</Project> | ||
49 | <Name>WixToolset.Extensibility</Name> | ||
50 | </ProjectReference> | ||
51 | <ProjectReference Include="..\wconsole\wconsole.csproj"> | ||
52 | <Project>{4B2BD779-59F7-4BF1-871C-A75952BCA749}</Project> | ||
53 | <Name>wconsole</Name> | ||
54 | </ProjectReference> | ||
55 | <ProjectReference Include="..\wix\Wix.csproj"> | ||
56 | <Project>{9E03A94C-C70E-45C6-A269-E737BBD8B319}</Project> | ||
57 | <Name>Wix</Name> | ||
58 | </ProjectReference> | ||
59 | <Reference Include="System.Xml.Linq" /> | ||
60 | </ItemGroup> | ||
61 | <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildProjectDirectory), wix.proj))\tools\WixBuild.targets" /> | ||
62 | </Project> | ||
diff --git a/src/candle/candle.exe.manifest b/src/candle/candle.exe.manifest new file mode 100644 index 00000000..b05ab18b --- /dev/null +++ b/src/candle/candle.exe.manifest | |||
@@ -0,0 +1,9 @@ | |||
1 | <?xml version="1.0" encoding="UTF-8" standalone="yes"?> | ||
2 | <!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. --> | ||
3 | |||
4 | |||
5 | <assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0"> | ||
6 | <assemblyIdentity name="WixToolset.Tools.Candle" version="2.0.0.0" processorArchitecture="x86" type="win32"/> | ||
7 | <description>WiX Toolset Compiler</description> | ||
8 | <trustInfo xmlns="urn:schemas-microsoft-com:asm.v3"><security><requestedPrivileges><requestedExecutionLevel level="asInvoker" uiAccess="false"/></requestedPrivileges></security></trustInfo> | ||
9 | </assembly> | ||
diff --git a/src/candle/candle.rc b/src/candle/candle.rc new file mode 100644 index 00000000..47864811 --- /dev/null +++ b/src/candle/candle.rc | |||
@@ -0,0 +1,10 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
3 | #define VER_APP | ||
4 | #define VER_ORIGINAL_FILENAME "candle.exe" | ||
5 | #define VER_INTERNAL_NAME "candle" | ||
6 | #define VER_FILE_DESCRIPTION "WiX Toolset Compiler" | ||
7 | #include "wix.rc" | ||
8 | |||
9 | #define MANIFEST_RESOURCE_ID 1 | ||
10 | MANIFEST_RESOURCE_ID RT_MANIFEST "candle.exe.manifest" | ||
diff --git a/src/test/WixToolsetTest.BuildTasks/WixToolsetTest.BuildTasks.csproj b/src/test/WixToolsetTest.BuildTasks/WixToolsetTest.BuildTasks.csproj new file mode 100644 index 00000000..46bbf4cf --- /dev/null +++ b/src/test/WixToolsetTest.BuildTasks/WixToolsetTest.BuildTasks.csproj | |||
@@ -0,0 +1,21 @@ | |||
1 | <?xml version="1.0" encoding="utf-8"?> | ||
2 | <!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. --> | ||
3 | |||
4 | <Project Sdk="Microsoft.NET.Sdk"> | ||
5 | <PropertyGroup> | ||
6 | <TargetFramework>netcoreapp2.0</TargetFramework> | ||
7 | <Description></Description> | ||
8 | <Title>WiX Toolset Tests for MSBuild Tasks</Title> | ||
9 | </PropertyGroup> | ||
10 | |||
11 | <PropertyGroup> | ||
12 | </PropertyGroup> | ||
13 | |||
14 | <ItemGroup> | ||
15 | <!-- <ProjectReference Include="..\..\WixToolset.BuildTasks\WixToolset.BuildTasks.csproj" /> --> | ||
16 | </ItemGroup> | ||
17 | |||
18 | <ItemGroup> | ||
19 | <PackageReference Include="Nerdbank.GitVersioning" Version="2.0.41" PrivateAssets="all" /> | ||
20 | </ItemGroup> | ||
21 | </Project> | ||
diff --git a/src/test/WixToolsetTest.BuildTasks/data/SimpleMsiPackage/MsiPackage/MsiPackage.wixproj b/src/test/WixToolsetTest.BuildTasks/data/SimpleMsiPackage/MsiPackage/MsiPackage.wixproj new file mode 100644 index 00000000..cd956964 --- /dev/null +++ b/src/test/WixToolsetTest.BuildTasks/data/SimpleMsiPackage/MsiPackage/MsiPackage.wixproj | |||
@@ -0,0 +1,51 @@ | |||
1 | <?xml version="1.0" encoding="utf-8"?> | ||
2 | <Project ToolsVersion="15.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> | ||
3 | <PropertyGroup> | ||
4 | <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> | ||
5 | <Platform Condition=" '$(Platform)' == '' ">x86</Platform> | ||
6 | <ProductVersion>0.9</ProductVersion> | ||
7 | <ProjectGuid>7fb77005-c6e0-454f-8c2d-0a4a79c918ba</ProjectGuid> | ||
8 | <OutputName>MsiPackage</OutputName> | ||
9 | <OutputType>Package</OutputType> | ||
10 | <Name>MsiPackage</Name> | ||
11 | <RootNamespace>MsiPackage</RootNamespace> | ||
12 | </PropertyGroup> | ||
13 | |||
14 | <PropertyGroup> | ||
15 | <WixTargetsPath>..\..\..\..\..\..\build\Debug\net462\wix.targets</WixTargetsPath> | ||
16 | <WixTargetsPath>..\..\..\..\..\..\build\Debug\netstandard2.0\wix.targets</WixTargetsPath> | ||
17 | <WixTargetsPath>..\..\..\..\..\..\build\publish\wix.targets</WixTargetsPath> | ||
18 | <WixTargetsPath>..\..\..\..\..\..\build\Debug\net462\wix.targets</WixTargetsPath> | ||
19 | </PropertyGroup> | ||
20 | |||
21 | <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|x86' "> | ||
22 | <PlatformName>$(Platform)</PlatformName> | ||
23 | <OutputPath>bin\$(Platform)\$(Configuration)\</OutputPath> | ||
24 | <DefineConstants>Debug</DefineConstants> | ||
25 | </PropertyGroup> | ||
26 | <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|x86' "> | ||
27 | <PlatformName>$(Platform)</PlatformName> | ||
28 | <OutputPath>bin\$(Platform)\$(Configuration)\</OutputPath> | ||
29 | </PropertyGroup> | ||
30 | <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|x64' "> | ||
31 | <PlatformName>$(Platform)</PlatformName> | ||
32 | <OutputPath>bin\$(Platform)\$(Configuration)\</OutputPath> | ||
33 | <DefineConstants>Debug</DefineConstants> | ||
34 | </PropertyGroup> | ||
35 | <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|x64' "> | ||
36 | <PlatformName>$(Platform)</PlatformName> | ||
37 | <OutputPath>bin\$(Platform)\$(Configuration)\</OutputPath> | ||
38 | </PropertyGroup> | ||
39 | <ItemGroup> | ||
40 | <Compile Include="Package.wxs" /> | ||
41 | <Compile Include="PackageComponents.wxs" /> | ||
42 | </ItemGroup> | ||
43 | <ItemGroup> | ||
44 | <EmbeddedResource Include="Package.en-us.wxl" /> | ||
45 | </ItemGroup> | ||
46 | <Import Project="$(WixTargetsPath)" Condition=" '$(WixTargetsPath)' != '' " /> | ||
47 | <Import Project="$(MSBuildExtensionsPath32)\Microsoft\WiX\v3.x\wix.targets" Condition=" '$(WixTargetsPath)' == '' AND Exists('$(MSBuildExtensionsPath32)\Microsoft\WiX\v3.x\wix.targets') " /> | ||
48 | <Target Name="EnsureWixToolsetInstalled" Condition=" '$(WixTargetsImported)' != 'true' "> | ||
49 | <Error Text="FG-WiX or WiX Toolset build tools (v3.11 or later) must be installed to build this project. To download FG-WiX, go to https://www.firegiant.com/downloads/. To download the WiX Toolset, go to http://wixtoolset.org/releases/." /> | ||
50 | </Target> | ||
51 | </Project> \ No newline at end of file | ||
diff --git a/src/test/WixToolsetTest.BuildTasks/data/SimpleMsiPackage/MsiPackage/Package.en-us.wxl b/src/test/WixToolsetTest.BuildTasks/data/SimpleMsiPackage/MsiPackage/Package.en-us.wxl new file mode 100644 index 00000000..38c12ac1 --- /dev/null +++ b/src/test/WixToolsetTest.BuildTasks/data/SimpleMsiPackage/MsiPackage/Package.en-us.wxl | |||
@@ -0,0 +1,11 @@ | |||
1 | <?xml version="1.0" encoding="utf-8"?> | ||
2 | |||
3 | <!-- | ||
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> | ||
diff --git a/src/test/WixToolsetTest.BuildTasks/data/SimpleMsiPackage/MsiPackage/Package.wxs b/src/test/WixToolsetTest.BuildTasks/data/SimpleMsiPackage/MsiPackage/Package.wxs new file mode 100644 index 00000000..d5a5a40d --- /dev/null +++ b/src/test/WixToolsetTest.BuildTasks/data/SimpleMsiPackage/MsiPackage/Package.wxs | |||
@@ -0,0 +1,21 @@ | |||
1 | <?xml version="1.0" encoding="utf-8"?> | ||
2 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"> | ||
3 | <Product Id="*" Name="MsiPackage" Language="1033" Version="1.0.0.0" Manufacturer="Example Corporation" UpgradeCode="047730a5-30fe-4a62-a520-da9381b8226a"> | ||
4 | <Package InstallerVersion="200" Compressed="yes" InstallScope="perMachine" /> | ||
5 | |||
6 | <MajorUpgrade DowngradeErrorMessage="!(loc.DowngradeError)" /> | ||
7 | <MediaTemplate /> | ||
8 | |||
9 | <Feature Id="ProductFeature" Title="!(loc.FeatureTitle)"> | ||
10 | <ComponentGroupRef Id="ProductComponents" /> | ||
11 | </Feature> | ||
12 | </Product> | ||
13 | |||
14 | <Fragment> | ||
15 | <Directory Id="TARGETDIR" Name="SourceDir"> | ||
16 | <Directory Id="ProgramFilesFolder"> | ||
17 | <Directory Id="INSTALLFOLDER" Name="MsiPackage" /> | ||
18 | </Directory> | ||
19 | </Directory> | ||
20 | </Fragment> | ||
21 | </Wix> | ||
diff --git a/src/test/WixToolsetTest.BuildTasks/data/SimpleMsiPackage/MsiPackage/PackageComponents.wxs b/src/test/WixToolsetTest.BuildTasks/data/SimpleMsiPackage/MsiPackage/PackageComponents.wxs new file mode 100644 index 00000000..76a12a9a --- /dev/null +++ b/src/test/WixToolsetTest.BuildTasks/data/SimpleMsiPackage/MsiPackage/PackageComponents.wxs | |||
@@ -0,0 +1,10 @@ | |||
1 | <?xml version="1.0" encoding="utf-8"?> | ||
2 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"> | ||
3 | <Fragment> | ||
4 | <ComponentGroup Id="ProductComponents" Directory="INSTALLFOLDER"> | ||
5 | <Component> | ||
6 | <File Source="Package.wxs" /> | ||
7 | </Component> | ||
8 | </ComponentGroup> | ||
9 | </Fragment> | ||
10 | </Wix> | ||
diff --git a/src/test/WixToolsetTest.BuildTasks/data/SimpleMsiPackage/MsiPackage/i.txt b/src/test/WixToolsetTest.BuildTasks/data/SimpleMsiPackage/MsiPackage/i.txt new file mode 100644 index 00000000..53f23c98 --- /dev/null +++ b/src/test/WixToolsetTest.BuildTasks/data/SimpleMsiPackage/MsiPackage/i.txt | |||
Binary files differ | |||
diff --git a/src/test/WixToolsetTest.BuildTasks/data/SimpleMsiPackage/SimpleMsiPackage.sln b/src/test/WixToolsetTest.BuildTasks/data/SimpleMsiPackage/SimpleMsiPackage.sln new file mode 100644 index 00000000..2c88704e --- /dev/null +++ b/src/test/WixToolsetTest.BuildTasks/data/SimpleMsiPackage/SimpleMsiPackage.sln | |||
@@ -0,0 +1,31 @@ | |||
1 |  | ||
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 | ||
diff --git a/src/wix/Program.cs b/src/wix/Program.cs new file mode 100644 index 00000000..3e4fa8f8 --- /dev/null +++ b/src/wix/Program.cs | |||
@@ -0,0 +1,499 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
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 | } | ||
diff --git a/src/wix/X_CommandLine.cs b/src/wix/X_CommandLine.cs new file mode 100644 index 00000000..2c7ceb83 --- /dev/null +++ b/src/wix/X_CommandLine.cs | |||
@@ -0,0 +1,394 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
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 | } | ||
diff --git a/src/wix/wix.csproj b/src/wix/wix.csproj new file mode 100644 index 00000000..88017e3f --- /dev/null +++ b/src/wix/wix.csproj | |||
@@ -0,0 +1,31 @@ | |||
1 | <?xml version="1.0" encoding="utf-8"?> | ||
2 | <!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. --> | ||
3 | |||
4 | <Project Sdk="Microsoft.NET.Sdk"> | ||
5 | <PropertyGroup> | ||
6 | <TargetFramework>netcoreapp2.0</TargetFramework> | ||
7 | <OutputType>Exe</OutputType> | ||
8 | <Description></Description> | ||
9 | <Title>WiX Toolset Compiler</Title> | ||
10 | </PropertyGroup> | ||
11 | |||
12 | <PropertyGroup> | ||
13 | <NoWarn>NU1701</NoWarn> | ||
14 | </PropertyGroup> | ||
15 | |||
16 | <ItemGroup> | ||
17 | <ProjectReference Include="..\WixToolset.Core\WixToolset.Core.csproj" /> | ||
18 | |||
19 | <ProjectReference Include="$(WixToolsetRootFolder)\Data\src\WixToolset.Data\WixToolset.Data.csproj" Condition=" '$(Configuration)' == 'Debug' And Exists('$(WixToolsetRootFolder)\Data\src\WixToolset.Data\WixToolset.Data.csproj') " /> | ||
20 | <PackageReference Include="WixToolset.Data" Version="4.0.*" Condition=" '$(Configuration)' == 'Release' Or !Exists('$(WixToolsetRootFolder)\Data\src\WixToolset.Data\WixToolset.Data.csproj') " /> | ||
21 | |||
22 | <ProjectReference Include="$(WixToolsetRootFolder)\Extensibility\src\WixToolset.Extensibility\WixToolset.Extensibility.csproj" Condition=" '$(Configuration)' == 'Debug' And Exists('$(WixToolsetRootFolder)\Extensibility\src\WixToolset.Extensibility\WixToolset.Extensibility.csproj') " /> | ||
23 | <PackageReference Include="WixToolset.Extensibility" Version="4.0.*" Condition=" '$(Configuration)' == 'Release' Or !Exists('$(WixToolsetRootFolder)\Extensibility\src\WixToolset.Extensibility\WixToolset.Extensibility.csproj') " /> | ||
24 | </ItemGroup> | ||
25 | |||
26 | <ItemGroup> | ||
27 | <PackageReference Include="Nerdbank.GitVersioning" Version="2.0.41" PrivateAssets="all" /> | ||
28 | <PackageReference Include="WixBuildTools.MsgGen" Version="4.0.*" PrivateAssets="all" /> | ||
29 | <PackageReference Include="WixBuildTools.XsdGen" Version="4.0.*" PrivateAssets="all" /> | ||
30 | </ItemGroup> | ||
31 | </Project> | ||