// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. namespace WixToolset.Core { using System; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; using System.Xml.Linq; using WixToolset.Data; using WixToolset.Data.Burn; using WixToolset.Data.Symbols; using WixToolset.Extensibility; /// /// Compiler of the WiX toolset. /// internal partial class Compiler : ICompiler { private static readonly Identifier BurnUXContainerId = new Identifier(AccessModifier.Section, BurnConstants.BurnUXContainerName); private static readonly Identifier BurnDefaultAttachedContainerId = new Identifier(AccessModifier.Section, BurnConstants.BurnDefaultAttachedContainerName); private static readonly Identifier BundleLayoutOnlyPayloads = new Identifier(AccessModifier.Section, BurnConstants.BundleLayoutOnlyPayloadsName); /// /// Parses an ApprovedExeForElevation element. /// /// Element to parse private void ParseApprovedExeForElevation(XElement node) { var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); Identifier id = null; string key = null; string valueName = null; var win64 = this.Context.IsCurrentPlatform64Bit; foreach (var attrib in node.Attributes()) { if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) { switch (attrib.Name.LocalName) { case "Id": id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); break; case "Bitness": var bitnessValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); switch (bitnessValue) { case "always32": win64 = false; break; case "always64": win64 = true; break; case "default": case "": break; default: this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, bitnessValue, "default", "always32", "always64")); break; } break; case "Key": key = this.Core.GetAttributeValue(sourceLineNumbers, attrib); break; case "Value": valueName = this.Core.GetAttributeValue(sourceLineNumbers, attrib); break; default: this.Core.UnexpectedAttribute(node, attrib); break; } } else { this.Core.ParseExtensionAttribute(node, attrib); } } if (null == id) { this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); } if (null == key) { this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Key")); } var attributes = WixApprovedExeForElevationAttributes.None; if (win64) { attributes |= WixApprovedExeForElevationAttributes.Win64; } this.Core.ParseForExtensionElements(node); if (!this.Core.EncounteredError) { this.Core.AddSymbol(new WixApprovedExeForElevationSymbol(sourceLineNumbers, id) { Key = key, ValueName = valueName, Attributes = attributes, }); } } /// /// Parses a Bundle element. /// /// Element to parse private void ParseBundleElement(XElement node) { var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); string copyright = null; string aboutUrl = null; var compressed = YesNoDefaultType.Default; WixBundleAttributes attributes = 0; string helpTelephone = null; string helpUrl = null; string manufacturer = null; string name = null; string tag = null; string updateUrl = null; string upgradeCode = null; string version = null; string condition = null; string parentName = null; string fileSystemSafeBundleName = null; string logVariablePrefixAndExtension = null; string iconSourceFile = null; string splashScreenSourceFile = null; // Process only standard attributes until the active section is initialized. foreach (var attrib in node.Attributes()) { if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) { switch (attrib.Name.LocalName) { case "AboutUrl": aboutUrl = this.Core.GetAttributeValue(sourceLineNumbers, attrib); break; case "Compressed": compressed = this.Core.GetAttributeYesNoDefaultValue(sourceLineNumbers, attrib); break; case "Condition": condition = this.Core.GetAttributeValue(sourceLineNumbers, attrib); break; case "Copyright": copyright = this.Core.GetAttributeValue(sourceLineNumbers, attrib); break; case "DisableModify": var value = this.Core.GetAttributeValue(sourceLineNumbers, attrib); switch (value) { case "button": attributes |= WixBundleAttributes.SingleChangeUninstallButton; break; case "yes": attributes |= WixBundleAttributes.DisableModify; break; case "no": break; default: this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, value, "button", "yes", "no")); break; } break; case "DisableRemove": if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) { attributes |= WixBundleAttributes.DisableRemove; } break; case "HelpTelephone": helpTelephone = this.Core.GetAttributeValue(sourceLineNumbers, attrib); break; case "HelpUrl": helpUrl = this.Core.GetAttributeValue(sourceLineNumbers, attrib); break; case "Manufacturer": manufacturer = this.Core.GetAttributeValue(sourceLineNumbers, attrib); break; case "IconSourceFile": iconSourceFile = this.Core.GetAttributeValue(sourceLineNumbers, attrib); break; case "Name": name = this.Core.GetAttributeValue(sourceLineNumbers, attrib); break; case "ParentName": parentName = this.Core.GetAttributeValue(sourceLineNumbers, attrib); break; case "ProviderKey": // This can't be processed until we create the section. break; case "SplashScreenSourceFile": splashScreenSourceFile = this.Core.GetAttributeValue(sourceLineNumbers, attrib); break; case "Tag": tag = this.Core.GetAttributeValue(sourceLineNumbers, attrib); break; case "UpdateUrl": updateUrl = this.Core.GetAttributeValue(sourceLineNumbers, attrib); break; case "UpgradeCode": upgradeCode = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib, false); break; case "Version": version = this.Core.GetAttributeVersionValue(sourceLineNumbers, attrib); break; default: this.Core.UnexpectedAttribute(node, attrib); break; } } } if (String.IsNullOrEmpty(version)) { this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Version")); } else if (!CompilerCore.IsValidModuleOrBundleVersion(version)) { this.Core.Write(WarningMessages.InvalidModuleOrBundleVersion(sourceLineNumbers, "Bundle", version)); } if (String.IsNullOrEmpty(upgradeCode)) { this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "UpgradeCode")); } if (String.IsNullOrEmpty(copyright)) { if (String.IsNullOrEmpty(manufacturer)) { copyright = "Copyright (c). All rights reserved."; } else { copyright = String.Format("Copyright (c) {0}. All rights reserved.", manufacturer); } } if (String.IsNullOrEmpty(name)) { logVariablePrefixAndExtension = String.Concat("WixBundleLog:Setup:log"); } else { // Ensure only allowable path characters are in "name" (and change spaces to underscores). fileSystemSafeBundleName = CompilerCore.MakeValidLongFileName(name.Replace(' ', '_'), '_'); logVariablePrefixAndExtension = String.Concat("WixBundleLog:", fileSystemSafeBundleName, ":log"); } this.activeName = String.IsNullOrEmpty(name) ? Common.GenerateGuid() : name; this.Core.CreateActiveSection(this.activeName, SectionType.Bundle, 0, this.Context.CompilationId); // Now that the active section is initialized, process only extension attributes and the special ProviderKey attribute. foreach (var attrib in node.Attributes()) { if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) { switch (attrib.Name.LocalName) { case "ProviderKey": this.ParseBundleProviderKeyAttribute(sourceLineNumbers, node, attrib); break; // Unknown attributes were reported earlier. } } else { this.Core.ParseExtensionAttribute(node, attrib); } } var baSeen = false; var chainSeen = false; var logSeen = false; foreach (var child in node.Elements()) { if (CompilerCore.WixNamespace == child.Name.Namespace) { switch (child.Name.LocalName) { case "ApprovedExeForElevation": this.ParseApprovedExeForElevation(child); break; case "BootstrapperApplication": if (baSeen) { var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child); this.Core.Write(ErrorMessages.TooManyChildren(childSourceLineNumbers, node.Name.LocalName, "BootstrapperApplication")); } this.ParseBootstrapperApplicationElement(child); baSeen = true; break; case "BootstrapperApplicationRef": this.ParseBootstrapperApplicationRefElement(child); break; case "BundleCustomData": this.ParseBundleCustomDataElement(child); break; case "BundleCustomDataRef": this.ParseBundleCustomDataRefElement(child); break; case "BundleExtension": this.ParseBundleExtensionElement(child); break; case "BundleExtensionRef": this.ParseSimpleRefElement(child, SymbolDefinitions.WixBundleExtension); break; case "OptionalUpdateRegistration": this.ParseOptionalUpdateRegistrationElement(child, manufacturer, parentName, name); break; case "Chain": if (chainSeen) { var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child); this.Core.Write(ErrorMessages.TooManyChildren(childSourceLineNumbers, node.Name.LocalName, "Chain")); } this.ParseChainElement(child); chainSeen = true; break; case "Container": this.ParseContainerElement(child); break; case "ContainerRef": this.ParseSimpleRefElement(child, SymbolDefinitions.WixBundleContainer); break; case "Log": if (logSeen) { var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child); this.Core.Write(ErrorMessages.TooManyChildren(childSourceLineNumbers, node.Name.LocalName, "Log")); } logVariablePrefixAndExtension = this.ParseLogElement(child, fileSystemSafeBundleName); logSeen = true; break; case "PayloadGroup": this.ParsePayloadGroupElement(child, ComplexReferenceParentType.Layout, Compiler.BundleLayoutOnlyPayloads); break; case "PayloadGroupRef": this.ParsePayloadGroupRefElement(child, ComplexReferenceParentType.Layout, Compiler.BundleLayoutOnlyPayloads, ComplexReferenceChildType.Unknown, null); break; case "RelatedBundle": this.ParseRelatedBundleElement(child); break; case "Requires": this.ParseRequiresElement(child, null, false); break; case "SetVariable": this.ParseSetVariableElement(child); break; case "SetVariableRef": this.ParseSimpleRefElement(child, SymbolDefinitions.WixSetVariable); break; case "SoftwareTag": this.ParseBundleTagElement(child); break; case "Update": this.ParseUpdateElement(child); break; case "Variable": this.ParseVariableElement(child); break; case "WixVariable": this.ParseWixVariableElement(child); break; default: this.Core.UnexpectedElement(node, child); break; } } else { this.Core.ParseExtensionElement(node, child); } } if (!chainSeen) { this.Core.Write(ErrorMessages.ExpectedElement(sourceLineNumbers, node.Name.LocalName, "Chain")); } if (!this.Core.EncounteredError) { var symbol = this.Core.AddSymbol(new WixBundleSymbol(sourceLineNumbers) { UpgradeCode = upgradeCode, Version = version, Copyright = copyright, Name = name, Manufacturer = manufacturer, Attributes = attributes, AboutUrl = aboutUrl, HelpUrl = helpUrl, HelpTelephone = helpTelephone, UpdateUrl = updateUrl, Compressed = YesNoDefaultType.Yes == compressed ? true : YesNoDefaultType.No == compressed ? (bool?)false : null, IconSourceFile = iconSourceFile, SplashScreenSourceFile = splashScreenSourceFile, Condition = condition, Tag = tag, Platform = this.CurrentPlatform, ParentName = parentName, }); if (!String.IsNullOrEmpty(logVariablePrefixAndExtension)) { var split = logVariablePrefixAndExtension.Split(':'); symbol.LogPathVariable = split[0]; symbol.LogPrefix = split[1]; symbol.LogExtension = split[2]; } if (null != upgradeCode) { this.Core.AddSymbol(new WixRelatedBundleSymbol(sourceLineNumbers) { BundleId = upgradeCode, Action = RelatedBundleActionType.Upgrade, }); } this.Core.AddSymbol(new WixBundleContainerSymbol(sourceLineNumbers, Compiler.BurnDefaultAttachedContainerId) { Name = "bundle-attached.cab", Type = ContainerType.Attached, }); // Ensure that the bundle stores the well-known persisted values. this.Core.AddSymbol(new WixBundleVariableSymbol(sourceLineNumbers, new Identifier(AccessModifier.Section, BurnConstants.BURN_BUNDLE_NAME)) { Hidden = false, Persisted = true, }); this.Core.AddSymbol(new WixBundleVariableSymbol(sourceLineNumbers, new Identifier(AccessModifier.Section, BurnConstants.BURN_BUNDLE_ORIGINAL_SOURCE)) { Hidden = false, Persisted = true, }); this.Core.AddSymbol(new WixBundleVariableSymbol(sourceLineNumbers, new Identifier(AccessModifier.Section, BurnConstants.BURN_BUNDLE_ORIGINAL_SOURCE_FOLDER)) { Hidden = false, Persisted = true, }); this.Core.AddSymbol(new WixBundleVariableSymbol(sourceLineNumbers, new Identifier(AccessModifier.Section, BurnConstants.BURN_BUNDLE_LAST_USED_SOURCE)) { Hidden = false, Persisted = true, }); } } /// /// Parse a Container element. /// /// Element to parse /// private string ParseLogElement(XElement node, string fileSystemSafeBundleName) { var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); var disableLog = YesNoType.NotSet; var variable = "WixBundleLog"; var logPrefix = fileSystemSafeBundleName ?? "Setup"; var logExtension = ".log"; foreach (var attrib in node.Attributes()) { if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) { switch (attrib.Name.LocalName) { case "Disable": disableLog = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); break; case "PathVariable": variable = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty); break; case "Prefix": logPrefix = this.Core.GetAttributeValue(sourceLineNumbers, attrib); break; case "Extension": logExtension = this.Core.GetAttributeValue(sourceLineNumbers, attrib); break; default: this.Core.UnexpectedAttribute(node, attrib); break; } } else { this.Core.ParseExtensionAttribute(node, attrib); } } if (!logExtension.StartsWith(".", StringComparison.Ordinal)) { logExtension = String.Concat(".", logExtension); } this.Core.ParseForExtensionElements(node); return YesNoType.Yes == disableLog ? null : String.Join(":", variable, logPrefix, logExtension); } /// /// Parse a Container element. /// /// Element to parse private void ParseContainerElement(XElement node) { var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); Identifier id = null; string downloadUrl = null; string name = null; var type = ContainerType.Detached; foreach (var attrib in node.Attributes()) { if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) { switch (attrib.Name.LocalName) { case "Id": id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); break; case "DownloadUrl": downloadUrl = this.Core.GetAttributeValue(sourceLineNumbers, attrib); break; case "Name": name = this.Core.GetAttributeValue(sourceLineNumbers, attrib); break; case "Type": var typeString = this.Core.GetAttributeValue(sourceLineNumbers, attrib); switch (typeString) { case "attached": type = ContainerType.Attached; break; case "detached": type = ContainerType.Detached; break; default: this.Core.Write(ErrorMessages.IllegalAttributeValueWithLegalList(sourceLineNumbers, node.Name.LocalName, "Type", typeString, "attached, detached")); break; } break; default: this.Core.UnexpectedAttribute(node, attrib); break; } } else { this.Core.ParseExtensionAttribute(node, attrib); } } if (null == id) { if (!String.IsNullOrEmpty(name)) { id = this.Core.CreateIdentifierFromFilename(name); } if (null == id) { this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); id = Identifier.Invalid; } else if (!Common.IsIdentifier(id.Id)) { this.Core.Write(ErrorMessages.IllegalIdentifier(sourceLineNumbers, node.Name.LocalName, "Id", id.Id)); } } else if (null == name) { name = id.Id; } if (!String.IsNullOrEmpty(downloadUrl) && ContainerType.Detached != type) { this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "DownloadUrl", "Type", "attached")); } foreach (var child in node.Elements()) { if (CompilerCore.WixNamespace == child.Name.Namespace) { switch (child.Name.LocalName) { case "PackageGroupRef": this.ParsePackageGroupRefElement(child, ComplexReferenceParentType.Container, id.Id); break; default: this.Core.UnexpectedElement(node, child); break; } } else { this.Core.ParseExtensionElement(node, child); } } if (!this.Core.EncounteredError) { this.Core.AddSymbol(new WixBundleContainerSymbol(sourceLineNumbers, id) { Name = name, Type = type, DownloadUrl = downloadUrl }); } } /// /// Parse the BoostrapperApplication element. /// /// Element to parse private void ParseBootstrapperApplicationElement(XElement node) { var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); Identifier id = null; Identifier previousId = null; var previousType = ComplexReferenceChildType.Unknown; foreach (var attrib in node.Attributes()) { if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) { switch (attrib.Name.LocalName) { case "Id": id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); break; default: this.Core.UnexpectedAttribute(node, attrib); break; } } } foreach (var child in node.Elements()) { if (CompilerCore.WixNamespace == child.Name.Namespace) { switch (child.Name.LocalName) { case "BootstrapperApplicationDll": previousId = this.ParseBootstrapperApplicationDllElement(child, id, previousType, previousId); previousType = ComplexReferenceChildType.Payload; break; case "Payload": previousId = this.ParsePayloadElement(child, ComplexReferenceParentType.Container, Compiler.BurnUXContainerId, previousType, previousId); previousType = ComplexReferenceChildType.Payload; break; case "PayloadGroupRef": previousId = this.ParsePayloadGroupRefElement(child, ComplexReferenceParentType.Container, Compiler.BurnUXContainerId, previousType, previousId); previousType = ComplexReferenceChildType.PayloadGroup; break; default: this.Core.UnexpectedElement(node, child); break; } } else { this.Core.ParseExtensionElement(node, child); } } if (id != null) { this.Core.AddSymbol(new WixBootstrapperApplicationSymbol(sourceLineNumbers, id)); } } /// /// Parse the BoostrapperApplication element. /// /// Element to parse /// /// /// private Identifier ParseBootstrapperApplicationDllElement(XElement node, Identifier defaultId, ComplexReferenceChildType previousType, Identifier previousId) { var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); var compilerPayload = new CompilerPayload(this.Core, sourceLineNumbers, node) { Id = defaultId, }; var dpiAwareness = WixBootstrapperApplicationDpiAwarenessType.PerMonitorV2; // This list lets us evaluate extension attributes *after* all core attributes // have been parsed and dealt with, regardless of authoring order. var extensionAttributes = new List(); foreach (var attrib in node.Attributes()) { if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) { switch (attrib.Name.LocalName) { case "Id": compilerPayload.ParseId(attrib); break; case "Name": compilerPayload.ParseName(attrib); break; case "SourceFile": compilerPayload.ParseSourceFile(attrib); break; case "DpiAwareness": var dpiAwarenessValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); switch (dpiAwarenessValue) { case "gdiScaled": dpiAwareness = WixBootstrapperApplicationDpiAwarenessType.GdiScaled; break; case "perMonitor": dpiAwareness = WixBootstrapperApplicationDpiAwarenessType.PerMonitor; break; case "perMonitorV2": dpiAwareness = WixBootstrapperApplicationDpiAwarenessType.PerMonitorV2; break; case "system": dpiAwareness = WixBootstrapperApplicationDpiAwarenessType.System; break; case "unaware": dpiAwareness = WixBootstrapperApplicationDpiAwarenessType.Unaware; break; default: this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, "DpiAwareness", dpiAwarenessValue, "gdiScaled", "perMonitor", "perMonitorV2", "system", "unaware")); break; } break; default: this.Core.UnexpectedAttribute(node, attrib); break; } } else { extensionAttributes.Add(attrib); } } compilerPayload.FinishCompilingPayload(); // Now that the Id is known, we can parse the extension attributes. var context = new Dictionary { ["Id"] = compilerPayload.Id.Id, }; foreach (var extensionAttribute in extensionAttributes) { this.Core.ParseExtensionAttribute(node, extensionAttribute, context); } foreach (var child in node.Elements()) { if (CompilerCore.WixNamespace == child.Name.Namespace) { switch (child.Name.LocalName) { default: this.Core.UnexpectedElement(node, child); break; } } else { this.Core.ParseExtensionElement(node, child); } } if (!this.Core.EncounteredError) { compilerPayload.CreatePayloadSymbol(ComplexReferenceParentType.Container, Compiler.BurnUXContainerId.Id, previousType, previousId?.Id); this.Core.AddSymbol(new WixBundleContainerSymbol(sourceLineNumbers, Compiler.BurnUXContainerId) { Name = "bundle-ux.cab", Type = ContainerType.Attached }); this.Core.AddSymbol(new WixBootstrapperApplicationDllSymbol(sourceLineNumbers, compilerPayload.Id) { DpiAwareness = dpiAwareness, }); } return compilerPayload.Id; } /// /// Parse the BoostrapperApplicationRef element. /// /// Element to parse private void ParseBootstrapperApplicationRefElement(XElement node) { var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); string id = null; Identifier previousId = null; var previousType = ComplexReferenceChildType.Unknown; foreach (var attrib in node.Attributes()) { if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) { switch (attrib.Name.LocalName) { case "Id": id = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); break; default: this.Core.UnexpectedAttribute(node, attrib); break; } } else { this.Core.ParseExtensionAttribute(node, attrib); } } foreach (var child in node.Elements()) { if (CompilerCore.WixNamespace == child.Name.Namespace) { switch (child.Name.LocalName) { case "Payload": previousId = this.ParsePayloadElement(child, ComplexReferenceParentType.Container, Compiler.BurnUXContainerId, previousType, previousId); previousType = ComplexReferenceChildType.Payload; break; case "PayloadGroupRef": previousId = this.ParsePayloadGroupRefElement(child, ComplexReferenceParentType.Container, Compiler.BurnUXContainerId, previousType, previousId); previousType = ComplexReferenceChildType.PayloadGroup; break; default: this.Core.UnexpectedElement(node, child); break; } } else { this.Core.ParseExtensionElement(node, child); } } if (String.IsNullOrEmpty(id)) { this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); } else { this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.WixBootstrapperApplication, id); } } /// /// Parses a BundleCustomData element. /// /// Element to parse. private void ParseBundleCustomDataElement(XElement node) { var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); string customDataId = null; WixBundleCustomDataType? customDataType = null; string extensionId = null; var attributeDefinitions = new List(); var foundAttributeDefinitions = false; foreach (var attrib in node.Attributes()) { if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) { switch (attrib.Name.LocalName) { case "Id": customDataId = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); break; case "Type": var typeValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); switch (typeValue) { case "bootstrapperApplication": customDataType = WixBundleCustomDataType.BootstrapperApplication; break; case "bundleExtension": customDataType = WixBundleCustomDataType.BundleExtension; break; default: this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, "Type", typeValue, "bootstrapperApplication", "bundleExtension")); customDataType = WixBundleCustomDataType.Unknown; // set a value to prevent expected attribute error below. break; } break; case "ExtensionId": extensionId = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.WixBundleExtension, extensionId); break; default: this.Core.UnexpectedAttribute(node, attrib); break; } } else { this.Core.ParseExtensionAttribute(node, attrib); } } if (null == customDataId) { this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); } var hasExtensionId = null != extensionId; if (!customDataType.HasValue) { customDataType = hasExtensionId ? WixBundleCustomDataType.BundleExtension : WixBundleCustomDataType.BootstrapperApplication; } if (!customDataType.HasValue) { this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Type")); } else if (hasExtensionId) { if (customDataType.Value == WixBundleCustomDataType.BootstrapperApplication) { this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "ExtensonId", "Type", "bootstrapperApplication")); } } else if (customDataType.Value == WixBundleCustomDataType.BundleExtension) { this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "ExtensionId", "Type", "bundleExtension")); } foreach (var child in node.Elements()) { if (CompilerCore.WixNamespace == child.Name.Namespace) { var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child); switch (child.Name.LocalName) { case "BundleAttributeDefinition": foundAttributeDefinitions = true; var attributeDefinition = this.ParseBundleAttributeDefinitionElement(child, childSourceLineNumbers, customDataId); if (attributeDefinition != null) { attributeDefinitions.Add(attributeDefinition); } break; case "BundleElement": this.ParseBundleElementElement(child, childSourceLineNumbers, customDataId); break; default: this.Core.UnexpectedElement(node, child); break; } } else { this.Core.ParseExtensionElement(node, child); } } if (attributeDefinitions.Count > 0) { if (!this.Core.EncounteredError) { var attributeNames = String.Join(new string(WixBundleCustomDataSymbol.AttributeNamesSeparator, 1), attributeDefinitions.Select(c => c.Name)); this.Core.AddSymbol(new WixBundleCustomDataSymbol(sourceLineNumbers, new Identifier(AccessModifier.Global, customDataId)) { AttributeNames = attributeNames, Type = customDataType.Value, BundleExtensionRef = extensionId, }); } } else if (!foundAttributeDefinitions) { this.Core.Write(ErrorMessages.ExpectedElement(sourceLineNumbers, node.Name.LocalName, "BundleAttributeDefinition")); } } /// /// Parses a BundleCustomDataRef element. /// /// Element to parse. private void ParseBundleCustomDataRefElement(XElement node) { var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); string customDataId = null; foreach (var attrib in node.Attributes()) { if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) { switch (attrib.Name.LocalName) { case "Id": customDataId = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.WixBundleCustomData, customDataId); break; default: this.Core.UnexpectedAttribute(node, attrib); break; } } else { this.Core.ParseExtensionAttribute(node, attrib); } } if (null == customDataId) { this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); } foreach (var child in node.Elements()) { if (CompilerCore.WixNamespace == child.Name.Namespace) { var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child); switch (child.Name.LocalName) { case "BundleElement": this.ParseBundleElementElement(child, childSourceLineNumbers, customDataId); break; default: this.Core.UnexpectedElement(node, child); break; } } else { this.Core.ParseExtensionElement(node, child); } } } /// /// Parses a BundleAttributeDefinition element. /// /// Element to parse. /// Element's SourceLineNumbers. /// BundleCustomData Id. private WixBundleCustomDataAttributeSymbol ParseBundleAttributeDefinitionElement(XElement node, SourceLineNumber sourceLineNumbers, string customDataId) { string attributeName = null; foreach (var attrib in node.Attributes()) { switch (attrib.Name.LocalName) { case "Id": attributeName = this.Core.GetAttributeValue(sourceLineNumbers, attrib); break; default: this.Core.UnexpectedAttribute(node, attrib); break; } } if (null == attributeName) { this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); } this.Core.ParseForExtensionElements(node); if (this.Core.EncounteredError) { return null; } var customDataAttribute = this.Core.AddSymbol(new WixBundleCustomDataAttributeSymbol(sourceLineNumbers, new Identifier(AccessModifier.Section, customDataId, attributeName)) { CustomDataRef = customDataId, Name = attributeName, }); return customDataAttribute; } /// /// Parses a BundleElement element. /// /// Element to parse. /// Element's SourceLineNumbers. /// BundleCustomData Id. private void ParseBundleElementElement(XElement node, SourceLineNumber sourceLineNumbers, string customDataId) { var elementId = Guid.NewGuid().ToString("N").ToUpperInvariant(); foreach (var attrib in node.Attributes()) { this.Core.ParseExtensionAttribute(node, attrib); } foreach (var child in node.Elements()) { var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child); switch (child.Name.LocalName) { case "BundleAttribute": string attributeName = null; string value = null; foreach (var attrib in child.Attributes()) { switch (attrib.Name.LocalName) { case "Id": attributeName = this.Core.GetAttributeValue(childSourceLineNumbers, attrib); break; case "Value": value = this.Core.GetAttributeValue(childSourceLineNumbers, attrib); break; default: this.Core.ParseExtensionAttribute(child, attrib); break; } } if (null == attributeName) { this.Core.Write(ErrorMessages.ExpectedAttribute(childSourceLineNumbers, child.Name.LocalName, "Id")); } if (!this.Core.EncounteredError) { this.Core.AddSymbol(new WixBundleCustomDataCellSymbol(childSourceLineNumbers, new Identifier(AccessModifier.Section, customDataId, elementId, attributeName)) { ElementId = elementId, AttributeRef = attributeName, CustomDataRef = customDataId, Value = value, }); } break; default: this.Core.UnexpectedElement(node, child); break; } } if (!this.Core.EncounteredError) { this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.WixBundleCustomData, customDataId); } } /// /// Parse the BundleExtension element. /// /// Element to parse private void ParseBundleExtensionElement(XElement node) { var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); var compilerPayload = new CompilerPayload(this.Core, sourceLineNumbers, node); Identifier previousId = null; var previousType = ComplexReferenceChildType.Unknown; // This list lets us evaluate extension attributes *after* all core attributes // have been parsed and dealt with, regardless of authoring order. var extensionAttributes = new List(); foreach (var attrib in node.Attributes()) { if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) { switch (attrib.Name.LocalName) { case "Id": compilerPayload.ParseId(attrib); break; case "Name": compilerPayload.ParseName(attrib); break; case "SourceFile": compilerPayload.ParseSourceFile(attrib); break; default: this.Core.UnexpectedAttribute(node, attrib); break; } } else { extensionAttributes.Add(attrib); } } compilerPayload.FinishCompilingPayload(); // Now that the Id is known, we can parse the extension attributes. var context = new Dictionary { ["Id"] = compilerPayload.Id.Id, }; foreach (var extensionAttribute in extensionAttributes) { this.Core.ParseExtensionAttribute(node, extensionAttribute, context); } compilerPayload.CreatePayloadSymbol(ComplexReferenceParentType.Container, Compiler.BurnUXContainerId.Id, previousType, previousId?.Id); previousId = compilerPayload.Id; previousType = ComplexReferenceChildType.Payload; foreach (var child in node.Elements()) { if (CompilerCore.WixNamespace == child.Name.Namespace) { switch (child.Name.LocalName) { case "Payload": previousId = this.ParsePayloadElement(child, ComplexReferenceParentType.Container, Compiler.BurnUXContainerId, previousType, previousId); previousType = ComplexReferenceChildType.Payload; break; case "PayloadGroupRef": previousId = this.ParsePayloadGroupRefElement(child, ComplexReferenceParentType.Container, Compiler.BurnUXContainerId, previousType, previousId); previousType = ComplexReferenceChildType.PayloadGroup; break; default: this.Core.UnexpectedElement(node, child); break; } } else { this.Core.ParseExtensionElement(node, child); } } // Add the BundleExtension. if (!this.Core.EncounteredError) { this.Core.AddSymbol(new WixBundleExtensionSymbol(sourceLineNumbers, compilerPayload.Id) { PayloadRef = compilerPayload.Id.Id, }); } } /// /// Parse the OptionalUpdateRegistration element. /// /// The element to parse. /// The manufacturer. /// The product family. /// The bundle name. private void ParseOptionalUpdateRegistrationElement(XElement node, string defaultManufacturer, string defaultProductFamily, string defaultName) { const string defaultClassification = "Update"; var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); string manufacturer = null; string department = null; string productFamily = null; string name = null; var classification = defaultClassification; foreach (var attrib in node.Attributes()) { if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) { switch (attrib.Name.LocalName) { case "Manufacturer": manufacturer = this.Core.GetAttributeValue(sourceLineNumbers, attrib); break; case "Department": department = this.Core.GetAttributeValue(sourceLineNumbers, attrib); break; case "ProductFamily": productFamily = this.Core.GetAttributeValue(sourceLineNumbers, attrib); break; case "Name": name = this.Core.GetAttributeValue(sourceLineNumbers, attrib); break; case "Classification": classification = this.Core.GetAttributeValue(sourceLineNumbers, attrib); break; default: this.Core.UnexpectedAttribute(node, attrib); break; } } else { this.Core.ParseExtensionAttribute(node, attrib); } } if (String.IsNullOrEmpty(manufacturer)) { if (!String.IsNullOrEmpty(defaultManufacturer)) { manufacturer = defaultManufacturer; } else { this.Core.Write(ErrorMessages.ExpectedAttributeInElementOrParent(sourceLineNumbers, node.Name.LocalName, "Manufacturer", node.Parent.Name.LocalName)); } } if (String.IsNullOrEmpty(productFamily)) { if (!String.IsNullOrEmpty(defaultProductFamily)) { productFamily = defaultProductFamily; } } if (String.IsNullOrEmpty(name)) { if (!String.IsNullOrEmpty(defaultName)) { name = defaultName; } else { this.Core.Write(ErrorMessages.ExpectedAttributeInElementOrParent(sourceLineNumbers, node.Name.LocalName, "Name", node.Parent.Name.LocalName)); } } if (String.IsNullOrEmpty(classification)) { this.Core.Write(ErrorMessages.IllegalEmptyAttributeValue(sourceLineNumbers, node.Name.LocalName, "Classification", defaultClassification)); } this.Core.ParseForExtensionElements(node); if (!this.Core.EncounteredError) { this.Core.AddSymbol(new WixUpdateRegistrationSymbol(sourceLineNumbers) { Manufacturer = manufacturer, Department = department, ProductFamily = productFamily, Name = name, Classification = classification }); } } /// /// Parse Payload element. /// /// Element to parse /// ComplexReferenceParentType of parent element. (BA or PayloadGroup) /// Identifier of parent element. /// /// private Identifier ParsePayloadElement(XElement node, ComplexReferenceParentType parentType, Identifier parentId, ComplexReferenceChildType previousType, Identifier previousId) { Debug.Assert(ComplexReferenceParentType.PayloadGroup == parentType || ComplexReferenceParentType.Package == parentType || ComplexReferenceParentType.Container == parentType); Debug.Assert(ComplexReferenceChildType.Unknown == previousType || ComplexReferenceChildType.PayloadGroup == previousType || ComplexReferenceChildType.Payload == previousType); var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); var compilerPayload = new CompilerPayload(this.Core, sourceLineNumbers, node); // This list lets us evaluate extension attributes *after* all core attributes // have been parsed and dealt with, regardless of authoring order. var extensionAttributes = new List(); foreach (var attrib in node.Attributes()) { if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) { var allowed = true; switch (attrib.Name.LocalName) { case "Id": compilerPayload.ParseId(attrib); break; case "Compressed": compilerPayload.ParseCompressed(attrib); break; case "Name": compilerPayload.ParseName(attrib); break; case "SourceFile": compilerPayload.ParseSourceFile(attrib); break; case "DownloadUrl": compilerPayload.ParseDownloadUrl(attrib); break; default: allowed = false; break; } if (!allowed) { this.Core.UnexpectedAttribute(node, attrib); } } else { extensionAttributes.Add(attrib); } } compilerPayload.FinishCompilingPayload(); // Now that the PayloadId is known, we can parse the extension attributes. var context = new Dictionary { ["Id"] = compilerPayload.Id.Id, }; foreach (var extensionAttribute in extensionAttributes) { this.Core.ParseExtensionAttribute(node, extensionAttribute, context); } foreach (var child in node.Elements()) { if (CompilerCore.WixNamespace == child.Name.Namespace) { switch (child.Name.LocalName) { default: this.Core.UnexpectedElement(node, child); break; } } else { this.Core.ParseExtensionElement(node, child, context); } } compilerPayload.CreatePayloadSymbol(parentType, parentId?.Id, previousType, previousId?.Id); return compilerPayload.Id; } /// /// Parse PayloadGroup element. /// /// Element to parse /// Optional ComplexReferenceParentType of parent element. (typically another PayloadGroup) /// Identifier of parent element. private void ParsePayloadGroupElement(XElement node, ComplexReferenceParentType parentType, Identifier parentId) { Debug.Assert(ComplexReferenceParentType.Unknown == parentType || ComplexReferenceParentType.Layout == parentType || ComplexReferenceParentType.PayloadGroup == parentType); var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); Identifier id = null; foreach (var attrib in node.Attributes()) { if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) { switch (attrib.Name.LocalName) { case "Id": id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); break; default: this.Core.UnexpectedAttribute(node, attrib); break; } } else { this.Core.ParseExtensionAttribute(node, attrib); } } if (null == id) { this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); id = Identifier.Invalid; } var previousType = ComplexReferenceChildType.Unknown; Identifier previousId = null; foreach (var child in node.Elements()) { if (CompilerCore.WixNamespace == child.Name.Namespace) { WixBundlePackageType? packageType = null; switch (child.Name.LocalName) { case "ExePackagePayload": packageType = WixBundlePackageType.Exe; break; case "MsiPackagePayload": packageType = WixBundlePackageType.Msi; break; case "MspPackagePayload": packageType = WixBundlePackageType.Msp; break; case "MsuPackagePayload": packageType = WixBundlePackageType.Msu; break; case "Payload": previousId = this.ParsePayloadElement(child, ComplexReferenceParentType.PayloadGroup, id, previousType, previousId); previousType = ComplexReferenceChildType.Payload; break; case "PayloadGroupRef": previousId = this.ParsePayloadGroupRefElement(child, ComplexReferenceParentType.PayloadGroup, id, previousType, previousId); previousType = ComplexReferenceChildType.PayloadGroup; break; default: this.Core.UnexpectedElement(node, child); break; } if (packageType.HasValue) { var compilerPayload = this.ParsePackagePayloadElement(null, child, packageType.Value, null); var payloadSymbol = compilerPayload.CreatePayloadSymbol(ComplexReferenceParentType.PayloadGroup, id?.Id, previousType, previousId?.Id); if (payloadSymbol != null) { previousId = payloadSymbol.Id; previousType = ComplexReferenceChildType.Payload; this.CreatePackagePayloadSymbol(payloadSymbol.SourceLineNumbers, packageType.Value, payloadSymbol.Id, ComplexReferenceParentType.PayloadGroup, id); } } } else { this.Core.ParseExtensionElement(node, child); } } if (!this.Core.EncounteredError) { this.Core.AddSymbol(new WixBundlePayloadGroupSymbol(sourceLineNumbers, id)); this.Core.CreateGroupAndOrderingRows(sourceLineNumbers, parentType, parentId?.Id, ComplexReferenceChildType.PayloadGroup, id.Id, ComplexReferenceChildType.Unknown, null); } } /// /// Parses a payload group reference element. /// /// Element to parse. /// ComplexReferenceParentType of parent element (BA or PayloadGroup). /// Identifier of parent element. /// /// private Identifier ParsePayloadGroupRefElement(XElement node, ComplexReferenceParentType parentType, Identifier parentId, ComplexReferenceChildType previousType, Identifier previousId) { Debug.Assert(ComplexReferenceParentType.Layout == parentType || ComplexReferenceParentType.PayloadGroup == parentType || ComplexReferenceParentType.Package == parentType || ComplexReferenceParentType.Container == parentType); Debug.Assert(ComplexReferenceChildType.Unknown == previousType || ComplexReferenceChildType.PayloadGroup == previousType || ComplexReferenceChildType.Payload == previousType); var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); Identifier id = null; foreach (var attrib in node.Attributes()) { if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) { switch (attrib.Name.LocalName) { case "Id": id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.WixBundlePayloadGroup, id.Id); break; default: this.Core.UnexpectedAttribute(node, attrib); break; } } else { this.Core.ParseExtensionAttribute(node, attrib); } } if (null == id) { this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); } this.Core.ParseForExtensionElements(node); this.Core.CreateGroupAndOrderingRows(sourceLineNumbers, parentType, parentId?.Id, ComplexReferenceChildType.PayloadGroup, id?.Id, previousType, previousId?.Id); return id; } /// /// Parse ExitCode element. /// /// Element to parse /// Id of parent element private void ParseExitCodeElement(XElement node, string packageId) { var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); var value = CompilerConstants.IntegerNotSet; var behavior = ExitCodeBehaviorType.NotSet; foreach (var attrib in node.Attributes()) { if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) { switch (attrib.Name.LocalName) { case "Value": value = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, Int32.MinValue + 2, Int32.MaxValue); break; case "Behavior": var behaviorString = this.Core.GetAttributeValue(sourceLineNumbers, attrib); switch (behaviorString) { case "error": behavior = ExitCodeBehaviorType.Error; break; case "forceReboot": behavior = ExitCodeBehaviorType.ForceReboot; break; case "scheduleReboot": behavior = ExitCodeBehaviorType.ScheduleReboot; break; case "success": behavior = ExitCodeBehaviorType.Success; break; default: this.Core.Write(ErrorMessages.IllegalAttributeValueWithLegalList(sourceLineNumbers, node.Name.LocalName, "Behavior", behaviorString, "success, error, scheduleReboot, forceReboot")); behavior = ExitCodeBehaviorType.Success; // set value to avoid ExpectedAttribute below. break; } break; default: this.Core.UnexpectedAttribute(node, attrib); break; } } else { this.Core.ParseExtensionAttribute(node, attrib); } } if (ExitCodeBehaviorType.NotSet == behavior) { this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Behavior")); } this.Core.ParseForExtensionElements(node); if (!this.Core.EncounteredError) { this.Core.AddSymbol(new WixBundlePackageExitCodeSymbol(sourceLineNumbers) { ChainPackageId = packageId, Code = value, Behavior = behavior }); } } /// /// Parse Chain element. /// /// Element to parse private void ParseChainElement(XElement node) { var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); var attributes = WixChainAttributes.None; foreach (var attrib in node.Attributes()) { if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) { switch (attrib.Name.LocalName) { case "DisableRollback": if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) { attributes |= WixChainAttributes.DisableRollback; } break; case "DisableSystemRestore": if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) { attributes |= WixChainAttributes.DisableSystemRestore; } break; case "ParallelCache": if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) { attributes |= WixChainAttributes.ParallelCache; } break; default: this.Core.UnexpectedAttribute(node, attrib); break; } } else { this.Core.ParseExtensionAttribute(node, attrib); } } // Ensure there is always a rollback boundary at the beginning of the chain. this.CreateRollbackBoundary(sourceLineNumbers, new Identifier(AccessModifier.Global, "WixDefaultBoundary"), YesNoType.Yes, YesNoType.No, ComplexReferenceParentType.PackageGroup, "WixChain", ComplexReferenceChildType.Unknown, null); var previousId = "WixDefaultBoundary"; var previousType = ComplexReferenceChildType.Package; foreach (var child in node.Elements()) { if (CompilerCore.WixNamespace == child.Name.Namespace) { switch (child.Name.LocalName) { case "MsiPackage": previousId = this.ParseMsiPackageElement(child, ComplexReferenceParentType.PackageGroup, "WixChain", previousType, previousId); previousType = ComplexReferenceChildType.Package; break; case "MspPackage": previousId = this.ParseMspPackageElement(child, ComplexReferenceParentType.PackageGroup, "WixChain", previousType, previousId); previousType = ComplexReferenceChildType.Package; break; case "MsuPackage": previousId = this.ParseMsuPackageElement(child, ComplexReferenceParentType.PackageGroup, "WixChain", previousType, previousId); previousType = ComplexReferenceChildType.Package; break; case "ExePackage": previousId = this.ParseExePackageElement(child, ComplexReferenceParentType.PackageGroup, "WixChain", previousType, previousId); previousType = ComplexReferenceChildType.Package; break; case "RollbackBoundary": previousId = this.ParseRollbackBoundaryElement(child, ComplexReferenceParentType.PackageGroup, "WixChain", previousType, previousId); previousType = ComplexReferenceChildType.Package; break; case "PackageGroupRef": previousId = this.ParsePackageGroupRefElement(child, ComplexReferenceParentType.PackageGroup, "WixChain", previousType, previousId); previousType = ComplexReferenceChildType.PackageGroup; break; default: this.Core.UnexpectedElement(node, child); break; } } else { this.Core.ParseExtensionElement(node, child); } } if (null == previousId) { this.Core.Write(ErrorMessages.ExpectedElement(sourceLineNumbers, node.Name.LocalName, "MsiPackage", "ExePackage", "PackageGroupRef")); } if (!this.Core.EncounteredError) { this.Core.AddSymbol(new WixChainSymbol(sourceLineNumbers) { Attributes = attributes }); } } /// /// Parse MsiPackage element /// /// Element to parse /// Type of parent group, if known. /// Identifier of parent group, if known. /// Type of previous item, if known. /// Identifier of previous item, if known /// Identifier for package element. private string ParseMsiPackageElement(XElement node, ComplexReferenceParentType parentType, string parentId, ComplexReferenceChildType previousType, string previousId) { return this.ParseChainPackage(node, WixBundlePackageType.Msi, parentType, parentId, previousType, previousId); } /// /// Parse MspPackage element /// /// Element to parse /// Type of parent group, if known. /// Identifier of parent group, if known. /// Type of previous item, if known. /// Identifier of previous item, if known /// Identifier for package element. private string ParseMspPackageElement(XElement node, ComplexReferenceParentType parentType, string parentId, ComplexReferenceChildType previousType, string previousId) { return this.ParseChainPackage(node, WixBundlePackageType.Msp, parentType, parentId, previousType, previousId); } /// /// Parse MsuPackage element /// /// Element to parse /// Type of parent group, if known. /// Identifier of parent group, if known. /// Type of previous item, if known. /// Identifier of previous item, if known /// Identifier for package element. private string ParseMsuPackageElement(XElement node, ComplexReferenceParentType parentType, string parentId, ComplexReferenceChildType previousType, string previousId) { return this.ParseChainPackage(node, WixBundlePackageType.Msu, parentType, parentId, previousType, previousId); } /// /// Parse ExePackage element /// /// Element to parse /// Type of parent group, if known. /// Identifier of parent group, if known. /// Type of previous item, if known. /// Identifier of previous item, if known /// Identifier for package element. private string ParseExePackageElement(XElement node, ComplexReferenceParentType parentType, string parentId, ComplexReferenceChildType previousType, string previousId) { return this.ParseChainPackage(node, WixBundlePackageType.Exe, parentType, parentId, previousType, previousId); } /// /// Parse RollbackBoundary element /// /// Element to parse /// Type of parent group, if known. /// Identifier of parent group, if known. /// Type of previous item, if known. /// Identifier of previous item, if known /// Identifier for package element. private string ParseRollbackBoundaryElement(XElement node, ComplexReferenceParentType parentType, string parentId, ComplexReferenceChildType previousType, string previousId) { Debug.Assert(ComplexReferenceParentType.PackageGroup == parentType); Debug.Assert(ComplexReferenceChildType.Unknown == previousType || ComplexReferenceChildType.PackageGroup == previousType || ComplexReferenceChildType.Package == previousType); var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); Identifier id = null; var vital = YesNoType.Yes; var transaction = YesNoType.No; // This list lets us evaluate extension attributes *after* all core attributes // have been parsed and dealt with, regardless of authoring order. var extensionAttributes = new List(); foreach (var attrib in node.Attributes()) { if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) { var allowed = true; switch (attrib.Name.LocalName) { case "Id": id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); break; case "Vital": vital = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); break; case "Transaction": transaction = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); break; default: allowed = false; break; } if (!allowed) { this.Core.UnexpectedAttribute(node, attrib); } } else { // Save the extension attributes for later... extensionAttributes.Add(attrib); } } if (null == id) { if (!String.IsNullOrEmpty(previousId)) { id = this.Core.CreateIdentifier("rba", previousId); } if (null == id) { this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); id = Identifier.Invalid; } else if (!Common.IsIdentifier(id.Id)) { this.Core.Write(ErrorMessages.IllegalIdentifier(sourceLineNumbers, node.Name.LocalName, "Id", id.Id)); } } // Now that the rollback identifier is known, we can parse the extension attributes... var contextValues = new Dictionary { ["RollbackBoundaryId"] = id.Id }; foreach (var attribute in extensionAttributes) { this.Core.ParseExtensionAttribute(node, attribute, contextValues); } this.Core.ParseForExtensionElements(node); if (!this.Core.EncounteredError) { this.CreateRollbackBoundary(sourceLineNumbers, id, vital, transaction, parentType, parentId, previousType, previousId); } return id.Id; } /// /// Parses one of the ChainPackage elements /// /// Element to parse /// Type of package to parse /// Type of parent group, if known. /// Identifier of parent group, if known. /// Type of previous item, if known. /// Identifier of previous item, if known /// Identifier for package element. /// This method contains the shared logic for parsing all of the ChainPackage /// types, as there is more in common between them than different. private string ParseChainPackage(XElement node, WixBundlePackageType packageType, ComplexReferenceParentType parentType, string parentId, ComplexReferenceChildType previousType, string previousId) { Debug.Assert(ComplexReferenceParentType.PackageGroup == parentType); Debug.Assert(ComplexReferenceChildType.Unknown == previousType || ComplexReferenceChildType.PackageGroup == previousType || ComplexReferenceChildType.Package == previousType); var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); var compilerPayload = new CompilerPayload(this.Core, sourceLineNumbers, node) { IsRequired = false, }; string after = null; string installCondition = null; var cache = YesNoAlwaysType.Yes; // the default is to cache everything in tradeoff for stability over disk space. string cacheId = null; var logPathVariable = (packageType == WixBundlePackageType.Msu) ? String.Empty : null; var rollbackPathVariable = (packageType == WixBundlePackageType.Msu) ? String.Empty : null; var permanent = YesNoType.NotSet; var visible = YesNoType.NotSet; var vital = YesNoType.Yes; string installArguments = null; string repairArguments = null; string uninstallArguments = null; var perMachine = YesNoDefaultType.NotSet; string detectCondition = null; string protocol = null; var installSize = CompilerConstants.IntegerNotSet; string msuKB = null; var enableFeatureSelection = YesNoType.NotSet; var forcePerMachine = YesNoType.NotSet; CompilerPayload childPackageCompilerPayload = null; var slipstream = YesNoType.NotSet; var hasPayloadInfo = false; var expectedNetFx4Args = new string[] { "/q", "/norestart" }; // This list lets us evaluate extension attributes *after* all core attributes // have been parsed and dealt with, regardless of authoring order. var extensionAttributes = new List(); foreach (var attrib in node.Attributes()) { if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) { var allowed = true; switch (attrib.Name.LocalName) { case "Id": compilerPayload.ParseId(attrib); break; case "Name": compilerPayload.ParseName(attrib); hasPayloadInfo = true; break; case "SourceFile": compilerPayload.ParseSourceFile(attrib); hasPayloadInfo = true; break; case "DownloadUrl": compilerPayload.ParseDownloadUrl(attrib); hasPayloadInfo = true; break; case "After": after = this.Core.GetAttributeValue(sourceLineNumbers, attrib); break; case "InstallCondition": installCondition = this.Core.GetAttributeValue(sourceLineNumbers, attrib); break; case "Cache": var value = this.Core.GetAttributeValue(sourceLineNumbers, attrib); switch (value) { case "always": cache = YesNoAlwaysType.Always; break; case "yes": cache = YesNoAlwaysType.Yes; break; case "no": cache = YesNoAlwaysType.No; break; case "": break; default: this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, value, "always", "yes", "no")); break; } break; case "CacheId": cacheId = this.Core.GetAttributeValue(sourceLineNumbers, attrib); break; case "Description": compilerPayload.ParseDescription(attrib); break; case "DisplayName": compilerPayload.ParseDisplayName(attrib); break; case "EnableFeatureSelection": enableFeatureSelection = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); allowed = (packageType == WixBundlePackageType.Msi); break; case "ForcePerMachine": forcePerMachine = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); allowed = (packageType == WixBundlePackageType.Msi); break; case "LogPathVariable": logPathVariable = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty); break; case "RollbackLogPathVariable": rollbackPathVariable = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty); break; case "Permanent": permanent = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); break; case "Visible": visible = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); allowed = (packageType == WixBundlePackageType.Msi); break; case "Vital": vital = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); break; case "InstallArguments": installArguments = this.Core.GetAttributeValue(sourceLineNumbers, attrib); allowed = (packageType == WixBundlePackageType.Exe); break; case "RepairArguments": repairArguments = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty); allowed = (packageType == WixBundlePackageType.Exe); break; case "UninstallArguments": uninstallArguments = this.Core.GetAttributeValue(sourceLineNumbers, attrib); allowed = (packageType == WixBundlePackageType.Exe); break; case "PerMachine": perMachine = this.Core.GetAttributeYesNoDefaultValue(sourceLineNumbers, attrib); allowed = (packageType == WixBundlePackageType.Exe || packageType == WixBundlePackageType.Msp); break; case "DetectCondition": detectCondition = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty); allowed = (packageType == WixBundlePackageType.Exe || packageType == WixBundlePackageType.Msu); break; case "Protocol": protocol = this.Core.GetAttributeValue(sourceLineNumbers, attrib); allowed = (packageType == WixBundlePackageType.Exe); break; case "InstallSize": installSize = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int32.MaxValue); break; case "KB": msuKB = this.Core.GetAttributeValue(sourceLineNumbers, attrib); allowed = (packageType == WixBundlePackageType.Msu); break; case "Compressed": compilerPayload.ParseCompressed(attrib); hasPayloadInfo = true; break; case "Slipstream": slipstream = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); allowed = (packageType == WixBundlePackageType.Msp); break; default: allowed = false; break; } if (!allowed) { this.Core.UnexpectedAttribute(node, attrib); } } else { // Save the extension attributes for later... extensionAttributes.Add(attrib); } } // We need to handle the package payload up front because it affects Id generation. Id is needed by other child elements. var packagePayloadElementName = packageType + "PackagePayload"; foreach (var child in node.Elements(CompilerCore.WixNamespace + packagePayloadElementName)) { var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child); if (childPackageCompilerPayload != null) { this.Core.Write(ErrorMessages.TooManyChildren(childSourceLineNumbers, node.Name.LocalName, child.Name.LocalName)); } else if (hasPayloadInfo) { this.Core.Write(ErrorMessages.UnexpectedElementWithAttribute(sourceLineNumbers, node.Name.LocalName, child.Name.LocalName, "SourceFile", "Name", "DownloadUrl", "Compressed")); } childPackageCompilerPayload = this.ParsePackagePayloadElement(childSourceLineNumbers, child, packageType, compilerPayload.Id); } if (compilerPayload.Id == null && childPackageCompilerPayload != null) { compilerPayload.Id = childPackageCompilerPayload.Id; } compilerPayload.FinishCompilingPackage(); var id = compilerPayload.Id; if (null == logPathVariable) { logPathVariable = String.Concat("WixBundleLog_", id.Id); } if (null == rollbackPathVariable) { rollbackPathVariable = String.Concat("WixBundleRollbackLog_", id.Id); } if (!String.IsNullOrEmpty(protocol) && !protocol.Equals("burn", StringComparison.Ordinal) && !protocol.Equals("netfx4", StringComparison.Ordinal) && !protocol.Equals("none", StringComparison.Ordinal)) { this.Core.Write(ErrorMessages.IllegalAttributeValueWithLegalList(sourceLineNumbers, node.Name.LocalName, "Protocol", protocol, "none, burn, netfx4")); } if (!String.IsNullOrEmpty(protocol) && protocol.Equals("netfx4", StringComparison.Ordinal)) { foreach (var expectedArgument in expectedNetFx4Args) { if (null == installArguments || -1 == installArguments.IndexOf(expectedArgument, StringComparison.OrdinalIgnoreCase)) { this.Core.Write(WarningMessages.AttributeShouldContain(sourceLineNumbers, node.Name.LocalName, "InstallArguments", installArguments, expectedArgument, "Protocol", "netfx4")); } if (!String.IsNullOrEmpty(repairArguments) && -1 == repairArguments.IndexOf(expectedArgument, StringComparison.OrdinalIgnoreCase)) { this.Core.Write(WarningMessages.AttributeShouldContain(sourceLineNumbers, node.Name.LocalName, "RepairArguments", repairArguments, expectedArgument, "Protocol", "netfx4")); } if (!String.IsNullOrEmpty(uninstallArguments) && -1 == uninstallArguments.IndexOf(expectedArgument, StringComparison.OrdinalIgnoreCase)) { this.Core.Write(WarningMessages.AttributeShouldContain(sourceLineNumbers, node.Name.LocalName, "UninstallArguments", uninstallArguments, expectedArgument, "Protocol", "netfx4")); } } } // Only set default scope for EXEs and MSPs if not already set. if ((WixBundlePackageType.Exe == packageType || WixBundlePackageType.Msp == packageType) && YesNoDefaultType.NotSet == perMachine) { perMachine = YesNoDefaultType.Default; } // Detect condition is recommended or required for Exe and Msu packages // (depending on whether uninstall arguments were provided). if ((packageType == WixBundlePackageType.Exe || packageType == WixBundlePackageType.Msu) && String.IsNullOrEmpty(detectCondition)) { if (String.IsNullOrEmpty(uninstallArguments)) { this.Core.Write(WarningMessages.DetectConditionRecommended(sourceLineNumbers, node.Name.LocalName)); } else { this.Core.Write(ErrorMessages.ExpectedAttributeWithValueWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "DetectCondition", "UninstallArguments")); } } // Now that the package ID is known, we can parse the extension attributes... var contextValues = new Dictionary() { { "PackageId", id.Id } }; foreach (var attribute in extensionAttributes) { this.Core.ParseExtensionAttribute(node, attribute, contextValues); } foreach (var child in node.Elements()) { if (CompilerCore.WixNamespace == child.Name.Namespace) { var allowed = true; switch (child.Name.LocalName) { case "SlipstreamMsp": allowed = (packageType == WixBundlePackageType.Msi); if (allowed) { this.ParseSlipstreamMspElement(child, id.Id); } break; case "MsiProperty": allowed = (packageType == WixBundlePackageType.Msi || packageType == WixBundlePackageType.Msp); if (allowed) { this.ParseMsiPropertyElement(child, id.Id); } break; case "Payload": this.ParsePayloadElement(child, ComplexReferenceParentType.Package, id, ComplexReferenceChildType.Unknown, null); break; case "PayloadGroupRef": this.ParsePayloadGroupRefElement(child, ComplexReferenceParentType.Package, id, ComplexReferenceChildType.Unknown, null); break; case "Provides": this.ParseProvidesElement(child, packageType, id.Id, out _); break; case "ExitCode": allowed = (packageType == WixBundlePackageType.Exe); if (allowed) { this.ParseExitCodeElement(child, id.Id); } break; case "CommandLine": allowed = (packageType == WixBundlePackageType.Exe); if (allowed) { this.ParseCommandLineElement(child, id.Id); } break; case "ExePackagePayload": case "MsiPackagePayload": case "MspPackagePayload": case "MsuPackagePayload": allowed = packagePayloadElementName == child.Name.LocalName; // Handled previously break; default: allowed = false; break; } if (!allowed) { this.Core.UnexpectedElement(node, child); } } else { var context = new Dictionary() { { "Id", id.Id } }; this.Core.ParseExtensionElement(node, child, context); } } if (!this.Core.EncounteredError) { var packageCompilerPayload = childPackageCompilerPayload ?? (hasPayloadInfo ? compilerPayload : null); if (packageCompilerPayload != null) { var payload = packageCompilerPayload.CreatePayloadSymbol(ComplexReferenceParentType.Package, id.Id); this.CreatePackagePayloadSymbol(sourceLineNumbers, packageType, payload.Id, ComplexReferenceParentType.Package, id); } this.Core.AddSymbol(new WixChainItemSymbol(sourceLineNumbers, id)); WixBundlePackageAttributes attributes = 0; attributes |= (YesNoType.Yes == permanent) ? WixBundlePackageAttributes.Permanent : 0; attributes |= (YesNoType.Yes == visible) ? WixBundlePackageAttributes.Visible : 0; var chainPackageSymbol = this.Core.AddSymbol(new WixBundlePackageSymbol(sourceLineNumbers, id) { Type = packageType, Attributes = attributes, InstallCondition = installCondition, CacheId = cacheId, LogPathVariable = logPathVariable, RollbackLogPathVariable = rollbackPathVariable, }); if (YesNoAlwaysType.NotSet != cache) { chainPackageSymbol.Cache = cache; } if (YesNoType.NotSet != vital) { chainPackageSymbol.Vital = (vital == YesNoType.Yes); } if (YesNoDefaultType.NotSet != perMachine) { chainPackageSymbol.PerMachine = perMachine; } if (CompilerConstants.IntegerNotSet != installSize) { chainPackageSymbol.InstallSize = installSize; } switch (packageType) { case WixBundlePackageType.Exe: this.Core.AddSymbol(new WixBundleExePackageSymbol(sourceLineNumbers, id) { Attributes = WixBundleExePackageAttributes.None, DetectCondition = detectCondition, InstallCommand = installArguments, RepairCommand = repairArguments, UninstallCommand = uninstallArguments, ExeProtocol = protocol }); break; case WixBundlePackageType.Msi: WixBundleMsiPackageAttributes msiAttributes = 0; msiAttributes |= (YesNoType.Yes == enableFeatureSelection) ? WixBundleMsiPackageAttributes.EnableFeatureSelection : 0; msiAttributes |= (YesNoType.Yes == forcePerMachine) ? WixBundleMsiPackageAttributes.ForcePerMachine : 0; this.Core.AddSymbol(new WixBundleMsiPackageSymbol(sourceLineNumbers, id) { Attributes = msiAttributes }); break; case WixBundlePackageType.Msp: WixBundleMspPackageAttributes mspAttributes = 0; mspAttributes |= (YesNoType.Yes == slipstream) ? WixBundleMspPackageAttributes.Slipstream : 0; this.Core.AddSymbol(new WixBundleMspPackageSymbol(sourceLineNumbers, id) { Attributes = mspAttributes }); break; case WixBundlePackageType.Msu: this.Core.AddSymbol(new WixBundleMsuPackageSymbol(sourceLineNumbers, id) { DetectCondition = detectCondition, MsuKB = msuKB }); break; } this.CreateChainPackageMetaRows(sourceLineNumbers, parentType, parentId, ComplexReferenceChildType.Package, id.Id, previousType, previousId, after); } return id.Id; } private void CreatePackagePayloadSymbol(SourceLineNumber sourceLineNumbers, WixBundlePackageType packageType, Identifier payloadId, ComplexReferenceParentType parentType, Identifier parentId) { switch (packageType) { case WixBundlePackageType.Exe: this.Core.AddSymbol(new WixBundleExePackagePayloadSymbol(sourceLineNumbers, payloadId)); break; case WixBundlePackageType.Msi: this.Core.AddSymbol(new WixBundleMsiPackagePayloadSymbol(sourceLineNumbers, payloadId)); break; case WixBundlePackageType.Msp: this.Core.AddSymbol(new WixBundleMspPackagePayloadSymbol(sourceLineNumbers, payloadId)); break; case WixBundlePackageType.Msu: this.Core.AddSymbol(new WixBundleMsuPackagePayloadSymbol(sourceLineNumbers, payloadId)); break; } this.Core.CreateGroupAndOrderingRows(sourceLineNumbers, parentType, parentId?.Id, ComplexReferenceChildType.PackagePayload, payloadId?.Id, ComplexReferenceChildType.Unknown, null); } private CompilerPayload ParsePackagePayloadElement(SourceLineNumber sourceLineNumbers, XElement node, WixBundlePackageType packageType, Identifier defaultId) { sourceLineNumbers = sourceLineNumbers ?? Preprocessor.GetSourceLineNumbers(node); var compilerPayload = new CompilerPayload(this.Core, sourceLineNumbers, node) { Id = defaultId, IsRemoteAllowed = packageType == WixBundlePackageType.Exe || packageType == WixBundlePackageType.Msu, }; // This list lets us evaluate extension attributes *after* all core attributes // have been parsed and dealt with, regardless of authoring order. var extensionAttributes = new List(); foreach (var attrib in node.Attributes()) { if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) { var allowed = true; switch (attrib.Name.LocalName) { case "Id": compilerPayload.ParseId(attrib); break; case "Compressed": compilerPayload.ParseCompressed(attrib); break; case "Name": compilerPayload.ParseName(attrib); break; case "SourceFile": compilerPayload.ParseSourceFile(attrib); break; case "DownloadUrl": compilerPayload.ParseDownloadUrl(attrib); break; case "Description": allowed = compilerPayload.IsRemoteAllowed; if (allowed) { compilerPayload.ParseDescription(attrib); } break; case "Hash": allowed = compilerPayload.IsRemoteAllowed; if (allowed) { compilerPayload.ParseHash(attrib); } break; case "ProductName": allowed = compilerPayload.IsRemoteAllowed; if (allowed) { compilerPayload.ParseProductName(attrib); } break; case "Size": allowed = compilerPayload.IsRemoteAllowed; if (allowed) { compilerPayload.ParseSize(attrib); } break; case "Version": allowed = compilerPayload.IsRemoteAllowed; if (allowed) { compilerPayload.ParseVersion(attrib); } break; default: allowed = false; break; } if (!allowed) { this.Core.UnexpectedAttribute(node, attrib); } } else { this.Core.ParseExtensionAttribute(node, attrib); } } compilerPayload.FinishCompilingPackagePayload(); // Now that the PayloadId is known, we can parse the extension attributes. var context = new Dictionary { ["Id"] = compilerPayload.Id.Id, }; foreach (var extensionAttribute in extensionAttributes) { this.Core.ParseExtensionAttribute(node, extensionAttribute, context); } this.Core.ParseForExtensionElements(node); return compilerPayload; } /// /// Parse CommandLine element. /// /// Element to parse /// Parent packageId private void ParseCommandLineElement(XElement node, string packageId) { var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); string installArgument = null; string uninstallArgument = null; string repairArgument = null; string condition = null; foreach (var attrib in node.Attributes()) { if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) { switch (attrib.Name.LocalName) { case "InstallArgument": installArgument = this.Core.GetAttributeValue(sourceLineNumbers, attrib); break; case "UninstallArgument": uninstallArgument = this.Core.GetAttributeValue(sourceLineNumbers, attrib); break; case "RepairArgument": repairArgument = this.Core.GetAttributeValue(sourceLineNumbers, attrib); break; case "Condition": condition = this.Core.GetAttributeValue(sourceLineNumbers, attrib); break; default: this.Core.UnexpectedAttribute(node, attrib); break; } } else { this.Core.ParseExtensionAttribute(node, attrib); } } if (String.IsNullOrEmpty(condition)) { this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Condition")); } this.Core.ParseForExtensionElements(node); if (!this.Core.EncounteredError) { this.Core.AddSymbol(new WixBundlePackageCommandLineSymbol(sourceLineNumbers) { WixBundlePackageRef = packageId, InstallArgument = installArgument, UninstallArgument = uninstallArgument, RepairArgument = repairArgument, Condition = condition }); } } /// /// Parse PackageGroup element. /// /// Element to parse private void ParsePackageGroupElement(XElement node) { var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); Identifier id = null; foreach (var attrib in node.Attributes()) { if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) { switch (attrib.Name.LocalName) { case "Id": id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); break; default: this.Core.UnexpectedAttribute(node, attrib); break; } } else { this.Core.ParseExtensionAttribute(node, attrib); } } if (null == id) { this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); id = Identifier.Invalid; } var previousType = ComplexReferenceChildType.Unknown; string previousId = null; foreach (var child in node.Elements()) { if (CompilerCore.WixNamespace == child.Name.Namespace) { switch (child.Name.LocalName) { case "MsiPackage": previousId = this.ParseMsiPackageElement(child, ComplexReferenceParentType.PackageGroup, id.Id, previousType, previousId); previousType = ComplexReferenceChildType.Package; break; case "MspPackage": previousId = this.ParseMspPackageElement(child, ComplexReferenceParentType.PackageGroup, id.Id, previousType, previousId); previousType = ComplexReferenceChildType.Package; break; case "MsuPackage": previousId = this.ParseMsuPackageElement(child, ComplexReferenceParentType.PackageGroup, id.Id, previousType, previousId); previousType = ComplexReferenceChildType.Package; break; case "ExePackage": previousId = this.ParseExePackageElement(child, ComplexReferenceParentType.PackageGroup, id.Id, previousType, previousId); previousType = ComplexReferenceChildType.Package; break; case "RollbackBoundary": previousId = this.ParseRollbackBoundaryElement(child, ComplexReferenceParentType.PackageGroup, id.Id, previousType, previousId); previousType = ComplexReferenceChildType.Package; break; case "PackageGroupRef": previousId = this.ParsePackageGroupRefElement(child, ComplexReferenceParentType.PackageGroup, id.Id, previousType, previousId); previousType = ComplexReferenceChildType.PackageGroup; break; default: this.Core.UnexpectedElement(node, child); break; } } else { this.Core.ParseExtensionElement(node, child); } } if (!this.Core.EncounteredError) { this.Core.AddSymbol(new WixBundlePackageGroupSymbol(sourceLineNumbers, id)); } } /// /// Parses a package group reference element. /// /// Element to parse. /// ComplexReferenceParentType of parent element (Unknown or PackageGroup). /// Identifier of parent element. /// Identifier for package group element. private string ParsePackageGroupRefElement(XElement node, ComplexReferenceParentType parentType, string parentId) { return this.ParsePackageGroupRefElement(node, parentType, parentId, ComplexReferenceChildType.Unknown, null); } /// /// Parses a package group reference element. /// /// Element to parse. /// ComplexReferenceParentType of parent element (Unknown or PackageGroup). /// Identifier of parent element. /// /// /// Identifier for package group element. private string ParsePackageGroupRefElement(XElement node, ComplexReferenceParentType parentType, string parentId, ComplexReferenceChildType previousType, string previousId) { Debug.Assert(ComplexReferenceParentType.Unknown == parentType || ComplexReferenceParentType.PackageGroup == parentType || ComplexReferenceParentType.Container == parentType); Debug.Assert(ComplexReferenceChildType.Unknown == previousType || ComplexReferenceChildType.PackageGroup == previousType || ComplexReferenceChildType.Package == previousType); var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); string id = null; string after = null; foreach (var attrib in node.Attributes()) { if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) { switch (attrib.Name.LocalName) { case "Id": id = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.WixBundlePackageGroup, id); break; case "After": after = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); break; default: this.Core.UnexpectedAttribute(node, attrib); break; } } else { this.Core.ParseExtensionAttribute(node, attrib); } } if (null == id) { this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); } if (null != after && ComplexReferenceParentType.Container == parentType) { this.Core.Write(ErrorMessages.IllegalAttributeWhenNested(sourceLineNumbers, node.Name.LocalName, "After", parentId)); } this.Core.ParseForExtensionElements(node); if (ComplexReferenceParentType.Container == parentType) { this.Core.CreateWixGroupRow(sourceLineNumbers, ComplexReferenceParentType.Container, parentId, ComplexReferenceChildType.PackageGroup, id); } else { this.CreateChainPackageMetaRows(sourceLineNumbers, parentType, parentId, ComplexReferenceChildType.PackageGroup, id, previousType, previousId, after); } return id; } /// /// Creates rollback boundary. /// /// Source line numbers. /// Identifier for the rollback boundary. /// Indicates whether the rollback boundary is vital or not. /// Indicates whether the rollback boundary will use an MSI transaction. /// Type of parent group. /// Identifier of parent group. /// Type of previous item, if any. /// Identifier of previous item, if any. private void CreateRollbackBoundary(SourceLineNumber sourceLineNumbers, Identifier id, YesNoType vital, YesNoType transaction, ComplexReferenceParentType parentType, string parentId, ComplexReferenceChildType previousType, string previousId) { this.Core.AddSymbol(new WixChainItemSymbol(sourceLineNumbers, id)); var rollbackBoundary = this.Core.AddSymbol(new WixBundleRollbackBoundarySymbol(sourceLineNumbers, id)); if (YesNoType.NotSet != vital) { rollbackBoundary.Vital = (vital == YesNoType.Yes); } if (YesNoType.NotSet != transaction) { rollbackBoundary.Transaction = (transaction == YesNoType.Yes); } this.CreateChainPackageMetaRows(sourceLineNumbers, parentType, parentId, ComplexReferenceChildType.Package, id.Id, previousType, previousId, null); } /// /// Creates group and ordering information for packages /// /// Source line numbers. /// Type of parent group, if known. /// Identifier of parent group, if known. /// Type of this item. /// Identifier for this item. /// Type of previous item, if known. /// Identifier of previous item, if known /// Identifier of explicit 'After' attribute, if given. private void CreateChainPackageMetaRows(SourceLineNumber sourceLineNumbers, ComplexReferenceParentType parentType, string parentId, ComplexReferenceChildType type, string id, ComplexReferenceChildType previousType, string previousId, string afterId) { // If there's an explicit 'After' attribute, it overrides the inferred previous item. if (null != afterId) { previousType = ComplexReferenceChildType.Package; previousId = afterId; } this.Core.CreateGroupAndOrderingRows(sourceLineNumbers, parentType, parentId, type, id, previousType, previousId); } /// /// Parse MsiProperty element /// /// Element to parse /// Id of parent element private void ParseMsiPropertyElement(XElement node, string packageId) { var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); string name = null; string value = null; string condition = null; foreach (var attrib in node.Attributes()) { if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) { switch (attrib.Name.LocalName) { case "Name": name = this.Core.GetAttributeMsiPropertyNameValue(sourceLineNumbers, attrib); break; case "Value": value = this.Core.GetAttributeValue(sourceLineNumbers, attrib); break; case "Condition": condition = this.Core.GetAttributeValue(sourceLineNumbers, attrib); break; default: this.Core.UnexpectedAttribute(node, attrib); break; } } else { this.Core.ParseExtensionAttribute(node, attrib); } } if (null == name) { this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name")); } if (null == value) { this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Value")); } this.Core.ParseForExtensionElements(node); if (!this.Core.EncounteredError) { var symbol = this.Core.AddSymbol(new WixBundleMsiPropertySymbol(sourceLineNumbers, new Identifier(AccessModifier.Section, packageId, name)) { PackageRef = packageId, Name = name, Value = value }); if (!String.IsNullOrEmpty(condition)) { symbol.Condition = condition; } } } /// /// Parse SlipstreamMsp element /// /// Element to parse /// Id of parent element private void ParseSlipstreamMspElement(XElement node, string packageId) { var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); string id = null; foreach (var attrib in node.Attributes()) { if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) { switch (attrib.Name.LocalName) { case "Id": id = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.WixBundlePackage, id); break; default: this.Core.UnexpectedAttribute(node, attrib); break; } } else { this.Core.ParseExtensionAttribute(node, attrib); } } if (null == id) { this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); } this.Core.ParseForExtensionElements(node); if (!this.Core.EncounteredError) { this.Core.AddSymbol(new WixBundleSlipstreamMspSymbol(sourceLineNumbers, new Identifier(AccessModifier.Section, packageId, id)) { TargetPackageRef = packageId, MspPackageRef = id }); } } /// /// Parse RelatedBundle element /// /// Element to parse private void ParseRelatedBundleElement(XElement node) { var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); string id = null; var actionType = RelatedBundleActionType.Detect; foreach (var attrib in node.Attributes()) { if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) { switch (attrib.Name.LocalName) { case "Id": id = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib, false); break; case "Action": var action = this.Core.GetAttributeValue(sourceLineNumbers, attrib); switch (action) { case "Detect": case "detect": actionType = RelatedBundleActionType.Detect; break; case "Upgrade": case "upgrade": actionType = RelatedBundleActionType.Upgrade; break; case "Addon": case "addon": actionType = RelatedBundleActionType.Addon; break; case "Patch": case "patch": actionType = RelatedBundleActionType.Patch; break; case "": break; default: this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, "Action", action, "Detect", "Upgrade", "Addon", "Patch")); break; } break; default: this.Core.UnexpectedAttribute(node, attrib); break; } } else { this.Core.ParseExtensionAttribute(node, attrib); } } if (null == id) { this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); } this.Core.ParseForExtensionElements(node); if (!this.Core.EncounteredError) { this.Core.AddSymbol(new WixRelatedBundleSymbol(sourceLineNumbers) { BundleId = id, Action = actionType, }); } } /// /// Parse Update element /// /// Element to parse private void ParseUpdateElement(XElement node) { var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); string location = null; foreach (var attrib in node.Attributes()) { if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) { switch (attrib.Name.LocalName) { case "Location": location = this.Core.GetAttributeValue(sourceLineNumbers, attrib); break; default: this.Core.UnexpectedAttribute(node, attrib); break; } } else { this.Core.ParseExtensionAttribute(node, attrib); } } if (null == location) { this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Location")); } this.Core.ParseForExtensionElements(node); if (!this.Core.EncounteredError) { this.Core.AddSymbol(new WixBundleUpdateSymbol(sourceLineNumbers) { Location = location }); } } /// /// Parse SetVariable element /// /// Element to parse private void ParseSetVariableElement(XElement node) { var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); Identifier id = null; string variable = null; string condition = null; string after = null; string value = null; string typeValue = null; foreach (var attrib in node.Attributes()) { if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) { switch (attrib.Name.LocalName) { case "Id": id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); break; case "Variable": variable = this.Core.GetAttributeValue(sourceLineNumbers, attrib); break; case "Condition": condition = this.Core.GetAttributeValue(sourceLineNumbers, attrib); break; case "After": after = this.Core.GetAttributeValue(sourceLineNumbers, attrib); break; case "Value": value = this.Core.GetAttributeValue(sourceLineNumbers, attrib); break; case "Type": typeValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); break; default: this.Core.UnexpectedAttribute(node, attrib); break; } } else { this.Core.ParseExtensionAttribute(node, attrib, null); } } var type = this.ValidateVariableTypeWithValue(sourceLineNumbers, node, typeValue, value); this.Core.ParseForExtensionElements(node); if (id == null) { id = this.Core.CreateIdentifier("sbv", variable, condition, after, value, type.ToString()); } this.Core.CreateWixSearchSymbol(sourceLineNumbers, node.Name.LocalName, id, variable, condition, after); if (!this.Messaging.EncounteredError) { this.Core.AddSymbol(new WixSetVariableSymbol(sourceLineNumbers, id) { Value = value, Type = type, }); } } /// /// Parse Variable element /// /// Element to parse private void ParseVariableElement(XElement node) { var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); var hidden = false; string name = null; var persisted = false; string value = null; string typeValue = null; foreach (var attrib in node.Attributes()) { if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) { switch (attrib.Name.LocalName) { case "Hidden": if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) { hidden = true; } break; case "Name": name = this.Core.GetAttributeBundleVariableValue(sourceLineNumbers, attrib); break; case "Persisted": if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) { persisted = true; } break; case "Value": value = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty); break; case "Type": typeValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); break; default: this.Core.UnexpectedAttribute(node, attrib); break; } } else { this.Core.ParseExtensionAttribute(node, attrib); } } if (null == name) { this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name")); } else if (name.StartsWith("Wix", StringComparison.OrdinalIgnoreCase)) { this.Core.Write(ErrorMessages.ReservedNamespaceViolation(sourceLineNumbers, node.Name.LocalName, "Name", "Wix")); } if (hidden && persisted) { this.Core.Write(ErrorMessages.IllegalAttributeValueWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Hidden", "yes", "Persisted")); } var type = this.ValidateVariableTypeWithValue(sourceLineNumbers, node, typeValue, value); this.Core.ParseForExtensionElements(node); if (!this.Core.EncounteredError) { this.Core.AddSymbol(new WixBundleVariableSymbol(sourceLineNumbers, new Identifier(AccessModifier.Section, name)) { Value = value, Type = type, Hidden = hidden, Persisted = persisted }); } } private WixBundleVariableType ValidateVariableTypeWithValue(SourceLineNumber sourceLineNumbers, XElement node, string typeValue, string value) { WixBundleVariableType type; switch (typeValue) { case "formatted": type = WixBundleVariableType.Formatted; break; case "numeric": type = WixBundleVariableType.Numeric; break; case "string": type = WixBundleVariableType.String; break; case "version": type = WixBundleVariableType.Version; break; case null: type = WixBundleVariableType.Unknown; break; default: this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, "Type", typeValue, "formatted", "numeric", "string", "version")); return WixBundleVariableType.Unknown; } if (type != WixBundleVariableType.Unknown) { if (value == null) { this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, "Variable", "Value", "Type")); } return type; } else if (value == null) { return type; } // Infer the type from the current value... if (value.StartsWith("v", StringComparison.OrdinalIgnoreCase)) { // Version constructor does not support simple "v#" syntax so check to see if the value is // non-negative real quick. if (Int32.TryParse(value.Substring(1), NumberStyles.None, CultureInfo.InvariantCulture.NumberFormat, out var _)) { return WixBundleVariableType.Version; } else if (Version.TryParse(value.Substring(1), out var _)) { return WixBundleVariableType.Version; } } // Not a version, check for numeric. if (Int64.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture.NumberFormat, out var _)) { return WixBundleVariableType.Numeric; } return WixBundleVariableType.String; } } }