// 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.PowerShell { using System; using System.Collections.Generic; using System.Globalization; using System.Xml.Linq; using WixToolset.Data; using WixToolset.Data.Symbols; using WixToolset.Extensibility; /// /// The compiler for the WiX Toolset PowerShell Extension. /// public sealed class PSCompiler : BaseCompilerExtension { private const string KeyFormat = @"SOFTWARE\Microsoft\PowerShell\{0}\PowerShellSnapIns\{1}"; private const string VarPrefix = "PSVersionMajor"; public override XNamespace Namespace => "http://wixtoolset.org/schemas/v4/wxs/powershell"; /// /// Processes an element for the Compiler. /// /// Source line number for the parent element. /// Parent element of element to process. /// Element to process. /// Extra information about the context in which this element is being parsed. public override void ParseElement(Intermediate intermediate, IntermediateSection section, XElement parentElement, XElement element, IDictionary context) { switch (parentElement.Name.LocalName) { case "File": var fileId = context["FileId"]; var componentId = context["ComponentId"]; switch (element.Name.LocalName) { case "FormatsFile": this.ParseExtensionsFile(intermediate, section, element, "Formats", fileId, componentId); break; case "SnapIn": this.ParseSnapInElement(intermediate, section, element, fileId, componentId); break; case "TypesFile": this.ParseExtensionsFile(intermediate, section, element, "Types", fileId, componentId); break; default: this.ParseHelper.UnexpectedElement(parentElement, element); break; } break; default: this.ParseHelper.UnexpectedElement(parentElement, element); break; } } /// /// Parses a SnapIn element. /// /// Element to parse. /// Identifier for parent file. /// Identifier for parent component. private void ParseSnapInElement(Intermediate intermediate, IntermediateSection section, XElement node, string fileId, string componentId) { var sourceLineNumbers = this.ParseHelper.GetSourceLineNumbers(node); string id = null; string customSnapInType = null; string description = null; string descriptionIndirect = null; var requiredPowerShellVersion = CompilerConstants.IllegalVersion; string vendor = null; string vendorIndirect = null; string version = null; foreach (var attrib in node.Attributes()) { if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || this.Namespace == attrib.Name.Namespace) { switch (attrib.Name.LocalName) { case "Id": id = this.ParseHelper.GetAttributeIdentifierValue(sourceLineNumbers, attrib); break; case "CustomSnapInType": customSnapInType = this.ParseHelper.GetAttributeValue(sourceLineNumbers, attrib); break; case "Description": description = this.ParseHelper.GetAttributeValue(sourceLineNumbers, attrib); break; case "DescriptionIndirect": descriptionIndirect = this.ParseHelper.GetAttributeValue(sourceLineNumbers, attrib); break; case "RequiredPowerShellVersion": var ver = this.ParseHelper.GetAttributeVersionValue(sourceLineNumbers, attrib); requiredPowerShellVersion = new Version(ver); break; case "Vendor": vendor = this.ParseHelper.GetAttributeValue(sourceLineNumbers, attrib); break; case "VendorIndirect": vendorIndirect = this.ParseHelper.GetAttributeValue(sourceLineNumbers, attrib); break; case "Version": version = this.ParseHelper.GetAttributeVersionValue(sourceLineNumbers, attrib); break; default: this.ParseHelper.UnexpectedAttribute(node, attrib); break; } } else { this.ParseHelper.ParseExtensionAttribute(this.Context.Extensions, intermediate, section, node, attrib); } } if (null == id) { this.Messaging.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); } // Default to require PowerShell 1.0. if (CompilerConstants.IllegalVersion == requiredPowerShellVersion) { requiredPowerShellVersion = new Version(1, 0); } // If the snap-in version isn't explicitly specified, get it // from the assembly version at bind time. if (null == version) { version = String.Format("!(bind.assemblyVersion.{0})", fileId); } foreach (var child in node.Elements()) { if (this.Namespace == child.Name.Namespace) { switch (child.Name.LocalName) { case "FormatsFile": this.ParseExtensionsFile(intermediate, section, child, "Formats", id, componentId); break; case "TypesFile": this.ParseExtensionsFile(intermediate, section, child, "Types", id, componentId); break; default: this.ParseHelper.UnexpectedElement(node, child); break; } } else { this.ParseHelper.ParseExtensionElement(this.Context.Extensions, intermediate, section, node, child); } } // Get the major part of the required PowerShell version which is // needed for the registry key, and put that into a WiX variable // for use in Formats and Types files. PowerShell v2 still uses 1. var major = (2 == requiredPowerShellVersion.Major) ? 1 : requiredPowerShellVersion.Major; var variableId = new Identifier(AccessModifier.Global, String.Format(CultureInfo.InvariantCulture, "{0}_{1}", VarPrefix, id)); section.AddSymbol(new WixVariableSymbol(sourceLineNumbers, variableId) { Value = major.ToString(CultureInfo.InvariantCulture), Overridable = false, }); var registryRoot = RegistryRootType.LocalMachine; // HKLM var registryKey = String.Format(CultureInfo.InvariantCulture, KeyFormat, major, id); this.ParseHelper.CreateRegistrySymbol(section, sourceLineNumbers, registryRoot, registryKey, "ApplicationBase", String.Format(CultureInfo.InvariantCulture, "[${0}]", componentId), componentId); // set the assembly name automatically when binding. // processorArchitecture is not handled correctly by PowerShell v1.0 // so format the assembly name explicitly. var assemblyName = String.Format(CultureInfo.InvariantCulture, "!(bind.assemblyName.{0}), Version=!(bind.assemblyVersion.{0}), Culture=!(bind.assemblyCulture.{0}), PublicKeyToken=!(bind.assemblyPublicKeyToken.{0})", fileId); this.ParseHelper.CreateRegistrySymbol(section, sourceLineNumbers, registryRoot, registryKey, "AssemblyName", assemblyName, componentId); if (null != customSnapInType) { this.ParseHelper.CreateRegistrySymbol(section, sourceLineNumbers, registryRoot, registryKey, "CustomPSSnapInType", customSnapInType, componentId); } if (null != description) { this.ParseHelper.CreateRegistrySymbol(section, sourceLineNumbers, registryRoot, registryKey, "Description", description, componentId); } if (null != descriptionIndirect) { this.ParseHelper.CreateRegistrySymbol(section, sourceLineNumbers, registryRoot, registryKey, "DescriptionIndirect", descriptionIndirect, componentId); } this.ParseHelper.CreateRegistrySymbol(section, sourceLineNumbers, registryRoot, registryKey, "ModuleName", String.Format(CultureInfo.InvariantCulture, "[#{0}]", fileId), componentId); this.ParseHelper.CreateRegistrySymbol(section, sourceLineNumbers, registryRoot, registryKey, "PowerShellVersion", requiredPowerShellVersion.ToString(2), componentId); if (null != vendor) { this.ParseHelper.CreateRegistrySymbol(section, sourceLineNumbers, registryRoot, registryKey, "Vendor", vendor, componentId); } if (null != vendorIndirect) { this.ParseHelper.CreateRegistrySymbol(section, sourceLineNumbers, registryRoot, registryKey, "VendorIndirect", vendorIndirect, componentId); } if (null != version) { this.ParseHelper.CreateRegistrySymbol(section, sourceLineNumbers, registryRoot, registryKey, "Version", version, componentId); } } /// /// Parses a FormatsFile and TypesFile element. /// /// Element to parse. /// Registry value name. /// Idendifier for parent file or snap-in. /// Identifier for parent component. private void ParseExtensionsFile(Intermediate intermediate, IntermediateSection section, XElement node, string valueName, string id, string componentId) { var sourceLineNumbers = this.ParseHelper.GetSourceLineNumbers(node); string fileId = null; string snapIn = null; foreach (var attrib in node.Attributes()) { if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || this.Namespace == attrib.Name.Namespace) { switch (attrib.Name.LocalName) { case "FileId": fileId = this.ParseHelper.GetAttributeValue(sourceLineNumbers, attrib); snapIn = id; break; case "SnapIn": fileId = id; snapIn = this.ParseHelper.GetAttributeValue(sourceLineNumbers, attrib); break; default: this.ParseHelper.UnexpectedAttribute(node, attrib); break; } } else { this.ParseHelper.ParseExtensionAttribute(this.Context.Extensions, intermediate, section, node, attrib); } } if (null == fileId && null == snapIn) { this.Messaging.Write(PSErrors.NeitherIdSpecified(sourceLineNumbers, valueName)); } this.ParseHelper.ParseForExtensionElements(this.Context.Extensions, intermediate, section, node); var registryRoot = RegistryRootType.LocalMachine; // HKLM var registryKey = String.Format(CultureInfo.InvariantCulture, KeyFormat, String.Format(CultureInfo.InvariantCulture, "!(wix.{0}_{1})", VarPrefix, snapIn), snapIn); this.ParseHelper.CreateSimpleReference(section, sourceLineNumbers, SymbolDefinitions.File, fileId); this.ParseHelper.CreateRegistrySymbol(section, sourceLineNumbers, registryRoot, registryKey, valueName, String.Format(CultureInfo.InvariantCulture, "[#{0}]", fileId), componentId, RegistryValueType.MultiString, RegistryValueActionType.Append); } } }