// 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
});
}
}
}