main
cs 384 lines 17.5 KB
Raw
1 // Copyright (c) .NET Foundation and contributors. All 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.Xml.Linq;
7 using WixToolset.Data;
8 using WixToolset.Data.Symbols;
9
10 /// <summary>
11 /// Compiler of the WiX toolset.
12 /// </summary>
13 internal partial class Compiler : ICompiler
14 {
15 // The root registry key for the dependency extension. We write to Software\Classes explicitly
16 // based on the current security context instead of HKCR. See
17 // https://learn.microsoft.com/en-us/windows/win32/sysinfo/hkey-classes-root-key for more information.
18 private const string DependencyRegistryRoot = @"Software\Classes\Installer\Dependencies\";
19
20 private static readonly char[] InvalidDependencyCharacters = new char[] { ' ', '\"', ';', '\\' };
21
22 /// <summary>
23 /// Processes the ProviderKey bundle attribute.
24 /// </summary>
25 /// <param name="sourceLineNumbers">Source line number for the parent element.</param>
26 /// <param name="parentElement">Parent element of attribute.</param>
27 /// <param name="attribute">The XML attribute for the ProviderKey attribute.</param>
28 private void ParseBundleProviderKeyAttribute(SourceLineNumber sourceLineNumbers, XElement parentElement, XAttribute attribute)
29 {
30 var providerKey = this.Core.GetAttributeValue(sourceLineNumbers, attribute);
31 int illegalChar;
32
33 // Make sure the key does not contain any illegal characters or values.
34 if (String.IsNullOrEmpty(providerKey))
35 {
36 this.Messaging.Write(ErrorMessages.IllegalEmptyAttributeValue(sourceLineNumbers, parentElement.Name.LocalName, attribute.Name.LocalName));
37 }
38 else if (0 <= (illegalChar = providerKey.IndexOfAny(InvalidDependencyCharacters)))
39 {
40 this.Messaging.Write(CompilerErrors.IllegalCharactersInProvider(sourceLineNumbers, "ProviderKey", providerKey[illegalChar], String.Join(" ", InvalidDependencyCharacters)));
41 }
42 else if ("ALL" == providerKey)
43 {
44 this.Messaging.Write(CompilerErrors.ReservedValue(sourceLineNumbers, parentElement.Name.LocalName, "ProviderKey", providerKey));
45 }
46
47 if (!this.Messaging.EncounteredError)
48 {
49 // Generate the primary key for the row.
50 var id = this.Core.CreateIdentifier("dep", attribute.Name.LocalName, providerKey);
51
52 // Create the provider symbol for the bundle. The Component_ field is required
53 // in the table definition but unused for bundles, so just set it to the valid ID.
54 this.Core.AddSymbol(new WixDependencyProviderSymbol(sourceLineNumbers, id)
55 {
56 ParentRef = id.Id,
57 ProviderKey = providerKey,
58 Attributes = WixDependencyProviderAttributes.ProvidesAttributesBundle,
59 });
60 }
61 }
62
63 /// <summary>
64 /// Processes the Provides element.
65 /// </summary>
66 /// <param name="node">The XML node for the Provides element.</param>
67 /// <param name="packageType">The type of the package being chained into a bundle, or null if building an MSI package.</param>
68 /// <param name="parentId">The identifier of the parent component or package.</param>
69 /// <param name="possibleKeyPath">Possible KeyPath identifier.</param>
70 /// <returns>Yes if this is the keypath.</returns>
71 private YesNoType ParseProvidesElement(XElement node, WixBundlePackageType? packageType, string parentId, out Identifier possibleKeyPath)
72 {
73 possibleKeyPath = null;
74
75 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
76 Identifier id = null;
77 string key = null;
78 string version = null;
79 string displayName = null;
80
81 foreach (var attrib in node.Attributes())
82 {
83 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
84 {
85 switch (attrib.Name.LocalName)
86 {
87 case "Id":
88 id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib);
89 break;
90 case "Key":
91 key = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
92 break;
93 case "Version":
94 version = this.Core.GetAttributeVersionValue(sourceLineNumbers, attrib);
95 break;
96 case "DisplayName":
97 displayName = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
98 break;
99 default:
100 this.Core.UnexpectedAttribute(node, attrib);
101 break;
102 }
103 }
104 else
105 {
106 this.Core.ParseExtensionAttribute(node, attrib);
107 }
108 }
109
110 // Make sure the key is valid. The key will default to the ProductCode for MSI packages
111 // and the package code for MSP packages in the binder if not specified.
112 if (!String.IsNullOrEmpty(key))
113 {
114 int illegalChar;
115
116 // Make sure the key does not contain any illegal characters or values.
117 if (0 <= (illegalChar = key.IndexOfAny(InvalidDependencyCharacters)))
118 {
119 this.Messaging.Write(CompilerErrors.IllegalCharactersInProvider(sourceLineNumbers, "Key", key[illegalChar], String.Join(" ", InvalidDependencyCharacters)));
120 }
121 else if ("ALL" == key)
122 {
123 this.Messaging.Write(CompilerErrors.ReservedValue(sourceLineNumbers, node.Name.LocalName, "Key", key));
124 }
125 }
126 else if (!packageType.HasValue)
127 {
128 // Make sure the ProductCode is authored and set the key.
129 this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Property, "ProductCode");
130 key = "!(bind.property.ProductCode)";
131 }
132 else if (WixBundlePackageType.Bundle == packageType || WixBundlePackageType.Exe == packageType || WixBundlePackageType.Msu == packageType)
133 {
134 // Must specify the provider key when authored for a package.
135 this.Messaging.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Key"));
136 }
137
138 // The Version attribute should not be authored in or for an MSI package.
139 if (!String.IsNullOrEmpty(version))
140 {
141 switch (packageType)
142 {
143 case null:
144 this.Messaging.Write(CompilerWarnings.DiscouragedVersionAttribute(sourceLineNumbers));
145 break;
146 case WixBundlePackageType.Msi:
147 this.Messaging.Write(CompilerWarnings.DiscouragedVersionAttribute(sourceLineNumbers, parentId));
148 break;
149 }
150 }
151 else if (WixBundlePackageType.Msp == packageType || WixBundlePackageType.Msu == packageType)
152 {
153 // Must specify the Version when authored for packages that do not contain a version.
154 this.Messaging.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Version"));
155 }
156
157 // Need the element ID for child element processing, so generate now if not authored.
158 if (null == id)
159 {
160 id = this.Core.CreateIdentifier("dep", node.Name.LocalName, parentId, key);
161 }
162
163 foreach (var child in node.Elements())
164 {
165 if (CompilerCore.WixNamespace == child.Name.Namespace)
166 {
167 switch (child.Name.LocalName)
168 {
169 case "Requires":
170 this.ParseRequiresElement(child, id.Id);
171 break;
172 case "RequiresRef":
173 this.ParseRequiresRefElement(child, id.Id, requiresAction: !packageType.HasValue);
174 break;
175 default:
176 this.Core.UnexpectedElement(node, child);
177 break;
178 }
179 }
180 else
181 {
182 this.Core.ParseExtensionElement(node, child);
183 }
184 }
185
186 if (!this.Messaging.EncounteredError)
187 {
188 var symbol = this.Core.AddSymbol(new WixDependencyProviderSymbol(sourceLineNumbers, id)
189 {
190 ParentRef = parentId,
191 ProviderKey = key,
192 });
193
194 if (!String.IsNullOrEmpty(version))
195 {
196 symbol.Version = version;
197 }
198
199 if (!String.IsNullOrEmpty(displayName))
200 {
201 symbol.DisplayName = displayName;
202 }
203
204 if (!packageType.HasValue)
205 {
206 // Generate registry rows for the provider using binder properties.
207 var keyProvides = String.Concat(DependencyRegistryRoot, key);
208 var root = RegistryRootType.MachineUser;
209
210 var value = "[ProductCode]";
211 this.Core.CreateRegistryStringSymbol(sourceLineNumbers, root, keyProvides, null, value, parentId);
212
213 value = !String.IsNullOrEmpty(version) ? version : "[ProductVersion]";
214 var versionRegistryId = this.Core.CreateRegistryStringSymbol(sourceLineNumbers, root, keyProvides, "Version", value, parentId);
215
216 value = !String.IsNullOrEmpty(displayName) ? displayName : "[ProductName]";
217 this.Core.CreateRegistryStringSymbol(sourceLineNumbers, root, keyProvides, "DisplayName", value, parentId);
218
219 // Use the Version registry value and use that as a potential key path.
220 possibleKeyPath = versionRegistryId;
221 }
222 }
223
224 return YesNoType.NotSet;
225 }
226
227 /// <summary>
228 /// Processes the Requires element.
229 /// </summary>
230 /// <param name="node">The XML node for the Requires element.</param>
231 /// <param name="providerId">The parent provider identifier.</param>
232 private void ParseRequiresElement(XElement node, string providerId)
233 {
234 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
235 Identifier id = null;
236 string providerKey = null;
237 string minVersion = null;
238 string maxVersion = null;
239 var attributes = WixDependencySymbolAttributes.None;
240 var illegalChar = -1;
241
242 foreach (var attrib in node.Attributes())
243 {
244 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
245 {
246 switch (attrib.Name.LocalName)
247 {
248 case "Id":
249 id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib);
250 break;
251 case "ProviderKey":
252 providerKey = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
253 break;
254 case "Minimum":
255 minVersion = this.Core.GetAttributeVersionValue(sourceLineNumbers, attrib);
256 break;
257 case "Maximum":
258 maxVersion = this.Core.GetAttributeVersionValue(sourceLineNumbers, attrib);
259 break;
260 case "IncludeMinimum":
261 if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib))
262 {
263 attributes |= WixDependencySymbolAttributes.RequiresAttributesMinVersionInclusive;
264 }
265 break;
266 case "IncludeMaximum":
267 if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib))
268 {
269 attributes |= WixDependencySymbolAttributes.RequiresAttributesMaxVersionInclusive;
270 }
271 break;
272 default:
273 this.Core.UnexpectedAttribute(node, attrib);
274 break;
275 }
276 }
277 else
278 {
279 this.Core.ParseExtensionAttribute(node, attrib);
280 }
281 }
282
283 this.Core.ParseForExtensionElements(node);
284
285 if (null == id)
286 {
287 // Generate an ID only if this element is authored under a Provides element; otherwise, a RequiresRef
288 // element will be necessary and the Id attribute will be required.
289 if (!String.IsNullOrEmpty(providerId))
290 {
291 id = this.Core.CreateIdentifier("dep", node.Name.LocalName, providerKey);
292 }
293 else
294 {
295 this.Messaging.Write(ErrorMessages.ExpectedAttributeWhenElementNotUnderElement(sourceLineNumbers, node.Name.LocalName, "Id", "Provides"));
296 id = Identifier.Invalid;
297 }
298 }
299
300 if (String.IsNullOrEmpty(providerKey))
301 {
302 this.Messaging.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "ProviderKey"));
303 }
304 // Make sure the key does not contain any illegal characters.
305 else if (0 <= (illegalChar = providerKey.IndexOfAny(InvalidDependencyCharacters)))
306 {
307 this.Messaging.Write(CompilerErrors.IllegalCharactersInProvider(sourceLineNumbers, "ProviderKey", providerKey[illegalChar], String.Join(" ", InvalidDependencyCharacters)));
308 }
309
310 if (!this.Messaging.EncounteredError)
311 {
312 this.Core.AddSymbol(new WixDependencySymbol(sourceLineNumbers, id)
313 {
314 ProviderKey = providerKey,
315 MinVersion = minVersion,
316 MaxVersion = maxVersion,
317 Attributes = attributes
318 });
319
320 // Create the relationship between this WixDependency symbol and the WixDependencyProvider symbol.
321 if (!String.IsNullOrEmpty(providerId))
322 {
323 this.Core.AddSymbol(new WixDependencyRefSymbol(sourceLineNumbers)
324 {
325 WixDependencyProviderRef = providerId,
326 WixDependencyRef = id.Id,
327 });
328 }
329 }
330 }
331
332 /// <summary>
333 /// Processes the RequiresRef element.
334 /// </summary>
335 /// <param name="node">The XML node for the RequiresRef element.</param>
336 /// <param name="providerId">The parent provider identifier.</param>
337 /// <param name="requiresAction">Whether the Requires custom action should be referenced.</param>
338 private void ParseRequiresRefElement(XElement node, string providerId, bool requiresAction)
339 {
340 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
341 string id = null;
342
343 foreach (var attrib in node.Attributes())
344 {
345 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
346 {
347 switch (attrib.Name.LocalName)
348 {
349 case "Id":
350 id = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
351 break;
352 default:
353 this.Core.UnexpectedAttribute(node, attrib);
354 break;
355 }
356 }
357 else
358 {
359 this.Core.ParseExtensionAttribute(node, attrib);
360 }
361 }
362
363 this.Core.ParseForExtensionElements(node);
364
365 if (String.IsNullOrEmpty(id))
366 {
367 this.Messaging.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id"));
368 }
369
370 if (!this.Messaging.EncounteredError)
371 {
372 // Create a link dependency on the row that contains information we'll need during bind.
373 this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.WixDependency, id);
374
375 // Create the relationship between the WixDependency row and the parent WixDependencyProvider row.
376 this.Core.AddSymbol(new WixDependencyRefSymbol(sourceLineNumbers)
377 {
378 WixDependencyProviderRef = providerId,
379 WixDependencyRef = id,
380 });
381 }
382 }
383 }
384 }