// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. namespace WixToolset.Core { using System; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.Xml.Linq; using WixToolset.Data; using WixToolset.Data.Tuples; using WixToolset.Extensibility; /// /// Compiler of the WiX toolset. /// internal partial class Compiler : ICompiler { /// /// Parses an patch element. /// /// The element to parse. private void ParsePatchElement(XElement node) { var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); string patchId = null; var codepage = 0; ////bool versionMismatches = false; ////bool productMismatches = false; var allowRemoval = false; string classification = null; string clientPatchId = null; string description = null; string displayName = null; string comments = null; string manufacturer = null; var minorUpdateTargetRTM = YesNoType.NotSet; string moreInfoUrl = null; var optimizeCA = CompilerConstants.IntegerNotSet; var optimizedInstallMode = YesNoType.NotSet; string targetProductName = null; // string replaceGuids = String.Empty; var apiPatchingSymbolFlags = 0; var optimizePatchSizeForLargeFiles = false; foreach (var attrib in node.Attributes()) { if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) { switch (attrib.Name.LocalName) { case "Id": patchId = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib, true); break; case "Codepage": codepage = this.Core.GetAttributeCodePageValue(sourceLineNumbers, attrib); break; case "AllowMajorVersionMismatches": ////versionMismatches = (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib)); break; case "AllowProductCodeMismatches": ////productMismatches = (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib)); break; case "AllowRemoval": allowRemoval = (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)); break; case "Classification": classification = this.Core.GetAttributeValue(sourceLineNumbers, attrib); break; case "ClientPatchId": clientPatchId = 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 "Comments": comments = this.Core.GetAttributeValue(sourceLineNumbers, attrib); break; case "Manufacturer": manufacturer = this.Core.GetAttributeValue(sourceLineNumbers, attrib); break; case "MinorUpdateTargetRTM": minorUpdateTargetRTM = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); break; case "MoreInfoURL": moreInfoUrl = this.Core.GetAttributeValue(sourceLineNumbers, attrib); break; case "OptimizedInstallMode": optimizedInstallMode = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); break; case "TargetProductName": targetProductName = this.Core.GetAttributeValue(sourceLineNumbers, attrib); break; case "ApiPatchingSymbolNoImagehlpFlag": apiPatchingSymbolFlags |= (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) ? (int)PatchSymbolFlagsType.PATCH_SYMBOL_NO_IMAGEHLP : 0; break; case "ApiPatchingSymbolNoFailuresFlag": apiPatchingSymbolFlags |= (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) ? (int)PatchSymbolFlagsType.PATCH_SYMBOL_NO_FAILURES : 0; break; case "ApiPatchingSymbolUndecoratedTooFlag": apiPatchingSymbolFlags |= (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) ? (int)PatchSymbolFlagsType.PATCH_SYMBOL_UNDECORATED_TOO : 0; break; case "OptimizePatchSizeForLargeFiles": optimizePatchSizeForLargeFiles = (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)); break; default: this.Core.UnexpectedAttribute(node, attrib); break; } } else { this.Core.ParseExtensionAttribute(node, attrib); } } if (patchId == null || patchId == "*") { // auto-generate at compile time, since this value gets dispersed to several locations patchId = Common.GenerateGuid(); } this.activeName = patchId; if (null == this.activeName) { this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); } if (null == classification) { this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Classification")); } if (null == clientPatchId) { clientPatchId = String.Concat("_", new Guid(patchId).ToString("N", CultureInfo.InvariantCulture).ToUpper(CultureInfo.InvariantCulture)); } if (null == description) { this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Description")); } if (null == displayName) { this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "DisplayName")); } if (null == manufacturer) { this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Manufacturer")); } this.Core.CreateActiveSection(this.activeName, SectionType.Patch, codepage, this.Context.CompilationId); foreach (var child in node.Elements()) { if (CompilerCore.WixNamespace == child.Name.Namespace) { switch (child.Name.LocalName) { case "PatchInformation": this.ParsePatchInformationElement(child); break; case "Media": this.ParseMediaElement(child, patchId); break; case "OptimizeCustomActions": optimizeCA = this.ParseOptimizeCustomActionsElement(child); break; case "PatchFamily": this.ParsePatchFamilyElement(child, ComplexReferenceParentType.Patch, patchId); break; case "PatchFamilyRef": this.ParsePatchFamilyRefElement(child, ComplexReferenceParentType.Patch, patchId); break; case "PatchFamilyGroup": this.ParsePatchFamilyGroupElement(child, ComplexReferenceParentType.Patch, patchId); break; case "PatchFamilyGroupRef": this.ParsePatchFamilyGroupRefElement(child, ComplexReferenceParentType.Patch, patchId); break; case "PatchProperty": this.ParsePatchPropertyElement(child, true); break; case "TargetProductCodes": this.ParseTargetProductCodesElement(child); break; default: this.Core.UnexpectedElement(node, child); break; } } else { this.Core.ParseExtensionElement(node, child); } } if (!this.Core.EncounteredError) { var tuple = new WixPatchIdTuple(sourceLineNumbers) { ProductCode = patchId, ClientPatchId = clientPatchId, OptimizePatchSizeForLargeFiles = optimizePatchSizeForLargeFiles, ApiPatchingSymbolFlags = apiPatchingSymbolFlags }; this.Core.AddTuple(tuple); if (allowRemoval) { this.AddMsiPatchMetadata(sourceLineNumbers, null, "AllowRemoval", allowRemoval ? "1" : "0"); } if (null != classification) { this.AddMsiPatchMetadata(sourceLineNumbers, null, "Classification", classification); } // always generate the CreationTimeUTC { this.AddMsiPatchMetadata(sourceLineNumbers, null, "CreationTimeUTC", DateTime.UtcNow.ToString("MM-dd-yy HH:mm", CultureInfo.InvariantCulture)); } if (null != description) { this.AddMsiPatchMetadata(sourceLineNumbers, null, "Description", description); } if (null != displayName) { this.AddMsiPatchMetadata(sourceLineNumbers, null, "DisplayName", displayName); } if (null != manufacturer) { this.AddMsiPatchMetadata(sourceLineNumbers, null, "ManufacturerName", manufacturer); } if (YesNoType.NotSet != minorUpdateTargetRTM) { this.AddMsiPatchMetadata(sourceLineNumbers, null, "MinorUpdateTargetRTM", YesNoType.Yes == minorUpdateTargetRTM ? "1" : "0"); } if (null != moreInfoUrl) { this.AddMsiPatchMetadata(sourceLineNumbers, null, "MoreInfoURL", moreInfoUrl); } if (CompilerConstants.IntegerNotSet != optimizeCA) { this.AddMsiPatchMetadata(sourceLineNumbers, null, "OptimizeCA", optimizeCA.ToString(CultureInfo.InvariantCulture)); } if (YesNoType.NotSet != optimizedInstallMode) { this.AddMsiPatchMetadata(sourceLineNumbers, null, "OptimizedInstallMode", YesNoType.Yes == optimizedInstallMode ? "1" : "0"); } if (null != targetProductName) { this.AddMsiPatchMetadata(sourceLineNumbers, null, "TargetProductName", targetProductName); } if (null != comments) { this.AddMsiPatchMetadata(sourceLineNumbers, null, "Comments", comments); } } // TODO: do something with versionMismatches and productMismatches } /// /// Parses the OptimizeCustomActions element. /// /// Element to parse. /// The combined integer value for callers to store as appropriate. private int ParseOptimizeCustomActionsElement(XElement node) { var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); var optimizeCA = OptimizeCA.None; foreach (var attrib in node.Attributes()) { if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) { switch (attrib.Name.LocalName) { case "SkipAssignment": if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) { optimizeCA |= OptimizeCA.SkipAssignment; } break; case "SkipImmediate": if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) { optimizeCA |= OptimizeCA.SkipImmediate; } break; case "SkipDeferred": if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) { optimizeCA |= OptimizeCA.SkipDeferred; } break; default: this.Core.UnexpectedAttribute(node, attrib); break; } } else { this.Core.ParseExtensionAttribute(node, attrib); } } return (int)optimizeCA; } /// /// Parses a PatchFamily element. /// /// The element to parse. private void ParsePatchFamilyElement(XElement node, ComplexReferenceParentType parentType, string parentId) { var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); Identifier id = null; string productCode = null; string version = null; var attributes = 0; 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 "ProductCode": productCode = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib, false); break; case "Version": version = this.Core.GetAttributeVersionValue(sourceLineNumbers, attrib); break; case "Supersede": if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) { attributes |= 0x1; } 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; } if (String.IsNullOrEmpty(version)) { this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Version")); } else if (!CompilerCore.IsValidProductVersion(version)) { this.Core.Write(ErrorMessages.InvalidProductVersion(sourceLineNumbers, version)); } // find unexpected child elements foreach (var child in node.Elements()) { if (CompilerCore.WixNamespace == child.Name.Namespace) { switch (child.Name.LocalName) { case "All": this.ParseAllElement(child); break; case "BinaryRef": this.ParsePatchChildRefElement(child, "Binary"); break; case "ComponentRef": this.ParsePatchChildRefElement(child, "Component"); break; case "CustomActionRef": this.ParsePatchChildRefElement(child, "CustomAction"); break; case "DirectoryRef": this.ParsePatchChildRefElement(child, "Directory"); break; case "DigitalCertificateRef": this.ParsePatchChildRefElement(child, "MsiDigitalCertificate"); break; case "FeatureRef": this.ParsePatchChildRefElement(child, "Feature"); break; case "IconRef": this.ParsePatchChildRefElement(child, "Icon"); break; case "PropertyRef": this.ParsePatchChildRefElement(child, "Property"); break; case "UIRef": this.ParsePatchChildRefElement(child, "WixUI"); break; default: this.Core.UnexpectedElement(node, child); break; } } else { this.Core.ParseExtensionElement(node, child); } } if (!this.Core.EncounteredError) { var tuple = new MsiPatchSequenceTuple(sourceLineNumbers) { PatchFamily = id.Id, ProductCode = productCode, Sequence = version, Attributes = attributes }; this.Core.AddTuple(tuple); if (ComplexReferenceParentType.Unknown != parentType) { this.Core.CreateComplexReference(sourceLineNumbers, parentType, parentId, null, ComplexReferenceChildType.PatchFamily, id.Id, ComplexReferenceParentType.Patch == parentType); } } } /// /// Parses a PatchFamilyGroup element. /// /// Element to parse. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1800:DoNotCastUnnecessarily")] private void ParsePatchFamilyGroupElement(XElement node, ComplexReferenceParentType parentType, string parentId) { var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); Identifier id = null; foreach (var attrib in node.Attributes()) { if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) { switch (attrib.Name.LocalName) { case "Id": id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); break; default: this.Core.UnexpectedAttribute(node, attrib); break; } } else { this.Core.ParseExtensionAttribute(node, attrib); } } if (null == id) { this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); id = Identifier.Invalid; } foreach (var child in node.Elements()) { if (CompilerCore.WixNamespace == child.Name.Namespace) { switch (child.Name.LocalName) { case "PatchFamily": this.ParsePatchFamilyElement(child, ComplexReferenceParentType.PatchFamilyGroup, id.Id); break; case "PatchFamilyRef": this.ParsePatchFamilyRefElement(child, ComplexReferenceParentType.PatchFamilyGroup, id.Id); break; case "PatchFamilyGroupRef": this.ParsePatchFamilyGroupRefElement(child, ComplexReferenceParentType.PatchFamilyGroup, id.Id); break; default: this.Core.UnexpectedElement(node, child); break; } } else { this.Core.ParseExtensionElement(node, child); } } if (!this.Core.EncounteredError) { this.Core.AddTuple(new WixPatchFamilyGroupTuple(sourceLineNumbers, id)); //Add this PatchFamilyGroup and its parent in WixGroup. this.Core.CreateWixGroupRow(sourceLineNumbers, parentType, parentId, ComplexReferenceChildType.PatchFamilyGroup, id.Id); } } /// /// Parses a PatchFamilyGroup reference element. /// /// Element to parse. /// The type of parent. /// Identifier of parent element. private void ParsePatchFamilyGroupRefElement(XElement node, ComplexReferenceParentType parentType, string parentId) { Debug.Assert(ComplexReferenceParentType.PatchFamilyGroup == parentType || ComplexReferenceParentType.Patch == parentType); var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); string id = null; foreach (var attrib in node.Attributes()) { if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) { switch (attrib.Name.LocalName) { case "Id": id = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); this.Core.CreateSimpleReference(sourceLineNumbers, "WixPatchFamilyGroup", id); break; default: this.Core.UnexpectedAttribute(node, attrib); break; } } else { this.Core.ParseExtensionAttribute(node, attrib); } } if (null == id) { this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); } this.Core.ParseForExtensionElements(node); if (!this.Core.EncounteredError) { this.Core.CreateComplexReference(sourceLineNumbers, parentType, parentId, null, ComplexReferenceChildType.PatchFamilyGroup, id, true); } } /// /// Parses a TargetProductCodes element. /// /// The element to parse. private void ParseTargetProductCodesElement(XElement node) { var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); var replace = false; var targetProductCodes = new List(); foreach (var attrib in node.Attributes()) { if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) { switch (attrib.Name.LocalName) { case "Replace": replace = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); break; default: this.Core.UnexpectedAttribute(node, attrib); break; } } else { this.Core.ParseExtensionAttribute(node, attrib); } } foreach (var child in node.Elements()) { if (CompilerCore.WixNamespace == child.Name.Namespace) { switch (child.Name.LocalName) { case "TargetProductCode": var id = this.ParseTargetProductCodeElement(child); if (0 == String.CompareOrdinal("*", id)) { this.Core.Write(ErrorMessages.IllegalAttributeValueWhenNested(sourceLineNumbers, child.Name.LocalName, "Id", id, node.Name.LocalName)); } else { targetProductCodes.Add(id); } break; default: this.Core.UnexpectedElement(node, child); break; } } else { this.Core.ParseExtensionElement(node, child); } } if (!this.Core.EncounteredError) { // By default, target ProductCodes should be added. if (!replace) { this.Core.AddTuple(new WixPatchTargetTuple(sourceLineNumbers) { ProductCode = "*" }); } foreach (var targetProductCode in targetProductCodes) { this.Core.AddTuple(new WixPatchTargetTuple(sourceLineNumbers) { ProductCode = targetProductCode }); } } } private void AddMsiPatchMetadata(SourceLineNumber sourceLineNumbers, string company, string property, string value) { this.Core.AddTuple(new MsiPatchMetadataTuple(sourceLineNumbers, new Identifier(AccessModifier.Private, company, property)) { Company = company, Property = property, Value = value }); } } }