From 5fd1b7ff82f17d55c8357fe76898a1bdc5953476 Mon Sep 17 00:00:00 2001 From: Rob Mensching Date: Fri, 26 Feb 2021 11:24:10 -0800 Subject: Absorb Dependency.wixext into Core Partly resolves wixtoolset/issues#5949 --- src/WixToolset.Core/Compiler_Package.cs | 4975 +++++++++++++++++++++++++++++++ 1 file changed, 4975 insertions(+) create mode 100644 src/WixToolset.Core/Compiler_Package.cs (limited to 'src/WixToolset.Core/Compiler_Package.cs') diff --git a/src/WixToolset.Core/Compiler_Package.cs b/src/WixToolset.Core/Compiler_Package.cs new file mode 100644 index 00000000..d2728e9c --- /dev/null +++ b/src/WixToolset.Core/Compiler_Package.cs @@ -0,0 +1,4975 @@ +// Copyright (c) .NET 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; + using System.Collections.Generic; + using System.Diagnostics.CodeAnalysis; + using System.Globalization; + using System.IO; + using System.Xml.Linq; + using WixToolset.Data; + using WixToolset.Data.Symbols; + using WixToolset.Data.WindowsInstaller; + using WixToolset.Extensibility; + + /// + /// Compiler of the WiX toolset. + /// + internal partial class Compiler : ICompiler + { + /// + /// Parses a product element. + /// + /// Element to parse. + private void ParsePackageElement(XElement node) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + var compressed = YesNoDefaultType.Default; + var sourceBits = 0; + var codepage = 65001; + var productCode = "*"; + var isPerMachine = true; + string installScope = null; + string upgradeCode = null; + string manufacturer = null; + string version = null; + string symbols = null; + var isCodepageSet = false; + var isPackageNameSet = false; + var isKeywordsSet = false; + var isPackageAuthorSet = false; + + this.GetDefaultPlatformAndInstallerVersion(out var platform, out var msiVersion); + + this.activeName = null; + this.activeLanguage = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Codepage": + codepage = this.Core.GetAttributeCodePageValue(sourceLineNumbers, attrib); + break; + case "Compressed": + compressed = this.Core.GetAttributeYesNoDefaultValue(sourceLineNumbers, attrib); + break; + case "InstallerVersion": + msiVersion = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int32.MaxValue); + break; + case "Language": + this.activeLanguage = this.Core.GetAttributeLocalizableIntegerValue(sourceLineNumbers, attrib, 0, Int16.MaxValue); + break; + case "Manufacturer": + manufacturer = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.MustHaveNonWhitespaceCharacters); + if ("PUT-COMPANY-NAME-HERE" == manufacturer) + { + this.Core.Write(WarningMessages.PlaceholderValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, manufacturer)); + } + break; + case "Name": + this.activeName = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.MustHaveNonWhitespaceCharacters); + if ("PUT-PRODUCT-NAME-HERE" == this.activeName) + { + this.Core.Write(WarningMessages.PlaceholderValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, this.activeName)); + } + break; + case "ProductCode": + productCode = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib, true); + break; + case "Scope": + installScope = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + switch (installScope) + { + case "perMachine": + // handled below after we create the section. + break; + case "perUser": + isPerMachine = false; + sourceBits |= 8; + break; + default: + this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, installScope, "perMachine", "perUser")); + break; + } + break; + case "ShortNames": + if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) + { + sourceBits |= 1; + } + break; + case "UpgradeCode": + upgradeCode = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib, false); + break; + case "Version": // if the attribute is valid version, use the attribute value as is (so "1.0000.01.01" would *not* get translated to "1.0.1.1"). + var verifiedVersion = this.Core.GetAttributeVersionValue(sourceLineNumbers, attrib); + if (!String.IsNullOrEmpty(verifiedVersion)) + { + version = attrib.Value; + } + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == productCode) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); + } + + if (null == this.activeLanguage) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Language")); + } + + if (null == manufacturer) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Manufacturer")); + } + + if (null == this.activeName) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name")); + } + + if (null == upgradeCode) + { + this.Core.Write(WarningMessages.MissingUpgradeCode(sourceLineNumbers)); + } + + if (null == version) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Version")); + } + else if (!CompilerCore.IsValidProductVersion(version)) + { + this.Core.Write(ErrorMessages.InvalidProductVersion(sourceLineNumbers, version)); + } + + if (compressed != YesNoDefaultType.No) + { + sourceBits |= 2; + } + + if (this.Core.EncounteredError) + { + return; + } + + try + { + this.compilingProduct = true; + this.Core.CreateActiveSection(productCode, SectionType.Product, codepage, this.Context.CompilationId); + + this.AddProperty(sourceLineNumbers, new Identifier(AccessModifier.Public, "Manufacturer"), manufacturer, false, false, false, true); + this.AddProperty(sourceLineNumbers, new Identifier(AccessModifier.Public, "ProductCode"), productCode, false, false, false, true); + this.AddProperty(sourceLineNumbers, new Identifier(AccessModifier.Public, "ProductLanguage"), this.activeLanguage, false, false, false, true); + this.AddProperty(sourceLineNumbers, new Identifier(AccessModifier.Public, "ProductName"), this.activeName, false, false, false, true); + this.AddProperty(sourceLineNumbers, new Identifier(AccessModifier.Public, "ProductVersion"), version, false, false, false, true); + if (null != upgradeCode) + { + this.AddProperty(sourceLineNumbers, new Identifier(AccessModifier.Public, "UpgradeCode"), upgradeCode, false, false, false, true); + } + + if (isPerMachine) + { + this.AddProperty(sourceLineNumbers, new Identifier(AccessModifier.Public, "ALLUSERS"), "1", false, false, false, false); + } + + this.ValidateAndAddCommonSummaryInformationSymbols(sourceLineNumbers, msiVersion, platform); + + this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) + { + PropertyId = SummaryInformationType.WordCount, + Value = sourceBits.ToString(CultureInfo.InvariantCulture) + }); + + var contextValues = new Dictionary + { + ["ProductLanguage"] = this.activeLanguage, + ["ProductVersion"] = version, + ["UpgradeCode"] = upgradeCode + }; + + var featureDisplay = 0; + foreach (var child in node.Elements()) + { + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + switch (child.Name.LocalName) + { + case "_locDefinition": + break; + case "AdminExecuteSequence": + this.ParseSequenceElement(child, SequenceTable.AdminExecuteSequence); + break; + case "AdminUISequence": + this.ParseSequenceElement(child, SequenceTable.AdminUISequence); + break; + case "AdvertiseExecuteSequence": + this.ParseSequenceElement(child, SequenceTable.AdvertiseExecuteSequence); + break; + case "InstallExecuteSequence": + this.ParseSequenceElement(child, SequenceTable.InstallExecuteSequence); + break; + case "InstallUISequence": + this.ParseSequenceElement(child, SequenceTable.InstallUISequence); + break; + case "AppId": + this.ParseAppIdElement(child, null, YesNoType.Yes, null, null, null); + break; + case "Binary": + this.ParseBinaryElement(child); + break; + case "ComplianceCheck": + this.ParseComplianceCheckElement(child); + break; + case "Component": + this.ParseComponentElement(child, ComplexReferenceParentType.Unknown, null, null, CompilerConstants.IntegerNotSet, null, null); + break; + case "ComponentGroup": + this.ParseComponentGroupElement(child, ComplexReferenceParentType.Unknown, null); + break; + case "CustomAction": + this.ParseCustomActionElement(child); + break; + case "CustomActionRef": + this.ParseSimpleRefElement(child, SymbolDefinitions.CustomAction); + break; + case "CustomTable": + this.ParseCustomTableElement(child); + break; + case "CustomTableRef": + this.ParseCustomTableRefElement(child); + break; + case "Directory": + this.ParseDirectoryElement(child, null, CompilerConstants.IntegerNotSet, String.Empty); + break; + case "DirectoryRef": + this.ParseDirectoryRefElement(child); + break; + case "EmbeddedChainer": + this.ParseEmbeddedChainerElement(child); + break; + case "EmbeddedChainerRef": + this.ParseSimpleRefElement(child, SymbolDefinitions.MsiEmbeddedChainer); + break; + case "EnsureTable": + this.ParseEnsureTableElement(child); + break; + case "Feature": + this.ParseFeatureElement(child, ComplexReferenceParentType.Product, productCode, ref featureDisplay); + break; + case "FeatureRef": + this.ParseFeatureRefElement(child, ComplexReferenceParentType.Product, productCode); + break; + case "FeatureGroupRef": + this.ParseFeatureGroupRefElement(child, ComplexReferenceParentType.Product, productCode); + break; + case "Icon": + this.ParseIconElement(child); + break; + case "InstanceTransforms": + this.ParseInstanceTransformsElement(child); + break; + case "Launch": + this.ParseLaunchElement(child); + break; + case "MajorUpgrade": + this.ParseMajorUpgradeElement(child, contextValues); + break; + case "Media": + this.ParseMediaElement(child, null); + break; + case "MediaTemplate": + this.ParseMediaTemplateElement(child, null); + break; + case "PackageCertificates": + case "PatchCertificates": + this.ParseCertificatesElement(child); + break; + case "Property": + this.ParsePropertyElement(child); + break; + case "PropertyRef": + this.ParseSimpleRefElement(child, SymbolDefinitions.Property); + break; + case "Requires": + this.ParseRequiresElement(child, null, false); + break; + case "SetDirectory": + this.ParseSetDirectoryElement(child); + break; + case "SetProperty": + this.ParseSetPropertyElement(child); + break; + case "SFPCatalog": + string parentName = null; + this.ParseSFPCatalogElement(child, ref parentName); + break; + case "SoftwareTag": + this.ParsePackageTagElement(child); + break; + case "SummaryInformation": + this.ParseSummaryInformationElement(child, ref isCodepageSet, ref isPackageNameSet, ref isKeywordsSet, ref isPackageAuthorSet); + break; + case "SymbolPath": + if (null != symbols) + { + symbols += ";" + this.ParseSymbolPathElement(child); + } + else + { + symbols = this.ParseSymbolPathElement(child); + } + break; + case "UI": + this.ParseUIElement(child); + break; + case "UIRef": + this.ParseSimpleRefElement(child, SymbolDefinitions.WixUI); + break; + case "Upgrade": + this.ParseUpgradeElement(child); + break; + case "WixVariable": + this.ParseWixVariableElement(child); + break; + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + else + { + this.Core.ParseExtensionElement(node, child); + } + } + + if (!this.Core.EncounteredError) + { + if (!isCodepageSet) + { + this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) + { + PropertyId = SummaryInformationType.Codepage, + Value = "1252" + }); + } + + if (!isPackageNameSet) + { + this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) + { + PropertyId = SummaryInformationType.Subject, + Value = this.activeName + }); + } + + if (!isPackageAuthorSet) + { + this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) + { + PropertyId = SummaryInformationType.Author, + Value = manufacturer + }); + } + + if (!isKeywordsSet) + { + this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) + { + PropertyId = SummaryInformationType.Keywords, + Value = "Installer" + }); + } + + if (null != symbols) + { + this.Core.AddSymbol(new WixDeltaPatchSymbolPathsSymbol(sourceLineNumbers) + { + SymbolId = productCode, + SymbolType = SymbolPathType.Product, + SymbolPaths = symbols, + }); + } + } + } + finally + { + this.compilingProduct = false; + } + } + + private void GetDefaultPlatformAndInstallerVersion(out string platform, out int msiVersion) + { + // Let's default to a modern version of MSI. Users can override, + // of course, subject to platform-specific limitations. + msiVersion = 500; + + switch (this.CurrentPlatform) + { + case Platform.X86: + platform = "Intel"; + break; + case Platform.X64: + platform = "x64"; + break; + case Platform.ARM64: + platform = "Arm64"; + break; + default: + throw new ArgumentException("Unknown platform enumeration '{0}' encountered.", this.CurrentPlatform.ToString()); + } + } + + private void ValidateAndAddCommonSummaryInformationSymbols(SourceLineNumber sourceLineNumbers, int msiVersion, string platform) + { + if (String.Equals(platform, "X64", StringComparison.OrdinalIgnoreCase) && 200 > msiVersion) + { + msiVersion = 200; + this.Core.Write(WarningMessages.RequiresMsi200for64bitPackage(sourceLineNumbers)); + } + + if (String.Equals(platform, "Arm64", StringComparison.OrdinalIgnoreCase) && 500 > msiVersion) + { + msiVersion = 500; + this.Core.Write(WarningMessages.RequiresMsi500forArmPackage(sourceLineNumbers)); + } + + this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) + { + PropertyId = SummaryInformationType.Comments, + Value = String.Format(CultureInfo.InvariantCulture, "This installer database contains the logic and data required to install {0}.", this.activeName) + }); + + this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) + { + PropertyId = SummaryInformationType.Title, + Value = "Installation Database" + }); + + this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) + { + PropertyId = SummaryInformationType.PlatformAndLanguage, + Value = String.Format(CultureInfo.InvariantCulture, "{0};{1}", platform, this.activeLanguage) + }); + + this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) + { + PropertyId = SummaryInformationType.WindowsInstallerVersion, + Value = msiVersion.ToString(CultureInfo.InvariantCulture) + }); + + this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) + { + PropertyId = SummaryInformationType.Security, + Value = "2" + }); + + } + + /// + /// Parses an odbc driver or translator element. + /// + /// Element to parse. + /// Identifier of parent component. + /// Default identifer for driver/translator file. + /// Symbol type we're processing for. + private void ParseODBCDriverOrTranslator(XElement node, string componentId, string fileId, SymbolDefinitionType symbolDefinitionType) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + Identifier id = null; + var driver = fileId; + string name = null; + var setup = fileId; + + 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 "File": + driver = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.File, driver); + break; + case "Name": + name = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "SetupFile": + setup = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.File, setup); + 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 == id) + { + id = this.Core.CreateIdentifier("odb", name, fileId, setup); + } + + // drivers have a few possible children + if (SymbolDefinitionType.ODBCDriver == symbolDefinitionType) + { + // process any data sources for the driver + foreach (var child in node.Elements()) + { + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + switch (child.Name.LocalName) + { + case "ODBCDataSource": + string ignoredKeyPath = null; + this.ParseODBCDataSource(child, componentId, name, out ignoredKeyPath); + break; + case "Property": + this.ParseODBCProperty(child, id.Id, SymbolDefinitionType.ODBCAttribute); + break; + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + else + { + this.Core.ParseExtensionElement(node, child); + } + } + } + else + { + this.Core.ParseForExtensionElements(node); + } + + if (!this.Core.EncounteredError) + { + switch (symbolDefinitionType) + { + case SymbolDefinitionType.ODBCDriver: + this.Core.AddSymbol(new ODBCDriverSymbol(sourceLineNumbers, id) + { + ComponentRef = componentId, + Description = name, + FileRef = driver, + SetupFileRef = setup, + }); + break; + case SymbolDefinitionType.ODBCTranslator: + this.Core.AddSymbol(new ODBCTranslatorSymbol(sourceLineNumbers, id) + { + ComponentRef = componentId, + Description = name, + FileRef = driver, + SetupFileRef = setup, + }); + break; + default: + throw new ArgumentOutOfRangeException(nameof(symbolDefinitionType)); + } + } + } + + /// + /// Parses a Property element underneath an ODBC driver or translator. + /// + /// Element to parse. + /// Identifier of parent driver or translator. + /// Name of the table to create property in. + private void ParseODBCProperty(XElement node, string parentId, SymbolDefinitionType symbolDefinitionType) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string id = null; + string propertyValue = 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.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Value": + propertyValue = 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")); + } + + this.Core.ParseForExtensionElements(node); + + if (!this.Core.EncounteredError) + { + var identifier = new Identifier(AccessModifier.Private, parentId, id); + switch (symbolDefinitionType) + { + case SymbolDefinitionType.ODBCAttribute: + this.Core.AddSymbol(new ODBCAttributeSymbol(sourceLineNumbers, identifier) + { + DriverRef = parentId, + Attribute = id, + Value = propertyValue, + }); + break; + case SymbolDefinitionType.ODBCSourceAttribute: + this.Core.AddSymbol(new ODBCSourceAttributeSymbol(sourceLineNumbers, identifier) + { + DataSourceRef = parentId, + Attribute = id, + Value = propertyValue, + }); + break; + default: + throw new ArgumentOutOfRangeException(nameof(symbolDefinitionType)); + } + } + } + + /// + /// Parse an odbc data source element. + /// + /// Element to parse. + /// Identifier of parent component. + /// Default name of driver. + /// Identifier of this element in case it is a keypath. + /// Yes if this element was marked as the parent component's key path, No if explicitly marked as not being a key path, or NotSet otherwise. + private YesNoType ParseODBCDataSource(XElement node, string componentId, string driverName, out string possibleKeyPath) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + Identifier id = null; + var keyPath = YesNoType.NotSet; + string name = null; + var registration = CompilerConstants.IntegerNotSet; + + 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 "DriverName": + driverName = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "KeyPath": + keyPath = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "Name": + name = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Registration": + var registrationValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + switch (registrationValue) + { + case "machine": + registration = 0; + break; + case "user": + registration = 1; + break; + case "": + break; + default: + this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, "Registration", registrationValue, "machine", "user")); + break; + } + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (CompilerConstants.IntegerNotSet == registration) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Registration")); + registration = CompilerConstants.IllegalInteger; + } + + if (null == id) + { + id = this.Core.CreateIdentifier("odc", name, driverName, registration.ToString()); + } + + foreach (var child in node.Elements()) + { + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + switch (child.Name.LocalName) + { + case "Property": + this.ParseODBCProperty(child, id.Id, SymbolDefinitionType.ODBCSourceAttribute); + break; + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + else + { + this.Core.ParseExtensionElement(node, child); + } + } + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new ODBCDataSourceSymbol(sourceLineNumbers, id) + { + ComponentRef = componentId, + Description = name, + DriverDescription = driverName, + Registration = registration + }); + } + + possibleKeyPath = id.Id; + return keyPath; + } + + /// + /// Parses a package element. + /// + /// Element to parse. + /// + /// + /// + /// + private void ParseSummaryInformationElement(XElement node, ref bool isCodepageSet, ref bool isPackageNameSet, ref bool isKeywordsSet, ref bool isPackageAuthorSet) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string codepage = null; + string packageName = null; + string keywords = null; + string packageAuthor = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Codepage": + codepage = this.Core.GetAttributeLocalizableCodePageValue(sourceLineNumbers, attrib, true); + break; + case "Description": + packageName = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Keywords": + keywords = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Manufacturer": + packageAuthor = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + if ("PUT-COMPANY-NAME-HERE" == packageAuthor) + { + this.Core.Write(WarningMessages.PlaceholderValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, packageAuthor)); + } + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + this.Core.ParseForExtensionElements(node); + + if (!this.Core.EncounteredError) + { + if (null != codepage) + { + isCodepageSet = true; + this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) + { + PropertyId = SummaryInformationType.Codepage, + Value = codepage + }); + } + + if (null != packageName) + { + isPackageNameSet = true; + this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) + { + PropertyId = SummaryInformationType.Subject, + Value = packageName + }); + } + + if (null != packageAuthor) + { + isPackageAuthorSet = true; + this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) + { + PropertyId = SummaryInformationType.Author, + Value = packageAuthor + }); + } + + if (null != keywords) + { + isKeywordsSet = true; + this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) + { + PropertyId = SummaryInformationType.Keywords, + Value = keywords + }); + } + } + } + + /// + /// Parses a patch information element. + /// + /// Element to parse. + private void ParsePatchInformationElement(XElement node) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + var codepage = "1252"; + string comments = null; + var keywords = "Installer,Patching,PCP,Database"; + var msiVersion = 1; // Should always be 1 for patches + string packageAuthor = null; + var packageName = this.activeName; + var security = YesNoDefaultType.Default; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "AdminImage": + this.Core.Write(WarningMessages.DeprecatedAttribute(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName)); + break; + case "Comments": + comments = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Compressed": + this.Core.Write(WarningMessages.DeprecatedAttribute(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName)); + break; + case "Description": + packageName = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Keywords": + keywords = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Languages": + this.Core.Write(WarningMessages.DeprecatedAttribute(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName)); + break; + case "Manufacturer": + packageAuthor = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Platforms": + this.Core.Write(WarningMessages.DeprecatedAttribute(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName)); + break; + case "ReadOnly": + security = this.Core.GetAttributeYesNoDefaultValue(sourceLineNumbers, attrib); + break; + case "ShortNames": + this.Core.Write(WarningMessages.DeprecatedAttribute(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName)); + break; + case "SummaryCodepage": + codepage = this.Core.GetAttributeLocalizableCodePageValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + this.Core.ParseForExtensionElements(node); + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) + { + PropertyId = SummaryInformationType.Codepage, + Value = codepage + }); + + this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) + { + PropertyId = SummaryInformationType.Title, + Value = "Patch" + }); + + if (null != packageName) + { + this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) + { + PropertyId = SummaryInformationType.Subject, + Value = packageName + }); + } + + if (null != packageAuthor) + { + this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) + { + PropertyId = SummaryInformationType.Author, + Value = packageAuthor + }); + } + + if (null != keywords) + { + this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) + { + PropertyId = SummaryInformationType.Keywords, + Value = keywords + }); + } + + if (null != comments) + { + this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) + { + PropertyId = SummaryInformationType.Comments, + Value = comments + }); + } + + this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) + { + PropertyId = SummaryInformationType.WindowsInstallerVersion, + Value = msiVersion.ToString(CultureInfo.InvariantCulture) + }); + + this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) + { + PropertyId = SummaryInformationType.WordCount, + Value = "0" + }); + + this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) + { + PropertyId = SummaryInformationType.Security, + Value = YesNoDefaultType.No == security ? "0" : YesNoDefaultType.Yes == security ? "4" : "2" + }); + } + } + + /// + /// Parses a permission element. + /// + /// Element to parse. + /// Identifier of object to be secured. + /// Name of table that contains objectId. + private void ParsePermissionElement(XElement node, string objectId, string tableName) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + var bits = new BitArray(32); + string domain = null; + string[] specialPermissions = null; + string user = null; + + switch (tableName) + { + case "CreateFolder": + specialPermissions = Common.FolderPermissions; + break; + case "File": + specialPermissions = Common.FilePermissions; + break; + case "Registry": + specialPermissions = Common.RegistryPermissions; + break; + default: + this.Core.UnexpectedElement(node.Parent, node); + return; // stop processing this element since no valid permissions are available + } + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Domain": + domain = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "User": + user = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "FileAllRights": + // match the WinNT.h mask FILE_ALL_ACCESS for value 0x001F01FF (aka 1 1111 0000 0001 1111 1111 or 2032127) + bits[0] = bits[1] = bits[2] = bits[3] = bits[4] = bits[5] = bits[6] = bits[7] = bits[8] = bits[16] = bits[17] = bits[18] = bits[19] = bits[20] = true; + break; + case "SpecificRightsAll": + // match the WinNT.h mask SPECIFIC_RIGHTS_ALL for value 0x0000FFFF (aka 1111 1111 1111 1111) + bits[0] = bits[1] = bits[2] = bits[3] = bits[4] = bits[5] = bits[6] = bits[7] = bits[8] = bits[9] = bits[10] = bits[11] = bits[12] = bits[13] = bits[14] = bits[15] = true; + break; + default: + var attribValue = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + if (!this.Core.TrySetBitFromName(Common.StandardPermissions, attrib.Name.LocalName, attribValue, bits, 16)) + { + if (!this.Core.TrySetBitFromName(Common.GenericPermissions, attrib.Name.LocalName, attribValue, bits, 28)) + { + if (!this.Core.TrySetBitFromName(specialPermissions, attrib.Name.LocalName, attribValue, bits, 0)) + { + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + } + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == user) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "User")); + } + + var permission = this.Core.CreateIntegerFromBitArray(bits); + + if (Int32.MinValue == permission) // just GENERIC_READ, which is MSI_NULL + { + this.Core.Write(ErrorMessages.GenericReadNotAllowed(sourceLineNumbers)); + } + + this.Core.ParseForExtensionElements(node); + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new LockPermissionsSymbol(sourceLineNumbers) + { + LockObject = objectId, + Table = tableName, + Domain = domain, + User = user, + Permission = permission + }); + } + } + + /// + /// Parses an extended permission element. + /// + /// Element to parse. + /// Identifier of object to be secured. + /// Name of table that contains objectId. + private void ParsePermissionExElement(XElement node, string objectId, string tableName) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string condition = null; + Identifier id = null; + string sddl = null; + + switch (tableName) + { + case "CreateFolder": + case "File": + case "Registry": + case "ServiceInstall": + break; + default: + this.Core.UnexpectedElement(node.Parent, node); + return; // stop processing this element since nothing will be valid. + } + + 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 "Condition": + condition = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Sddl": + sddl = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + this.Core.VerifyNoInnerText(sourceLineNumbers, node); + + if (null == sddl) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Sddl")); + } + + if (null == id) + { + id = this.Core.CreateIdentifier("pme", objectId, tableName, sddl); + } + + this.Core.ParseForExtensionElements(node); + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new MsiLockPermissionsExSymbol(sourceLineNumbers, id) + { + LockObject = objectId, + Table = tableName, + SDDLText = sddl, + Condition = condition + }); + } + } + + /// + /// Parses a progid element + /// + /// Element to parse. + /// Identifier of parent component. + /// Flag if progid is advertised. + /// CLSID related to ProgId. + /// Default description of ProgId + /// Optional parent ProgId + /// Set to true if an extension is found; used for error-checking. + /// Whether or not this ProgId is the first one found in the parent class. + /// This element's Id. + private string ParseProgIdElement(XElement node, string componentId, YesNoType advertise, string classId, string description, string parent, ref bool foundExtension, YesNoType firstProgIdForClass) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string icon = null; + var iconIndex = CompilerConstants.IntegerNotSet; + string noOpen = null; + string progId = null; + var progIdAdvertise = YesNoType.NotSet; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + progId = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Advertise": + progIdAdvertise = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "Description": + description = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty); + break; + case "Icon": + icon = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + break; + case "IconIndex": + iconIndex = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, Int16.MinValue + 1, Int16.MaxValue); + break; + case "NoOpen": + noOpen = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if ((YesNoType.No == advertise && YesNoType.Yes == progIdAdvertise) || (YesNoType.Yes == advertise && YesNoType.No == progIdAdvertise)) + { + this.Core.Write(ErrorMessages.AdvertiseStateMustMatch(sourceLineNumbers, advertise.ToString(), progIdAdvertise.ToString())); + } + else if (YesNoType.NotSet != progIdAdvertise) + { + advertise = progIdAdvertise; + } + + if (YesNoType.NotSet == advertise) + { + advertise = YesNoType.No; + } + + if (null != parent && (null != icon || CompilerConstants.IntegerNotSet != iconIndex)) + { + this.Core.Write(ErrorMessages.VersionIndependentProgIdsCannotHaveIcons(sourceLineNumbers)); + } + + var firstProgIdForNestedClass = YesNoType.Yes; + foreach (var child in node.Elements()) + { + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + switch (child.Name.LocalName) + { + case "Extension": + this.ParseExtensionElement(child, componentId, advertise, progId); + foundExtension = true; + break; + case "ProgId": + // Only allow one nested ProgId. If we have a child, we should not have a parent. + if (null == parent) + { + if (YesNoType.Yes == advertise) + { + this.ParseProgIdElement(child, componentId, advertise, null, description, progId, ref foundExtension, firstProgIdForNestedClass); + } + else if (YesNoType.No == advertise) + { + this.ParseProgIdElement(child, componentId, advertise, classId, description, progId, ref foundExtension, firstProgIdForNestedClass); + } + + firstProgIdForNestedClass = YesNoType.No; // any ProgId after this one is definitely not the first. + } + else + { + var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child); + this.Core.Write(ErrorMessages.ProgIdNestedTooDeep(childSourceLineNumbers)); + } + break; + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + else + { + this.Core.ParseExtensionElement(node, child); + } + } + + if (YesNoType.Yes == advertise) + { + if (!this.Core.EncounteredError) + { + var symbol = this.Core.AddSymbol(new ProgIdSymbol(sourceLineNumbers, new Identifier(AccessModifier.Public, progId)) + { + ProgId = progId, + ParentProgIdRef = parent, + ClassRef = classId, + Description = description, + }); + + if (null != icon) + { + symbol.IconRef = icon; + this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Icon, icon); + } + + if (CompilerConstants.IntegerNotSet != iconIndex) + { + symbol.IconIndex = iconIndex; + } + + this.Core.EnsureTable(sourceLineNumbers, WindowsInstallerTableDefinitions.Class); + } + } + else if (YesNoType.No == advertise) + { + this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, progId, String.Empty, description, componentId); + if (null != classId) + { + this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat(progId, "\\CLSID"), String.Empty, classId, componentId); + if (null != parent) // if this is a version independent ProgId + { + if (YesNoType.Yes == firstProgIdForClass) + { + this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("CLSID\\", classId, "\\VersionIndependentProgID"), String.Empty, progId, componentId); + } + + this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat(progId, "\\CurVer"), String.Empty, parent, componentId); + } + else + { + if (YesNoType.Yes == firstProgIdForClass) + { + this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("CLSID\\", classId, "\\ProgID"), String.Empty, progId, componentId); + } + } + } + + if (null != icon) // ProgId's Default Icon + { + this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.File, icon); + + icon = String.Format(CultureInfo.InvariantCulture, "\"[#{0}]\"", icon); + + if (CompilerConstants.IntegerNotSet != iconIndex) + { + icon = String.Concat(icon, ",", iconIndex); + } + + this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat(progId, "\\DefaultIcon"), String.Empty, icon, componentId); + } + } + + if (null != noOpen) + { + this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, progId, "NoOpen", noOpen, componentId); // ProgId NoOpen name + } + + // raise an error for an orphaned ProgId + if (YesNoType.Yes == advertise && !foundExtension && null == parent && null == classId) + { + this.Core.Write(WarningMessages.OrphanedProgId(sourceLineNumbers, progId)); + } + + return progId; + } + + /// + /// Parses a property element. + /// + /// Element to parse. + private void ParsePropertyElement(XElement node) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + Identifier id = null; + var admin = false; + var complianceCheck = false; + var hidden = false; + var secure = false; + var suppressModularization = YesNoType.NotSet; + string value = 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 "Admin": + admin = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "ComplianceCheck": + complianceCheck = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "Hidden": + hidden = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "Secure": + secure = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "SuppressModularization": + suppressModularization = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "Value": + value = 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")); + id = Identifier.Invalid; + } + else if ("ProductID" == id.Id) + { + this.Core.Write(WarningMessages.ProductIdAuthored(sourceLineNumbers)); + } + else if ("SecureCustomProperties" == id.Id || "AdminProperties" == id.Id || "MsiHiddenProperties" == id.Id) + { + this.Core.Write(ErrorMessages.CannotAuthorSpecialProperties(sourceLineNumbers, id.Id)); + } + + this.Core.VerifyNoInnerText(sourceLineNumbers, node); + + if ("ErrorDialog" == id.Id) + { + this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Dialog, value); + } + + foreach (var child in node.Elements()) + { + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + { + switch (child.Name.LocalName) + { + case "ProductSearch": + this.ParseProductSearchElement(child, id.Id); + secure = true; + break; + default: + // let ParseSearchSignatures handle standard AppSearch children and unknown elements + break; + } + } + } + } + + // see if this property is used for appSearch + var signatures = this.ParseSearchSignatures(node); + + // If we're doing CCP then there must be a signature. + if (complianceCheck && 0 == signatures.Count) + { + this.Core.Write(ErrorMessages.SearchElementRequiredWithAttribute(sourceLineNumbers, node.Name.LocalName, "ComplianceCheck", "yes")); + } + + foreach (var sig in signatures) + { + if (complianceCheck && !this.Core.EncounteredError) + { + this.Core.AddSymbol(new CCPSearchSymbol(sourceLineNumbers, new Identifier(AccessModifier.Private, sig))); + } + + this.AddAppSearch(sourceLineNumbers, id, sig); + } + + // If we're doing AppSearch get that setup. + if (0 < signatures.Count) + { + this.AddProperty(sourceLineNumbers, id, value, admin, secure, hidden, false); + } + else // just a normal old property. + { + // If the property value is empty and none of the flags are set, print out a warning that we're ignoring + // the element. + if (String.IsNullOrEmpty(value) && !admin && !secure && !hidden) + { + this.Core.Write(WarningMessages.PropertyUseless(sourceLineNumbers, id.Id)); + } + else // there is a value and/or a flag set, do that. + { + this.AddProperty(sourceLineNumbers, id, value, admin, secure, hidden, false); + } + } + + if (!this.Core.EncounteredError && YesNoType.Yes == suppressModularization) + { + this.Core.Write(WarningMessages.PropertyModularizationSuppressed(sourceLineNumbers)); + + this.Core.AddSymbol(new WixSuppressModularizationSymbol(sourceLineNumbers, id)); + } + } + + /// + /// Parses a RegistryKey element. + /// + /// Element to parse. + /// Identifier for parent component. + /// Root specified when element is nested under another Registry element, otherwise CompilerConstants.IntegerNotSet. + /// Parent key for this Registry element when nested. + /// true if the component is 64-bit. + /// Identifier of this registry key since it could be the component's keypath. + /// Yes if this element was marked as the parent component's key path, No if explicitly marked as not being a key path, or NotSet otherwise. + private YesNoType ParseRegistryKeyElement(XElement node, string componentId, RegistryRootType? root, string parentKey, bool win64Component, out string possibleKeyPath) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + Identifier id = null; + var key = parentKey; // default to parent key path + var forceCreateOnInstall = false; + var forceDeleteOnUninstall = false; + var keyPath = YesNoType.NotSet; + + possibleKeyPath = 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 "Action": + this.Core.Write(WarningMessages.DeprecatedRegistryKeyActionAttribute(sourceLineNumbers)); + var actionValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + switch (actionValue) + { + case "create": + forceCreateOnInstall = true; + break; + case "createAndRemoveOnUninstall": + forceCreateOnInstall = true; + forceDeleteOnUninstall = true; + break; + case "none": + break; + case "": + break; + default: + this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, actionValue, "create", "createAndRemoveOnUninstall", "none")); + break; + } + break; + case "ForceCreateOnInstall": + forceCreateOnInstall = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "ForceDeleteOnUninstall": + forceDeleteOnUninstall = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "Key": + key = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + if (null != parentKey) + { + key = Path.Combine(parentKey, key); + } + key = key?.TrimEnd('\\'); + break; + case "Root": + if (root.HasValue) + { + this.Core.Write(ErrorMessages.RegistryRootInvalid(sourceLineNumbers)); + } + + root = this.Core.GetAttributeRegistryRootValue(sourceLineNumbers, attrib, true); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + var name = forceCreateOnInstall ? (forceDeleteOnUninstall ? "*" : "+") : (forceDeleteOnUninstall ? "-" : null); + + if (forceCreateOnInstall || forceDeleteOnUninstall) // generates a Registry row, so an Id must be present + { + // generate the identifier if it wasn't provided + if (null == id) + { + id = this.Core.CreateIdentifier("reg", componentId, ((int)root).ToString(CultureInfo.InvariantCulture.NumberFormat), LowercaseOrNull(key), LowercaseOrNull(name)); + } + } + else // does not generate a Registry row, so no Id should be present + { + if (null != id) + { + this.Core.Write(ErrorMessages.IllegalAttributeWithoutOtherAttributes(sourceLineNumbers, node.Name.LocalName, "Id", "ForceCreateOnInstall", "ForceDeleteOnUninstall", "yes", true)); + } + } + + if (!root.HasValue) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Root")); + } + + if (null == key) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Key")); + key = String.Empty; // set the key to something to prevent null reference exceptions + } + + foreach (var child in node.Elements()) + { + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + string possibleChildKeyPath = null; + + switch (child.Name.LocalName) + { + case "RegistryKey": + if (YesNoType.Yes == this.ParseRegistryKeyElement(child, componentId, root, key, win64Component, out possibleChildKeyPath)) + { + if (YesNoType.Yes == keyPath) + { + this.Core.Write(ErrorMessages.ComponentMultipleKeyPaths(sourceLineNumbers, child.Name.LocalName, "KeyPath", "yes", "File", "RegistryValue", "ODBCDataSource")); + } + + possibleKeyPath = possibleChildKeyPath; // the child is the key path + keyPath = YesNoType.Yes; + } + else if (null == possibleKeyPath && null != possibleChildKeyPath) + { + possibleKeyPath = possibleChildKeyPath; + } + break; + case "RegistryValue": + if (YesNoType.Yes == this.ParseRegistryValueElement(child, componentId, root, key, win64Component, out possibleChildKeyPath)) + { + if (YesNoType.Yes == keyPath) + { + this.Core.Write(ErrorMessages.ComponentMultipleKeyPaths(sourceLineNumbers, child.Name.LocalName, "KeyPath", "yes", "File", "RegistryValue", "ODBCDataSource")); + } + + possibleKeyPath = possibleChildKeyPath; // the child is the key path + keyPath = YesNoType.Yes; + } + else if (null == possibleKeyPath && null != possibleChildKeyPath) + { + possibleKeyPath = possibleChildKeyPath; + } + break; + case "Permission": + if (!forceCreateOnInstall) + { + this.Core.Write(ErrorMessages.UnexpectedElementWithAttributeValue(sourceLineNumbers, node.Name.LocalName, child.Name.LocalName, "ForceCreateOnInstall", "yes")); + } + this.ParsePermissionElement(child, id.Id, "Registry"); + break; + case "PermissionEx": + if (!forceCreateOnInstall) + { + this.Core.Write(ErrorMessages.UnexpectedElementWithAttributeValue(sourceLineNumbers, node.Name.LocalName, child.Name.LocalName, "ForceCreateOnInstall", "yes")); + } + this.ParsePermissionExElement(child, id.Id, "Registry"); + break; + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + else + { + var context = new Dictionary() { { "RegistryId", id?.Id }, { "ComponentId", componentId }, { "Win64", win64Component.ToString() } }; + this.Core.ParseExtensionElement(node, child, context); + } + } + + if (!this.Core.EncounteredError && null != name) + { + this.Core.AddSymbol(new RegistrySymbol(sourceLineNumbers, id) + { + Root = root.Value, + Key = key, + Name = name, + ComponentRef = componentId, + }); + } + + return keyPath; + } + + /// + /// Parses a RegistryValue element. + /// + /// Element to parse. + /// Identifier for parent component. + /// Root specified when element is nested under a RegistryKey element, otherwise CompilerConstants.IntegerNotSet. + /// Root specified when element is nested under a RegistryKey element, otherwise CompilerConstants.IntegerNotSet. + /// true if the component is 64-bit. + /// Identifier of this registry key since it could be the component's keypath. + /// Yes if this element was marked as the parent component's key path, No if explicitly marked as not being a key path, or NotSet otherwise. + private YesNoType ParseRegistryValueElement(XElement node, string componentId, RegistryRootType? root, string parentKey, bool win64Component, out string possibleKeyPath) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + Identifier id = null; + var key = parentKey; // default to parent key path + string name = null; + string value = null; + string action = null; + var valueType = RegistryValueType.String; + var actionType = RegistryValueActionType.Write; + var keyPath = YesNoType.NotSet; + + possibleKeyPath = 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 "Action": + var actionValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + switch (actionValue) + { + case "append": + actionType = RegistryValueActionType.Append; + break; + case "prepend": + actionType = RegistryValueActionType.Prepend; + break; + case "write": + actionType = RegistryValueActionType.Write; + break; + case "": + break; + default: + this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, actionValue, "append", "prepend", "write")); + break; + } + break; + case "Key": + key = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + if (null != parentKey) + { + if (parentKey.EndsWith("\\", StringComparison.Ordinal)) + { + key = String.Concat(parentKey, key); + } + else + { + key = String.Concat(parentKey, "\\", key); + } + } + break; + case "KeyPath": + keyPath = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "Name": + name = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Root": + if (root.HasValue) + { + this.Core.Write(ErrorMessages.RegistryRootInvalid(sourceLineNumbers)); + } + + root = this.Core.GetAttributeRegistryRootValue(sourceLineNumbers, attrib, true); + break; + case "Type": + var typeValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + switch (typeValue) + { + case "binary": + valueType = RegistryValueType.Binary; + break; + case "expandable": + valueType = RegistryValueType.Expandable; + break; + case "integer": + valueType = RegistryValueType.Integer; + break; + case "multiString": + valueType = RegistryValueType.MultiString; + break; + case "string": + valueType = RegistryValueType.String; + break; + case "": + break; + default: + this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, typeValue, "binary", "expandable", "integer", "multiString", "string")); + break; + } + break; + case "Value": + value = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + // generate the identifier if it wasn't provided + if (null == id) + { + id = this.Core.CreateIdentifier("reg", componentId, ((int)(root ?? RegistryRootType.Unknown)).ToString(), LowercaseOrNull(key), LowercaseOrNull(name)); + } + + if (RegistryValueType.MultiString != valueType && (RegistryValueActionType.Append == actionType || RegistryValueActionType.Prepend == actionType)) + { + this.Core.Write(ErrorMessages.IllegalAttributeValueWithoutOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Action", action, "Type", "multiString")); + } + + if (null == key) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Key")); + } + + if (!root.HasValue) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Root")); + } + + foreach (var child in node.Elements()) + { + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + switch (child.Name.LocalName) + { + case "MultiString": + case "MultiStringValue": + if (RegistryValueType.MultiString != valueType && null != value) + { + this.Core.Write(ErrorMessages.RegistryMultipleValuesWithoutMultiString(sourceLineNumbers, node.Name.LocalName, "Value", child.Name.LocalName, "Type")); + } + else + { + value = this.ParseRegistryMultiStringElement(child, value); + } + break; + case "Permission": + this.ParsePermissionElement(child, id.Id, "Registry"); + break; + case "PermissionEx": + this.ParsePermissionExElement(child, id.Id, "Registry"); + break; + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + else + { + var context = new Dictionary() { { "RegistryId", id?.Id }, { "ComponentId", componentId }, { "Win64", win64Component.ToString() } }; + this.Core.ParseExtensionElement(node, child, context); + } + } + + //switch (typeType) + //{ + //case Wix.RegistryValue.TypeType.binary: + // value = String.Concat("#x", value); + // break; + //case Wix.RegistryValue.TypeType.expandable: + // value = String.Concat("#%", value); + // break; + //case Wix.RegistryValue.TypeType.integer: + // value = String.Concat("#", value); + // break; + //case Wix.RegistryValue.TypeType.multiString: + // switch (actionType) + // { + // case Wix.RegistryValue.ActionType.append: + // value = String.Concat("[~]", value); + // break; + // case Wix.RegistryValue.ActionType.prepend: + // value = String.Concat(value, "[~]"); + // break; + // case Wix.RegistryValue.ActionType.write: + // default: + // if (null != value && -1 == value.IndexOf("[~]", StringComparison.Ordinal)) + // { + // value = String.Format(CultureInfo.InvariantCulture, "[~]{0}[~]", value); + // } + // break; + // } + // break; + //case Wix.RegistryValue.TypeType.@string: + // // escape the leading '#' character for string registry keys + // if (null != value && value.StartsWith("#", StringComparison.Ordinal)) + // { + // value = String.Concat("#", value); + // } + // break; + //} + + // value may be set by child MultiStringValue elements, so it must be checked here + if (null == value && valueType != RegistryValueType.Binary) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Value")); + } + else if (0 == value?.Length && ("+" == name || "-" == name || "*" == name)) // prevent accidental authoring of special name values + { + this.Core.Write(ErrorMessages.RegistryNameValueIncorrect(sourceLineNumbers, node.Name.LocalName, "Name", name)); + } + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new RegistrySymbol(sourceLineNumbers, id) + { + Root = root.Value, + Key = key, + Name = name, + Value = value, + ValueType = valueType, + ValueAction = actionType, + ComponentRef = componentId, + }); + } + + // If this was just a regular registry key (that could be the key path) + // and no child registry key set the possible key path, let's make this + // Registry/@Id a possible key path. + if (null == possibleKeyPath) + { + possibleKeyPath = id.Id; + } + + return keyPath; + } + + private string ParseRegistryMultiStringElement(XElement node, string value) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string multiStringValue = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Value": + multiStringValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + } + + this.Core.VerifyNoInnerText(sourceLineNumbers, node); + + this.Core.ParseForExtensionElements(node); + + return null == value ? multiStringValue ?? "[~]" : String.Concat(value, "[~]", multiStringValue); + } + + /// + /// Parses a RemoveRegistryKey element. + /// + /// The element to parse. + /// The component identifier of the parent element. + private void ParseRemoveRegistryKeyElement(XElement node, string componentId) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + Identifier id = null; + RemoveRegistryActionType? actionType = null; + string key = null; + var name = "-"; + RegistryRootType? root = 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 "Action": + var actionValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + switch (actionValue) + { + case "removeOnInstall": + actionType = RemoveRegistryActionType.RemoveOnInstall; + break; + case "removeOnUninstall": + actionType = RemoveRegistryActionType.RemoveOnUninstall; + break; + case "": + break; + default: + this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, actionValue, "removeOnInstall", "removeOnUninstall")); + break; + } + //if (0 < action.Length) + //{ + // if (!Wix.RemoveRegistryKey.TryParseActionType(action, out actionType)) + // { + // this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, action, "removeOnInstall", "removeOnUninstall")); + // } + //} + break; + case "Key": + key = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Root": + root = this.Core.GetAttributeRegistryRootValue(sourceLineNumbers, attrib, true); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + // generate the identifier if it wasn't provided + if (null == id) + { + id = this.Core.CreateIdentifier("reg", componentId, ((int)root).ToString(CultureInfo.InvariantCulture.NumberFormat), LowercaseOrNull(key), LowercaseOrNull(name)); + } + + if (!root.HasValue) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Root")); + } + + if (null == key) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Key")); + } + + if (!actionType.HasValue) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Action")); + } + + this.Core.ParseForExtensionElements(node); + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new RemoveRegistrySymbol(sourceLineNumbers, id) + { + Root = root.Value, + Key = key, + Name = name, + Action = actionType.Value, + ComponentRef = componentId, + }); + } + } + + /// + /// Parses a RemoveRegistryValue element. + /// + /// The element to parse. + /// The component identifier of the parent element. + private void ParseRemoveRegistryValueElement(XElement node, string componentId) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + Identifier id = null; + string key = null; + string name = null; + RegistryRootType? root = 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 "Key": + key = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Name": + name = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Root": + root = this.Core.GetAttributeRegistryRootValue(sourceLineNumbers, attrib, true); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + // generate the identifier if it wasn't provided + if (null == id) + { + id = this.Core.CreateIdentifier("reg", componentId, ((int)root).ToString(CultureInfo.InvariantCulture.NumberFormat), LowercaseOrNull(key), LowercaseOrNull(name)); + } + + if (!root.HasValue) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Root")); + } + + if (null == key) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Key")); + } + + this.Core.ParseForExtensionElements(node); + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new RemoveRegistrySymbol(sourceLineNumbers, id) + { + Root = root.Value, + Key = key, + Name = name, + ComponentRef = componentId + }); + } + } + + /// + /// Parses a remove file element. + /// + /// Element to parse. + /// Identifier of parent component. + /// Identifier of the parent component's directory. + private void ParseRemoveFileElement(XElement node, string componentId, string parentDirectory) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + Identifier id = null; + string directory = null; + string name = null; + bool? onInstall = null; + bool? onUninstall = null; + string property = null; + string shortName = 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 "Directory": + directory = this.Core.CreateDirectoryReferenceFromInlineSyntax(sourceLineNumbers, attrib, parentDirectory); + break; + case "Name": + name = this.Core.GetAttributeLongFilename(sourceLineNumbers, attrib, true); + break; + case "On": + var onValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + switch (onValue) + { + case "install": + onInstall = true; + break; + case "uninstall": + onUninstall = true; + break; + case "both": + onInstall = true; + onUninstall = true; + break; + } + break; + case "Property": + property = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + break; + case "ShortName": + shortName = this.Core.GetAttributeShortFilename(sourceLineNumbers, attrib, true); + 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 (!onInstall.HasValue && !onUninstall.HasValue) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "On")); + } + + if (null != directory && null != property) + { + this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Property", "Directory", directory)); + } + + if (null == id) + { + var on = (onInstall == true && onUninstall == true) ? 3 : (onUninstall == true) ? 2 : (onInstall == true) ? 1 : 0; + id = this.Core.CreateIdentifier("rmf", directory ?? property ?? parentDirectory, LowercaseOrNull(shortName), LowercaseOrNull(name), on.ToString()); + } + + this.Core.ParseForExtensionElements(node); + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new RemoveFileSymbol(sourceLineNumbers, id) + { + ComponentRef = componentId, + FileName = name, + ShortFileName = shortName, + DirPropertyRef = directory ?? property ?? parentDirectory, + OnInstall = onInstall, + OnUninstall = onUninstall, + }); + } + } + + /// + /// Parses a RemoveFolder element. + /// + /// Element to parse. + /// Identifier of parent component. + /// Identifier of parent component's directory. + private void ParseRemoveFolderElement(XElement node, string componentId, string parentDirectory) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + Identifier id = null; + string directory = null; + bool? onInstall = null; + bool? onUninstall = null; + string property = 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 "Directory": + directory = this.Core.CreateDirectoryReferenceFromInlineSyntax(sourceLineNumbers, attrib, parentDirectory); + break; + case "On": + var onValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + switch (onValue) + { + case "install": + onInstall = true; + break; + case "uninstall": + onUninstall = true; + break; + case "both": + onInstall = true; + onUninstall = true; + break; + } + break; + case "Property": + property = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (!onInstall.HasValue && !onUninstall.HasValue) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "On")); + } + + if (null != directory && null != property) + { + this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Property", "Directory", directory)); + } + + if (null == id) + { + var on = (onInstall == true && onUninstall == true) ? 3 : (onUninstall == true) ? 2 : (onInstall == true) ? 1 : 0; + id = this.Core.CreateIdentifier("rmf", directory ?? property ?? parentDirectory, on.ToString()); + } + + this.Core.ParseForExtensionElements(node); + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new RemoveFileSymbol(sourceLineNumbers, id) + { + ComponentRef = componentId, + DirPropertyRef = directory ?? property ?? parentDirectory, + OnInstall = onInstall, + OnUninstall = onUninstall + }); + } + } + + /// + /// Parses a reserve cost element. + /// + /// Element to parse. + /// Identifier of parent component. + /// Optional and default identifier of referenced directory. + private void ParseReserveCostElement(XElement node, string componentId, string directoryId) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + Identifier id = null; + var runFromSource = CompilerConstants.IntegerNotSet; + var runLocal = CompilerConstants.IntegerNotSet; + + 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 "Directory": + directoryId = this.Core.CreateDirectoryReferenceFromInlineSyntax(sourceLineNumbers, attrib, directoryId); + break; + case "RunFromSource": + runFromSource = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int32.MaxValue); + break; + case "RunLocal": + runLocal = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int32.MaxValue); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == id) + { + id = this.Core.CreateIdentifier("rc", componentId, directoryId); + } + + if (CompilerConstants.IntegerNotSet == runFromSource) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "RunFromSource")); + } + + if (CompilerConstants.IntegerNotSet == runLocal) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "RunLocal")); + } + + this.Core.ParseForExtensionElements(node); + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new ReserveCostSymbol(sourceLineNumbers, id) + { + ComponentRef = componentId, + ReserveFolder = directoryId, + ReserveLocal = runLocal, + ReserveSource = runFromSource + }); + } + } + + /// + /// Parses a sequence element. + /// + /// Element to parse. + /// Name of sequence table. + private void ParseSequenceElement(XElement node, SequenceTable sequenceTable) + { + // Parse each action in the sequence. + foreach (var child in node.Elements()) + { + var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child); + var actionName = child.Name.LocalName; + string afterAction = null; + string beforeAction = null; + string condition = null; + var customAction = "Custom" == actionName; + var overridable = false; + var exitSequence = CompilerConstants.IntegerNotSet; + var sequence = CompilerConstants.IntegerNotSet; + var showDialog = "Show" == actionName; + var specialAction = "InstallExecute" == actionName || "InstallExecuteAgain" == actionName || "RemoveExistingProducts" == actionName || "DisableRollback" == actionName || "ScheduleReboot" == actionName || "ForceReboot" == actionName || "ResolveSource" == actionName; + var specialStandardAction = "AppSearch" == actionName || "CCPSearch" == actionName || "RMCCPSearch" == actionName || "LaunchConditions" == actionName || "FindRelatedProducts" == actionName; + var suppress = false; + + foreach (var attrib in child.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Action": + if (customAction) + { + actionName = this.Core.GetAttributeIdentifierValue(childSourceLineNumbers, attrib); + this.Core.CreateSimpleReference(childSourceLineNumbers, SymbolDefinitions.CustomAction, actionName); + } + else + { + this.Core.UnexpectedAttribute(child, attrib); + } + break; + case "After": + if (customAction || showDialog || specialAction || specialStandardAction) + { + afterAction = this.Core.GetAttributeIdentifierValue(childSourceLineNumbers, attrib); + this.Core.CreateSimpleReference(childSourceLineNumbers, SymbolDefinitions.WixAction, sequenceTable.ToString(), afterAction); + } + else + { + this.Core.UnexpectedAttribute(child, attrib); + } + break; + case "Before": + if (customAction || showDialog || specialAction || specialStandardAction) + { + beforeAction = this.Core.GetAttributeIdentifierValue(childSourceLineNumbers, attrib); + this.Core.CreateSimpleReference(childSourceLineNumbers, SymbolDefinitions.WixAction, sequenceTable.ToString(), beforeAction); + } + else + { + this.Core.UnexpectedAttribute(child, attrib); + } + break; + case "Condition": + condition = this.Core.GetAttributeValue(childSourceLineNumbers, attrib); + break; + case "Dialog": + if (showDialog) + { + actionName = this.Core.GetAttributeIdentifierValue(childSourceLineNumbers, attrib); + this.Core.CreateSimpleReference(childSourceLineNumbers, SymbolDefinitions.Dialog, actionName); + } + else + { + this.Core.UnexpectedAttribute(child, attrib); + } + break; + case "OnExit": + if (customAction || showDialog || specialAction) + { + var exitValue = this.Core.GetAttributeValue(childSourceLineNumbers, attrib); + switch (exitValue) + { + case "success": + exitSequence = -1; + break; + case "cancel": + exitSequence = -2; + break; + case "error": + exitSequence = -3; + break; + case "suspend": + exitSequence = -4; + break; + } + } + else + { + this.Core.UnexpectedAttribute(child, attrib); + } + break; + case "Overridable": + overridable = YesNoType.Yes == this.Core.GetAttributeYesNoValue(childSourceLineNumbers, attrib); + break; + case "Sequence": + sequence = this.Core.GetAttributeIntegerValue(childSourceLineNumbers, attrib, 1, Int16.MaxValue); + break; + case "Suppress": + suppress = YesNoType.Yes == this.Core.GetAttributeYesNoValue(childSourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + this.Core.VerifyNoInnerText(childSourceLineNumbers, node); + + if (customAction && "Custom" == actionName) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(childSourceLineNumbers, child.Name.LocalName, "Action")); + } + else if (showDialog && "Show" == actionName) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(childSourceLineNumbers, child.Name.LocalName, "Dialog")); + } + + if (CompilerConstants.IntegerNotSet != sequence) + { + if (CompilerConstants.IntegerNotSet != exitSequence) + { + this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(childSourceLineNumbers, child.Name.LocalName, "Sequence", "OnExit")); + } + else if (null != beforeAction || null != afterAction) + { + this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(childSourceLineNumbers, child.Name.LocalName, "Sequence", "Before", "After")); + } + } + else // sequence not specified use OnExit (which may also be not set). + { + sequence = exitSequence; + } + + if (null != beforeAction && null != afterAction) + { + this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(childSourceLineNumbers, child.Name.LocalName, "After", "Before")); + } + else if ((customAction || showDialog || specialAction) && !suppress && CompilerConstants.IntegerNotSet == sequence && null == beforeAction && null == afterAction) + { + this.Core.Write(ErrorMessages.NeedSequenceBeforeOrAfter(childSourceLineNumbers, child.Name.LocalName)); + } + + // action that is scheduled to occur before/after itself + if (beforeAction == actionName) + { + this.Core.Write(ErrorMessages.ActionScheduledRelativeToItself(childSourceLineNumbers, child.Name.LocalName, "Before", beforeAction)); + } + else if (afterAction == actionName) + { + this.Core.Write(ErrorMessages.ActionScheduledRelativeToItself(childSourceLineNumbers, child.Name.LocalName, "After", afterAction)); + } + + // normal standard actions cannot be set overridable by the user (since they are overridable by default) + if (overridable && WindowsInstallerStandard.IsStandardAction(actionName) && !specialAction) + { + this.Core.Write(ErrorMessages.UnexpectedAttribute(childSourceLineNumbers, child.Name.LocalName, "Overridable")); + } + + // suppress cannot be specified at the same time as Before, After, or Sequence + if (suppress && (null != afterAction || null != beforeAction || CompilerConstants.IntegerNotSet != sequence || overridable)) + { + this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttributes(childSourceLineNumbers, child.Name.LocalName, "Suppress", "Before", "After", "Sequence", "Overridable")); + } + + this.Core.ParseForExtensionElements(child); + + // add the row and any references needed + if (!this.Core.EncounteredError) + { + if (suppress) + { + this.Core.AddSymbol(new WixSuppressActionSymbol(childSourceLineNumbers, new Identifier(AccessModifier.Public, sequenceTable, actionName)) + { + SequenceTable = sequenceTable, + Action = actionName + }); + } + else + { + var symbol = this.Core.AddSymbol(new WixActionSymbol(childSourceLineNumbers, new Identifier(AccessModifier.Public, sequenceTable, actionName)) + { + SequenceTable = sequenceTable, + Action = actionName, + Condition = condition, + Before = beforeAction, + After = afterAction, + Overridable = overridable, + }); + + if (CompilerConstants.IntegerNotSet != sequence) + { + symbol.Sequence = sequence; + } + } + } + } + } + + + /// + /// Parses a service config element. + /// + /// Element to parse. + /// Identifier of parent component. + /// Optional element containing parent's service name. + private void ParseServiceConfigElement(XElement node, string componentId, string serviceName) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + Identifier id = null; + string delayedAutoStart = null; + string failureActionsWhen = null; + var name = serviceName; + var install = false; + var reinstall = false; + var uninstall = false; + string preShutdownDelay = null; + string requiredPrivileges = null; + string sid = null; + + this.Core.Write(WarningMessages.ServiceConfigFamilyNotSupported(sourceLineNumbers, node.Name.LocalName)); + + 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 "DelayedAutoStart": + delayedAutoStart = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + switch (delayedAutoStart) + { + case "no": + delayedAutoStart = "0"; + break; + case "yes": + delayedAutoStart = "1"; + break; + default: + // allow everything else to pass through that are hopefully "formatted" Properties. + break; + } + break; + case "FailureActionsWhen": + failureActionsWhen = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + switch (failureActionsWhen) + { + case "failedToStop": + failureActionsWhen = "0"; + break; + case "failedToStopOrReturnedError": + failureActionsWhen = "1"; + break; + default: + // allow everything else to pass through that are hopefully "formatted" Properties. + break; + } + break; + case "OnInstall": + install = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + //if (YesNoType.Yes == install) + //{ + // events |= MsiInterop.MsidbServiceConfigEventInstall; + //} + break; + case "OnReinstall": + reinstall = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + //if (YesNoType.Yes == reinstall) + //{ + // events |= MsiInterop.MsidbServiceConfigEventReinstall; + //} + break; + case "OnUninstall": + uninstall = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + //if (YesNoType.Yes == uninstall) + //{ + // events |= MsiInterop.MsidbServiceConfigEventUninstall; + //} + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + case "PreShutdownDelay": + preShutdownDelay = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty); + break; + case "ServiceName": + if (!String.IsNullOrEmpty(serviceName)) + { + this.Core.Write(ErrorMessages.IllegalAttributeWhenNested(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "ServiceInstall")); + } + + name = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "ServiceSid": + sid = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + switch (sid) + { + case "none": + sid = "0"; + break; + case "restricted": + sid = "3"; + break; + case "unrestricted": + sid = "1"; + break; + default: + // allow everything else to pass through that are hopefully "formatted" Properties. + break; + } + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + // Get the ServiceConfig required privilegs. + foreach (var child in node.Elements()) + { + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + switch (child.Name.LocalName) + { + case "RequiredPrivilege": + requiredPrivileges = this.ParseRequiredPrivilege(child, requiredPrivileges); + break; + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + else + { + this.Core.ParseExtensionElement(node, child); + } + } + + if (String.IsNullOrEmpty(name)) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "ServiceName")); + } + else if (null == id) + { + id = this.Core.CreateIdentifierFromFilename(name); + } + + if (!install && !reinstall && !uninstall) + { + this.Core.Write(ErrorMessages.ExpectedAttributes(sourceLineNumbers, node.Name.LocalName, "OnInstall", "OnReinstall", "OnUninstall")); + } + + if (String.IsNullOrEmpty(delayedAutoStart) && String.IsNullOrEmpty(failureActionsWhen) && String.IsNullOrEmpty(preShutdownDelay) && String.IsNullOrEmpty(requiredPrivileges) && String.IsNullOrEmpty(sid)) + { + this.Core.Write(ErrorMessages.ExpectedAttributes(sourceLineNumbers, node.Name.LocalName, "DelayedAutoStart", "FailureActionsWhen", "PreShutdownDelay", "ServiceSid", "RequiredPrivilege")); + } + + if (!this.Core.EncounteredError) + { + if (!String.IsNullOrEmpty(delayedAutoStart)) + { + this.Core.AddSymbol(new MsiServiceConfigSymbol(sourceLineNumbers, new Identifier(id.Access, String.Concat(id.Id, ".DS"))) + { + Name = name, + OnInstall = install, + OnReinstall = reinstall, + OnUninstall = uninstall, + ConfigType = MsiServiceConfigType.DelayedAutoStart, + Argument = delayedAutoStart, + ComponentRef = componentId, + }); + } + + if (!String.IsNullOrEmpty(failureActionsWhen)) + { + this.Core.AddSymbol(new MsiServiceConfigSymbol(sourceLineNumbers, new Identifier(id.Access, String.Concat(id.Id, ".FA"))) + { + Name = name, + OnInstall = install, + OnReinstall = reinstall, + OnUninstall = uninstall, + ConfigType = MsiServiceConfigType.FailureActionsFlag, + Argument = failureActionsWhen, + ComponentRef = componentId, + }); + } + + if (!String.IsNullOrEmpty(sid)) + { + this.Core.AddSymbol(new MsiServiceConfigSymbol(sourceLineNumbers, new Identifier(id.Access, String.Concat(id.Id, ".SS"))) + { + Name = name, + OnInstall = install, + OnReinstall = reinstall, + OnUninstall = uninstall, + ConfigType = MsiServiceConfigType.ServiceSidInfo, + Argument = sid, + ComponentRef = componentId, + }); + } + + if (!String.IsNullOrEmpty(requiredPrivileges)) + { + this.Core.AddSymbol(new MsiServiceConfigSymbol(sourceLineNumbers, new Identifier(id.Access, String.Concat(id.Id, ".RP"))) + { + Name = name, + OnInstall = install, + OnReinstall = reinstall, + OnUninstall = uninstall, + ConfigType = MsiServiceConfigType.RequiredPrivilegesInfo, + Argument = requiredPrivileges, + ComponentRef = componentId, + }); + } + + if (!String.IsNullOrEmpty(preShutdownDelay)) + { + this.Core.AddSymbol(new MsiServiceConfigSymbol(sourceLineNumbers, new Identifier(id.Access, String.Concat(id.Id, ".PD"))) + { + Name = name, + OnInstall = install, + OnReinstall = reinstall, + OnUninstall = uninstall, + ConfigType = MsiServiceConfigType.PreshutdownInfo, + Argument = preShutdownDelay, + ComponentRef = componentId, + }); + } + } + } + + private string ParseRequiredPrivilege(XElement node, string requiredPrivileges) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string privilege = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Name": + privilege = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + switch (privilege) + { + case "assignPrimaryToken": + privilege = "SeAssignPrimaryTokenPrivilege"; + break; + case "audit": + privilege = "SeAuditPrivilege"; + break; + case "backup": + privilege = "SeBackupPrivilege"; + break; + case "changeNotify": + privilege = "SeChangeNotifyPrivilege"; + break; + case "createGlobal": + privilege = "SeCreateGlobalPrivilege"; + break; + case "createPagefile": + privilege = "SeCreatePagefilePrivilege"; + break; + case "createPermanent": + privilege = "SeCreatePermanentPrivilege"; + break; + case "createSymbolicLink": + privilege = "SeCreateSymbolicLinkPrivilege"; + break; + case "createToken": + privilege = "SeCreateTokenPrivilege"; + break; + case "debug": + privilege = "SeDebugPrivilege"; + break; + case "enableDelegation": + privilege = "SeEnableDelegationPrivilege"; + break; + case "impersonate": + privilege = "SeImpersonatePrivilege"; + break; + case "increaseBasePriority": + privilege = "SeIncreaseBasePriorityPrivilege"; + break; + case "increaseQuota": + privilege = "SeIncreaseQuotaPrivilege"; + break; + case "increaseWorkingSet": + privilege = "SeIncreaseWorkingSetPrivilege"; + break; + case "loadDriver": + privilege = "SeLoadDriverPrivilege"; + break; + case "lockMemory": + privilege = "SeLockMemoryPrivilege"; + break; + case "machineAccount": + privilege = "SeMachineAccountPrivilege"; + break; + case "manageVolume": + privilege = "SeManageVolumePrivilege"; + break; + case "profileSingleProcess": + privilege = "SeProfileSingleProcessPrivilege"; + break; + case "relabel": + privilege = "SeRelabelPrivilege"; + break; + case "remoteShutdown": + privilege = "SeRemoteShutdownPrivilege"; + break; + case "restore": + privilege = "SeRestorePrivilege"; + break; + case "security": + privilege = "SeSecurityPrivilege"; + break; + case "shutdown": + privilege = "SeShutdownPrivilege"; + break; + case "syncAgent": + privilege = "SeSyncAgentPrivilege"; + break; + case "systemEnvironment": + privilege = "SeSystemEnvironmentPrivilege"; + break; + case "systemProfile": + privilege = "SeSystemProfilePrivilege"; + break; + case "systemTime": + case "modifySystemTime": + privilege = "SeSystemtimePrivilege"; + break; + case "takeOwnership": + privilege = "SeTakeOwnershipPrivilege"; + break; + case "tcb": + case "trustedComputerBase": + privilege = "SeTcbPrivilege"; + break; + case "timeZone": + case "modifyTimeZone": + privilege = "SeTimeZonePrivilege"; + break; + case "trustedCredManAccess": + case "trustedCredentialManagerAccess": + privilege = "SeTrustedCredManAccessPrivilege"; + break; + case "undock": + privilege = "SeUndockPrivilege"; + break; + case "unsolicitedInput": + privilege = "SeUnsolicitedInputPrivilege"; + break; + default: + // allow everything else to pass through that are hopefully "formatted" Properties. + break; + } + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + } + + this.Core.VerifyNoInnerText(sourceLineNumbers, node); + + if (privilege == null) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name")); + } + + this.Core.ParseForExtensionElements(node); + + return (requiredPrivileges == null) ? privilege : String.Concat(requiredPrivileges, "[~]", privilege); + } + + /// + /// Parses a service config failure actions element. + /// + /// Element to parse. + /// Identifier of parent component. + /// Optional element containing parent's service name. + private void ParseServiceConfigFailureActionsElement(XElement node, string componentId, string serviceName) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + Identifier id = null; + var name = serviceName; + var install = false; + var reinstall = false; + var uninstall = false; + int? resetPeriod = null; + string rebootMessage = null; + string command = null; + string actions = null; + string actionsDelays = null; + + this.Core.Write(WarningMessages.ServiceConfigFamilyNotSupported(sourceLineNumbers, node.Name.LocalName)); + + 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 "Command": + command = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty); + break; + case "OnInstall": + install = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "OnReinstall": + reinstall = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "OnUninstall": + uninstall = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "RebootMessage": + rebootMessage = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty); + break; + case "ResetPeriod": + resetPeriod = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int32.MaxValue); + break; + case "ServiceName": + if (!String.IsNullOrEmpty(serviceName)) + { + this.Core.Write(ErrorMessages.IllegalAttributeWhenNested(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "ServiceInstall")); + } + + name = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + // Get the ServiceConfigFailureActions actions. + foreach (var child in node.Elements()) + { + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + switch (child.Name.LocalName) + { + case "Failure": + string action = null; + string delay = null; + var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child); + + foreach (var childAttrib in child.Attributes()) + { + if (String.IsNullOrEmpty(childAttrib.Name.NamespaceName) || CompilerCore.WixNamespace == childAttrib.Name.Namespace) + { + switch (childAttrib.Name.LocalName) + { + case "Action": + action = this.Core.GetAttributeValue(childSourceLineNumbers, childAttrib); + switch (action) + { + case "none": + action = "0"; + break; + case "restartComputer": + action = "2"; + break; + case "restartService": + action = "1"; + break; + case "runCommand": + action = "3"; + break; + default: + // allow everything else to pass through that are hopefully "formatted" Properties. + break; + } + break; + case "Delay": + delay = this.Core.GetAttributeValue(childSourceLineNumbers, childAttrib); + break; + default: + this.Core.UnexpectedAttribute(child, childAttrib); + break; + } + } + } + + if (String.IsNullOrEmpty(action)) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, child.Name.LocalName, "Action")); + } + + if (String.IsNullOrEmpty(delay)) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, child.Name.LocalName, "Delay")); + } + + if (!String.IsNullOrEmpty(actions)) + { + actions = String.Concat(actions, "[~]"); + } + actions = String.Concat(actions, action); + + if (!String.IsNullOrEmpty(actionsDelays)) + { + actionsDelays = String.Concat(actionsDelays, "[~]"); + } + actionsDelays = String.Concat(actionsDelays, delay); + break; + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + else + { + this.Core.ParseExtensionElement(node, child); + } + } + + if (String.IsNullOrEmpty(name)) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "ServiceName")); + } + else if (null == id) + { + id = this.Core.CreateIdentifierFromFilename(name); + } + + if (!install && !reinstall && !uninstall) + { + this.Core.Write(ErrorMessages.ExpectedAttributes(sourceLineNumbers, node.Name.LocalName, "OnInstall", "OnReinstall", "OnUninstall")); + } + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new MsiServiceConfigFailureActionsSymbol(sourceLineNumbers, id) + { + Name = name, + OnInstall = install, + OnReinstall = reinstall, + OnUninstall = uninstall, + ResetPeriod = resetPeriod, + RebootMessage = rebootMessage, + Command = command, + Actions = actions, + DelayActions = actionsDelays, + ComponentRef = componentId, + }); + } + } + + /// + /// Parses a service control element. + /// + /// Element to parse. + /// Identifier of parent component. + private void ParseServiceControlElement(XElement node, string componentId) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string arguments = null; + Identifier id = null; + string name = null; + var installRemove = false; + var uninstallRemove = false; + var installStart = false; + var uninstallStart = false; + var installStop = false; + var uninstallStop = false; + bool? wait = 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 "Name": + name = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Remove": + var removeValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + switch (removeValue) + { + case "install": + installRemove = true; + break; + case "uninstall": + uninstallRemove = true; + break; + case "both": + installRemove = true; + uninstallRemove = true; + break; + case "": + break; + } + break; + case "Start": + var startValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + switch (startValue) + { + case "install": + installStart = true; + break; + case "uninstall": + uninstallStart = true; + break; + case "both": + installStart = true; + uninstallStart = true; + break; + case "": + break; + } + break; + case "Stop": + var stopValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + switch (stopValue) + { + case "install": + installStop = true; + break; + case "uninstall": + uninstallStop = true; + break; + case "both": + installStop = true; + uninstallStop = true; + break; + case "": + break; + } + break; + case "Wait": + wait = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == id) + { + id = this.Core.CreateIdentifierFromFilename(name); + } + + if (null == name) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name")); + } + + // get the ServiceControl arguments + foreach (var child in node.Elements()) + { + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + switch (child.Name.LocalName) + { + case "ServiceArgument": + arguments = this.ParseServiceArgument(child, arguments); + break; + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + else + { + this.Core.ParseExtensionElement(node, child); + } + } + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new ServiceControlSymbol(sourceLineNumbers, id) + { + Name = name, + InstallRemove = installRemove, + UninstallRemove = uninstallRemove, + InstallStart = installStart, + UninstallStart = uninstallStart, + InstallStop = installStop, + UninstallStop = uninstallStop, + Arguments = arguments, + Wait = wait, + ComponentRef = componentId + }); + } + } + + private string ParseServiceArgument(XElement node, string arguments) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string argument = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Value": + argument = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + } + + this.Core.VerifyNoInnerText(sourceLineNumbers, node); + + if (argument == null) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Value")); + } + + this.Core.ParseForExtensionElements(node); + + return (arguments == null) ? argument : String.Concat(arguments, "[~]", argument); + } + + /// + /// Parses a service dependency element. + /// + /// Element to parse. + /// Parsed sevice dependency name. + private string ParseServiceDependencyElement(XElement node) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string dependency = null; + var group = false; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + dependency = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Group": + group = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == dependency) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); + } + + this.Core.ParseForExtensionElements(node); + + return group ? String.Concat("+", dependency) : dependency; + } + + /// + /// Parses a service install element. + /// + /// Element to parse. + /// Identifier of parent component. + /// + private void ParseServiceInstallElement(XElement node, string componentId, bool win64Component) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + Identifier id = null; + string account = null; + string arguments = null; + string dependencies = null; + string description = null; + string displayName = null; + var eraseDescription = false; + string loadOrderGroup = null; + string name = null; + string password = null; + + var serviceType = ServiceType.OwnProcess; + var startType = ServiceStartType.Demand; + var errorControl = ServiceErrorControl.Normal; + var interactive = false; + var vital = false; + + 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 "Account": + account = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Arguments": + arguments = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Description": + description = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "DisplayName": + displayName = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "EraseDescription": + eraseDescription = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "ErrorControl": + var errorControlValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + switch (errorControlValue) + { + case "ignore": + errorControl = ServiceErrorControl.Ignore; + break; + case "normal": + errorControl = ServiceErrorControl.Normal; + break; + case "critical": + errorControl = ServiceErrorControl.Critical; + break; + case "": // error case handled by GetAttributeValue() + break; + default: + this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, errorControlValue, "ignore", "normal", "critical")); + break; + } + break; + case "Interactive": + interactive = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "LoadOrderGroup": + loadOrderGroup = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Name": + name = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Password": + password = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Start": + var startValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + switch (startValue) + { + case "auto": + startType = ServiceStartType.Auto; + break; + case "demand": + startType = ServiceStartType.Demand; + break; + case "disabled": + startType = ServiceStartType.Disabled; + break; + case "boot": + case "system": + this.Core.Write(ErrorMessages.ValueNotSupported(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, startValue)); + break; + case "": + break; + default: + this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, startValue, "auto", "demand", "disabled")); + break; + } + break; + case "Type": + var typeValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + switch (typeValue) + { + case "ownProcess": + serviceType = ServiceType.OwnProcess; + break; + case "shareProcess": + serviceType = ServiceType.ShareProcess; + break; + case "kernelDriver": + case "systemDriver": + this.Core.Write(ErrorMessages.ValueNotSupported(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, typeValue)); + break; + case "": + break; + default: + this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, node.Name.LocalName, typeValue, "ownProcess", "shareProcess")); + break; + } + break; + case "Vital": + vital = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (String.IsNullOrEmpty(name)) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name")); + } + else if (null == id) + { + id = this.Core.CreateIdentifierFromFilename(name); + } + + if (0 == startType) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Start")); + } + + if (eraseDescription) + { + description = "[~]"; + } + + // get the ServiceInstall dependencies and config + foreach (var child in node.Elements()) + { + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + switch (child.Name.LocalName) + { + case "PermissionEx": + this.ParsePermissionExElement(child, id.Id, "ServiceInstall"); + break; + case "ServiceConfig": + this.ParseServiceConfigElement(child, componentId, name); + break; + case "ServiceConfigFailureActions": + this.ParseServiceConfigFailureActionsElement(child, componentId, name); + break; + case "ServiceDependency": + dependencies = String.Concat(dependencies, this.ParseServiceDependencyElement(child), "[~]"); + break; + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + else + { + var context = new Dictionary() { { "ServiceInstallId", id?.Id }, { "ServiceInstallName", name }, { "ServiceInstallComponentId", componentId }, { "Win64", win64Component.ToString() } }; + this.Core.ParseExtensionElement(node, child, context); + } + } + + if (null != dependencies) + { + dependencies = String.Concat(dependencies, "[~]"); + } + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new ServiceInstallSymbol(sourceLineNumbers, id) + { + Name = name, + DisplayName = displayName, + ServiceType = serviceType, + StartType = startType, + ErrorControl = errorControl, + LoadOrderGroup = loadOrderGroup, + Dependencies = dependencies, + StartName = account, + Password = password, + Arguments = arguments, + ComponentRef = componentId, + Description = description, + Interactive = interactive, + Vital = vital + }); + } + } + + /// + /// Parses a SetDirectory element. + /// + /// Element to parse. + private void ParseSetDirectoryElement(XElement node) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string actionName = null; + string id = null; + string condition = null; + var executionType = CustomActionExecutionType.Immediate; + var sequences = new[] { SequenceTable.InstallUISequence, SequenceTable.InstallExecuteSequence }; // default to "both" + string value = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Action": + actionName = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + break; + case "Condition": + condition = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Id": + id = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Directory, id); + break; + case "Sequence": + var sequenceValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + switch (sequenceValue) + { + case "execute": + sequences = new[] { SequenceTable.InstallExecuteSequence }; + break; + case "first": + executionType = CustomActionExecutionType.FirstSequence; + break; + case "ui": + sequences = new[] { SequenceTable.InstallUISequence }; + break; + case "both": + break; + case "": + break; + default: + this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, sequenceValue, "execute", "ui", "both")); + break; + } + break; + case "Value": + value = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + this.Core.VerifyNoInnerText(sourceLineNumbers, node); + + if (null == id) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); + } + else if (String.IsNullOrEmpty(actionName)) + { + actionName = String.Concat("Set", id); + } + + if (null == value) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Value")); + } + + this.Core.ParseForExtensionElements(node); + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new CustomActionSymbol(sourceLineNumbers, new Identifier(AccessModifier.Public, actionName)) + { + ExecutionType = executionType, + SourceType = CustomActionSourceType.Directory, + TargetType = CustomActionTargetType.TextData, + Source = id, + Target = value + }); + + foreach (var sequence in sequences) + { + this.Core.ScheduleActionSymbol(sourceLineNumbers, AccessModifier.Public, sequence, actionName, condition, afterAction: "CostInitialize"); + } + } + } + + /// + /// Parses a SetProperty element. + /// + /// Element to parse. + private void ParseSetPropertyElement(XElement node) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string actionName = null; + string id = null; + string condition = null; + string afterAction = null; + string beforeAction = null; + var executionType = CustomActionExecutionType.Immediate; + var sequences = new[] { SequenceTable.InstallUISequence, SequenceTable.InstallExecuteSequence }; // default to "both" + string value = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Action": + actionName = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + break; + case "Id": + id = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + break; + case "Condition": + condition = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "After": + afterAction = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + break; + case "Before": + beforeAction = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + break; + case "Sequence": + var sequenceValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + switch (sequenceValue) + { + case "execute": + sequences = new[] { SequenceTable.InstallExecuteSequence }; + break; + case "first": + executionType = CustomActionExecutionType.FirstSequence; + break; + case "ui": + sequences = new[] { SequenceTable.InstallUISequence }; + break; + case "both": + break; + case "": + break; + default: + this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, sequenceValue, "execute", "ui", "both")); + break; + } + break; + case "Value": + value = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + this.Core.VerifyNoInnerText(sourceLineNumbers, node); + + if (null == id) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); + } + else if (String.IsNullOrEmpty(actionName)) + { + actionName = String.Concat("Set", id); + } + + if (null == value) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Value")); + } + + if (null != beforeAction && null != afterAction) + { + this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "After", "Before")); + } + else if (null == beforeAction && null == afterAction) + { + this.Core.Write(ErrorMessages.ExpectedAttributesWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "After", "Before", "Id")); + } + + this.Core.ParseForExtensionElements(node); + + // add the row and any references needed + if (!this.Core.EncounteredError) + { + // action that is scheduled to occur before/after itself + if (beforeAction == actionName) + { + this.Core.Write(ErrorMessages.ActionScheduledRelativeToItself(sourceLineNumbers, node.Name.LocalName, "Before", beforeAction)); + } + else if (afterAction == actionName) + { + this.Core.Write(ErrorMessages.ActionScheduledRelativeToItself(sourceLineNumbers, node.Name.LocalName, "After", afterAction)); + } + + this.Core.AddSymbol(new CustomActionSymbol(sourceLineNumbers, new Identifier(AccessModifier.Public, actionName)) + { + ExecutionType = executionType, + SourceType = CustomActionSourceType.Property, + TargetType = CustomActionTargetType.TextData, + Source = id, + Target = value, + }); + + foreach (var sequence in sequences) + { + this.Core.ScheduleActionSymbol(sourceLineNumbers, AccessModifier.Public, sequence, actionName, condition, beforeAction, afterAction); + } + } + } + + /// + /// Parses a SFP catalog element. + /// + /// Element to parse. + /// Parent SFPCatalog. + private void ParseSFPFileElement(XElement node, string parentSFPCatalog) + { + 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); + 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 FileSFPCatalogSymbol(sourceLineNumbers) + { + FileRef = id, + SFPCatalogRef = parentSFPCatalog + }); + } + } + + /// + /// Parses a SFP catalog element. + /// + /// Element to parse. + /// Parent SFPCatalog. + private void ParseSFPCatalogElement(XElement node, ref string parentSFPCatalog) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string parentName = null; + string dependency = null; + string name = null; + string sourceFile = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Dependency": + dependency = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Name": + name = this.Core.GetAttributeShortFilename(sourceLineNumbers, attrib, false); + parentSFPCatalog = name; + break; + case "SourceFile": + sourceFile = 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 == sourceFile) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "SourceFile")); + } + + foreach (var child in node.Elements()) + { + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + switch (child.Name.LocalName) + { + case "SFPCatalog": + this.ParseSFPCatalogElement(child, ref parentName); + if (null != dependency && parentName == dependency) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Dependency")); + } + dependency = parentName; + break; + case "SFPFile": + this.ParseSFPFileElement(child, name); + break; + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + else + { + this.Core.ParseExtensionElement(node, child); + } + } + + if (null == dependency) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Dependency")); + } + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new SFPCatalogSymbol(sourceLineNumbers) + { + SFPCatalog = name, + Catalog = sourceFile, + Dependency = dependency + }); + } + } + + /// + /// Parses a shortcut element. + /// + /// Element to parse. + /// Identifer for parent component. + /// Local name of parent element. + /// Default identifier of parent (which is usually the target). + /// Flag to indicate whether the parent element is the keypath of a component or not (will only be true for file parent elements). + private void ParseShortcutElement(XElement node, string componentId, string parentElementLocalName, string defaultTarget, YesNoType parentKeyPath) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + Identifier id = null; + var advertise = false; + string arguments = null; + string description = null; + string descriptionResourceDll = null; + int? descriptionResourceId = null; + string directory = null; + string displayResourceDll = null; + int? displayResourceId = null; + int? hotkey = null; + string icon = null; + int? iconIndex = null; + string name = null; + string shortName = null; + ShortcutShowType? show = null; + string target = null; + string workingDirectory = 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 "Advertise": + advertise = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "Arguments": + arguments = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Description": + description = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "DescriptionResourceDll": + descriptionResourceDll = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "DescriptionResourceId": + descriptionResourceId = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int16.MaxValue); + break; + case "Directory": + directory = this.Core.CreateDirectoryReferenceFromInlineSyntax(sourceLineNumbers, attrib, null); + break; + case "DisplayResourceDll": + displayResourceDll = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "DisplayResourceId": + displayResourceId = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int16.MaxValue); + break; + case "Hotkey": + hotkey = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int16.MaxValue); + break; + case "Icon": + icon = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Icon, icon); + break; + case "IconIndex": + iconIndex = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, Int16.MinValue + 1, Int16.MaxValue); + break; + case "Name": + name = this.Core.GetAttributeLongFilename(sourceLineNumbers, attrib, false); + break; + case "ShortName": + shortName = this.Core.GetAttributeShortFilename(sourceLineNumbers, attrib, false); + break; + case "Show": + var showValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + switch (showValue) + { + case "normal": + show = ShortcutShowType.Normal; + break; + case "maximized": + show = ShortcutShowType.Maximized; + break; + case "minimized": + show = ShortcutShowType.Minimized; + break; + case "": + break; + default: + this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, "Show", showValue, "normal", "maximized", "minimized")); + break; + } + break; + case "Target": + target = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "WorkingDirectory": + workingDirectory = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (advertise && null != target) + { + this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Target", "Advertise", "yes")); + } + + if (null == directory) + { + if ("Component" == parentElementLocalName) + { + directory = defaultTarget; + } + else + { + this.Core.Write(ErrorMessages.ExpectedAttributeWhenElementNotUnderElement(sourceLineNumbers, node.Name.LocalName, "Directory", "Component")); + } + } + + if (null != descriptionResourceDll) + { + if (!descriptionResourceId.HasValue) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "DescriptionResourceDll", "DescriptionResourceId")); + } + } + else + { + if (descriptionResourceId.HasValue) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "DescriptionResourceId", "DescriptionResourceDll")); + } + } + + if (null != displayResourceDll) + { + if (!displayResourceId.HasValue) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "DisplayResourceDll", "DisplayResourceId")); + } + } + else + { + if (displayResourceId.HasValue) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "DisplayResourceId", "DisplayResourceDll")); + } + } + + if (null == name) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name")); + } + + if ("Component" != parentElementLocalName && null != target) + { + this.Core.Write(ErrorMessages.IllegalAttributeWhenNested(sourceLineNumbers, node.Name.LocalName, "Target", parentElementLocalName)); + } + + if (null == id) + { + id = this.Core.CreateIdentifier("sct", directory, LowercaseOrNull(name)); + } + + foreach (var child in node.Elements()) + { + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + switch (child.Name.LocalName) + { + case "Icon": + icon = this.ParseIconElement(child); + break; + case "ShortcutProperty": + this.ParseShortcutPropertyElement(child, id.Id); + break; + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + else + { + this.Core.ParseExtensionElement(node, child); + } + } + + if (!this.Core.EncounteredError) + { + if (advertise) + { + if (YesNoType.Yes != parentKeyPath && "Component" != parentElementLocalName) + { + this.Core.Write(WarningMessages.UnclearShortcut(sourceLineNumbers, id.Id, componentId, defaultTarget)); + } + + target = Guid.Empty.ToString("B"); + } + else if (null != target) + { + } + else if ("Component" == parentElementLocalName || "CreateFolder" == parentElementLocalName) + { + target = "[" + defaultTarget + "]"; + } + else if ("File" == parentElementLocalName) + { + target = "[#" + defaultTarget + "]"; + } + + this.Core.AddSymbol(new ShortcutSymbol(sourceLineNumbers, id) + { + DirectoryRef = directory, + Name = name, + ShortName = shortName, + ComponentRef = componentId, + Target = target, + Arguments = arguments, + Description = description, + Hotkey = hotkey, + IconRef = icon, + IconIndex = iconIndex, + Show = show, + WorkingDirectory = workingDirectory, + DisplayResourceDll = displayResourceDll, + DisplayResourceId = displayResourceId, + DescriptionResourceDll = descriptionResourceDll, + DescriptionResourceId = descriptionResourceId, + }); + } + } + + /// + /// Parses a shortcut property element. + /// + /// Element to parse. + /// + private void ParseShortcutPropertyElement(XElement node, string shortcutId) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + Identifier id = null; + string key = null; + string value = 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 "Key": + key = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Value": + value = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + this.Core.VerifyNoInnerText(sourceLineNumbers, node); + + if (String.IsNullOrEmpty(key)) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Key")); + } + else if (null == id) + { + id = this.Core.CreateIdentifier("scp", shortcutId, key.ToUpperInvariant()); + } + + if (String.IsNullOrEmpty(value)) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Value")); + } + + this.Core.ParseForExtensionElements(node); + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new MsiShortcutPropertySymbol(sourceLineNumbers, id) + { + ShortcutRef = shortcutId, + PropertyKey = key, + PropVariantValue = value + }); + } + } + + /// + /// Parses a typelib element. + /// + /// Element to parse. + /// Identifier of parent component. + /// Identifier of file that acts as typelib server. + /// true if the component is 64-bit. + private void ParseTypeLibElement(XElement node, string componentId, string fileServer, bool win64Component) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string id = null; + var advertise = YesNoType.NotSet; + var cost = CompilerConstants.IntegerNotSet; + string description = null; + var flags = 0; + string helpDirectory = null; + var language = CompilerConstants.IntegerNotSet; + var majorVersion = CompilerConstants.IntegerNotSet; + var minorVersion = CompilerConstants.IntegerNotSet; + var resourceId = CompilerConstants.LongNotSet; + + 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 "Advertise": + advertise = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "Control": + if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) + { + flags |= 2; + } + break; + case "Cost": + cost = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int32.MaxValue); + break; + case "Description": + description = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "HasDiskImage": + if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) + { + flags |= 8; + } + break; + case "HelpDirectory": + helpDirectory = this.Core.CreateDirectoryReferenceFromInlineSyntax(sourceLineNumbers, attrib, null); + break; + case "Hidden": + if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) + { + flags |= 4; + } + break; + case "Language": + language = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int16.MaxValue); + break; + case "MajorVersion": + majorVersion = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, UInt16.MaxValue); + break; + case "MinorVersion": + minorVersion = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Byte.MaxValue); + break; + case "ResourceId": + resourceId = this.Core.GetAttributeLongValue(sourceLineNumbers, attrib, Int32.MinValue, Int32.MaxValue); + break; + case "Restricted": + if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) + { + flags |= 1; + } + 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 (CompilerConstants.IntegerNotSet == language) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Language")); + language = CompilerConstants.IllegalInteger; + } + + // build up the typelib version string for the registry if the major or minor version was specified + string registryVersion = null; + if (CompilerConstants.IntegerNotSet != majorVersion || CompilerConstants.IntegerNotSet != minorVersion) + { + if (CompilerConstants.IntegerNotSet != majorVersion) + { + registryVersion = majorVersion.ToString("x", CultureInfo.InvariantCulture.NumberFormat); + } + else + { + registryVersion = "0"; + } + + if (CompilerConstants.IntegerNotSet != minorVersion) + { + registryVersion = String.Concat(registryVersion, ".", minorVersion.ToString("x", CultureInfo.InvariantCulture.NumberFormat)); + } + else + { + registryVersion = String.Concat(registryVersion, ".0"); + } + } + + // if the advertise state has not been set, default to non-advertised + if (YesNoType.NotSet == advertise) + { + advertise = YesNoType.No; + } + + foreach (var child in node.Elements()) + { + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + switch (child.Name.LocalName) + { + case "AppId": + this.ParseAppIdElement(child, componentId, YesNoType.NotSet, fileServer, id, registryVersion); + break; + case "Class": + this.ParseClassElement(child, componentId, YesNoType.NotSet, fileServer, id, registryVersion, null); + break; + case "Interface": + this.ParseInterfaceElement(child, componentId, null, null, id, registryVersion); + break; + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + else + { + this.Core.ParseExtensionElement(node, child); + } + } + + + if (YesNoType.Yes == advertise) + { + if (CompilerConstants.LongNotSet != resourceId) + { + this.Core.Write(ErrorMessages.IllegalAttributeWhenAdvertised(sourceLineNumbers, node.Name.LocalName, "ResourceId")); + } + + if (0 != flags) + { + if (0x1 == (flags & 0x1)) + { + this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Restricted", "Advertise", "yes")); + } + + if (0x2 == (flags & 0x2)) + { + this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Control", "Advertise", "yes")); + } + + if (0x4 == (flags & 0x4)) + { + this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Hidden", "Advertise", "yes")); + } + + if (0x8 == (flags & 0x8)) + { + this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "HasDiskImage", "Advertise", "yes")); + } + } + + if (!this.Core.EncounteredError) + { + var symbol = this.Core.AddSymbol(new TypeLibSymbol(sourceLineNumbers) + { + LibId = id, + Language = language, + ComponentRef = componentId, + Description = description, + DirectoryRef = helpDirectory, + FeatureRef = Guid.Empty.ToString("B") + }); + + if (CompilerConstants.IntegerNotSet != majorVersion || CompilerConstants.IntegerNotSet != minorVersion) + { + symbol.Version = (CompilerConstants.IntegerNotSet != majorVersion ? majorVersion * 256 : 0) + (CompilerConstants.IntegerNotSet != minorVersion ? minorVersion : 0); + } + + if (CompilerConstants.IntegerNotSet != cost) + { + symbol.Cost = cost; + } + } + } + else if (YesNoType.No == advertise) + { + if (CompilerConstants.IntegerNotSet != cost && CompilerConstants.IllegalInteger != cost) + { + this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Cost", "Advertise", "no")); + } + + if (null == fileServer) + { + this.Core.Write(ErrorMessages.MissingTypeLibFile(sourceLineNumbers, node.Name.LocalName, "File")); + } + + if (null == registryVersion) + { + this.Core.Write(ErrorMessages.ExpectedAttributesWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "MajorVersion", "MinorVersion", "Advertise", "no")); + } + + // HKCR\TypeLib\[ID]\[MajorVersion].[MinorVersion], (Default) = [Description] + this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Format(CultureInfo.InvariantCulture, @"TypeLib\{0}\{1}", id, registryVersion), null, description, componentId); + + // HKCR\TypeLib\[ID]\[MajorVersion].[MinorVersion]\[Language]\[win16|win32|win64], (Default) = [TypeLibPath]\[ResourceId] + var path = String.Concat("[#", fileServer, "]"); + if (CompilerConstants.LongNotSet != resourceId) + { + path = String.Concat(path, Path.DirectorySeparatorChar, resourceId.ToString(CultureInfo.InvariantCulture.NumberFormat)); + } + this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Format(CultureInfo.InvariantCulture, @"TypeLib\{0}\{1}\{2}\{3}", id, registryVersion, language, (win64Component ? "win64" : "win32")), null, path, componentId); + + // HKCR\TypeLib\[ID]\[MajorVersion].[MinorVersion]\FLAGS, (Default) = [TypeLibFlags] + this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Format(CultureInfo.InvariantCulture, @"TypeLib\{0}\{1}\FLAGS", id, registryVersion), null, flags.ToString(CultureInfo.InvariantCulture.NumberFormat), componentId); + + if (null != helpDirectory) + { + // HKCR\TypeLib\[ID]\[MajorVersion].[MinorVersion]\HELPDIR, (Default) = [HelpDirectory] + this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Format(CultureInfo.InvariantCulture, @"TypeLib\{0}\{1}\HELPDIR", id, registryVersion), null, String.Concat("[", helpDirectory, "]"), componentId); + } + } + } + + /// + /// Parses an upgrade element. + /// + /// Element to parse. + private void ParseUpgradeElement(XElement node) + { + 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.GetAttributeGuidValue(sourceLineNumbers, attrib, false); + 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")); + } + + // process the UpgradeVersion children here + foreach (var child in node.Elements()) + { + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child); + + switch (child.Name.LocalName) + { + case "Property": + this.ParsePropertyElement(child); + this.Core.Write(WarningMessages.DeprecatedUpgradeProperty(childSourceLineNumbers)); + break; + case "UpgradeVersion": + this.ParseUpgradeVersionElement(child, id); + break; + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + else + { + this.Core.ParseExtensionElement(node, child); + } + } + + // No rows created here. All row creation is done in ParseUpgradeVersionElement. + } + + /// + /// Parse upgrade version element. + /// + /// Element to parse. + /// Upgrade code. + private void ParseUpgradeVersionElement(XElement node, string upgradeId) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + + string actionProperty = null; + string language = null; + string maximum = null; + string minimum = null; + var excludeLanguages = false; + var ignoreFailures = false; + var includeMax = false; + var includeMin = true; + var migrateFeatures = false; + var onlyDetect = false; + string removeFeatures = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "ExcludeLanguages": + excludeLanguages = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "IgnoreRemoveFailure": + ignoreFailures = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "IncludeMaximum": + includeMax = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "IncludeMinimum": // this is "yes" by default + includeMin = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "Language": + language = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Minimum": + minimum = this.Core.GetAttributeVersionValue(sourceLineNumbers, attrib); + break; + case "Maximum": + maximum = this.Core.GetAttributeVersionValue(sourceLineNumbers, attrib); + break; + case "MigrateFeatures": + migrateFeatures = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "OnlyDetect": + onlyDetect = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "Property": + actionProperty = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + break; + case "RemoveFeatures": + removeFeatures = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == actionProperty) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Property")); + } + else if (actionProperty.ToUpper(CultureInfo.InvariantCulture) != actionProperty) + { + this.Core.Write(ErrorMessages.SecurePropertyNotUppercase(sourceLineNumbers, node.Name.LocalName, "Property", actionProperty)); + } + + if (null == minimum && null == maximum) + { + this.Core.Write(ErrorMessages.ExpectedAttributes(sourceLineNumbers, node.Name.LocalName, "Minimum", "Maximum")); + } + + this.Core.ParseForExtensionElements(node); + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new UpgradeSymbol(sourceLineNumbers) + { + UpgradeCode = upgradeId, + VersionMin = minimum, + VersionMax = maximum, + Language = language, + ExcludeLanguages = excludeLanguages, + IgnoreRemoveFailures = ignoreFailures, + VersionMaxInclusive = includeMax, + VersionMinInclusive = includeMin, + MigrateFeatures = migrateFeatures, + OnlyDetect = onlyDetect, + Remove = removeFeatures, + ActionProperty = actionProperty + }); + + // Ensure that RemoveExistingProducts is authored in InstallExecuteSequence + // if at least one row in Upgrade table lacks the OnlyDetect attribute. + if (!onlyDetect) + { + this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.WixAction, "InstallExecuteSequence", "RemoveExistingProducts"); + } + } + } + + /// + /// Parses a verb element. + /// + /// Element to parse. + /// Extension verb is releated to. + /// Optional progId for extension. + /// Identifier for parent component. + /// Flag if verb is advertised. + private void ParseVerbElement(XElement node, string extension, string progId, string componentId, YesNoType advertise) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string id = null; + string argument = null; + string command = null; + var sequence = CompilerConstants.IntegerNotSet; + string targetFile = null; + string targetProperty = 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.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Argument": + argument = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Command": + command = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Sequence": + sequence = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 1, Int16.MaxValue); + break; + case "TargetFile": + targetFile = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.File, targetFile); + break; + case "TargetProperty": + targetProperty = 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 != targetFile && null != targetProperty) + { + this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "TargetFile", "TargetProperty")); + } + + this.Core.ParseForExtensionElements(node); + + if (YesNoType.Yes == advertise) + { + if (null != targetFile) + { + this.Core.Write(ErrorMessages.IllegalAttributeWhenAdvertised(sourceLineNumbers, node.Name.LocalName, "TargetFile")); + } + + if (null != targetProperty) + { + this.Core.Write(ErrorMessages.IllegalAttributeWhenAdvertised(sourceLineNumbers, node.Name.LocalName, "TargetProperty")); + } + + if (!this.Core.EncounteredError) + { + var symbol = this.Core.AddSymbol(new VerbSymbol(sourceLineNumbers) + { + ExtensionRef = extension, + Verb = id, + Command = command, + Argument = argument, + }); + + if (CompilerConstants.IntegerNotSet != sequence) + { + symbol.Sequence = sequence; + } + } + } + else if (YesNoType.No == advertise) + { + if (CompilerConstants.IntegerNotSet != sequence) + { + this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Sequence", "Advertise", "no")); + } + + if (null == targetFile && null == targetProperty) + { + this.Core.Write(ErrorMessages.ExpectedAttributesWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "TargetFile", "TargetProperty", "Advertise", "no")); + } + + string target = null; + if (null != targetFile) + { + target = String.Concat("\"[#", targetFile, "]\""); + } + else if (null != targetProperty) + { + target = String.Concat("\"[", targetProperty, "]\""); + } + + if (null != argument) + { + target = String.Concat(target, " ", argument); + } + + var prefix = progId ?? String.Concat(".", extension); + + if (null != command) + { + this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat(prefix, "\\shell\\", id), String.Empty, command, componentId); + } + + this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat(prefix, "\\shell\\", id, "\\command"), String.Empty, target, componentId); + } + } + + /// + /// Parses a WixVariable element. + /// + /// Element to parse. + private void ParseWixVariableElement(XElement node) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + Identifier id = null; + var overridable = false; + string value = 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 "Overridable": + overridable = (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)); + break; + case "Value": + value = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty); + 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 == value) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Value")); + } + + this.Core.ParseForExtensionElements(node); + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new WixVariableSymbol(sourceLineNumbers, id) + { + Value = value, + Overridable = overridable + }); + } + } + + private CompressionLevel? ParseCompressionLevel(SourceLineNumber sourceLineNumbers, XElement node, XAttribute attribute) + { + var compressionLevel = this.Core.GetAttributeValue(sourceLineNumbers, attribute); + switch (compressionLevel) + { + case "high": + return CompressionLevel.High; + case "low": + return CompressionLevel.Low; + case "medium": + return CompressionLevel.Medium; + case "mszip": + return CompressionLevel.Mszip; + case "none": + return CompressionLevel.None; + case "": + break; + default: + this.Core.Write(ErrorMessages.IllegalCompressionLevel(sourceLineNumbers, compressionLevel)); + break; + } + + return null; + } + } +} -- cgit v1.2.3-55-g6feb