main
cs 8,375 lines 370 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.Collections.Generic;
7 using System.Diagnostics;
8 using System.Globalization;
9 using System.IO;
10 using System.Linq;
11 using System.Text;
12 using System.Xml.Linq;
13 using WixToolset.Data;
14 using WixToolset.Data.Symbols;
15 using WixToolset.Data.WindowsInstaller;
16 using WixToolset.Extensibility;
17 using WixToolset.Extensibility.Data;
18 using WixToolset.Extensibility.Services;
19
20 /// <summary>
21 /// Compiler of the WiX toolset.
22 /// </summary>
23 internal partial class Compiler : ICompiler
24 {
25 private const int MinValueOfMaxCabSizeForLargeFileSplitting = 20; // 20 MB
26 private const int MaxValueOfMaxCabSizeForLargeFileSplitting = 2 * 1024; // 2048 MB (i.e. 2 GB)
27
28 private const char ComponentIdPlaceholderStart = (char)167;
29 private const char ComponentIdPlaceholderEnd = (char)167;
30 private Dictionary<string, string> componentIdPlaceholders;
31
32 // If these are true you know you are building a module or product
33 // but if they are false you cannot not be sure they will not end
34 // up a product or module. Use these flags carefully.
35 private bool compilingModule;
36 private bool compilingProduct;
37
38 private string activeName;
39 private string activeLanguage;
40
41 /// <summary>
42 /// Type of RadioButton element in a group.
43 /// </summary>
44 private enum RadioButtonType
45 {
46 /// <summary>Not set, yet.</summary>
47 NotSet,
48
49 /// <summary>Text</summary>
50 Text,
51
52 /// <summary>Bitmap</summary>
53 Bitmap,
54
55 /// <summary>Icon</summary>
56 Icon,
57 }
58
59 internal Compiler(IServiceProvider serviceProvider)
60 {
61 this.Messaging = serviceProvider.GetService<IMessaging>();
62 }
63
64 public IMessaging Messaging { get; }
65
66 private ICompileContext Context { get; set; }
67
68 private CompilerCore Core { get; set; }
69
70 /// <summary>
71 /// Gets or sets the platform which the compiler will use when defaulting 64-bit attributes and elements.
72 /// </summary>
73 /// <value>The platform which the compiler will use when defaulting 64-bit attributes and elements.</value>
74 public Platform CurrentPlatform => this.Context.Platform;
75
76 /// <summary>
77 /// Gets or sets the option to show pedantic messages.
78 /// </summary>
79 /// <value>The option to show pedantic messages.</value>
80 public bool ShowPedanticMessages { get; set; }
81
82 /// <summary>
83 /// Compiles the provided Xml document into an intermediate object
84 /// </summary>
85 /// <returns>Intermediate object representing compiled source document.</returns>
86 /// <remarks>This method is not thread-safe.</remarks>
87 public Intermediate Compile(ICompileContext context)
88 {
89 var target = new Intermediate();
90
91 if (String.IsNullOrEmpty(context.CompilationId))
92 {
93 context.CompilationId = target.Id;
94 }
95
96 this.Context = context;
97
98 var extensionsByNamespace = new Dictionary<XNamespace, ICompilerExtension>();
99
100 foreach (var extension in this.Context.Extensions)
101 {
102 if (!extensionsByNamespace.TryGetValue(extension.Namespace, out var collidingExtension))
103 {
104 extensionsByNamespace.Add(extension.Namespace, extension);
105 }
106 else
107 {
108 this.Messaging.Write(ErrorMessages.DuplicateExtensionXmlSchemaNamespace(extension.GetType().ToString(), extension.Namespace.NamespaceName, collidingExtension.GetType().ToString()));
109 }
110
111 extension.PreCompile(this.Context);
112 }
113
114 // Try to compile it.
115 try
116 {
117 var bundleValidator = this.Context.ServiceProvider.GetService<IBundleValidator>();
118 var parseHelper = this.Context.ServiceProvider.GetService<IParseHelper>();
119
120 this.Core = new CompilerCore(target, this.Messaging, bundleValidator, parseHelper, extensionsByNamespace)
121 {
122 ShowPedanticMessages = this.ShowPedanticMessages
123 };
124 this.componentIdPlaceholders = new Dictionary<string, string>();
125
126 // parse the document
127 var source = this.Context.Source;
128 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(source.Root);
129 if ("Wix" == source.Root.Name.LocalName)
130 {
131 if (CompilerCore.WixNamespace == source.Root.Name.Namespace)
132 {
133 this.ParseWixElement(source.Root);
134 }
135 else // invalid or missing namespace
136 {
137 if (String.IsNullOrEmpty(source.Root.Name.NamespaceName))
138 {
139 this.Core.Write(ErrorMessages.InvalidWixXmlNamespace(sourceLineNumbers, "Wix", CompilerCore.WixNamespace.ToString()));
140 }
141 else
142 {
143 this.Core.Write(ErrorMessages.InvalidWixXmlNamespace(sourceLineNumbers, "Wix", source.Root.Name.NamespaceName, CompilerCore.WixNamespace.ToString()));
144 }
145 }
146 }
147 else
148 {
149 this.Core.Write(ErrorMessages.InvalidDocumentElement(sourceLineNumbers, source.Root.Name.LocalName, "source", "Wix"));
150 }
151
152 // Resolve any Component Id placeholders compiled into the intermediate.
153 this.ResolveComponentIdPlaceholders(target);
154 }
155 finally
156 {
157 foreach (var extension in this.Context.Extensions)
158 {
159 extension.PostCompile(target);
160 }
161
162 this.Core = null;
163 }
164
165 target.UpdateLevel(Data.IntermediateLevels.Compiled);
166
167 return target;
168 }
169
170 /// <summary>
171 /// Parses a Wix element.
172 /// </summary>
173 /// <param name="node">Element to parse.</param>
174 private void ParseWixElement(XElement node)
175 {
176 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
177 string requiredVersion = null;
178
179 foreach (var attrib in node.Attributes())
180 {
181 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
182 {
183 switch (attrib.Name.LocalName)
184 {
185 case "RequiredVersion":
186 requiredVersion = this.Core.GetAttributeVersionValue(sourceLineNumbers, attrib);
187 break;
188 default:
189 this.Core.UnexpectedAttribute(node, attrib);
190 break;
191 }
192 }
193 else
194 {
195 this.Core.ParseExtensionAttribute(node, attrib);
196 }
197 }
198
199 if (null != requiredVersion)
200 {
201 this.Core.VerifyRequiredVersion(sourceLineNumbers, requiredVersion);
202 }
203
204 foreach (var child in node.Elements())
205 {
206 if (CompilerCore.WixNamespace == child.Name.Namespace)
207 {
208 switch (child.Name.LocalName)
209 {
210 case "Bundle":
211 this.ParseBundleElement(child);
212 break;
213 case "Fragment":
214 this.ParseFragmentElement(child);
215 break;
216 case "Module":
217 this.ParseModuleElement(child);
218 break;
219 case "PatchCreation":
220 this.ParsePatchCreationElement(child);
221 break;
222 case "Package":
223 this.ParsePackageElement(child);
224 break;
225 case "Patch":
226 this.ParsePatchElement(child);
227 break;
228 default:
229 this.Core.UnexpectedElement(node, child);
230 break;
231 }
232 }
233 else
234 {
235 this.Core.ParseExtensionElement(node, child);
236 }
237 }
238 }
239
240 private void ResolveComponentIdPlaceholders(Intermediate target)
241 {
242 if (0 < this.componentIdPlaceholders.Count)
243 {
244 foreach (var section in target.Sections)
245 {
246 foreach (var symbol in section.Symbols)
247 {
248 foreach (var field in symbol.Fields)
249 {
250 if (field != null && field.Type == IntermediateFieldType.String)
251 {
252 var data = field.AsString();
253 if (!String.IsNullOrEmpty(data))
254 {
255 var changed = false;
256 var start = data.IndexOf(ComponentIdPlaceholderStart);
257 while (start != -1)
258 {
259 var end = data.IndexOf(ComponentIdPlaceholderEnd, start + 1);
260 if (end == -1)
261 {
262 break;
263 }
264
265 var placeholderId = data.Substring(start, end - start + 1);
266 if (this.componentIdPlaceholders.TryGetValue(placeholderId, out var value))
267 {
268 var sb = new StringBuilder(data);
269 sb.Remove(start, end - start + 1);
270 sb.Insert(start, value);
271
272 data = sb.ToString();
273 changed = true;
274
275 end = start + value.Length;
276 }
277
278 start = data.IndexOf(ComponentIdPlaceholderStart, end);
279 }
280
281 if (changed)
282 {
283 field.Overwrite(data);
284 }
285 }
286 }
287 }
288 }
289 }
290 }
291 }
292
293 /// <summary>
294 /// Uppercases the first character of a string.
295 /// </summary>
296 /// <param name="s">String to uppercase first character of.</param>
297 /// <returns>String with first character uppercased.</returns>
298 private static string UppercaseFirstChar(string s)
299 {
300 if (0 == s.Length)
301 {
302 return s;
303 }
304
305 return String.Concat(s.Substring(0, 1).ToUpperInvariant(), s.Substring(1));
306 }
307
308 /// <summary>
309 /// Lowercases the string if present.
310 /// </summary>
311 /// <param name="s">String to lowercase.</param>
312 /// <returns>Null if the string is null, otherwise returns the lowercase.</returns>
313 private static string LowercaseOrNull(string s)
314 {
315 return s?.ToLowerInvariant();
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="propertyId">Property to add to search.</param>
323 /// <param name="signature">Signature for search.</param>
324 private void AddAppSearch(SourceLineNumber sourceLineNumbers, Identifier propertyId, string signature)
325 {
326 if (!this.Core.EncounteredError)
327 {
328 if (propertyId.Id != propertyId.Id.ToUpperInvariant())
329 {
330 this.Core.Write(ErrorMessages.SearchPropertyNotUppercase(sourceLineNumbers, "Property", "Id", propertyId.Id));
331 }
332
333 this.Core.AddSymbol(new AppSearchSymbol(sourceLineNumbers, new Identifier(propertyId.Access, propertyId.Id, signature))
334 {
335 PropertyRef = propertyId.Id,
336 SignatureRef = signature
337 });
338 }
339 }
340
341 /// <summary>
342 /// Adds a property to the active section.
343 /// </summary>
344 /// <param name="sourceLineNumbers">Current source/line number of processing.</param>
345 /// <param name="propertyId">Identifier of property to add.</param>
346 /// <param name="value">Value of property.</param>
347 /// <param name="admin">Flag if property is an admin property.</param>
348 /// <param name="secure">Flag if property is a secure property.</param>
349 /// <param name="hidden">Flag if property is to be hidden.</param>
350 /// <param name="fragment">Adds the property to a new section.</param>
351 private void AddProperty(SourceLineNumber sourceLineNumbers, Identifier propertyId, string value, bool admin, bool secure, bool hidden, bool fragment)
352 {
353 // properties without a valid identifier should not be processed any further
354 if (null == propertyId || String.IsNullOrEmpty(propertyId.Id))
355 {
356 return;
357 }
358
359 if (!String.IsNullOrEmpty(value))
360 {
361 var start = value.IndexOf('[');
362 while (start != -1 && start < value.Length)
363 {
364 var end = value.IndexOf(']', start + 1);
365 if (end == -1)
366 {
367 break;
368 }
369
370 var id = value.Substring(start + 1, end - start - 1);
371 if (Common.IsIdentifier(id))
372 {
373 this.Core.Write(WarningMessages.PropertyValueContainsPropertyReference(sourceLineNumbers, propertyId.Id, id));
374 }
375
376 start = (end < value.Length) ? value.IndexOf('[', end + 1) : -1;
377 }
378 }
379
380 if (!this.Core.EncounteredError)
381 {
382 var section = this.Core.ActiveSection;
383
384 // Add the symbol to a separate section if requested.
385 if (fragment)
386 {
387 var id = String.Concat(this.Core.ActiveSection.Id, ".", propertyId.Id);
388
389 section = this.Core.CreateSection(id, SectionType.Fragment, this.Context.CompilationId);
390
391 // Reference the property in the active section.
392 this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Property, propertyId.Id);
393 }
394
395 // Allow symbol to exist with no value so that PropertyRefs can be made for *Search elements
396 // the linker will remove these symbols before the final output is created.
397 section.AddSymbol(new PropertySymbol(sourceLineNumbers, propertyId)
398 {
399 Value = value,
400 });
401
402 if (admin || hidden || secure)
403 {
404 this.AddWixPropertySymbol(sourceLineNumbers, propertyId, admin, secure, hidden, section);
405 }
406 }
407 }
408
409 private void AddWixPropertySymbol(SourceLineNumber sourceLineNumbers, Identifier property, bool admin, bool secure, bool hidden, IntermediateSection section = null)
410 {
411 if (secure && property.Id != property.Id.ToUpperInvariant())
412 {
413 this.Core.Write(ErrorMessages.SecurePropertyNotUppercase(sourceLineNumbers, "Property", "Id", property.Id));
414 }
415
416 if (null == section)
417 {
418 section = this.Core.ActiveSection;
419
420 this.Core.EnsureTable(sourceLineNumbers, WindowsInstallerTableDefinitions.Property); // Property table is always required when using WixProperty table.
421 }
422
423 section.AddSymbol(new WixPropertySymbol(sourceLineNumbers)
424 {
425 PropertyRef = property.Id,
426 Admin = admin,
427 Hidden = hidden,
428 Secure = secure
429 });
430 }
431
432 /// <summary>
433 /// Adds a "implemented category" registry key to active section.
434 /// </summary>
435 /// <param name="sourceLineNumbers">Current source/line number of processing.</param>
436 /// <param name="categoryId">GUID for category.</param>
437 /// <param name="classId">ClassId for to mark "implemented".</param>
438 /// <param name="componentId">Identifier of parent component.</param>
439 private void RegisterImplementedCategories(SourceLineNumber sourceLineNumbers, string categoryId, string classId, string componentId)
440 {
441 this.Core.CreateRegistryStringSymbol(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("CLSID\\", classId, "\\Implemented Categories\\", categoryId), "*", null, componentId);
442 }
443
444 /// <summary>
445 /// Parses an application identifer element.
446 /// </summary>
447 /// <param name="node">Element to parse.</param>
448 /// <param name="componentId">Identifier of parent component.</param>
449 /// <param name="advertise">The required advertise state (set depending upon the parent).</param>
450 /// <param name="fileServer">Optional file identifier for CLSID when not advertised.</param>
451 /// <param name="typeLibId">Optional TypeLib GUID for CLSID.</param>
452 /// <param name="typeLibVersion">Optional TypeLib Version for CLSID Interfaces (if any).</param>
453 private void ParseAppIdElement(XElement node, string componentId, YesNoType advertise, string fileServer, string typeLibId, string typeLibVersion)
454 {
455 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
456 string appId = null;
457 string remoteServerName = null;
458 string localService = null;
459 string serviceParameters = null;
460 string dllSurrogate = null;
461 bool? activateAtStorage = null;
462 var appIdAdvertise = YesNoType.NotSet;
463 bool? runAsInteractiveUser = null;
464 string description = null;
465
466 foreach (var attrib in node.Attributes())
467 {
468 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
469 {
470 switch (attrib.Name.LocalName)
471 {
472 case "Id":
473 appId = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib, false);
474 break;
475 case "ActivateAtStorage":
476 activateAtStorage = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
477 break;
478 case "Advertise":
479 appIdAdvertise = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
480 break;
481 case "Description":
482 description = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
483 break;
484 case "DllSurrogate":
485 dllSurrogate = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty);
486 break;
487 case "LocalService":
488 localService = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
489 break;
490 case "RemoteServerName":
491 remoteServerName = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
492 break;
493 case "RunAsInteractiveUser":
494 runAsInteractiveUser = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
495 break;
496 case "ServiceParameters":
497 serviceParameters = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
498 break;
499 default:
500 this.Core.UnexpectedAttribute(node, attrib);
501 break;
502 }
503 }
504 else
505 {
506 this.Core.ParseExtensionAttribute(node, attrib);
507 }
508 }
509
510 if (null == appId)
511 {
512 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id"));
513 }
514
515 if ((YesNoType.No == advertise && YesNoType.Yes == appIdAdvertise) || (YesNoType.Yes == advertise && YesNoType.No == appIdAdvertise))
516 {
517 this.Core.Write(ErrorMessages.AppIdIncompatibleAdvertiseState(sourceLineNumbers, node.Name.LocalName, "Advertise", appIdAdvertise.ToString(), advertise.ToString()));
518 }
519 else if (appIdAdvertise != YesNoType.NotSet)
520 {
521 advertise = appIdAdvertise;
522 }
523
524 // if the advertise state has not been set, default to non-advertised
525 if (YesNoType.NotSet == advertise)
526 {
527 advertise = YesNoType.No;
528 }
529
530 foreach (var child in node.Elements())
531 {
532 if (CompilerCore.WixNamespace == child.Name.Namespace)
533 {
534 switch (child.Name.LocalName)
535 {
536 case "Class":
537 this.ParseClassElement(child, componentId, advertise, fileServer, typeLibId, typeLibVersion, appId);
538 break;
539 default:
540 this.Core.UnexpectedElement(node, child);
541 break;
542 }
543 }
544 else
545 {
546 this.Core.ParseExtensionElement(node, child);
547 }
548 }
549
550 if (YesNoType.Yes == advertise)
551 {
552 if (null != description)
553 {
554 this.Core.Write(ErrorMessages.IllegalAttributeWhenAdvertised(sourceLineNumbers, node.Name.LocalName, "Description"));
555 }
556
557 if (!this.Core.EncounteredError)
558 {
559 this.Core.AddSymbol(new AppIdSymbol(sourceLineNumbers, new Identifier(AccessModifier.Global, appId))
560 {
561 AppId = appId,
562 RemoteServerName = remoteServerName,
563 LocalService = localService,
564 ServiceParameters = serviceParameters,
565 DllSurrogate = dllSurrogate,
566 ActivateAtStorage = activateAtStorage,
567 RunAsInteractiveUser = runAsInteractiveUser,
568 });
569 }
570 }
571 else if (YesNoType.No == advertise)
572 {
573 if (null != description)
574 {
575 this.Core.CreateRegistryStringSymbol(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("AppID\\", appId), null, description, componentId);
576 }
577 else
578 {
579 this.Core.CreateRegistryStringSymbol(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("AppID\\", appId), "+", null, componentId);
580 }
581
582 if (null != remoteServerName)
583 {
584 this.Core.CreateRegistryStringSymbol(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("AppID\\", appId), "RemoteServerName", remoteServerName, componentId);
585 }
586
587 if (null != localService)
588 {
589 this.Core.CreateRegistryStringSymbol(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("AppID\\", appId), "LocalService", localService, componentId);
590 }
591
592 if (null != serviceParameters)
593 {
594 this.Core.CreateRegistryStringSymbol(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("AppID\\", appId), "ServiceParameters", serviceParameters, componentId);
595 }
596
597 if (null != dllSurrogate)
598 {
599 this.Core.CreateRegistryStringSymbol(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("AppID\\", appId), "DllSurrogate", dllSurrogate, componentId);
600 }
601
602 if (true == activateAtStorage)
603 {
604 this.Core.CreateRegistryStringSymbol(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("AppID\\", appId), "ActivateAtStorage", "Y", componentId);
605 }
606
607 if (true == runAsInteractiveUser)
608 {
609 this.Core.CreateRegistryStringSymbol(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("AppID\\", appId), "RunAs", "Interactive User", componentId);
610 }
611 }
612 }
613
614 /// <summary>
615 /// Parses an AssemblyName element.
616 /// </summary>
617 /// <param name="node">File element to parse.</param>
618 /// <param name="componentId">Parent's component id.</param>
619 private void ParseAssemblyName(XElement node, string componentId)
620 {
621 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
622 string id = null;
623 string value = null;
624
625 foreach (var attrib in node.Attributes())
626 {
627 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
628 {
629 switch (attrib.Name.LocalName)
630 {
631 case "Id":
632 id = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
633 break;
634 case "Value":
635 value = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
636 break;
637 default:
638 this.Core.UnexpectedAttribute(node, attrib);
639 break;
640 }
641 }
642 else
643 {
644 this.Core.ParseExtensionAttribute(node, attrib);
645 }
646 }
647
648 if (null == id)
649 {
650 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id"));
651 }
652
653 this.Core.ParseForExtensionElements(node);
654
655 if (!this.Core.EncounteredError)
656 {
657 this.Core.AddSymbol(new MsiAssemblyNameSymbol(sourceLineNumbers, new Identifier(AccessModifier.Section, componentId, id))
658 {
659 ComponentRef = componentId,
660 Name = id,
661 Value = value,
662 });
663 }
664 }
665
666 /// <summary>
667 /// Parses a binary element.
668 /// </summary>
669 /// <param name="node">Element to parse.</param>
670 /// <returns>Identifier for the new row.</returns>
671 private Identifier ParseBinaryElement(XElement node)
672 {
673 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
674 Identifier id = null;
675 string sourceFile = null;
676 var suppressModularization = YesNoType.NotSet;
677
678 foreach (var attrib in node.Attributes())
679 {
680 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
681 {
682 switch (attrib.Name.LocalName)
683 {
684 case "Id":
685 id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib);
686 break;
687 case "SourceFile":
688 sourceFile = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
689 break;
690 case "SuppressModularization":
691 suppressModularization = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
692 break;
693 default:
694 this.Core.UnexpectedAttribute(node, attrib);
695 break;
696 }
697 }
698 else
699 {
700 this.Core.ParseExtensionAttribute(node, attrib);
701 }
702 }
703
704 if (null == id)
705 {
706 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id"));
707 id = Identifier.Invalid;
708 }
709 else if (!String.IsNullOrEmpty(id.Id)) // only check legal values
710 {
711 if (55 < id.Id.Length)
712 {
713 this.Core.Write(ErrorMessages.StreamNameTooLong(sourceLineNumbers, node.Name.LocalName, "Id", id.Id, id.Id.Length, 55));
714 }
715 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
716 {
717 if (18 < id.Id.Length)
718 {
719 this.Core.Write(WarningMessages.IdentifierCannotBeModularized(sourceLineNumbers, node.Name.LocalName, "Id", id.Id, id.Id.Length, 18));
720 }
721 }
722 }
723
724 if (null == sourceFile)
725 {
726 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "SourceFile"));
727 }
728
729 this.Core.ParseForExtensionElements(node);
730
731 if (!this.Core.EncounteredError)
732 {
733 this.Core.AddSymbol(new BinarySymbol(sourceLineNumbers, id)
734 {
735 Data = new IntermediateFieldPathValue { Path = sourceFile }
736 });
737
738 if (YesNoType.Yes == suppressModularization)
739 {
740 this.Core.AddSymbol(new WixSuppressModularizationSymbol(sourceLineNumbers)
741 {
742 SuppressIdentifier = id.Id
743 });
744 }
745 }
746
747 return id;
748 }
749
750 /// <summary>
751 /// Parses an icon element.
752 /// </summary>
753 /// <param name="node">Element to parse.</param>
754 /// <returns>Identifier for the new row.</returns>
755 private string ParseIconElement(XElement node)
756 {
757 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
758 Identifier id = null;
759 string sourceFile = null;
760
761 foreach (var attrib in node.Attributes())
762 {
763 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
764 {
765 switch (attrib.Name.LocalName)
766 {
767 case "Id":
768 id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib);
769 break;
770 case "SourceFile":
771 sourceFile = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
772 break;
773 default:
774 this.Core.UnexpectedAttribute(node, attrib);
775 break;
776 }
777 }
778 else
779 {
780 this.Core.ParseExtensionAttribute(node, attrib);
781 }
782 }
783
784 if (null == id)
785 {
786 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id"));
787 id = Identifier.Invalid;
788 }
789 else if (!String.IsNullOrEmpty(id.Id)) // only check legal values
790 {
791 if (57 < id.Id.Length)
792 {
793 this.Core.Write(ErrorMessages.StreamNameTooLong(sourceLineNumbers, node.Name.LocalName, "Id", id.Id, id.Id.Length, 57));
794 }
795 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
796 {
797 if (20 < id.Id.Length)
798 {
799 this.Core.Write(WarningMessages.IdentifierCannotBeModularized(sourceLineNumbers, node.Name.LocalName, "Id", id.Id, id.Id.Length, 20));
800 }
801 }
802 }
803
804 if (null == sourceFile)
805 {
806 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "SourceFile"));
807 }
808
809 this.Core.ParseForExtensionElements(node);
810
811 if (!this.Core.EncounteredError)
812 {
813 this.Core.AddSymbol(new IconSymbol(sourceLineNumbers, id)
814 {
815 Data = new IntermediateFieldPathValue { Path = sourceFile },
816 });
817 }
818
819 return id.Id;
820 }
821
822 /// <summary>
823 /// Parses an InstanceTransforms element.
824 /// </summary>
825 /// <param name="node">Element to parse.</param>
826 private void ParseInstanceTransformsElement(XElement node)
827 {
828 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
829 string property = null;
830
831 foreach (var attrib in node.Attributes())
832 {
833 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
834 {
835 switch (attrib.Name.LocalName)
836 {
837 case "Property":
838 property = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
839 this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Property, property);
840 break;
841 default:
842 this.Core.UnexpectedAttribute(node, attrib);
843 break;
844 }
845 }
846 else
847 {
848 this.Core.ParseExtensionAttribute(node, attrib);
849 }
850 }
851
852 if (null == property)
853 {
854 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Property"));
855 }
856
857 // find unexpected child elements
858 foreach (var child in node.Elements())
859 {
860 if (CompilerCore.WixNamespace == child.Name.Namespace)
861 {
862 switch (child.Name.LocalName)
863 {
864 case "Instance":
865 this.ParseInstanceElement(child, property);
866 break;
867 default:
868 this.Core.UnexpectedElement(node, child);
869 break;
870 }
871 }
872 else
873 {
874 this.Core.ParseExtensionElement(node, child);
875 }
876 }
877 }
878
879 /// <summary>
880 /// Parses an instance element.
881 /// </summary>
882 /// <param name="node">Element to parse.</param>
883 /// <param name="propertyId">Identifier of instance property.</param>
884 private void ParseInstanceElement(XElement node, string propertyId)
885 {
886 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
887 Identifier id = null;
888 string productCode = null;
889 string productName = null;
890 string upgradeCode = null;
891
892 foreach (var attrib in node.Attributes())
893 {
894 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
895 {
896 switch (attrib.Name.LocalName)
897 {
898 case "Id":
899 id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib);
900 break;
901 case "ProductCode":
902 productCode = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib, true);
903 break;
904 case "ProductName":
905 productName = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
906 break;
907 case "UpgradeCode":
908 upgradeCode = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib, false);
909 break;
910 default:
911 this.Core.UnexpectedAttribute(node, attrib);
912 break;
913 }
914 }
915 else
916 {
917 this.Core.ParseExtensionAttribute(node, attrib);
918 }
919 }
920
921 if (null == id)
922 {
923 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id"));
924 }
925
926 if (null == productCode)
927 {
928 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "ProductCode"));
929 }
930
931 this.Core.ParseForExtensionElements(node);
932
933 if (!this.Core.EncounteredError)
934 {
935 this.Core.AddSymbol(new WixInstanceTransformsSymbol(sourceLineNumbers, id)
936 {
937 PropertyId = propertyId,
938 ProductCode = productCode,
939 ProductName = productName,
940 UpgradeCode = upgradeCode
941 });
942 }
943 }
944
945 /// <summary>
946 /// Parses a category element.
947 /// </summary>
948 /// <param name="node">Element to parse.</param>
949 /// <param name="componentId">Identifier of parent component.</param>
950 private void ParseCategoryElement(XElement node, string componentId)
951 {
952 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
953 string id = null;
954 string appData = null;
955 string feature = null;
956 string qualifier = null;
957
958 foreach (var attrib in node.Attributes())
959 {
960 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
961 {
962 switch (attrib.Name.LocalName)
963 {
964 case "Id":
965 id = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib, false);
966 break;
967 case "AppData":
968 appData = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
969 break;
970 case "Feature":
971 feature = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
972 this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Feature, feature);
973 break;
974 case "Qualifier":
975 qualifier = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
976 break;
977 default:
978 this.Core.UnexpectedAttribute(node, attrib);
979 break;
980 }
981 }
982 else
983 {
984 this.Core.ParseExtensionAttribute(node, attrib);
985 }
986 }
987
988 if (null == id)
989 {
990 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id"));
991 }
992
993 if (null == qualifier)
994 {
995 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Qualifier"));
996 }
997
998 this.Core.ParseForExtensionElements(node);
999
1000 if (!this.Core.EncounteredError)
1001 {
1002 this.Core.AddSymbol(new PublishComponentSymbol(sourceLineNumbers)
1003 {
1004 ComponentId = id,
1005 Qualifier = qualifier,
1006 ComponentRef = componentId,
1007 AppData = appData,
1008 FeatureRef = feature ?? Guid.Empty.ToString("B"),
1009 });
1010 }
1011 }
1012
1013 /// <summary>
1014 /// Parses a class element.
1015 /// </summary>
1016 /// <param name="node">Element to parse.</param>
1017 /// <param name="componentId">Identifier of parent component.</param>
1018 /// <param name="advertise">Optional Advertise State for the parent AppId element (if any).</param>
1019 /// <param name="fileServer">Optional file identifier for CLSID when not advertised.</param>
1020 /// <param name="typeLibId">Optional TypeLib GUID for CLSID.</param>
1021 /// <param name="typeLibVersion">Optional TypeLib Version for CLSID Interfaces (if any).</param>
1022 /// <param name="parentAppId">Optional parent AppId.</param>
1023 private void ParseClassElement(XElement node, string componentId, YesNoType advertise, string fileServer, string typeLibId, string typeLibVersion, string parentAppId)
1024 {
1025 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
1026
1027 string appId = null;
1028 string argument = null;
1029 var class16bit = false;
1030 var class32bit = false;
1031 string classId = null;
1032 var classAdvertise = YesNoType.NotSet;
1033 var contexts = new string[0];
1034 string formattedContextString = null;
1035 var control = false;
1036 string defaultInprocHandler = null;
1037 string defaultProgId = null;
1038 string description = null;
1039 string fileTypeMask = null;
1040 string foreignServer = null;
1041 string icon = null;
1042 var iconIndex = CompilerConstants.IntegerNotSet;
1043 string insertable = null;
1044 string localFileServer = null;
1045 var programmable = false;
1046 var relativePath = YesNoType.NotSet;
1047 var safeForInit = false;
1048 var safeForScripting = false;
1049 var shortServerPath = false;
1050 string threadingModel = null;
1051 string version = null;
1052
1053 foreach (var attrib in node.Attributes())
1054 {
1055 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
1056 {
1057 switch (attrib.Name.LocalName)
1058 {
1059 case "Id":
1060 classId = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib, false);
1061 break;
1062 case "Advertise":
1063 classAdvertise = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
1064 break;
1065 case "AppId":
1066 appId = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib, false);
1067 break;
1068 case "Argument":
1069 argument = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
1070 break;
1071 case "Context":
1072 contexts = this.Core.GetAttributeValue(sourceLineNumbers, attrib).Split("\r\n\t ".ToCharArray(), StringSplitOptions.RemoveEmptyEntries);
1073 break;
1074 case "Control":
1075 control = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
1076 break;
1077 case "Description":
1078 description = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
1079 break;
1080 case "Handler":
1081 defaultInprocHandler = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
1082 break;
1083 case "Icon":
1084 icon = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
1085 break;
1086 case "IconIndex":
1087 iconIndex = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, Int16.MinValue + 1, Int16.MaxValue);
1088 break;
1089 case "RelativePath":
1090 relativePath = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
1091 break;
1092
1093 // The following attributes result in rows always added to the Registry table rather than the Class table
1094 case "Insertable":
1095 insertable = (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) ? "Insertable" : "NotInsertable";
1096 break;
1097 case "Programmable":
1098 programmable = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
1099 break;
1100 case "SafeForInitializing":
1101 safeForInit = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
1102 break;
1103 case "SafeForScripting":
1104 safeForScripting = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
1105 break;
1106 case "ForeignServer":
1107 foreignServer = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
1108 break;
1109 case "Server":
1110 localFileServer = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
1111 break;
1112 case "ShortPath":
1113 shortServerPath = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
1114 break;
1115 case "ThreadingModel":
1116 threadingModel = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
1117 break;
1118 case "Version":
1119 version = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
1120 break;
1121 default:
1122 this.Core.UnexpectedAttribute(node, attrib);
1123 break;
1124 }
1125 }
1126 else
1127 {
1128 this.Core.ParseExtensionAttribute(node, attrib);
1129 }
1130 }
1131
1132 if (null == classId)
1133 {
1134 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id"));
1135 }
1136
1137 var uniqueContexts = new HashSet<string>();
1138 foreach (var context in contexts)
1139 {
1140 if (uniqueContexts.Contains(context))
1141 {
1142 this.Core.Write(ErrorMessages.DuplicateContextValue(sourceLineNumbers, context));
1143 }
1144 else
1145 {
1146 uniqueContexts.Add(context);
1147 }
1148
1149 if (context.EndsWith("32", StringComparison.Ordinal))
1150 {
1151 class32bit = true;
1152 }
1153 else
1154 {
1155 class16bit = true;
1156 }
1157 }
1158
1159 if ((YesNoType.No == advertise && YesNoType.Yes == classAdvertise) || (YesNoType.Yes == advertise && YesNoType.No == classAdvertise))
1160 {
1161 this.Core.Write(ErrorMessages.AdvertiseStateMustMatch(sourceLineNumbers, classAdvertise.ToString(), advertise.ToString()));
1162 }
1163 else
1164 {
1165 advertise = classAdvertise;
1166 }
1167
1168 // If the advertise state has not been set, default to non-advertised.
1169 if (YesNoType.NotSet == advertise)
1170 {
1171 advertise = YesNoType.No;
1172 }
1173
1174 if (YesNoType.Yes == advertise && 0 == contexts.Length)
1175 {
1176 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Context", "Advertise", "yes"));
1177 }
1178
1179 if (!String.IsNullOrEmpty(parentAppId) && !String.IsNullOrEmpty(appId))
1180 {
1181 this.Core.Write(ErrorMessages.IllegalAttributeWhenNested(sourceLineNumbers, node.Name.LocalName, "AppId", node.Parent.Name.LocalName));
1182 }
1183
1184 if (!String.IsNullOrEmpty(localFileServer))
1185 {
1186 this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.File, localFileServer);
1187 }
1188
1189 // Local variables used strictly for child node processing.
1190 var fileTypeMaskIndex = 0;
1191 var firstProgIdForClass = YesNoType.Yes;
1192
1193 foreach (var child in node.Elements())
1194 {
1195 if (CompilerCore.WixNamespace == child.Name.Namespace)
1196 {
1197 switch (child.Name.LocalName)
1198 {
1199 case "FileTypeMask":
1200 if (YesNoType.Yes == advertise)
1201 {
1202 fileTypeMask = String.Concat(fileTypeMask, null == fileTypeMask ? String.Empty : ";", this.ParseFileTypeMaskElement(child));
1203 }
1204 else if (YesNoType.No == advertise)
1205 {
1206 var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child);
1207 this.Core.CreateRegistryStringSymbol(childSourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("FileType\\", classId, "\\", fileTypeMaskIndex.ToString()), String.Empty, this.ParseFileTypeMaskElement(child), componentId);
1208 fileTypeMaskIndex++;
1209 }
1210 break;
1211 case "Interface":
1212 this.ParseInterfaceElement(child, componentId, class16bit ? classId : null, class32bit ? classId : null, typeLibId, typeLibVersion);
1213 break;
1214 case "ProgId":
1215 {
1216 var foundExtension = false;
1217 var progId = this.ParseProgIdElement(child, componentId, advertise, classId, description, null, ref foundExtension, firstProgIdForClass);
1218 if (null == defaultProgId)
1219 {
1220 defaultProgId = progId;
1221 }
1222 firstProgIdForClass = YesNoType.No;
1223 }
1224 break;
1225 default:
1226 this.Core.UnexpectedElement(node, child);
1227 break;
1228 }
1229 }
1230 else
1231 {
1232 this.Core.ParseExtensionElement(node, child);
1233 }
1234 }
1235
1236 // If this Class is being advertised.
1237 if (YesNoType.Yes == advertise)
1238 {
1239 if (null != fileServer || null != localFileServer)
1240 {
1241 this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Server", "Advertise", "yes"));
1242 }
1243
1244 if (null != foreignServer)
1245 {
1246 this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "ForeignServer", "Advertise", "yes"));
1247 }
1248
1249 if (null == appId && null != parentAppId)
1250 {
1251 appId = parentAppId;
1252 }
1253
1254 // add a Class row for each context
1255 if (!this.Core.EncounteredError)
1256 {
1257 foreach (var context in contexts)
1258 {
1259 var symbol = this.Core.AddSymbol(new ClassSymbol(sourceLineNumbers)
1260 {
1261 CLSID = classId,
1262 Context = context,
1263 ComponentRef = componentId,
1264 DefaultProgIdRef = defaultProgId,
1265 Description = description,
1266 FileTypeMask = fileTypeMask,
1267 DefInprocHandler = defaultInprocHandler,
1268 Argument = argument,
1269 FeatureRef = Guid.Empty.ToString("B"),
1270 RelativePath = YesNoType.Yes == relativePath,
1271 });
1272
1273 if (null != appId)
1274 {
1275 symbol.AppIdRef = appId;
1276 this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.AppId, appId);
1277 }
1278
1279 if (null != icon)
1280 {
1281 symbol.IconRef = icon;
1282 this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Icon, icon);
1283 }
1284
1285 if (CompilerConstants.IntegerNotSet != iconIndex)
1286 {
1287 symbol.IconIndex = iconIndex;
1288 }
1289 }
1290 }
1291 }
1292 else if (YesNoType.No == advertise)
1293 {
1294 if (null == fileServer && null == localFileServer && null == foreignServer)
1295 {
1296 this.Core.Write(ErrorMessages.ExpectedAttributes(sourceLineNumbers, node.Name.LocalName, "ForeignServer", "Server"));
1297 }
1298
1299 if (null != fileServer && null != foreignServer)
1300 {
1301 this.Core.Write(ErrorMessages.IllegalAttributeWhenNested(sourceLineNumbers, node.Name.LocalName, "ForeignServer", "File"));
1302 }
1303 else if (null != localFileServer && null != foreignServer)
1304 {
1305 this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "ForeignServer", "Server"));
1306 }
1307 else if (null == fileServer)
1308 {
1309 fileServer = localFileServer;
1310 }
1311
1312 if (null != appId) // need to use nesting (not a reference) for the unadvertised Class elements
1313 {
1314 this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "AppId", "Advertise", "no"));
1315 }
1316
1317 // add the core registry keys for each context in the class
1318 foreach (var context in contexts)
1319 {
1320 if (context.StartsWith("InprocServer", StringComparison.Ordinal)) // dll server
1321 {
1322 if (null != argument)
1323 {
1324 this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Arguments", "Context", context));
1325 }
1326
1327 if (null != fileServer)
1328 {
1329 formattedContextString = String.Concat("[", shortServerPath ? "!" : "#", fileServer, "]");
1330 }
1331 else if (null != foreignServer)
1332 {
1333 formattedContextString = foreignServer;
1334 }
1335 }
1336 else if (context.StartsWith("LocalServer", StringComparison.Ordinal)) // exe server (quote the long path)
1337 {
1338 if (null != fileServer)
1339 {
1340 if (shortServerPath)
1341 {
1342 formattedContextString = String.Concat("[!", fileServer, "]");
1343 }
1344 else
1345 {
1346 formattedContextString = String.Concat("\"[#", fileServer, "]\"");
1347 }
1348 }
1349 else if (null != foreignServer)
1350 {
1351 formattedContextString = foreignServer;
1352 }
1353
1354 if (null != argument)
1355 {
1356 formattedContextString = String.Concat(formattedContextString, " ", argument);
1357 }
1358 }
1359 else
1360 {
1361 this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, "Context", context, "InprocServer", "InprocServer32", "LocalServer", "LocalServer32"));
1362 }
1363
1364 this.Core.CreateRegistryStringSymbol(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("CLSID\\", classId, "\\", context), String.Empty, formattedContextString, componentId); // ClassId context
1365
1366 if (null != icon) // ClassId default icon
1367 {
1368 this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.File, icon);
1369
1370 icon = String.Format(CultureInfo.InvariantCulture, "\"[#{0}]\"", icon);
1371
1372 if (CompilerConstants.IntegerNotSet != iconIndex)
1373 {
1374 icon = String.Concat(icon, ",", iconIndex);
1375 }
1376 this.Core.CreateRegistryStringSymbol(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("CLSID\\", classId, "\\DefaultIcon"), String.Empty, icon, componentId);
1377 }
1378 }
1379
1380 if (null != parentAppId) // ClassId AppId (must be specified via nesting, not with the AppId attribute)
1381 {
1382 this.Core.CreateRegistryStringSymbol(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("CLSID\\", classId), "AppID", parentAppId, componentId);
1383 }
1384
1385 if (null != description) // ClassId description
1386 {
1387 this.Core.CreateRegistryStringSymbol(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("CLSID\\", classId), String.Empty, description, componentId);
1388 }
1389
1390 if (null != defaultInprocHandler)
1391 {
1392 switch (defaultInprocHandler) // ClassId Default Inproc Handler
1393 {
1394 case "1":
1395 this.Core.CreateRegistryStringSymbol(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("CLSID\\", classId, "\\InprocHandler"), String.Empty, "ole2.dll", componentId);
1396 break;
1397 case "2":
1398 this.Core.CreateRegistryStringSymbol(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("CLSID\\", classId, "\\InprocHandler32"), String.Empty, "ole32.dll", componentId);
1399 break;
1400 case "3":
1401 this.Core.CreateRegistryStringSymbol(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("CLSID\\", classId, "\\InprocHandler"), String.Empty, "ole2.dll", componentId);
1402 this.Core.CreateRegistryStringSymbol(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("CLSID\\", classId, "\\InprocHandler32"), String.Empty, "ole32.dll", componentId);
1403 break;
1404 default:
1405 this.Core.CreateRegistryStringSymbol(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("CLSID\\", classId, "\\InprocHandler32"), String.Empty, defaultInprocHandler, componentId);
1406 break;
1407 }
1408 }
1409
1410 if (YesNoType.NotSet != relativePath) // ClassId's RelativePath
1411 {
1412 this.Core.Write(ErrorMessages.RelativePathForRegistryElement(sourceLineNumbers));
1413 }
1414 }
1415
1416 if (null != threadingModel)
1417 {
1418 threadingModel = Compiler.UppercaseFirstChar(threadingModel);
1419
1420 // add a threading model for each context in the class
1421 foreach (var context in contexts)
1422 {
1423 this.Core.CreateRegistryStringSymbol(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("CLSID\\", classId, "\\", context), "ThreadingModel", threadingModel, componentId);
1424 }
1425 }
1426
1427 if (null != typeLibId)
1428 {
1429 this.Core.CreateRegistryStringSymbol(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("CLSID\\", classId, "\\TypeLib"), null, typeLibId, componentId);
1430 }
1431
1432 if (null != version)
1433 {
1434 this.Core.CreateRegistryStringSymbol(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("CLSID\\", classId, "\\Version"), null, version, componentId);
1435 }
1436
1437 if (null != insertable)
1438 {
1439 // Add "*" for name so that any subkeys (shouldn't be any) are removed on uninstall.
1440 this.Core.CreateRegistryStringSymbol(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("CLSID\\", classId, "\\", insertable), "*", null, componentId);
1441 }
1442
1443 if (control)
1444 {
1445 // Add "*" for name so that any subkeys (shouldn't be any) are removed on uninstall.
1446 this.Core.CreateRegistryStringSymbol(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("CLSID\\", classId, "\\Control"), "*", null, componentId);
1447 }
1448
1449 if (programmable)
1450 {
1451 // Add "*" for name so that any subkeys (shouldn't be any) are removed on uninstall.
1452 this.Core.CreateRegistryStringSymbol(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("CLSID\\", classId, "\\Programmable"), "*", null, componentId);
1453 }
1454
1455 if (safeForInit)
1456 {
1457 this.RegisterImplementedCategories(sourceLineNumbers, "{7DD95802-9882-11CF-9FA9-00AA006C42C4}", classId, componentId);
1458 }
1459
1460 if (safeForScripting)
1461 {
1462 this.RegisterImplementedCategories(sourceLineNumbers, "{7DD95801-9882-11CF-9FA9-00AA006C42C4}", classId, componentId);
1463 }
1464 }
1465
1466 /// <summary>
1467 /// Parses an Interface element.
1468 /// </summary>
1469 /// <param name="node">Element to parse.</param>
1470 /// <param name="componentId">Identifier of parent component.</param>
1471 /// <param name="proxyId">16-bit proxy for interface.</param>
1472 /// <param name="proxyId32">32-bit proxy for interface.</param>
1473 /// <param name="typeLibId">Optional TypeLib GUID for CLSID.</param>
1474 /// <param name="typelibVersion">Version of the TypeLib to which this interface belongs. Required if typeLibId is specified</param>
1475 private void ParseInterfaceElement(XElement node, string componentId, string proxyId, string proxyId32, string typeLibId, string typelibVersion)
1476 {
1477 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
1478 string baseInterface = null;
1479 string interfaceId = null;
1480 string name = null;
1481 var numMethods = CompilerConstants.IntegerNotSet;
1482 var versioned = true;
1483
1484 foreach (var attrib in node.Attributes())
1485 {
1486 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
1487 {
1488 switch (attrib.Name.LocalName)
1489 {
1490 case "Id":
1491 interfaceId = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib, false);
1492 break;
1493 case "BaseInterface":
1494 baseInterface = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib, false);
1495 break;
1496 case "Name":
1497 name = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
1498 break;
1499 case "NumMethods":
1500 numMethods = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int32.MaxValue);
1501 break;
1502 case "ProxyStubClassId":
1503 proxyId = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib);
1504 break;
1505 case "ProxyStubClassId32":
1506 proxyId32 = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib, false);
1507 break;
1508 case "Versioned":
1509 versioned = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
1510 break;
1511 default:
1512 this.Core.UnexpectedAttribute(node, attrib);
1513 break;
1514 }
1515 }
1516 else
1517 {
1518 this.Core.ParseExtensionAttribute(node, attrib);
1519 }
1520 }
1521
1522 if (null == interfaceId)
1523 {
1524 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id"));
1525 }
1526
1527 if (null == name)
1528 {
1529 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name"));
1530 }
1531
1532 this.Core.ParseForExtensionElements(node);
1533
1534 this.Core.CreateRegistryStringSymbol(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("Interface\\", interfaceId), null, name, componentId);
1535 if (null != typeLibId)
1536 {
1537 this.Core.CreateRegistryStringSymbol(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("Interface\\", interfaceId, "\\TypeLib"), null, typeLibId, componentId);
1538 if (versioned)
1539 {
1540 this.Core.CreateRegistryStringSymbol(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("Interface\\", interfaceId, "\\TypeLib"), "Version", typelibVersion, componentId);
1541 }
1542 }
1543
1544 if (null != baseInterface)
1545 {
1546 this.Core.CreateRegistryStringSymbol(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("Interface\\", interfaceId, "\\BaseInterface"), null, baseInterface, componentId);
1547 }
1548
1549 if (CompilerConstants.IntegerNotSet != numMethods)
1550 {
1551 this.Core.CreateRegistryStringSymbol(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("Interface\\", interfaceId, "\\NumMethods"), null, numMethods.ToString(), componentId);
1552 }
1553
1554 if (null != proxyId)
1555 {
1556 this.Core.CreateRegistryStringSymbol(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("Interface\\", interfaceId, "\\ProxyStubClsid"), null, proxyId, componentId);
1557 }
1558
1559 if (null != proxyId32)
1560 {
1561 this.Core.CreateRegistryStringSymbol(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("Interface\\", interfaceId, "\\ProxyStubClsid32"), null, proxyId32, componentId);
1562 }
1563 }
1564
1565 /// <summary>
1566 /// Parses a CLSID's file type mask element.
1567 /// </summary>
1568 /// <param name="node">Element to parse.</param>
1569 /// <returns>String representing the file type mask elements.</returns>
1570 private string ParseFileTypeMaskElement(XElement node)
1571 {
1572 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
1573 var cb = 0;
1574 var offset = CompilerConstants.IntegerNotSet;
1575 string mask = null;
1576 string value = null;
1577
1578 foreach (var attrib in node.Attributes())
1579 {
1580 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
1581 {
1582 switch (attrib.Name.LocalName)
1583 {
1584 case "Mask":
1585 mask = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
1586 break;
1587 case "Offset":
1588 offset = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int32.MaxValue);
1589 break;
1590 case "Value":
1591 value = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
1592 break;
1593 default:
1594 this.Core.UnexpectedAttribute(node, attrib);
1595 break;
1596 }
1597 }
1598 else
1599 {
1600 this.Core.ParseExtensionAttribute(node, attrib);
1601 }
1602 }
1603
1604
1605 if (null == mask)
1606 {
1607 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Mask"));
1608 }
1609
1610 if (CompilerConstants.IntegerNotSet == offset)
1611 {
1612 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Offset"));
1613 }
1614
1615 if (null == value)
1616 {
1617 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Value"));
1618 }
1619
1620 this.Core.ParseForExtensionElements(node);
1621
1622 if (!this.Core.EncounteredError)
1623 {
1624 if (mask.Length != value.Length)
1625 {
1626 this.Core.Write(ErrorMessages.ValueAndMaskMustBeSameLength(sourceLineNumbers));
1627 }
1628 cb = mask.Length / 2;
1629 }
1630
1631 return String.Concat(offset.ToString(CultureInfo.InvariantCulture.NumberFormat), ",", cb.ToString(CultureInfo.InvariantCulture.NumberFormat), ",", mask, ",", value);
1632 }
1633
1634 /// <summary>
1635 /// Parses a product search element.
1636 /// </summary>
1637 /// <param name="node">Element to parse.</param>
1638 /// <param name="propertyId"></param>
1639 /// <returns>Signature for search element.</returns>
1640 private void ParseProductSearchElement(XElement node, string propertyId)
1641 {
1642 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
1643
1644 string upgradeCode = null;
1645 string language = null;
1646 string maximum = null;
1647 string minimum = null;
1648 var excludeLanguages = false;
1649 var maxInclusive = false;
1650 var minInclusive = true;
1651
1652 foreach (var attrib in node.Attributes())
1653 {
1654 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
1655 {
1656 switch (attrib.Name.LocalName)
1657 {
1658 case "ExcludeLanguages":
1659 excludeLanguages = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
1660 break;
1661 case "IncludeMaximum":
1662 maxInclusive = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
1663 break;
1664 case "IncludeMinimum":
1665 minInclusive = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
1666 break;
1667 case "Language":
1668 language = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
1669 break;
1670 case "Minimum":
1671 minimum = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
1672 break;
1673 case "Maximum":
1674 maximum = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
1675 break;
1676 case "UpgradeCode":
1677 upgradeCode = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib, false);
1678 break;
1679 default:
1680 this.Core.UnexpectedAttribute(node, attrib);
1681 break;
1682 }
1683 }
1684 else
1685 {
1686 this.Core.ParseExtensionAttribute(node, attrib);
1687 }
1688 }
1689
1690 if (null == minimum && null == maximum)
1691 {
1692 this.Core.Write(ErrorMessages.ExpectedAttributes(sourceLineNumbers, node.Name.LocalName, "Minimum", "Maximum"));
1693 }
1694
1695 this.Core.ParseForExtensionElements(node);
1696
1697 if (!this.Core.EncounteredError)
1698 {
1699 this.Core.AddSymbol(new UpgradeSymbol(sourceLineNumbers)
1700 {
1701 UpgradeCode = upgradeCode,
1702 VersionMin = minimum,
1703 VersionMax = maximum,
1704 Language = language,
1705 ActionProperty = propertyId,
1706 OnlyDetect = true,
1707 ExcludeLanguages = excludeLanguages,
1708 VersionMaxInclusive = maxInclusive,
1709 VersionMinInclusive = minInclusive,
1710 });
1711 }
1712 }
1713
1714 /// <summary>
1715 /// Parses a registry search element.
1716 /// </summary>
1717 /// <param name="node">Element to parse.</param>
1718 /// <returns>Signature for search element.</returns>
1719 private string ParseRegistrySearchElement(XElement node)
1720 {
1721 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
1722 Identifier id = null;
1723 string key = null;
1724 string name = null;
1725 RegistryRootType? root = null;
1726 RegLocatorType? type = null;
1727 var search64bit = this.Context.IsCurrentPlatform64Bit;
1728
1729 foreach (var attrib in node.Attributes())
1730 {
1731 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
1732 {
1733 switch (attrib.Name.LocalName)
1734 {
1735 case "Id":
1736 id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib);
1737 break;
1738 case "Bitness":
1739 var bitnessValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
1740 switch (bitnessValue)
1741 {
1742 case "always32":
1743 search64bit = false;
1744 break;
1745 case "always64":
1746 search64bit = true;
1747 break;
1748 case "default":
1749 case "":
1750 break;
1751 default:
1752 this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, bitnessValue, "default", "always32", "always64"));
1753 break;
1754 }
1755 break;
1756 case "Key":
1757 key = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
1758 break;
1759 case "Name":
1760 name = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
1761 break;
1762 case "Root":
1763 root = this.Core.GetAttributeRegistryRootValue(sourceLineNumbers, attrib, false);
1764 break;
1765 case "Type":
1766 var typeValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
1767 switch (typeValue)
1768 {
1769 case "directory":
1770 type = RegLocatorType.Directory;
1771 break;
1772 case "file":
1773 type = RegLocatorType.FileName;
1774 break;
1775 case "raw":
1776 type = RegLocatorType.Raw;
1777 break;
1778 case "":
1779 break;
1780 default:
1781 this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, "Type", typeValue, "directory", "file", "raw"));
1782 break;
1783 }
1784 break;
1785 default:
1786 this.Core.UnexpectedAttribute(node, attrib);
1787 break;
1788 }
1789 }
1790 else
1791 {
1792 this.Core.ParseExtensionAttribute(node, attrib);
1793 }
1794 }
1795
1796 if (null == id)
1797 {
1798 id = this.Core.CreateIdentifier("reg", root.ToString(), key, name, type.ToString(), search64bit.ToString());
1799 }
1800
1801 if (null == key)
1802 {
1803 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Key"));
1804 }
1805
1806 if (!root.HasValue)
1807 {
1808 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Root"));
1809 }
1810
1811 if (!type.HasValue)
1812 {
1813 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Type"));
1814 }
1815
1816 var signature = id.Id;
1817 var oneChild = false;
1818 foreach (var child in node.Elements())
1819 {
1820 if (CompilerCore.WixNamespace == child.Name.Namespace)
1821 {
1822 switch (child.Name.LocalName)
1823 {
1824 case "DirectorySearch":
1825 if (oneChild)
1826 {
1827 this.Core.Write(ErrorMessages.TooManySearchElements(sourceLineNumbers, node.Name.LocalName));
1828 }
1829 oneChild = true;
1830
1831 // directorysearch parentage should work like directory element, not the rest of the signature type because of the DrLocator.Parent column
1832 signature = this.ParseDirectorySearchElement(child, id.Id);
1833 break;
1834 case "DirectorySearchRef":
1835 if (oneChild)
1836 {
1837 this.Core.Write(ErrorMessages.TooManySearchElements(sourceLineNumbers, node.Name.LocalName));
1838 }
1839 oneChild = true;
1840 signature = this.ParseDirectorySearchRefElement(child, id.Id);
1841 break;
1842 case "FileSearch":
1843 if (oneChild)
1844 {
1845 this.Core.Write(ErrorMessages.TooManySearchElements(sourceLineNumbers, node.Name.LocalName));
1846 }
1847 oneChild = true;
1848 signature = this.ParseFileSearchElement(child, id.Id, false, CompilerConstants.IntegerNotSet);
1849 id = new Identifier(AccessModifier.Section, signature); // FileSearch signatures override parent signatures
1850 break;
1851 case "FileSearchRef":
1852 if (oneChild)
1853 {
1854 this.Core.Write(ErrorMessages.TooManySearchElements(sourceLineNumbers, node.Name.LocalName));
1855 }
1856 oneChild = true;
1857 var newId = this.ParseSimpleRefElement(child, SymbolDefinitions.Signature); // FileSearch signatures override parent signatures
1858 id = new Identifier(AccessModifier.Section, newId);
1859 signature = null;
1860 break;
1861 default:
1862 this.Core.UnexpectedElement(node, child);
1863 break;
1864 }
1865 }
1866 else
1867 {
1868 this.Core.ParseExtensionElement(node, child);
1869 }
1870 }
1871
1872 if (!this.Core.EncounteredError)
1873 {
1874 this.Core.AddSymbol(new RegLocatorSymbol(sourceLineNumbers, id)
1875 {
1876 Root = root.Value,
1877 Key = key,
1878 Name = name,
1879 Type = type.Value,
1880 Win64 = search64bit,
1881 });
1882 }
1883
1884 return signature;
1885 }
1886
1887 /// <summary>
1888 /// Parses a registry search reference element.
1889 /// </summary>
1890 /// <param name="node">Element to parse.</param>
1891 /// <returns>Signature of referenced search element.</returns>
1892 private string ParseRegistrySearchRefElement(XElement node)
1893 {
1894 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
1895 string id = null;
1896
1897 foreach (var attrib in node.Attributes())
1898 {
1899 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
1900 {
1901 switch (attrib.Name.LocalName)
1902 {
1903 case "Id":
1904 id = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
1905 this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.RegLocator, id);
1906 break;
1907 default:
1908 this.Core.UnexpectedAttribute(node, attrib);
1909 break;
1910 }
1911 }
1912 else
1913 {
1914 this.Core.ParseExtensionAttribute(node, attrib);
1915 }
1916 }
1917
1918 if (null == id)
1919 {
1920 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id"));
1921 }
1922
1923 this.Core.ParseForExtensionElements(node);
1924
1925 return id; // the id of the RegistrySearchRef element is its signature
1926 }
1927
1928 /// <summary>
1929 /// Parses child elements for search signatures.
1930 /// </summary>
1931 /// <param name="node">Node whose children we are parsing.</param>
1932 /// <returns>Returns list of string signatures.</returns>
1933 private List<string> ParseSearchSignatures(XElement node)
1934 {
1935 var signatures = new List<string>();
1936
1937 foreach (var child in node.Elements())
1938 {
1939 string signature = null;
1940 if (CompilerCore.WixNamespace == child.Name.Namespace)
1941 {
1942 switch (child.Name.LocalName)
1943 {
1944 case "ComplianceDrive":
1945 signature = this.ParseComplianceDriveElement(child);
1946 break;
1947 case "ComponentSearch":
1948 signature = this.ParseComponentSearchElement(child);
1949 break;
1950 case "DirectorySearch":
1951 signature = this.ParseDirectorySearchElement(child, null);
1952 break;
1953 case "DirectorySearchRef":
1954 signature = this.ParseDirectorySearchRefElement(child, null);
1955 break;
1956 case "IniFileSearch":
1957 signature = this.ParseIniFileSearchElement(child);
1958 break;
1959 case "ProductSearch":
1960 // handled in ParsePropertyElement
1961 break;
1962 case "RegistrySearch":
1963 signature = this.ParseRegistrySearchElement(child);
1964 break;
1965 case "RegistrySearchRef":
1966 signature = this.ParseRegistrySearchRefElement(child);
1967 break;
1968 default:
1969 this.Core.UnexpectedElement(node, child);
1970 break;
1971 }
1972 }
1973 else
1974 {
1975 this.Core.ParseExtensionElement(node, child);
1976 }
1977
1978
1979 if (!String.IsNullOrEmpty(signature))
1980 {
1981 signatures.Add(signature);
1982 }
1983 }
1984
1985 return signatures;
1986 }
1987
1988 /// <summary>
1989 /// Parses a compliance drive element.
1990 /// </summary>
1991 /// <param name="node">Element to parse.</param>
1992 /// <returns>Signature of nested search elements.</returns>
1993 private string ParseComplianceDriveElement(XElement node)
1994 {
1995 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
1996 string signature = null;
1997
1998 var oneChild = false;
1999 foreach (var child in node.Elements())
2000 {
2001 if (CompilerCore.WixNamespace == child.Name.Namespace)
2002 {
2003 var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
2004 switch (child.Name.LocalName)
2005 {
2006 case "DirectorySearch":
2007 if (oneChild)
2008 {
2009 this.Core.Write(ErrorMessages.TooManySearchElements(childSourceLineNumbers, node.Name.LocalName));
2010 }
2011 oneChild = true;
2012 signature = this.ParseDirectorySearchElement(child, "CCP_DRIVE");
2013 break;
2014 case "DirectorySearchRef":
2015 if (oneChild)
2016 {
2017 this.Core.Write(ErrorMessages.TooManySearchElements(childSourceLineNumbers, node.Name.LocalName));
2018 }
2019 oneChild = true;
2020 signature = this.ParseDirectorySearchRefElement(child, "CCP_DRIVE");
2021 break;
2022 default:
2023 this.Core.UnexpectedElement(node, child);
2024 break;
2025 }
2026 }
2027 else
2028 {
2029 this.Core.ParseExtensionElement(node, child);
2030 }
2031 }
2032
2033 if (null == signature)
2034 {
2035 this.Core.Write(ErrorMessages.SearchElementRequired(sourceLineNumbers, node.Name.LocalName));
2036 }
2037
2038 return signature;
2039 }
2040
2041 /// <summary>
2042 /// Parses a compilance check element.
2043 /// </summary>
2044 /// <param name="node">Element to parse.</param>
2045 private void ParseComplianceCheckElement(XElement node)
2046 {
2047 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
2048
2049 foreach (var attrib in node.Attributes())
2050 {
2051 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
2052 {
2053 switch (attrib.Name.LocalName)
2054 {
2055 default:
2056 this.Core.UnexpectedAttribute(node, attrib);
2057 break;
2058 }
2059 }
2060 else
2061 {
2062 this.Core.ParseExtensionAttribute(node, attrib);
2063 }
2064 }
2065
2066 string signature = null;
2067
2068 // see if this property is used for appSearch
2069 var signatures = this.ParseSearchSignatures(node);
2070 foreach (var sig in signatures)
2071 {
2072 // if we haven't picked a signature for this ComplianceCheck pick
2073 // this one
2074 if (null == signature)
2075 {
2076 signature = sig;
2077 }
2078 else if (signature != sig)
2079 {
2080 // all signatures under a ComplianceCheck must be the same
2081 this.Core.Write(ErrorMessages.MultipleIdentifiersFound(sourceLineNumbers, node.Name.LocalName, sig, signature));
2082 }
2083 }
2084
2085 if (null == signature)
2086 {
2087 this.Core.Write(ErrorMessages.SearchElementRequired(sourceLineNumbers, node.Name.LocalName));
2088 }
2089
2090 if (!this.Core.EncounteredError)
2091 {
2092 this.Core.AddSymbol(new CCPSearchSymbol(sourceLineNumbers, new Identifier(AccessModifier.Section, signature)));
2093 }
2094 }
2095
2096 /// <summary>
2097 /// Parses a component element.
2098 /// </summary>
2099 /// <param name="node">Element to parse.</param>
2100 /// <param name="parentType">Type of component's complex reference parent. Will be Unknown if there is no parent.</param>
2101 /// <param name="parentId">Optional identifier for component's primary parent.</param>
2102 /// <param name="parentLanguage">Optional string for component's parent's language.</param>
2103 /// <param name="diskId">Optional disk id inherited from parent directory.</param>
2104 /// <param name="directoryId">Optional identifier for component's directory.</param>
2105 /// <param name="srcPath">Optional source path for files up to this point.</param>
2106 private void ParseComponentElement(XElement node, ComplexReferenceParentType parentType, string parentId, string parentLanguage, int diskId, string directoryId, string srcPath)
2107 {
2108 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
2109
2110 var comPlusBits = CompilerConstants.IntegerNotSet;
2111 string condition = null;
2112 string subdirectory = null;
2113 var encounteredODBCDataSource = false;
2114 var files = 0;
2115 var guid = "*";
2116 Identifier id = null;
2117 string componentIdPlaceholder = null;
2118 var keyFound = false;
2119 Identifier keyPath = null;
2120
2121 var keyPathType = ComponentKeyPathType.Directory;
2122 var location = ComponentLocation.LocalOnly;
2123 var disableRegistryReflection = false;
2124
2125 var neverOverwrite = false;
2126 var permanent = false;
2127 var shared = false;
2128 var sharedDllRefCount = false;
2129 var transitive = false;
2130 var uninstallWhenSuperseded = false;
2131 var win64 = this.Context.IsCurrentPlatform64Bit;
2132
2133 var multiInstance = false;
2134 var symbols = new List<string>();
2135 string feature = null;
2136
2137 foreach (var attrib in node.Attributes())
2138 {
2139 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
2140 {
2141 switch (attrib.Name.LocalName)
2142 {
2143 case "Id":
2144 id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib);
2145 break;
2146 case "Bitness":
2147 var bitnessValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
2148 switch (bitnessValue)
2149 {
2150 case "always32":
2151 win64 = false;
2152 break;
2153 case "always64":
2154 win64 = true;
2155 break;
2156 case "default":
2157 case "":
2158 break;
2159 default:
2160 this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, bitnessValue, "default", "always32", "always64"));
2161 break;
2162 }
2163 break;
2164 case "ComPlusFlags":
2165 comPlusBits = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int16.MaxValue);
2166 break;
2167 case "DisableRegistryReflection":
2168 disableRegistryReflection = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
2169 break;
2170 case "Condition":
2171 condition = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
2172 break;
2173 case "Directory":
2174 directoryId = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
2175 this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Directory, directoryId);
2176 break;
2177 case "Subdirectory":
2178 subdirectory = this.Core.GetAttributeLongFilename(sourceLineNumbers, attrib, allowRelative: true);
2179 break;
2180 case "DiskId":
2181 diskId = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 1, Int16.MaxValue);
2182 break;
2183 case "Feature":
2184 feature = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
2185 break;
2186 case "Guid":
2187 guid = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib, true, true);
2188 break;
2189 case "KeyPath":
2190 if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib))
2191 {
2192 keyFound = true;
2193 keyPath = null;
2194 }
2195 break;
2196 case "Location":
2197 var locationValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
2198 switch (locationValue)
2199 {
2200 case "either":
2201 location = ComponentLocation.Either;
2202 break;
2203 case "local": // this is the default
2204 location = ComponentLocation.LocalOnly;
2205 break;
2206 case "source":
2207 location = ComponentLocation.SourceOnly;
2208 break;
2209 case "":
2210 break;
2211 default:
2212 this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, locationValue, "either", "local", "source"));
2213 break;
2214 }
2215 break;
2216 case "MultiInstance":
2217 multiInstance = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
2218 break;
2219 case "NeverOverwrite":
2220 neverOverwrite = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
2221 break;
2222 case "Permanent":
2223 permanent = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
2224 break;
2225 case "Shared":
2226 shared = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
2227 break;
2228 case "SharedDllRefCount":
2229 sharedDllRefCount = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
2230 break;
2231 case "Transitive":
2232 transitive = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
2233 break;
2234 case "UninstallWhenSuperseded":
2235 uninstallWhenSuperseded = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
2236 break;
2237 default:
2238 this.Core.UnexpectedAttribute(node, attrib);
2239 break;
2240 }
2241 }
2242 else
2243 {
2244 this.Core.ParseExtensionAttribute(node, attrib);
2245 }
2246 }
2247
2248 if (id == null)
2249 {
2250 // Placeholder id for defaulting Component/@Id to keypath id.
2251 componentIdPlaceholder = String.Concat(Compiler.ComponentIdPlaceholderStart, this.componentIdPlaceholders.Count, Compiler.ComponentIdPlaceholderEnd);
2252 id = new Identifier(AccessModifier.Section, componentIdPlaceholder);
2253 }
2254
2255 if (String.IsNullOrEmpty(directoryId))
2256 {
2257 directoryId = "INSTALLFOLDER";
2258 this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Directory, directoryId);
2259 }
2260
2261 if (!String.IsNullOrEmpty(subdirectory))
2262 {
2263 directoryId = this.Core.CreateDirectoryReferenceFromInlineSyntax(sourceLineNumbers, directoryId, subdirectory);
2264 }
2265
2266 if (String.IsNullOrEmpty(guid) && shared)
2267 {
2268 this.Core.Write(ErrorMessages.IllegalAttributeValueWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Shared", "yes", "Guid", ""));
2269 }
2270
2271 if (String.IsNullOrEmpty(guid) && permanent)
2272 {
2273 this.Core.Write(ErrorMessages.IllegalAttributeValueWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Permanent", "yes", "Guid", ""));
2274 }
2275
2276 if (null != feature)
2277 {
2278 if (this.compilingModule)
2279 {
2280 this.Core.Write(ErrorMessages.IllegalAttributeInMergeModule(sourceLineNumbers, node.Name.LocalName, "Feature"));
2281 }
2282 else
2283 {
2284 if (ComplexReferenceParentType.Feature == parentType || ComplexReferenceParentType.FeatureGroup == parentType)
2285 {
2286 this.Core.Write(ErrorMessages.IllegalAttributeWhenNested(sourceLineNumbers, node.Name.LocalName, "Feature", node.Parent.Name.LocalName));
2287 }
2288 else
2289 {
2290 this.Core.CreateComplexReference(sourceLineNumbers, ComplexReferenceParentType.Feature, feature, null, ComplexReferenceChildType.Component, id.Id, true);
2291 }
2292 }
2293 }
2294
2295 foreach (var child in node.Elements())
2296 {
2297 var keyPathSet = YesNoType.NotSet;
2298 Identifier keyPossible = null;
2299 ComponentKeyPathType? keyBit = null;
2300
2301 if (CompilerCore.WixNamespace == child.Name.Namespace)
2302 {
2303 switch (child.Name.LocalName)
2304 {
2305 case "AppId":
2306 this.ParseAppIdElement(child, id.Id, YesNoType.NotSet, null, null, null);
2307 break;
2308 case "Category":
2309 this.ParseCategoryElement(child, id.Id);
2310 break;
2311 case "Class":
2312 this.ParseClassElement(child, id.Id, YesNoType.NotSet, null, null, null, null);
2313 break;
2314 case "CopyFile":
2315 this.ParseCopyFileElement(child, id.Id, null);
2316 break;
2317 case "CreateFolder":
2318 var createdFolder = this.ParseCreateFolderElement(child, id.Id, directoryId, win64);
2319 break;
2320 case "Environment":
2321 this.ParseEnvironmentElement(child, id.Id);
2322 break;
2323 case "Extension":
2324 this.ParseExtensionElement(child, id.Id, YesNoType.NotSet, null);
2325 break;
2326 case "File":
2327 keyPathSet = this.ParseFileElement(child, id.Id, directoryId, diskId, srcPath, out keyPossible, win64, guid);
2328 keyBit = ComponentKeyPathType.File;
2329 files++;
2330 break;
2331 case "IniFile":
2332 this.ParseIniFileElement(child, id.Id);
2333 break;
2334 case "Interface":
2335 this.ParseInterfaceElement(child, id.Id, null, null, null, null);
2336 break;
2337 case "IsolateComponent":
2338 this.ParseIsolateComponentElement(child, id.Id);
2339 break;
2340 case "ODBCDataSource":
2341 keyPathSet = this.ParseODBCDataSource(child, id.Id, null, out keyPossible);
2342 keyBit = ComponentKeyPathType.OdbcDataSource;
2343 encounteredODBCDataSource = true;
2344 break;
2345 case "ODBCDriver":
2346 this.ParseODBCDriverOrTranslator(child, id.Id, null, SymbolDefinitionType.ODBCDriver);
2347 break;
2348 case "ODBCTranslator":
2349 this.ParseODBCDriverOrTranslator(child, id.Id, null, SymbolDefinitionType.ODBCTranslator);
2350 break;
2351 case "ProgId":
2352 var foundExtension = false;
2353 this.ParseProgIdElement(child, id.Id, YesNoType.NotSet, null, null, null, ref foundExtension, YesNoType.NotSet);
2354 break;
2355 case "Provides":
2356 if (win64)
2357 {
2358 this.Messaging.Write(CompilerWarnings.Win64Component(sourceLineNumbers, id.Id));
2359 }
2360
2361 keyPathSet = this.ParseProvidesElement(child, null, id.Id, out keyPossible);
2362 keyBit = ComponentKeyPathType.Registry;
2363 break;
2364
2365 case "RegistryKey":
2366 keyPathSet = this.ParseRegistryKeyElement(child, id.Id, null, null, win64, out keyPossible);
2367 keyBit = ComponentKeyPathType.Registry;
2368 break;
2369 case "RegistryValue":
2370 keyPathSet = this.ParseRegistryValueElement(child, id.Id, null, null, win64, out keyPossible);
2371 keyBit = ComponentKeyPathType.Registry;
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 var context = new Dictionary<string, string>() { { "ComponentId", id?.Id }, { "DirectoryId", directoryId }, { "Win64", win64.ToString() }, };
2417 var possibleKeyPath = this.Core.ParsePossibleKeyPathExtensionElement(node, child, context);
2418 if (null != possibleKeyPath)
2419 {
2420 if (PossibleKeyPathType.None == possibleKeyPath.Type)
2421 {
2422 keyPathSet = YesNoType.No;
2423 }
2424 else
2425 {
2426 keyPathSet = possibleKeyPath.Explicit ? YesNoType.Yes : YesNoType.NotSet;
2427
2428 switch (possibleKeyPath.Type)
2429 {
2430 case PossibleKeyPathType.File:
2431 keyBit = ComponentKeyPathType.File;
2432 keyPossible = possibleKeyPath.Id;
2433 break;
2434
2435 case PossibleKeyPathType.Directory:
2436 keyBit = ComponentKeyPathType.Directory;
2437 keyPossible = null;
2438 break;
2439
2440 case PossibleKeyPathType.OdbcDataSource:
2441 keyBit = ComponentKeyPathType.OdbcDataSource;
2442 keyPossible = possibleKeyPath.Id;
2443 break;
2444
2445 case PossibleKeyPathType.Registry:
2446 case PossibleKeyPathType.RegistryFormatted:
2447 keyBit = ComponentKeyPathType.Registry;
2448 keyPossible = possibleKeyPath.Id;
2449 break;
2450
2451 case PossibleKeyPathType.None:
2452 default:
2453 keyBit = null;
2454 keyPossible = null;
2455 break;
2456 }
2457 }
2458 }
2459 }
2460
2461 // Verify that either the key path is not set, or it is set along with a key path ID.
2462 Debug.Assert(YesNoType.Yes != keyPathSet || (YesNoType.Yes == keyPathSet && null != keyPossible));
2463
2464 if (keyFound && YesNoType.Yes == keyPathSet)
2465 {
2466 this.Core.Write(ErrorMessages.ComponentMultipleKeyPaths(sourceLineNumbers, node.Name.LocalName, "KeyPath", "yes", "File", "RegistryValue", "ODBCDataSource"));
2467 }
2468
2469 // if a possible KeyPath has been found and that value was explicitly set as
2470 // the KeyPath of the component, set it now. Alternatively, if a possible
2471 // KeyPath has been found and no KeyPath has been previously set, use this
2472 // value as the default KeyPath of the component
2473 if (keyPossible != null && (YesNoType.Yes == keyPathSet || (YesNoType.NotSet == keyPathSet && keyPath == null && !keyFound)))
2474 {
2475 keyFound = YesNoType.Yes == keyPathSet;
2476 keyPath = keyPossible;
2477 keyPathType = keyBit.Value;
2478 }
2479 }
2480
2481 // Check for conditions that exclude this component from using implicit ids and/or generated guids.
2482 var allowImplicitIds = true;
2483 if (encounteredODBCDataSource || ComponentKeyPathType.Directory == keyPathType)
2484 {
2485 allowImplicitIds = false;
2486 if (guid == "*")
2487 {
2488 this.Core.Write(ErrorMessages.IllegalComponentWithAutoGeneratedGuid(sourceLineNumbers));
2489 }
2490 }
2491 else if (0 < files && ComponentKeyPathType.Registry == keyPathType)
2492 {
2493 allowImplicitIds = false;
2494 if (guid == "*")
2495 {
2496 this.Core.Write(ErrorMessages.IllegalComponentWithAutoGeneratedGuid(sourceLineNumbers, true));
2497 }
2498 }
2499
2500 // Check for implicit KeyPath which can easily be accidentally changed
2501 if (this.ShowPedanticMessages && !keyFound && !allowImplicitIds)
2502 {
2503 this.Core.Write(ErrorMessages.ImplicitComponentKeyPath(sourceLineNumbers, id.Id));
2504 }
2505
2506 // If there isn't an @Id attribute value, replace the placeholder with the id of the keypath.
2507 // either an explicit KeyPath="yes" attribute must be specified or requirements for
2508 // generatable guid must be met.
2509 if (componentIdPlaceholder == id.Id)
2510 {
2511 if (allowImplicitIds || keyFound && keyPath != null)
2512 {
2513 this.componentIdPlaceholders.Add(componentIdPlaceholder, keyPath.Id);
2514
2515 id = keyPath;
2516 }
2517 else
2518 {
2519 this.Core.Write(ErrorMessages.CannotDefaultComponentId(sourceLineNumbers));
2520 }
2521 }
2522
2523 // finally add the Component table row
2524 if (!this.Core.EncounteredError)
2525 {
2526 this.Core.AddSymbol(new ComponentSymbol(sourceLineNumbers, id)
2527 {
2528 ComponentId = guid,
2529 DirectoryRef = directoryId,
2530 Location = location,
2531 Condition = condition,
2532 KeyPath = keyPath?.Id,
2533 KeyPathType = keyPathType,
2534 DisableRegistryReflection = disableRegistryReflection,
2535 NeverOverwrite = neverOverwrite,
2536 Permanent = permanent,
2537 SharedDllRefCount = sharedDllRefCount,
2538 Shared = shared,
2539 Transitive = transitive,
2540 UninstallWhenSuperseded = uninstallWhenSuperseded,
2541 Win64 = win64,
2542 });
2543
2544 if (multiInstance)
2545 {
2546 this.Core.AddSymbol(new WixInstanceComponentSymbol(sourceLineNumbers, id)
2547 {
2548 ComponentRef = id.Id,
2549 });
2550 }
2551
2552 if (0 < symbols.Count)
2553 {
2554 this.Core.AddSymbol(new WixDeltaPatchSymbolPathsSymbol(sourceLineNumbers, new Identifier(AccessModifier.Section, SymbolPathType.Component, id.Id))
2555 {
2556 SymbolType = SymbolPathType.Component,
2557 SymbolId = id.Id,
2558 SymbolPaths = String.Join(";", symbols),
2559 });
2560 }
2561
2562 // Complus
2563 if (CompilerConstants.IntegerNotSet != comPlusBits)
2564 {
2565 this.Core.AddSymbol(new ComplusSymbol(sourceLineNumbers)
2566 {
2567 ComponentRef = id.Id,
2568 ExpType = comPlusBits,
2569 });
2570 }
2571
2572 // if this is a module, automatically add this component to the references to ensure it gets in the ModuleComponents table
2573 if (this.compilingModule)
2574 {
2575 this.Core.CreateComplexReference(sourceLineNumbers, ComplexReferenceParentType.Module, this.activeName, this.activeLanguage, ComplexReferenceChildType.Component, id.Id, false);
2576 }
2577 else if (ComplexReferenceParentType.Unknown != parentType && null != parentId) // if parent was provided, add a complex reference to that.
2578 {
2579 // If the Component is defined directly under a feature, then mark the complex reference primary.
2580 this.Core.CreateComplexReference(sourceLineNumbers, parentType, parentId, parentLanguage, ComplexReferenceChildType.Component, id.Id, ComplexReferenceParentType.Feature == parentType);
2581 }
2582 }
2583 }
2584
2585 /// <summary>
2586 /// Parses a component group element.
2587 /// </summary>
2588 /// <param name="node">Element to parse.</param>
2589 /// <param name="parentType">Type of complex reference parent. Will be Unknown if there is no parent.</param>
2590 /// <param name="parentId">Optional identifier for primary parent.</param>
2591 private void ParseComponentGroupElement(XElement node, ComplexReferenceParentType parentType, string parentId)
2592 {
2593 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
2594 Identifier id = null;
2595 string directoryId = null;
2596 string subdirectory = null;
2597 string source = null;
2598
2599 foreach (var attrib in node.Attributes())
2600 {
2601 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
2602 {
2603 switch (attrib.Name.LocalName)
2604 {
2605 case "Id":
2606 id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib);
2607 break;
2608 case "Directory":
2609 directoryId = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
2610 this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Directory, directoryId);
2611 break;
2612 case "Subdirectory":
2613 subdirectory = this.Core.GetAttributeLongFilename(sourceLineNumbers, attrib, allowRelative: true);
2614 break;
2615 case "Source":
2616 source = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
2617 break;
2618 default:
2619 this.Core.UnexpectedAttribute(node, attrib);
2620 break;
2621 }
2622 }
2623 else
2624 {
2625 this.Core.ParseExtensionAttribute(node, attrib);
2626 }
2627 }
2628
2629 if (null == id)
2630 {
2631 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id"));
2632 id = Identifier.Invalid;
2633 }
2634
2635 directoryId = this.HandleSubdirectory(sourceLineNumbers, node, directoryId, subdirectory, "Directory", "Subdirectory");
2636
2637 if (!String.IsNullOrEmpty(source) && !source.EndsWith(Path.DirectorySeparatorChar.ToString(), StringComparison.Ordinal))
2638 {
2639 source = String.Concat(source, Path.DirectorySeparatorChar);
2640 }
2641
2642 foreach (var child in node.Elements())
2643 {
2644 if (CompilerCore.WixNamespace == child.Name.Namespace)
2645 {
2646 switch (child.Name.LocalName)
2647 {
2648 case "ComponentGroupRef":
2649 this.ParseComponentGroupRefElement(child, ComplexReferenceParentType.ComponentGroup, id.Id, null);
2650 break;
2651 case "ComponentRef":
2652 this.ParseComponentRefElement(child, ComplexReferenceParentType.ComponentGroup, id.Id, null);
2653 break;
2654 case "Component":
2655 this.ParseComponentElement(child, ComplexReferenceParentType.ComponentGroup, id.Id, null, CompilerConstants.IntegerNotSet, directoryId, source);
2656 break;
2657 case "File":
2658 this.ParseNakedFileElement(child, ComplexReferenceParentType.ComponentGroup, id.Id, directoryId, source);
2659 break;
2660 case "Files":
2661 this.ParseFilesElement(child, ComplexReferenceParentType.ComponentGroup, id.Id, directoryId, source);
2662 break;
2663 default:
2664 this.Core.UnexpectedElement(node, child);
2665 break;
2666 }
2667 }
2668 else
2669 {
2670 this.Core.ParseExtensionElement(node, child);
2671 }
2672 }
2673
2674 if (!this.Core.EncounteredError)
2675 {
2676 this.Core.AddSymbol(new WixComponentGroupSymbol(sourceLineNumbers, id)
2677 {
2678 DirectoryRef = directoryId,
2679 Source = source
2680 });
2681
2682 this.Core.CreateWixGroupRow(sourceLineNumbers, parentType, parentId, ComplexReferenceChildType.ComponentGroup, id.Id);
2683 }
2684 }
2685
2686 /// <summary>
2687 /// Parses a component group reference element.
2688 /// </summary>
2689 /// <param name="node">Element to parse.</param>
2690 /// <param name="parentType">ComplexReferenceParentType of parent element.</param>
2691 /// <param name="parentId">Identifier of parent element (usually a Feature or Module).</param>
2692 /// <param name="parentLanguage">Optional language of parent (only useful for Modules).</param>
2693 private void ParseComponentGroupRefElement(XElement node, ComplexReferenceParentType parentType, string parentId, string parentLanguage)
2694 {
2695 Debug.Assert(ComplexReferenceParentType.ComponentGroup == parentType || ComplexReferenceParentType.FeatureGroup == parentType || ComplexReferenceParentType.Feature == parentType || ComplexReferenceParentType.Module == parentType || ComplexReferenceParentType.Product == parentType);
2696
2697 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
2698 string id = null;
2699 var primary = YesNoType.NotSet;
2700
2701 foreach (var attrib in node.Attributes())
2702 {
2703 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
2704 {
2705 switch (attrib.Name.LocalName)
2706 {
2707 case "Id":
2708 id = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
2709 this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.WixComponentGroup, id);
2710 break;
2711 case "Primary":
2712 primary = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
2713 break;
2714 default:
2715 this.Core.UnexpectedAttribute(node, attrib);
2716 break;
2717 }
2718 }
2719 else
2720 {
2721 this.Core.ParseExtensionAttribute(node, attrib);
2722 }
2723 }
2724
2725 if (null == id)
2726 {
2727 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id"));
2728 }
2729
2730 this.Core.ParseForExtensionElements(node);
2731
2732 this.Core.CreateComplexReference(sourceLineNumbers, parentType, parentId, parentLanguage, ComplexReferenceChildType.ComponentGroup, id, (YesNoType.Yes == primary));
2733 }
2734
2735 /// <summary>
2736 /// Parses a component reference element.
2737 /// </summary>
2738 /// <param name="node">Element to parse.</param>
2739 /// <param name="parentType">ComplexReferenceParentType of parent element.</param>
2740 /// <param name="parentId">Identifier of parent element (usually a Feature or Module).</param>
2741 /// <param name="parentLanguage">Optional language of parent (only useful for Modules).</param>
2742 private void ParseComponentRefElement(XElement node, ComplexReferenceParentType parentType, string parentId, string parentLanguage)
2743 {
2744 Debug.Assert(ComplexReferenceParentType.FeatureGroup == parentType || ComplexReferenceParentType.ComponentGroup == parentType || ComplexReferenceParentType.Feature == parentType || ComplexReferenceParentType.Module == parentType || ComplexReferenceParentType.Product == parentType);
2745
2746 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
2747 string id = null;
2748 var primary = YesNoType.NotSet;
2749
2750 foreach (var attrib in node.Attributes())
2751 {
2752 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
2753 {
2754 switch (attrib.Name.LocalName)
2755 {
2756 case "Id":
2757 id = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
2758 this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Component, id);
2759 break;
2760 case "Primary":
2761 primary = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
2762 break;
2763 default:
2764 this.Core.UnexpectedAttribute(node, attrib);
2765 break;
2766 }
2767 }
2768 else
2769 {
2770 this.Core.ParseExtensionAttribute(node, attrib);
2771 }
2772 }
2773
2774 if (null == id)
2775 {
2776 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id"));
2777 }
2778
2779 this.Core.ParseForExtensionElements(node);
2780
2781 this.Core.CreateComplexReference(sourceLineNumbers, parentType, parentId, parentLanguage, ComplexReferenceChildType.Component, id, (YesNoType.Yes == primary));
2782 }
2783
2784 /// <summary>
2785 /// Parses a component search element.
2786 /// </summary>
2787 /// <param name="node">Element to parse.</param>
2788 /// <returns>Signature for search element.</returns>
2789 private string ParseComponentSearchElement(XElement node)
2790 {
2791 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
2792 Identifier id = null;
2793 string componentId = null;
2794 var type = LocatorType.Filename;
2795
2796 foreach (var attrib in node.Attributes())
2797 {
2798 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
2799 {
2800 switch (attrib.Name.LocalName)
2801 {
2802 case "Id":
2803 id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib);
2804 break;
2805 case "Guid":
2806 componentId = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib, false);
2807 break;
2808 case "Type":
2809 var typeValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
2810 switch (typeValue)
2811 {
2812 case "directory":
2813 type = LocatorType.Directory;
2814 break;
2815 case "file":
2816 type = LocatorType.Filename;
2817 break;
2818 case "":
2819 break;
2820 default:
2821 this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, typeValue, "directory", "file"));
2822 break;
2823 }
2824 break;
2825 default:
2826 this.Core.UnexpectedAttribute(node, attrib);
2827 break;
2828 }
2829 }
2830 else
2831 {
2832 this.Core.ParseExtensionAttribute(node, attrib);
2833 }
2834 }
2835
2836 if (null == id)
2837 {
2838 id = this.Core.CreateIdentifier("cmp", componentId, type.ToString());
2839 }
2840
2841 var signature = id.Id;
2842 var oneChild = false;
2843 foreach (var child in node.Elements())
2844 {
2845 if (CompilerCore.WixNamespace == child.Name.Namespace)
2846 {
2847 switch (child.Name.LocalName)
2848 {
2849 case "DirectorySearch":
2850 if (oneChild)
2851 {
2852 this.Core.Write(ErrorMessages.TooManySearchElements(sourceLineNumbers, node.Name.LocalName));
2853 }
2854 oneChild = true;
2855
2856 // directorysearch parentage should work like directory element, not the rest of the signature type because of the DrLocator.Parent column
2857 signature = this.ParseDirectorySearchElement(child, id.Id);
2858 break;
2859 case "DirectorySearchRef":
2860 if (oneChild)
2861 {
2862 this.Core.Write(ErrorMessages.TooManySearchElements(sourceLineNumbers, node.Name.LocalName));
2863 }
2864 oneChild = true;
2865 signature = this.ParseDirectorySearchRefElement(child, id.Id);
2866 break;
2867 case "FileSearch":
2868 if (oneChild)
2869 {
2870 this.Core.Write(ErrorMessages.TooManySearchElements(sourceLineNumbers, node.Name.LocalName));
2871 }
2872 oneChild = true;
2873 signature = this.ParseFileSearchElement(child, id.Id, false, CompilerConstants.IntegerNotSet);
2874 id = new Identifier(AccessModifier.Section, signature); // FileSearch signatures override parent signatures
2875 break;
2876 case "FileSearchRef":
2877 if (oneChild)
2878 {
2879 this.Core.Write(ErrorMessages.TooManySearchElements(sourceLineNumbers, node.Name.LocalName));
2880 }
2881 oneChild = true;
2882 var newId = this.ParseSimpleRefElement(child, SymbolDefinitions.Signature); // FileSearch signatures override parent signatures
2883 id = new Identifier(AccessModifier.Section, newId);
2884 signature = null;
2885 break;
2886 default:
2887 this.Core.UnexpectedElement(node, child);
2888 break;
2889 }
2890 }
2891 else
2892 {
2893 this.Core.ParseExtensionElement(node, child);
2894 }
2895 }
2896
2897 if (!this.Core.EncounteredError)
2898 {
2899 this.Core.AddSymbol(new CompLocatorSymbol(sourceLineNumbers, id)
2900 {
2901 SignatureRef = id.Id,
2902 ComponentId = componentId,
2903 Type = type,
2904 });
2905 }
2906
2907 return signature;
2908 }
2909
2910 /// <summary>
2911 /// Parses a create folder element.
2912 /// </summary>
2913 /// <param name="node">Element to parse.</param>
2914 /// <param name="componentId">Identifier for parent component.</param>
2915 /// <param name="directoryId">Default identifier for directory to create.</param>
2916 /// <param name="win64Component">true if the component is 64-bit.</param>
2917 /// <returns>Identifier for the directory that will be created</returns>
2918 private string ParseCreateFolderElement(XElement node, string componentId, string directoryId, bool win64Component)
2919 {
2920 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
2921 string subdirectory = null;
2922
2923 foreach (var attrib in node.Attributes())
2924 {
2925 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
2926 {
2927 switch (attrib.Name.LocalName)
2928 {
2929 case "Directory":
2930 directoryId = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
2931 this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Directory, directoryId);
2932 break;
2933 case "Subdirectory":
2934 subdirectory = this.Core.GetAttributeLongFilename(sourceLineNumbers, attrib, allowRelative: true);
2935 break;
2936 default:
2937 this.Core.UnexpectedAttribute(node, attrib);
2938 break;
2939 }
2940 }
2941 else
2942 {
2943 this.Core.ParseExtensionAttribute(node, attrib);
2944 }
2945 }
2946
2947 directoryId = this.HandleSubdirectory(sourceLineNumbers, node, directoryId, subdirectory, "Directory", "Subdirectory");
2948
2949 foreach (var child in node.Elements())
2950 {
2951 if (CompilerCore.WixNamespace == child.Name.Namespace)
2952 {
2953 switch (child.Name.LocalName)
2954 {
2955 case "Shortcut":
2956 this.ParseShortcutElement(child, componentId, node.Name.LocalName, directoryId, YesNoType.No);
2957 break;
2958 case "Permission":
2959 this.ParsePermissionElement(child, directoryId, "CreateFolder");
2960 break;
2961 case "PermissionEx":
2962 this.ParsePermissionExElement(child, directoryId, "CreateFolder");
2963 break;
2964 default:
2965 this.Core.UnexpectedElement(node, child);
2966 break;
2967 }
2968 }
2969 else
2970 {
2971 var context = new Dictionary<string, string>() { { "DirectoryId", directoryId }, { "ComponentId", componentId }, { "Win64", win64Component.ToString() } };
2972 this.Core.ParseExtensionElement(node, child, context);
2973 }
2974 }
2975
2976 if (!this.Core.EncounteredError)
2977 {
2978 this.Core.AddSymbol(new CreateFolderSymbol(sourceLineNumbers)
2979 {
2980 DirectoryRef = directoryId,
2981 ComponentRef = componentId,
2982 });
2983 }
2984
2985 return directoryId;
2986 }
2987
2988 /// <summary>
2989 /// Parses a copy file element.
2990 /// </summary>
2991 /// <param name="node">Element to parse.</param>
2992 /// <param name="componentId">Identifier of parent component.</param>
2993 /// <param name="fileId">Identifier of file to copy (null if moving the file).</param>
2994 private void ParseCopyFileElement(XElement node, string componentId, string fileId)
2995 {
2996 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
2997 Identifier id = null;
2998 var delete = false;
2999 string destinationDirectory = null;
3000 string destinationSubdirectory = null;
3001 string destinationName = null;
3002 string destinationShortName = null;
3003 string destinationProperty = null;
3004 string sourceDirectory = null;
3005 string sourceSubdirectory = null;
3006 string sourceFolder = null;
3007 string sourceName = null;
3008 string sourceProperty = null;
3009
3010 foreach (var attrib in node.Attributes())
3011 {
3012 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
3013 {
3014 switch (attrib.Name.LocalName)
3015 {
3016 case "Id":
3017 id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib);
3018 break;
3019 case "Delete":
3020 delete = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
3021 break;
3022 case "DestinationDirectory":
3023 destinationDirectory = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
3024 this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Directory, destinationDirectory);
3025 break;
3026 case "DestinationSubdirectory":
3027 destinationSubdirectory = this.Core.GetAttributeLongFilename(sourceLineNumbers, attrib, allowRelative: true);
3028 break;
3029 case "DestinationName":
3030 destinationName = this.Core.GetAttributeLongFilename(sourceLineNumbers, attrib);
3031 break;
3032 case "DestinationProperty":
3033 destinationProperty = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
3034 break;
3035 case "DestinationShortName":
3036 destinationShortName = this.Core.GetAttributeShortFilename(sourceLineNumbers, attrib);
3037 break;
3038 case "FileId":
3039 if (null != fileId)
3040 {
3041 this.Core.Write(ErrorMessages.IllegalAttributeWhenNested(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, node.Parent.Name.LocalName));
3042 }
3043 fileId = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
3044 this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.File, fileId);
3045 break;
3046 case "SourceDirectory":
3047 sourceDirectory = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
3048 this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Directory, sourceDirectory);
3049 break;
3050 case "SourceSubdirectory":
3051 sourceSubdirectory = this.Core.GetAttributeLongFilename(sourceLineNumbers, attrib, allowRelative: true);
3052 break;
3053 case "SourceName":
3054 sourceName = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
3055 break;
3056 case "SourceProperty":
3057 sourceProperty = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
3058 break;
3059 default:
3060 this.Core.UnexpectedAttribute(node, attrib);
3061 break;
3062 }
3063 }
3064 else
3065 {
3066 this.Core.ParseExtensionAttribute(node, attrib);
3067 }
3068 }
3069
3070 if (null != sourceFolder && null != sourceDirectory) // SourceFolder and SourceDirectory cannot coexist
3071 {
3072 this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "SourceFolder", "SourceDirectory"));
3073 }
3074
3075 if (null != sourceFolder && null != sourceProperty) // SourceFolder and SourceProperty cannot coexist
3076 {
3077 this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "SourceFolder", "SourceProperty"));
3078 }
3079
3080 if (null != sourceDirectory && null != sourceProperty) // SourceDirectory and SourceProperty cannot coexist
3081 {
3082 this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "SourceProperty", "SourceDirectory"));
3083 }
3084
3085 sourceDirectory = this.HandleSubdirectory(sourceLineNumbers, node, sourceDirectory, sourceSubdirectory, "SourceDirectory", "SourceSubdirectory");
3086
3087 if (null != destinationDirectory && null != destinationProperty) // DestinationDirectory and DestinationProperty cannot coexist
3088 {
3089 this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "DestinationProperty", "DestinationDirectory"));
3090 }
3091
3092 destinationDirectory = this.HandleSubdirectory(sourceLineNumbers, node, destinationDirectory, destinationSubdirectory, "DestinationDirectory", "DestinationSubdirectory");
3093
3094 if (null == id)
3095 {
3096 id = this.Core.CreateIdentifier("cf", sourceFolder, sourceDirectory, sourceProperty, destinationDirectory, destinationProperty, destinationName);
3097 }
3098
3099 this.Core.ParseForExtensionElements(node);
3100
3101 if (null == fileId)
3102 {
3103 // DestinationDirectory or DestinationProperty must be specified
3104 if (null == destinationDirectory && null == destinationProperty)
3105 {
3106 this.Core.Write(ErrorMessages.ExpectedAttributesWithoutOtherAttribute(sourceLineNumbers, node.Name.LocalName, "DestinationDirectory", "DestinationProperty", "FileId"));
3107 }
3108
3109 if (!this.Core.EncounteredError)
3110 {
3111 this.Core.AddSymbol(new MoveFileSymbol(sourceLineNumbers, id)
3112 {
3113 ComponentRef = componentId,
3114 SourceName = sourceName,
3115 DestinationName = destinationName,
3116 DestinationShortName = destinationShortName,
3117 SourceFolder = sourceDirectory ?? sourceProperty,
3118 DestFolder = destinationDirectory ?? destinationProperty,
3119 Delete = delete,
3120 });
3121 }
3122 }
3123 else // copy the file
3124 {
3125 if (null != sourceDirectory)
3126 {
3127 this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "SourceDirectory", "FileId"));
3128 }
3129
3130 if (null != sourceFolder)
3131 {
3132 this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "SourceFolder", "FileId"));
3133 }
3134
3135 if (null != sourceName)
3136 {
3137 this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "SourceName", "FileId"));
3138 }
3139
3140 if (null != sourceProperty)
3141 {
3142 this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "SourceProperty", "FileId"));
3143 }
3144
3145 if (delete)
3146 {
3147 this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Delete", "FileId"));
3148 }
3149
3150 if (null == destinationName && null == destinationDirectory && null == destinationProperty)
3151 {
3152 this.Core.Write(WarningMessages.CopyFileFileIdUseless(sourceLineNumbers));
3153 }
3154
3155 if (!this.Core.EncounteredError)
3156 {
3157 this.Core.AddSymbol(new DuplicateFileSymbol(sourceLineNumbers, id)
3158 {
3159 ComponentRef = componentId,
3160 FileRef = fileId,
3161 DestinationName = destinationName,
3162 DestinationShortName = destinationShortName,
3163 DestinationFolder = destinationDirectory ?? destinationProperty,
3164 });
3165 }
3166 }
3167 }
3168
3169 /// <summary>
3170 /// Parses a CustomAction element.
3171 /// </summary>
3172 /// <param name="node">Element to parse.</param>
3173 private void ParseCustomActionElement(XElement node)
3174 {
3175 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
3176 Identifier id = null;
3177 var inlineScript = false;
3178 var suppressModularization = YesNoType.NotSet;
3179 string source = null;
3180 string target = null;
3181 var explicitWin64 = false;
3182
3183 string scriptFile = null;
3184 string subdirectory = null;
3185
3186 CustomActionSourceType? sourceType = null;
3187 CustomActionTargetType? targetType = null;
3188 var executionType = CustomActionExecutionType.Immediate;
3189 var hidden = false;
3190 var impersonate = true;
3191 var patchUninstall = false;
3192 var tsAware = false;
3193 var win64 = false;
3194 var async = false;
3195 var ignoreResult = false;
3196
3197 foreach (var attrib in node.Attributes())
3198 {
3199 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
3200 {
3201 switch (attrib.Name.LocalName)
3202 {
3203 case "Id":
3204 id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib);
3205 break;
3206 case "BinaryRef":
3207 if (null != source)
3208 {
3209 this.Core.Write(ErrorMessages.CustomActionMultipleSources(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "BinaryRef", "Directory", "FileRef", "Property", "Script"));
3210 }
3211 source = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
3212 sourceType = CustomActionSourceType.Binary;
3213 this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Binary, source); // add a reference to the appropriate Binary
3214 break;
3215 case "Bitness":
3216 var bitnessValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
3217 switch (bitnessValue)
3218 {
3219 case "always32":
3220 explicitWin64 = true;
3221 win64 = false;
3222 break;
3223 case "always64":
3224 explicitWin64 = true;
3225 win64 = true;
3226 break;
3227 case "default":
3228 case "":
3229 break;
3230 default:
3231 this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, bitnessValue, "default", "always32", "always64"));
3232 break;
3233 }
3234 break;
3235 case "Directory":
3236 if (null != source)
3237 {
3238 this.Core.Write(ErrorMessages.CustomActionMultipleSources(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "BinaryKey", "Directory", "FileRef", "Property", "Script"));
3239 }
3240 source = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
3241 sourceType = CustomActionSourceType.Directory;
3242 this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Directory, source);
3243 break;
3244 case "DllEntry":
3245 if (null != target)
3246 {
3247 this.Core.Write(ErrorMessages.CustomActionMultipleTargets(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "DllEntry", "Error", "ExeCommand", "JScriptCall", "Script", "Value", "VBScriptCall"));
3248 }
3249 target = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
3250 targetType = CustomActionTargetType.Dll;
3251 break;
3252 case "Error":
3253 if (null != target)
3254 {
3255 this.Core.Write(ErrorMessages.CustomActionMultipleTargets(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "DllEntry", "Error", "ExeCommand", "JScriptCall", "Script", "Value", "VBScriptCall"));
3256 }
3257 target = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
3258 sourceType = CustomActionSourceType.File;
3259 targetType = CustomActionTargetType.TextData;
3260
3261 // The target can be either a formatted error string or a literal
3262 // error number. Try to convert to error number to determine whether
3263 // to add a reference. No need to look at the value.
3264 if (Int32.TryParse(target, out var ignored))
3265 {
3266 this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Error, target);
3267 }
3268 break;
3269 case "ExeCommand":
3270 if (null != target)
3271 {
3272 this.Core.Write(ErrorMessages.CustomActionMultipleTargets(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "DllEntry", "Error", "ExeCommand", "JScriptCall", "Script", "Value", "VBScriptCall"));
3273 }
3274 target = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty); // one of the few cases where an empty string value is valid
3275 targetType = CustomActionTargetType.Exe;
3276 break;
3277 case "Execute":
3278 var execute = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
3279 switch (execute)
3280 {
3281 case "commit":
3282 executionType = CustomActionExecutionType.Commit;
3283 break;
3284 case "deferred":
3285 executionType = CustomActionExecutionType.Deferred;
3286 break;
3287 case "firstSequence":
3288 executionType = CustomActionExecutionType.FirstSequence;
3289 break;
3290 case "immediate":
3291 executionType = CustomActionExecutionType.Immediate;
3292 break;
3293 case "oncePerProcess":
3294 executionType = CustomActionExecutionType.OncePerProcess;
3295 break;
3296 case "rollback":
3297 executionType = CustomActionExecutionType.Rollback;
3298 break;
3299 case "secondSequence":
3300 executionType = CustomActionExecutionType.ClientRepeat;
3301 break;
3302 default:
3303 this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, execute, "commit", "deferred", "firstSequence", "immediate", "oncePerProcess", "rollback", "secondSequence"));
3304 break;
3305 }
3306 break;
3307 case "FileRef":
3308 if (null != source)
3309 {
3310 this.Core.Write(ErrorMessages.CustomActionMultipleSources(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "BinaryRef", "Directory", "FileRef", "Property", "Script"));
3311 }
3312 source = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
3313 sourceType = CustomActionSourceType.File;
3314 this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.File, source); // add a reference to the appropriate File
3315 break;
3316 case "HideTarget":
3317 hidden = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
3318 break;
3319 case "Impersonate":
3320 impersonate = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
3321 break;
3322 case "JScriptCall":
3323 if (null != target)
3324 {
3325 this.Core.Write(ErrorMessages.CustomActionMultipleTargets(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "DllEntry", "Error", "ExeCommand", "JScriptCall", "Script", "Value", "VBScriptCall"));
3326 }
3327 target = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty); // one of the few cases where an empty string value is valid
3328 targetType = CustomActionTargetType.JScript;
3329 break;
3330 case "PatchUninstall":
3331 patchUninstall = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
3332 break;
3333 case "Property":
3334 if (null != source)
3335 {
3336 this.Core.Write(ErrorMessages.CustomActionMultipleSources(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "BinaryRef", "Directory", "FileRef", "Property", "Script"));
3337 }
3338 source = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
3339 sourceType = CustomActionSourceType.Property;
3340 break;
3341 case "Return":
3342 var returnValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
3343 switch (returnValue)
3344 {
3345 case "asyncNoWait":
3346 async = true;
3347 ignoreResult = true;
3348 break;
3349 case "asyncWait":
3350 async = true;
3351 break;
3352 case "check":
3353 break;
3354 case "ignore":
3355 ignoreResult = true;
3356 break;
3357 case "":
3358 break;
3359 default:
3360 this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, returnValue, "asyncNoWait", "asyncWait", "check", "ignore"));
3361 break;
3362 }
3363 break;
3364 case "Script":
3365 if (null != source)
3366 {
3367 this.Core.Write(ErrorMessages.CustomActionMultipleSources(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "BinaryRef", "Directory", "FileRef", "Property", "Script"));
3368 }
3369
3370 if (null != target)
3371 {
3372 this.Core.Write(ErrorMessages.CustomActionMultipleTargets(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "DllEntry", "Error", "ExeCommand", "JScriptCall", "Script", "Value", "VBScriptCall"));
3373 }
3374
3375 // set the source and target to empty string for error messages when the user sets multiple sources or targets
3376 source = String.Empty;
3377 target = String.Empty;
3378
3379 inlineScript = true;
3380
3381 var script = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
3382 switch (script)
3383 {
3384 case "jscript":
3385 sourceType = CustomActionSourceType.Directory;
3386 targetType = CustomActionTargetType.JScript;
3387 break;
3388 case "vbscript":
3389 sourceType = CustomActionSourceType.Directory;
3390 targetType = CustomActionTargetType.VBScript;
3391 break;
3392 case "":
3393 break;
3394 default:
3395 this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, script, "jscript", "vbscript"));
3396 break;
3397 }
3398 break;
3399 case "ScriptSourceFile":
3400 scriptFile = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
3401 break;
3402 case "Subdirectory":
3403 subdirectory = this.Core.GetAttributeLongFilename(sourceLineNumbers, attrib, allowRelative: true);
3404 break;
3405 case "SuppressModularization":
3406 suppressModularization = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
3407 break;
3408 case "TerminalServerAware":
3409 tsAware = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
3410 break;
3411 case "Value":
3412 if (null != target)
3413 {
3414 this.Core.Write(ErrorMessages.CustomActionMultipleTargets(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "DllEntry", "Error", "ExeCommand", "JScriptCall", "Script", "Value", "VBScriptCall"));
3415 }
3416 target = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty); // one of the few cases where an empty string value is valid
3417 targetType = CustomActionTargetType.TextData;
3418 break;
3419 case "VBScriptCall":
3420 if (null != target)
3421 {
3422 this.Core.Write(ErrorMessages.CustomActionMultipleTargets(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "DllEntry", "Error", "ExeCommand", "JScriptCall", "Script", "Value", "VBScriptCall"));
3423 }
3424 target = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty); // one of the few cases where an empty string value is valid
3425 targetType = CustomActionTargetType.VBScript;
3426 break;
3427 default:
3428 this.Core.UnexpectedAttribute(node, attrib);
3429 break;
3430 }
3431 }
3432 else
3433 {
3434 this.Core.ParseExtensionAttribute(node, attrib);
3435 }
3436 }
3437
3438 if (null == id)
3439 {
3440 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id"));
3441 id = Identifier.Invalid;
3442 }
3443
3444 if (!explicitWin64 && this.Context.IsCurrentPlatform64Bit && (CustomActionTargetType.VBScript == targetType || CustomActionTargetType.JScript == targetType))
3445 {
3446 win64 = true;
3447 }
3448
3449 if (!String.IsNullOrEmpty(subdirectory))
3450 {
3451 if (sourceType == CustomActionSourceType.Directory)
3452 {
3453 source = this.HandleSubdirectory(sourceLineNumbers, node, source, subdirectory, "Directory", "Subdirectory");
3454 }
3455 else
3456 {
3457 this.Core.Write(ErrorMessages.IllegalAttributeWithoutOtherAttributes(sourceLineNumbers, node.Name.LocalName, "Subdirectory", "Directory"));
3458 }
3459 }
3460
3461 if (targetType == CustomActionTargetType.VBScript)
3462 {
3463 this.Core.Write(WarningMessages.VBScriptIsDeprecated(sourceLineNumbers));
3464 }
3465
3466 // if we have an in-lined Script CustomAction ensure no source or target attributes were provided
3467 if (inlineScript)
3468 {
3469 if (String.IsNullOrEmpty(scriptFile))
3470 {
3471 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "ScriptSourceFile", "Script"));
3472 }
3473 }
3474 else if (CustomActionTargetType.VBScript == targetType) // non-inline vbscript
3475 {
3476 if (null == source)
3477 {
3478 this.Core.Write(ErrorMessages.IllegalAttributeWithoutOtherAttributes(sourceLineNumbers, node.Name.LocalName, "VBScriptCall", "BinaryRef", "FileRef", "Property"));
3479 }
3480 else if (CustomActionSourceType.Directory == sourceType)
3481 {
3482 this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "VBScriptCall", "Directory"));
3483 }
3484 }
3485 else if (CustomActionTargetType.JScript == targetType) // non-inline jscript
3486 {
3487 if (null == source)
3488 {
3489 this.Core.Write(ErrorMessages.IllegalAttributeWithoutOtherAttributes(sourceLineNumbers, node.Name.LocalName, "JScriptCall", "BinaryRef", "FileRef", "Property"));
3490 }
3491 else if (CustomActionSourceType.Directory == sourceType)
3492 {
3493 this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "JScriptCall", "Directory"));
3494 }
3495 }
3496 else if (CustomActionTargetType.Exe == targetType) // exe-command
3497 {
3498 if (null == source)
3499 {
3500 this.Core.Write(ErrorMessages.IllegalAttributeWithoutOtherAttributes(sourceLineNumbers, node.Name.LocalName, "ExeCommand", "BinaryRef", "Directory", "FileRef", "Property"));
3501 }
3502 }
3503 else if (CustomActionTargetType.TextData == targetType && CustomActionSourceType.Directory != sourceType && CustomActionSourceType.Property != sourceType && CustomActionSourceType.File != sourceType)
3504 {
3505 this.Core.Write(ErrorMessages.IllegalAttributeWithoutOtherAttributes(sourceLineNumbers, node.Name.LocalName, "Value", "Directory", "Property", "Error"));
3506 }
3507
3508 if (!inlineScript && !String.IsNullOrEmpty(scriptFile))
3509 {
3510 this.Core.Write(ErrorMessages.IllegalAttributeWithoutOtherAttributes(sourceLineNumbers, node.Name.LocalName, "ScriptSourceFile", "Script"));
3511 }
3512
3513 if (win64 && CustomActionTargetType.VBScript != targetType && CustomActionTargetType.JScript != targetType)
3514 {
3515 this.Core.Write(ErrorMessages.IllegalAttributeWithoutOtherAttributes(sourceLineNumbers, node.Name.LocalName, "Win64", "Script", "VBScriptCall", "JScriptCall"));
3516 }
3517
3518 if (async && ignoreResult && CustomActionTargetType.Exe != targetType)
3519 {
3520 this.Core.Write(ErrorMessages.IllegalAttributeValueWithoutOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Return", "asyncNoWait", "ExeCommand"));
3521 }
3522
3523 // TS-aware CAs are valid only when deferred.
3524 if (tsAware &
3525 CustomActionExecutionType.Deferred != executionType &&
3526 CustomActionExecutionType.Rollback != executionType &&
3527 CustomActionExecutionType.Commit != executionType)
3528 {
3529 this.Core.Write(ErrorMessages.IllegalTerminalServerCustomActionAttributes(sourceLineNumbers));
3530 }
3531
3532 // MSI doesn't support in-script property setting, so disallow it
3533 if (CustomActionSourceType.Property == sourceType &&
3534 CustomActionTargetType.TextData == targetType &&
3535 (CustomActionExecutionType.Deferred == executionType ||
3536 CustomActionExecutionType.Rollback == executionType ||
3537 CustomActionExecutionType.Commit == executionType))
3538 {
3539 this.Core.Write(ErrorMessages.IllegalPropertyCustomActionAttributes(sourceLineNumbers));
3540 }
3541
3542 if (!targetType.HasValue)
3543 {
3544 this.Core.Write(ErrorMessages.ExpectedAttributes(sourceLineNumbers, node.Name.LocalName, "DllEntry", "Error", "ExeCommand", "JScriptCall", "Script", "Value", "VBScriptCall"));
3545 }
3546
3547 if (!sourceType.HasValue)
3548 {
3549 this.Core.Write(ErrorMessages.ExpectedAttributes(sourceLineNumbers, node.Name.LocalName, "BinaryRef", "Directory", "Error", "FileRef", "Property", "Script"));
3550 }
3551
3552 this.Core.ParseForExtensionElements(node);
3553
3554 if (!this.Core.EncounteredError)
3555 {
3556 this.Core.AddSymbol(new CustomActionSymbol(sourceLineNumbers, id)
3557 {
3558 ExecutionType = executionType,
3559 Source = source,
3560 SourceType = sourceType.Value,
3561 Target = target,
3562 TargetType = targetType.Value,
3563 Async = async,
3564 IgnoreResult = ignoreResult,
3565 Impersonate = impersonate,
3566 PatchUninstall = patchUninstall,
3567 TSAware = tsAware,
3568 Win64 = win64,
3569 Hidden = hidden,
3570 ScriptFile = new IntermediateFieldPathValue { Path = scriptFile }
3571 });
3572
3573 if (YesNoType.Yes == suppressModularization)
3574 {
3575 this.Core.AddSymbol(new WixSuppressModularizationSymbol(sourceLineNumbers)
3576 {
3577 SuppressIdentifier = id.Id
3578 });
3579 }
3580 }
3581 }
3582
3583 /// <summary>
3584 /// Parses a simple reference element.
3585 /// </summary>
3586 /// <param name="node">Element to parse.</param>
3587 /// <param name="symbolDefinition">Symbol which contains the target of the simple reference.</param>
3588 /// <returns>Id of the referenced element.</returns>
3589 private string ParseSimpleRefElement(XElement node, IntermediateSymbolDefinition symbolDefinition)
3590 {
3591 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
3592 string id = null;
3593
3594 foreach (var attrib in node.Attributes())
3595 {
3596 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
3597 {
3598 switch (attrib.Name.LocalName)
3599 {
3600 case "Id":
3601 id = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
3602 this.Core.CreateSimpleReference(sourceLineNumbers, symbolDefinition.Name, id);
3603 break;
3604 default:
3605 this.Core.UnexpectedAttribute(node, attrib);
3606 break;
3607 }
3608 }
3609 else
3610 {
3611 this.Core.ParseExtensionAttribute(node, attrib);
3612 }
3613 }
3614
3615 if (null == id)
3616 {
3617 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id"));
3618 }
3619
3620 this.Core.ParseForExtensionElements(node);
3621
3622 return id;
3623 }
3624
3625 /// <summary>
3626 /// Parses a PatchFamilyRef element.
3627 /// </summary>
3628 /// <param name="node">Element to parse.</param>
3629 /// <param name="parentType">The parent type.</param>
3630 /// <param name="parentId">The ID of the parent.</param>
3631 /// <returns>Id of the referenced element.</returns>
3632 private void ParsePatchFamilyRefElement(XElement node, ComplexReferenceParentType parentType, string parentId)
3633 {
3634 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
3635 var primaryKeys = new string[2];
3636
3637 foreach (var attrib in node.Attributes())
3638 {
3639 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
3640 {
3641 switch (attrib.Name.LocalName)
3642 {
3643 case "Id":
3644 primaryKeys[0] = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
3645 break;
3646 case "ProductCode":
3647 primaryKeys[1] = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
3648 break;
3649 default:
3650 this.Core.UnexpectedAttribute(node, attrib);
3651 break;
3652 }
3653 }
3654 else
3655 {
3656 this.Core.ParseExtensionAttribute(node, attrib);
3657 }
3658 }
3659
3660 if (null == primaryKeys[0])
3661 {
3662 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id"));
3663 }
3664
3665 this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.MsiPatchFamily, primaryKeys);
3666
3667 this.Core.ParseForExtensionElements(node);
3668
3669 if (!this.Core.EncounteredError)
3670 {
3671 this.Core.CreateComplexReference(sourceLineNumbers, parentType, parentId, null, ComplexReferenceChildType.PatchFamily, primaryKeys[0], true);
3672 }
3673 }
3674
3675 /// <summary>
3676 /// Parses an ensure table element.
3677 /// </summary>
3678 /// <param name="node">Element to parse.</param>
3679 private void ParseEnsureTableElement(XElement node)
3680 {
3681 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
3682 string id = null;
3683
3684 foreach (var attrib in node.Attributes())
3685 {
3686 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
3687 {
3688 switch (attrib.Name.LocalName)
3689 {
3690 case "Id":
3691 id = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
3692 break;
3693 default:
3694 this.Core.UnexpectedAttribute(node, attrib);
3695 break;
3696 }
3697 }
3698 else
3699 {
3700 this.Core.ParseExtensionAttribute(node, attrib);
3701 }
3702 }
3703
3704 if (null == id)
3705 {
3706 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id"));
3707 }
3708 else if (31 < id.Length)
3709 {
3710 this.Core.Write(ErrorMessages.TableNameTooLong(sourceLineNumbers, node.Name.LocalName, "Id", id));
3711 }
3712
3713 this.Core.ParseForExtensionElements(node);
3714
3715 this.Core.EnsureTable(sourceLineNumbers, id);
3716 }
3717
3718 /// <summary>
3719 /// Parses a directory element.
3720 /// </summary>
3721 /// <param name="node">Element to parse.</param>
3722 /// <param name="parentId">Optional identifier of parent directory.</param>
3723 /// <param name="diskId">Disk id inherited from parent directory.</param>
3724 /// <param name="fileSource">Path to source file as of yet.</param>
3725 private void ParseDirectoryElement(XElement node, string parentId, int diskId, string fileSource)
3726 {
3727 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
3728 Identifier id = null;
3729 string componentGuidGenerationSeed = null;
3730 var fileSourceAttribSet = false;
3731 XAttribute nameAttribute = null;
3732 var name = "."; // default to parent directory.
3733 string shortName = null;
3734 string sourceName = null;
3735 string shortSourceName = null;
3736 string symbols = null;
3737
3738 foreach (var attrib in node.Attributes())
3739 {
3740 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
3741 {
3742 switch (attrib.Name.LocalName)
3743 {
3744 case "Id":
3745 id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib);
3746 break;
3747 case "ComponentGuidGenerationSeed":
3748 componentGuidGenerationSeed = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib, false);
3749 break;
3750 case "DiskId":
3751 diskId = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 1, Int16.MaxValue);
3752 break;
3753 case "FileSource":
3754 fileSource = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
3755 fileSourceAttribSet = true;
3756 break;
3757 case "Name":
3758 if ("." == attrib.Value)
3759 {
3760 name = attrib.Value;
3761 }
3762 else
3763 {
3764 name = this.Core.GetAttributeLongFilename(sourceLineNumbers, attrib, allowRelative: true);
3765 }
3766 nameAttribute = attrib;
3767 break;
3768 case "ShortName":
3769 shortName = this.Core.GetAttributeShortFilename(sourceLineNumbers, attrib, false);
3770 break;
3771 case "ShortSourceName":
3772 shortSourceName = this.Core.GetAttributeShortFilename(sourceLineNumbers, attrib, false);
3773 break;
3774 case "SourceName":
3775 if ("." == attrib.Value)
3776 {
3777 sourceName = attrib.Value;
3778 }
3779 else
3780 {
3781 sourceName = this.Core.GetAttributeLongFilename(sourceLineNumbers, attrib, false);
3782 }
3783 break;
3784 default:
3785 this.Core.UnexpectedAttribute(node, attrib);
3786 break;
3787 }
3788 }
3789 else
3790 {
3791 this.Core.ParseExtensionAttribute(node, attrib);
3792 }
3793 }
3794
3795 if (nameAttribute == null)
3796 {
3797 if (!String.IsNullOrEmpty(shortName))
3798 {
3799 this.Core.Write(ErrorMessages.IllegalAttributeWithoutOtherAttributes(sourceLineNumbers, node.Name.LocalName, "ShortName", "Name"));
3800 }
3801 }
3802 else if (!String.IsNullOrEmpty(name))
3803 {
3804 if (String.IsNullOrEmpty(shortName))
3805 {
3806 }
3807 else if (name == ".")
3808 {
3809 this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "ShortName", "Name", name));
3810 }
3811 else if (name.Equals(shortName, StringComparison.OrdinalIgnoreCase))
3812 {
3813 this.Core.Write(WarningMessages.DirectoryRedundantNames(sourceLineNumbers, node.Name.LocalName, "Name", "ShortName", name));
3814 }
3815 }
3816
3817 if (String.IsNullOrEmpty(sourceName))
3818 {
3819 if (!String.IsNullOrEmpty(shortSourceName))
3820 {
3821 this.Core.Write(ErrorMessages.IllegalAttributeWithoutOtherAttributes(sourceLineNumbers, node.Name.LocalName, "ShortSourceName", "SourceName"));
3822 }
3823 }
3824 else
3825 {
3826 if (String.IsNullOrEmpty(shortSourceName))
3827 {
3828 }
3829 else if (sourceName == ".")
3830 {
3831 this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "ShortSourceName", "SourceName", sourceName));
3832 }
3833 else if (sourceName.Equals(shortSourceName, StringComparison.OrdinalIgnoreCase))
3834 {
3835 this.Core.Write(WarningMessages.DirectoryRedundantNames(sourceLineNumbers, node.Name.LocalName, "SourceName", "ShortSourceName", sourceName));
3836 }
3837 }
3838
3839 if (null == id)
3840 {
3841 id = this.Core.CreateIdentifier("d", parentId, name, shortName, sourceName, shortSourceName);
3842 }
3843 else if (WindowsInstallerStandard.IsStandardDirectory(id.Id))
3844 {
3845 if (String.IsNullOrEmpty(sourceName))
3846 {
3847 this.Core.Write(CompilerWarnings.DefiningStandardDirectoryDeprecated(sourceLineNumbers, id.Id));
3848 }
3849
3850 if (id.Id == "TARGETDIR" && name != "SourceDir" && shortName == null && shortSourceName == null && sourceName == null)
3851 {
3852 this.Core.Write(ErrorMessages.IllegalTargetDirDefaultDir(sourceLineNumbers, name));
3853 }
3854 }
3855
3856 // Update the file source path appropriately.
3857 if (fileSourceAttribSet)
3858 {
3859 if (!fileSource.EndsWith(Path.DirectorySeparatorChar.ToString(), StringComparison.Ordinal))
3860 {
3861 fileSource = String.Concat(fileSource, Path.DirectorySeparatorChar);
3862 }
3863 }
3864 else // add the appropriate part of this directory element to the file source.
3865 {
3866 var append = String.IsNullOrEmpty(sourceName) ? name : sourceName;
3867
3868 if (!String.IsNullOrEmpty(append))
3869 {
3870 fileSource = String.Concat(fileSource, append, Path.DirectorySeparatorChar);
3871 }
3872 }
3873
3874 foreach (var child in node.Elements())
3875 {
3876 if (CompilerCore.WixNamespace == child.Name.Namespace)
3877 {
3878 switch (child.Name.LocalName)
3879 {
3880 case "Component":
3881 this.ParseComponentElement(child, ComplexReferenceParentType.Unknown, null, null, diskId, id.Id, fileSource);
3882 break;
3883 case "Directory":
3884 this.ParseDirectoryElement(child, id.Id, diskId, fileSource);
3885 break;
3886 case "File":
3887 this.ParseNakedFileElement(child, ComplexReferenceParentType.Unknown, null, id.Id, fileSource);
3888 break;
3889 case "Files":
3890 this.ParseFilesElement(child, ComplexReferenceParentType.Unknown, null, id.Id, fileSource);
3891 break;
3892 case "Merge":
3893 this.ParseMergeElement(child, id.Id, diskId);
3894 break;
3895 case "SymbolPath":
3896 if (null != symbols)
3897 {
3898 symbols += ";" + this.ParseSymbolPathElement(child);
3899 }
3900 else
3901 {
3902 symbols = this.ParseSymbolPathElement(child);
3903 }
3904 break;
3905 default:
3906 this.Core.UnexpectedElement(node, child);
3907 break;
3908 }
3909 }
3910 else
3911 {
3912 this.Core.ParseExtensionElement(node, child);
3913 }
3914 }
3915
3916 if (!this.Core.EncounteredError)
3917 {
3918 this.Core.AddSymbol(new DirectorySymbol(sourceLineNumbers, id)
3919 {
3920 ParentDirectoryRef = parentId,
3921 Name = name,
3922 ShortName = shortName,
3923 SourceName = sourceName,
3924 SourceShortName = shortSourceName,
3925 ComponentGuidGenerationSeed = componentGuidGenerationSeed
3926 });
3927
3928 if (null != symbols)
3929 {
3930 this.Core.AddSymbol(new WixDeltaPatchSymbolPathsSymbol(sourceLineNumbers, id)
3931 {
3932 SymbolType = SymbolPathType.Directory,
3933 SymbolId = id.Id,
3934 SymbolPaths = symbols,
3935 });
3936 }
3937 }
3938 }
3939
3940 /// <summary>
3941 /// Parses a directory reference element.
3942 /// </summary>
3943 /// <param name="node">Element to parse.</param>
3944 private void ParseDirectoryRefElement(XElement node)
3945 {
3946 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
3947 string id = null;
3948 var diskId = CompilerConstants.IntegerNotSet;
3949 var fileSource = String.Empty;
3950
3951 foreach (var attrib in node.Attributes())
3952 {
3953 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
3954 {
3955 switch (attrib.Name.LocalName)
3956 {
3957 case "Id":
3958 id = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
3959 this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Directory, id);
3960 break;
3961 case "DiskId":
3962 diskId = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 1, Int16.MaxValue);
3963 break;
3964 case "FileSource":
3965 fileSource = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
3966 break;
3967 default:
3968 this.Core.UnexpectedAttribute(node, attrib);
3969 break;
3970 }
3971 }
3972 else
3973 {
3974 this.Core.ParseExtensionAttribute(node, attrib);
3975 }
3976 }
3977
3978 if (null == id)
3979 {
3980 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id"));
3981 }
3982 else if (WindowsInstallerStandard.IsStandardDirectory(id))
3983 {
3984 this.Core.Write(CompilerWarnings.DirectoryRefStandardDirectoryDeprecated(sourceLineNumbers, id));
3985 }
3986
3987 if (!String.IsNullOrEmpty(fileSource) && !fileSource.EndsWith(Path.DirectorySeparatorChar.ToString(), StringComparison.Ordinal))
3988 {
3989 fileSource = String.Concat(fileSource, Path.DirectorySeparatorChar);
3990 }
3991
3992 foreach (var child in node.Elements())
3993 {
3994 if (CompilerCore.WixNamespace == child.Name.Namespace)
3995 {
3996 switch (child.Name.LocalName)
3997 {
3998 case "Component":
3999 this.ParseComponentElement(child, ComplexReferenceParentType.Unknown, null, null, diskId, id, fileSource);
4000 break;
4001 case "Directory":
4002 this.ParseDirectoryElement(child, id, diskId, fileSource);
4003 break;
4004 case "File":
4005 this.ParseNakedFileElement(child, ComplexReferenceParentType.Unknown, null, id, fileSource);
4006 break;
4007 case "Files":
4008 this.ParseFilesElement(child, ComplexReferenceParentType.Unknown, null, id, fileSource);
4009 break;
4010 case "Merge":
4011 this.ParseMergeElement(child, id, diskId);
4012 break;
4013 default:
4014 this.Core.UnexpectedElement(node, child);
4015 break;
4016 }
4017 }
4018 else
4019 {
4020 this.Core.ParseExtensionElement(node, child);
4021 }
4022 }
4023 }
4024
4025 /// <summary>
4026 /// Parses a directory search element.
4027 /// </summary>
4028 /// <param name="node">Element to parse.</param>
4029 /// <param name="parentSignature">Signature of parent search element.</param>
4030 /// <returns>Signature of search element.</returns>
4031 private string ParseDirectorySearchElement(XElement node, string parentSignature)
4032 {
4033 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
4034 Identifier id = null;
4035 var depth = CompilerConstants.IntegerNotSet;
4036 string path = null;
4037 var assignToProperty = false;
4038
4039 foreach (var attrib in node.Attributes())
4040 {
4041 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
4042 {
4043 switch (attrib.Name.LocalName)
4044 {
4045 case "Id":
4046 id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib);
4047 break;
4048 case "Depth":
4049 depth = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int16.MaxValue);
4050 break;
4051 case "Path":
4052 path = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
4053 break;
4054 case "AssignToProperty":
4055 assignToProperty = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
4056 break;
4057 default:
4058 this.Core.UnexpectedAttribute(node, attrib);
4059 break;
4060 }
4061 }
4062 else
4063 {
4064 this.Core.ParseExtensionAttribute(node, attrib);
4065 }
4066 }
4067
4068 if (null == id)
4069 {
4070 id = this.Core.CreateIdentifier("dir", path, depth.ToString());
4071 }
4072
4073 var signature = id.Id;
4074
4075 var oneChild = false;
4076 var hasFileSearch = false;
4077 foreach (var child in node.Elements())
4078 {
4079 if (CompilerCore.WixNamespace == child.Name.Namespace)
4080 {
4081 var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child);
4082 switch (child.Name.LocalName)
4083 {
4084 case "DirectorySearch":
4085 if (oneChild)
4086 {
4087 this.Core.Write(ErrorMessages.TooManySearchElements(childSourceLineNumbers, node.Name.LocalName));
4088 }
4089 oneChild = true;
4090 signature = this.ParseDirectorySearchElement(child, id.Id);
4091 break;
4092 case "DirectorySearchRef":
4093 if (oneChild)
4094 {
4095 this.Core.Write(ErrorMessages.TooManySearchElements(childSourceLineNumbers, node.Name.LocalName));
4096 }
4097 oneChild = true;
4098 signature = this.ParseDirectorySearchRefElement(child, id.Id);
4099 break;
4100 case "FileSearch":
4101 if (oneChild)
4102 {
4103 this.Core.Write(ErrorMessages.TooManySearchElements(sourceLineNumbers, node.Name.LocalName));
4104 }
4105 oneChild = true;
4106 hasFileSearch = true;
4107 signature = this.ParseFileSearchElement(child, id.Id, assignToProperty, depth);
4108 break;
4109 case "FileSearchRef":
4110 if (oneChild)
4111 {
4112 this.Core.Write(ErrorMessages.TooManySearchElements(sourceLineNumbers, node.Name.LocalName));
4113 }
4114 oneChild = true;
4115 signature = this.ParseSimpleRefElement(child, SymbolDefinitions.Signature);
4116 break;
4117 default:
4118 this.Core.UnexpectedElement(node, child);
4119 break;
4120 }
4121
4122 // If AssignToProperty is set, only a FileSearch
4123 // or no child element can be nested.
4124 if (assignToProperty)
4125 {
4126 if (!hasFileSearch)
4127 {
4128 this.Core.Write(ErrorMessages.IllegalParentAttributeWhenNested(sourceLineNumbers, node.Name.LocalName, "AssignToProperty", child.Name.LocalName));
4129 }
4130 else if (!oneChild)
4131 {
4132 // This a normal directory search.
4133 assignToProperty = false;
4134 }
4135 }
4136 }
4137 else
4138 {
4139 this.Core.ParseExtensionElement(node, child);
4140 }
4141 }
4142
4143 if (!this.Core.EncounteredError)
4144 {
4145 var access = id.Access;
4146 var rowId = id.Id;
4147
4148 // If AssignToProperty is set, the DrLocator row created by
4149 // ParseFileSearchElement creates the directory entry to return
4150 // and the row created here is for the file search.
4151 if (assignToProperty)
4152 {
4153 access = AccessModifier.Section;
4154 rowId = signature;
4155
4156 // The property should be set to the directory search Id.
4157 signature = id.Id;
4158 }
4159
4160 var symbol = this.Core.AddSymbol(new DrLocatorSymbol(sourceLineNumbers, new Identifier(access, rowId, parentSignature, path))
4161 {
4162 SignatureRef = rowId,
4163 Parent = parentSignature,
4164 Path = path,
4165 });
4166
4167 if (CompilerConstants.IntegerNotSet != depth)
4168 {
4169 symbol.Depth = depth;
4170 }
4171 }
4172
4173 return signature;
4174 }
4175
4176 /// <summary>
4177 /// Parses a directory search reference element.
4178 /// </summary>
4179 /// <param name="node">Element to parse.</param>
4180 /// <param name="parentSignature">Signature of parent search element.</param>
4181 /// <returns>Signature of search element.</returns>
4182 private string ParseDirectorySearchRefElement(XElement node, string parentSignature)
4183 {
4184 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
4185 Identifier id = null;
4186 Identifier parent = null;
4187 string path = null;
4188
4189 foreach (var attrib in node.Attributes())
4190 {
4191 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
4192 {
4193 switch (attrib.Name.LocalName)
4194 {
4195 case "Id":
4196 id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib);
4197 break;
4198 case "Parent":
4199 parent = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib);
4200 break;
4201 case "Path":
4202 path = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
4203 break;
4204 default:
4205 this.Core.UnexpectedAttribute(node, attrib);
4206 break;
4207 }
4208 }
4209 else
4210 {
4211 this.Core.ParseExtensionAttribute(node, attrib);
4212 }
4213 }
4214
4215 if (null != parent)
4216 {
4217 if (!String.IsNullOrEmpty(parentSignature))
4218 {
4219 this.Core.Write(ErrorMessages.CanNotHaveTwoParents(sourceLineNumbers, id.Id, parent.Id, parentSignature));
4220 }
4221 else
4222 {
4223 parentSignature = parent.Id;
4224 }
4225 }
4226
4227 if (null == id)
4228 {
4229 id = this.Core.CreateIdentifier("dsr", parentSignature, path);
4230 }
4231
4232 var signature = id.Id;
4233
4234 var oneChild = false;
4235 foreach (var child in node.Elements())
4236 {
4237 if (CompilerCore.WixNamespace == child.Name.Namespace)
4238 {
4239 var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child);
4240 switch (child.Name.LocalName)
4241 {
4242 case "DirectorySearch":
4243 if (oneChild)
4244 {
4245 this.Core.Write(ErrorMessages.TooManySearchElements(childSourceLineNumbers, node.Name.LocalName));
4246 }
4247 oneChild = true;
4248 signature = this.ParseDirectorySearchElement(child, id.Id);
4249 break;
4250 case "DirectorySearchRef":
4251 if (oneChild)
4252 {
4253 this.Core.Write(ErrorMessages.TooManySearchElements(childSourceLineNumbers, node.Name.LocalName));
4254 }
4255 oneChild = true;
4256 signature = this.ParseDirectorySearchRefElement(child, id.Id);
4257 break;
4258 case "FileSearch":
4259 if (oneChild)
4260 {
4261 this.Core.Write(ErrorMessages.TooManySearchElements(childSourceLineNumbers, node.Name.LocalName));
4262 }
4263 oneChild = true;
4264 signature = this.ParseFileSearchElement(child, id.Id, false, CompilerConstants.IntegerNotSet);
4265 break;
4266 case "FileSearchRef":
4267 if (oneChild)
4268 {
4269 this.Core.Write(ErrorMessages.TooManySearchElements(sourceLineNumbers, node.Name.LocalName));
4270 }
4271 oneChild = true;
4272 signature = this.ParseSimpleRefElement(child, SymbolDefinitions.Signature);
4273 break;
4274 default:
4275 this.Core.UnexpectedElement(node, child);
4276 break;
4277 }
4278 }
4279 else
4280 {
4281 this.Core.ParseExtensionElement(node, child);
4282 }
4283 }
4284
4285
4286 this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.DrLocator, id.Id, parentSignature, path);
4287
4288 return signature;
4289 }
4290
4291 /// <summary>
4292 /// Parses a feature element.
4293 /// </summary>
4294 /// <param name="node">Element to parse.</param>
4295 /// <param name="parentType">The type of parent.</param>
4296 /// <param name="parentId">Optional identifer for parent feature.</param>
4297 /// <param name="lastDisplay">Display value for last feature used to get the features to display in the same order as specified
4298 /// in the source code.</param>
4299 private void ParseFeatureElement(XElement node, ComplexReferenceParentType parentType, string parentId, ref int lastDisplay)
4300 {
4301 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
4302 Identifier id = null;
4303 string configurableDirectory = null;
4304 string description = null;
4305 var displayValue = "collapse";
4306 var level = 1;
4307 string title = null;
4308
4309 var installDefault = FeatureInstallDefault.Local;
4310 var typicalDefault = FeatureTypicalDefault.Install;
4311 var disallowAbsent = false;
4312 var disallowAdvertise = false;
4313
4314 foreach (var attrib in node.Attributes())
4315 {
4316 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
4317 {
4318 switch (attrib.Name.LocalName)
4319 {
4320 case "Id":
4321 id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib);
4322 break;
4323 case "AllowAbsent":
4324 disallowAbsent = (this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib) == YesNoType.No);
4325 break;
4326 case "AllowAdvertise":
4327 disallowAdvertise = (this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib) == YesNoType.No);
4328 break;
4329 case "ConfigurableDirectory":
4330 configurableDirectory = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
4331 this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Directory, configurableDirectory);
4332 break;
4333 case "Description":
4334 description = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
4335 break;
4336 case "Display":
4337 displayValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
4338 break;
4339 case "InstallDefault":
4340 var installDefaultValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
4341 switch (installDefaultValue)
4342 {
4343 case "followParent":
4344 if (ComplexReferenceParentType.Product == parentType)
4345 {
4346 this.Core.Write(ErrorMessages.RootFeatureCannotFollowParent(sourceLineNumbers));
4347 }
4348 //bits = bits | MsiInterop.MsidbFeatureAttributesFollowParent;
4349 installDefault = FeatureInstallDefault.FollowParent;
4350 break;
4351 case "local": // this is the default
4352 installDefault = FeatureInstallDefault.Local;
4353 break;
4354 case "source":
4355 //bits = bits | MsiInterop.MsidbFeatureAttributesFavorSource;
4356 installDefault = FeatureInstallDefault.Source;
4357 break;
4358 case "":
4359 break;
4360 default:
4361 this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, installDefaultValue, "followParent", "local", "source"));
4362 break;
4363 }
4364 break;
4365 case "Level":
4366 level = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int16.MaxValue);
4367 break;
4368 case "Title":
4369 title = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
4370 if ("PUT-FEATURE-TITLE-HERE" == title)
4371 {
4372 this.Core.Write(WarningMessages.PlaceholderValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, title));
4373 }
4374 break;
4375 case "TypicalDefault":
4376 var typicalValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
4377 switch (typicalValue)
4378 {
4379 case "advertise":
4380 //bits |= MsiInterop.MsidbFeatureAttributesFavorAdvertise;
4381 typicalDefault = FeatureTypicalDefault.Advertise;
4382 break;
4383 case "install": // this is the default
4384 typicalDefault = FeatureTypicalDefault.Install;
4385 break;
4386 default:
4387 this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, typicalValue, "advertise", "install"));
4388 break;
4389 }
4390 break;
4391 default:
4392 this.Core.UnexpectedAttribute(node, attrib);
4393 break;
4394 }
4395 }
4396 else
4397 {
4398 this.Core.ParseExtensionAttribute(node, attrib);
4399 }
4400 }
4401
4402 if (null == id)
4403 {
4404 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id"));
4405 id = Identifier.Invalid;
4406 }
4407 else if (38 < id.Id.Length)
4408 {
4409 this.Core.Write(ErrorMessages.FeatureNameTooLong(sourceLineNumbers, node.Name.LocalName, "Id", id.Id));
4410 }
4411
4412 if (null != configurableDirectory && configurableDirectory.ToUpper(CultureInfo.InvariantCulture) != configurableDirectory)
4413 {
4414 this.Core.Write(ErrorMessages.FeatureConfigurableDirectoryNotUppercase(sourceLineNumbers, node.Name.LocalName, "ConfigurableDirectory", configurableDirectory));
4415 }
4416
4417 if (FeatureTypicalDefault.Advertise == typicalDefault && disallowAdvertise)
4418 {
4419 this.Core.Write(ErrorMessages.FeatureCannotFavorAndDisallowAdvertise(sourceLineNumbers, node.Name.LocalName, "TypicalDefault", "advertise", "AllowAdvertise", "no"));
4420 }
4421
4422 var childDisplay = 0;
4423 foreach (var child in node.Elements())
4424 {
4425 if (CompilerCore.WixNamespace == child.Name.Namespace)
4426 {
4427 switch (child.Name.LocalName)
4428 {
4429 case "ComponentGroupRef":
4430 this.ParseComponentGroupRefElement(child, ComplexReferenceParentType.Feature, id.Id, null);
4431 break;
4432 case "ComponentRef":
4433 this.ParseComponentRefElement(child, ComplexReferenceParentType.Feature, id.Id, null);
4434 break;
4435 case "Component":
4436 this.ParseComponentElement(child, ComplexReferenceParentType.Feature, id.Id, null, CompilerConstants.IntegerNotSet, null, null);
4437 break;
4438 case "Feature":
4439 this.ParseFeatureElement(child, ComplexReferenceParentType.Feature, id.Id, ref childDisplay);
4440 break;
4441 case "FeatureGroupRef":
4442 this.ParseFeatureGroupRefElement(child, ComplexReferenceParentType.Feature, id.Id);
4443 break;
4444 case "FeatureRef":
4445 this.ParseFeatureRefElement(child, ComplexReferenceParentType.Feature, id.Id);
4446 break;
4447 case "File":
4448 this.ParseNakedFileElement(child, ComplexReferenceParentType.Feature, id.Id, null, null);
4449 break;
4450 case "Files":
4451 this.ParseFilesElement(child, ComplexReferenceParentType.Feature, id.Id, null, null);
4452 break;
4453 case "Level":
4454 this.ParseLevelElement(child, id.Id);
4455 break;
4456 case "MergeRef":
4457 this.ParseMergeRefElement(child, ComplexReferenceParentType.Feature, id.Id);
4458 break;
4459 default:
4460 this.Core.UnexpectedElement(node, child);
4461 break;
4462 }
4463 }
4464 else
4465 {
4466 this.Core.ParseExtensionElement(node, child);
4467 }
4468 }
4469
4470 int display;
4471 switch (displayValue)
4472 {
4473 case "collapse":
4474 lastDisplay = (lastDisplay | 1) + 1;
4475 display = lastDisplay;
4476 break;
4477 case "expand":
4478 lastDisplay = (lastDisplay + 1) | 1;
4479 display = lastDisplay;
4480 break;
4481 case "hidden":
4482 display = 0;
4483 break;
4484 default:
4485 if (!Int32.TryParse(displayValue, NumberStyles.Integer, CultureInfo.InvariantCulture, out display))
4486 {
4487 this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, "Display", displayValue, "collapse", "expand", "hidden"));
4488 }
4489 else
4490 {
4491 // Save the display value (if its not hidden) for subsequent rows
4492 if (0 != display)
4493 {
4494 lastDisplay = display;
4495 }
4496 }
4497 break;
4498 }
4499
4500 if (!this.Core.EncounteredError)
4501 {
4502 this.Core.AddSymbol(new FeatureSymbol(sourceLineNumbers, id)
4503 {
4504 ParentFeatureRef = null, // this field is set in the linker
4505 Title = title,
4506 Description = description,
4507 Display = display,
4508 Level = level,
4509 DirectoryRef = configurableDirectory,
4510 DisallowAbsent = disallowAbsent,
4511 DisallowAdvertise = disallowAdvertise,
4512 InstallDefault = installDefault,
4513 TypicalDefault = typicalDefault,
4514 });
4515
4516 if (ComplexReferenceParentType.Unknown != parentType)
4517 {
4518 this.Core.CreateComplexReference(sourceLineNumbers, parentType, parentId, null, ComplexReferenceChildType.Feature, id.Id, false);
4519 }
4520 }
4521 }
4522
4523 /// <summary>
4524 /// Parses a feature reference element.
4525 /// </summary>
4526 /// <param name="node">Element to parse.</param>
4527 /// <param name="parentType">The type of parent.</param>
4528 /// <param name="parentId">Optional identifier for parent feature.</param>
4529 private void ParseFeatureRefElement(XElement node, ComplexReferenceParentType parentType, string parentId)
4530 {
4531 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
4532 string id = null;
4533 var ignoreParent = YesNoType.NotSet;
4534
4535 foreach (var attrib in node.Attributes())
4536 {
4537 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
4538 {
4539 switch (attrib.Name.LocalName)
4540 {
4541 case "Id":
4542 id = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
4543 this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Feature, id);
4544 break;
4545 case "IgnoreParent":
4546 ignoreParent = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
4547 break;
4548 default:
4549 this.Core.UnexpectedAttribute(node, attrib);
4550 break;
4551 }
4552 }
4553 else
4554 {
4555 this.Core.ParseExtensionAttribute(node, attrib);
4556 }
4557 }
4558
4559
4560 if (null == id)
4561 {
4562 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id"));
4563 }
4564
4565 var lastDisplay = 0;
4566 foreach (var child in node.Elements())
4567 {
4568 if (CompilerCore.WixNamespace == child.Name.Namespace)
4569 {
4570 switch (child.Name.LocalName)
4571 {
4572 case "ComponentGroupRef":
4573 this.ParseComponentGroupRefElement(child, ComplexReferenceParentType.Feature, id, null);
4574 break;
4575 case "ComponentRef":
4576 this.ParseComponentRefElement(child, ComplexReferenceParentType.Feature, id, null);
4577 break;
4578 case "Component":
4579 this.ParseComponentElement(child, ComplexReferenceParentType.Feature, id, null, CompilerConstants.IntegerNotSet, null, null);
4580 break;
4581 case "Feature":
4582 this.ParseFeatureElement(child, ComplexReferenceParentType.Feature, id, ref lastDisplay);
4583 break;
4584 case "FeatureGroup":
4585 this.ParseFeatureGroupElement(child, ComplexReferenceParentType.Feature, id);
4586 break;
4587 case "FeatureGroupRef":
4588 this.ParseFeatureGroupRefElement(child, ComplexReferenceParentType.Feature, id);
4589 break;
4590 case "FeatureRef":
4591 this.ParseFeatureRefElement(child, ComplexReferenceParentType.Feature, id);
4592 break;
4593 case "File":
4594 this.ParseNakedFileElement(child, ComplexReferenceParentType.Feature, id, null, null);
4595 break;
4596 case "Files":
4597 this.ParseFilesElement(child, ComplexReferenceParentType.Feature, id, null, null);
4598 break;
4599 case "MergeRef":
4600 this.ParseMergeRefElement(child, ComplexReferenceParentType.Feature, id);
4601 break;
4602 default:
4603 this.Core.UnexpectedElement(node, child);
4604 break;
4605 }
4606 }
4607 else
4608 {
4609 this.Core.ParseExtensionElement(node, child);
4610 }
4611 }
4612
4613 if (!this.Core.EncounteredError)
4614 {
4615 if (ComplexReferenceParentType.Unknown != parentType && YesNoType.Yes != ignoreParent)
4616 {
4617 this.Core.CreateComplexReference(sourceLineNumbers, parentType, parentId, null, ComplexReferenceChildType.Feature, id, false);
4618 }
4619 }
4620 }
4621
4622 /// <summary>
4623 /// Parses a feature group element.
4624 /// </summary>
4625 /// <param name="node">Element to parse.</param>
4626 /// <param name="parentType"></param>
4627 /// <param name="parentId"></param>
4628 private void ParseFeatureGroupElement(XElement node, ComplexReferenceParentType parentType, string parentId)
4629 {
4630 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
4631 Identifier id = null;
4632
4633 foreach (var attrib in node.Attributes())
4634 {
4635 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
4636 {
4637 switch (attrib.Name.LocalName)
4638 {
4639 case "Id":
4640 id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib);
4641 break;
4642 default:
4643 this.Core.UnexpectedAttribute(node, attrib);
4644 break;
4645 }
4646 }
4647 else
4648 {
4649 this.Core.ParseExtensionAttribute(node, attrib);
4650 }
4651 }
4652
4653 if (null == id)
4654 {
4655 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id"));
4656 id = Identifier.Invalid;
4657 }
4658
4659 var lastDisplay = 0;
4660 foreach (var child in node.Elements())
4661 {
4662 if (CompilerCore.WixNamespace == child.Name.Namespace)
4663 {
4664 switch (child.Name.LocalName)
4665 {
4666 case "ComponentGroupRef":
4667 this.ParseComponentGroupRefElement(child, ComplexReferenceParentType.FeatureGroup, id.Id, null);
4668 break;
4669 case "ComponentRef":
4670 this.ParseComponentRefElement(child, ComplexReferenceParentType.FeatureGroup, id.Id, null);
4671 break;
4672 case "Component":
4673 this.ParseComponentElement(child, ComplexReferenceParentType.FeatureGroup, id.Id, null, CompilerConstants.IntegerNotSet, null, null);
4674 break;
4675 case "Feature":
4676 this.ParseFeatureElement(child, ComplexReferenceParentType.FeatureGroup, id.Id, ref lastDisplay);
4677 break;
4678 case "FeatureGroupRef":
4679 this.ParseFeatureGroupRefElement(child, ComplexReferenceParentType.FeatureGroup, id.Id);
4680 break;
4681 case "FeatureRef":
4682 this.ParseFeatureRefElement(child, ComplexReferenceParentType.FeatureGroup, id.Id);
4683 break;
4684 case "File":
4685 this.ParseNakedFileElement(child, ComplexReferenceParentType.FeatureGroup, id.Id, null, null);
4686 break;
4687 case "Files":
4688 this.ParseFilesElement(child, ComplexReferenceParentType.Feature, id.Id, null, null);
4689 break;
4690 case "MergeRef":
4691 this.ParseMergeRefElement(child, ComplexReferenceParentType.FeatureGroup, id.Id);
4692 break;
4693 default:
4694 this.Core.UnexpectedElement(node, child);
4695 break;
4696 }
4697 }
4698 else
4699 {
4700 this.Core.ParseExtensionElement(node, child);
4701 }
4702 }
4703
4704 if (!this.Core.EncounteredError)
4705 {
4706 this.Core.AddSymbol(new WixFeatureGroupSymbol(sourceLineNumbers, id));
4707
4708 //Add this FeatureGroup and its parent in WixGroup.
4709 this.Core.CreateWixGroupRow(sourceLineNumbers, parentType, parentId, ComplexReferenceChildType.FeatureGroup, id.Id);
4710 }
4711 }
4712
4713 /// <summary>
4714 /// Parses a feature group reference element.
4715 /// </summary>
4716 /// <param name="node">Element to parse.</param>
4717 /// <param name="parentType">The type of parent.</param>
4718 /// <param name="parentId">Identifier of parent element.</param>
4719 private void ParseFeatureGroupRefElement(XElement node, ComplexReferenceParentType parentType, string parentId)
4720 {
4721 Debug.Assert(ComplexReferenceParentType.Feature == parentType || ComplexReferenceParentType.FeatureGroup == parentType || ComplexReferenceParentType.ComponentGroup == parentType || ComplexReferenceParentType.Product == parentType);
4722
4723 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
4724 string id = null;
4725 var ignoreParent = YesNoType.NotSet;
4726 var primary = YesNoType.NotSet;
4727
4728 foreach (var attrib in node.Attributes())
4729 {
4730 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
4731 {
4732 switch (attrib.Name.LocalName)
4733 {
4734 case "Id":
4735 id = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
4736 this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.WixFeatureGroup, id);
4737 break;
4738 case "IgnoreParent":
4739 ignoreParent = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
4740 break;
4741 case "Primary":
4742 primary = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
4743 break;
4744 default:
4745 this.Core.UnexpectedAttribute(node, attrib);
4746 break;
4747 }
4748 }
4749 else
4750 {
4751 this.Core.ParseExtensionAttribute(node, attrib);
4752 }
4753 }
4754
4755 if (null == id)
4756 {
4757 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id"));
4758 }
4759
4760 this.Core.ParseForExtensionElements(node);
4761
4762 if (!this.Core.EncounteredError)
4763 {
4764 if (YesNoType.Yes != ignoreParent)
4765 {
4766 this.Core.CreateComplexReference(sourceLineNumbers, parentType, parentId, null, ComplexReferenceChildType.FeatureGroup, id, (YesNoType.Yes == primary));
4767 }
4768 }
4769 }
4770
4771 /// <summary>
4772 /// Parses an environment element.
4773 /// </summary>
4774 /// <param name="node">Element to parse.</param>
4775 /// <param name="componentId">Identifier of parent component.</param>
4776 private void ParseEnvironmentElement(XElement node, string componentId)
4777 {
4778 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
4779 Identifier id = null;
4780 string name = null;
4781 EnvironmentActionType? action = null;
4782 EnvironmentPartType? part = null;
4783 var permanent = false;
4784 var separator = ";"; // default to ';'
4785 var system = false;
4786 string value = null;
4787
4788 foreach (var attrib in node.Attributes())
4789 {
4790 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
4791 {
4792 switch (attrib.Name.LocalName)
4793 {
4794 case "Id":
4795 id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib);
4796 break;
4797 case "Action":
4798 var actionValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
4799 switch (actionValue)
4800 {
4801 case "create":
4802 action = EnvironmentActionType.Create;
4803 break;
4804 case "set":
4805 action = EnvironmentActionType.Set;
4806 break;
4807 case "remove":
4808 action = EnvironmentActionType.Remove;
4809 break;
4810 default:
4811 this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, value, "create", "set", "remove"));
4812 break;
4813 }
4814 break;
4815 case "Name":
4816 name = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
4817 break;
4818 case "Part":
4819 var partValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
4820 switch (partValue)
4821 {
4822 case "all":
4823 part = EnvironmentPartType.All;
4824 break;
4825 case "first":
4826 part = EnvironmentPartType.First;
4827 break;
4828 case "last":
4829 part = EnvironmentPartType.Last;
4830 break;
4831 case "":
4832 break;
4833 default:
4834 this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, "Part", partValue, "all", "first", "last"));
4835 break;
4836 }
4837 break;
4838 case "Permanent":
4839 permanent = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
4840 break;
4841 case "Separator":
4842 separator = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
4843 break;
4844 case "System":
4845 system = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
4846 break;
4847 case "Value":
4848 value = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
4849 break;
4850 default:
4851 this.Core.UnexpectedAttribute(node, attrib);
4852 break;
4853 }
4854 }
4855 else
4856 {
4857 this.Core.ParseExtensionAttribute(node, attrib);
4858 }
4859 }
4860
4861 if (null == id)
4862 {
4863 id = this.Core.CreateIdentifier("env", ((int?)action)?.ToString(), name, ((int?)part)?.ToString(), system.ToString());
4864 }
4865
4866 if (null == name)
4867 {
4868 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name"));
4869 }
4870
4871 if (part.HasValue && action == EnvironmentActionType.Create)
4872 {
4873 this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Part", "Action", "create"));
4874 }
4875
4876 //if (Wix.Environment.PartType.NotSet != partType)
4877 //{
4878 // if ("+" == action)
4879 // {
4880 // this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Part", "Action", "create"));
4881 // }
4882
4883 // switch (partType)
4884 // {
4885 // case Wix.Environment.PartType.all:
4886 // break;
4887 // case Wix.Environment.PartType.first:
4888 // text = String.Concat(text, separator, "[~]");
4889 // break;
4890 // case Wix.Environment.PartType.last:
4891 // text = String.Concat("[~]", separator, text);
4892 // break;
4893 // }
4894 //}
4895
4896 //if (permanent)
4897 //{
4898 // uninstall = null;
4899 //}
4900
4901 this.Core.ParseForExtensionElements(node);
4902
4903 if (!this.Core.EncounteredError)
4904 {
4905 this.Core.AddSymbol(new EnvironmentSymbol(sourceLineNumbers, id)
4906 {
4907 Name = name,
4908 Value = value,
4909 Separator = separator,
4910 Action = action,
4911 Part = part,
4912 Permanent = permanent,
4913 System = system,
4914 ComponentRef = componentId
4915 });
4916 }
4917 }
4918
4919 /// <summary>
4920 /// Parses an error element.
4921 /// </summary>
4922 /// <param name="node">Element to parse.</param>
4923 private void ParseErrorElement(XElement node)
4924 {
4925 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
4926 var id = CompilerConstants.IntegerNotSet;
4927 string message = null;
4928
4929 foreach (var attrib in node.Attributes())
4930 {
4931 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
4932 {
4933 switch (attrib.Name.LocalName)
4934 {
4935 case "Id":
4936 id = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int16.MaxValue);
4937 break;
4938 case "Message":
4939 message = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty);
4940 break;
4941 default:
4942 this.Core.UnexpectedAttribute(node, attrib);
4943 break;
4944 }
4945 }
4946 else
4947 {
4948 this.Core.ParseExtensionAttribute(node, attrib);
4949 }
4950 }
4951
4952 if (CompilerConstants.IntegerNotSet == id)
4953 {
4954 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id"));
4955 id = CompilerConstants.IllegalInteger;
4956 }
4957
4958 this.Core.ParseForExtensionElements(node);
4959
4960 if (!this.Core.EncounteredError)
4961 {
4962 this.Core.AddSymbol(new ErrorSymbol(sourceLineNumbers, new Identifier(AccessModifier.Global, id))
4963 {
4964 Message = message
4965 });
4966 }
4967 }
4968
4969 /// <summary>
4970 /// Parses an extension element.
4971 /// </summary>
4972 /// <param name="node">Element to parse.</param>
4973 /// <param name="componentId">Identifier of parent component.</param>
4974 /// <param name="advertise">Flag if this extension is advertised.</param>
4975 /// <param name="progId">ProgId for extension.</param>
4976 private void ParseExtensionElement(XElement node, string componentId, YesNoType advertise, string progId)
4977 {
4978 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
4979 string extension = null;
4980 string mime = null;
4981
4982 foreach (var attrib in node.Attributes())
4983 {
4984 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
4985 {
4986 switch (attrib.Name.LocalName)
4987 {
4988 case "Id":
4989 extension = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
4990 break;
4991 case "Advertise":
4992 var extensionAdvertise = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
4993 if ((YesNoType.No == advertise && YesNoType.Yes == extensionAdvertise) || (YesNoType.Yes == advertise && YesNoType.No == extensionAdvertise))
4994 {
4995 this.Core.Write(ErrorMessages.AdvertiseStateMustMatch(sourceLineNumbers, extensionAdvertise.ToString(), advertise.ToString()));
4996 }
4997 advertise = extensionAdvertise;
4998 break;
4999 case "ContentType":
5000 mime = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
Showing first 5,000 of 8,375 lines. View raw