From 2bb37beda887d120a0ddabf874ad25357101faa1 Mon Sep 17 00:00:00 2001 From: Rob Mensching Date: Wed, 1 Nov 2017 10:59:45 -0700 Subject: Update to WiX Intermediate Representation --- src/WixToolset.Core.WindowsInstaller/Melter.cs | 400 +++++++++++++++++++++++++ 1 file changed, 400 insertions(+) create mode 100644 src/WixToolset.Core.WindowsInstaller/Melter.cs (limited to 'src/WixToolset.Core.WindowsInstaller/Melter.cs') diff --git a/src/WixToolset.Core.WindowsInstaller/Melter.cs b/src/WixToolset.Core.WindowsInstaller/Melter.cs new file mode 100644 index 00000000..a57f73a5 --- /dev/null +++ b/src/WixToolset.Core.WindowsInstaller/Melter.cs @@ -0,0 +1,400 @@ +// 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 +{ + using System; + using System.CodeDom.Compiler; + using System.Collections; + using System.Collections.Generic; + using System.Collections.Specialized; + using System.Globalization; + using System.IO; + using System.Text; + using System.Text.RegularExpressions; + using WixToolset.Data; + using Wix = WixToolset.Data.Serialize; + + /// + /// Converts a wixout representation of an MSM database into a ComponentGroup the form of WiX source. + /// + public sealed class Melter + { +#if TODO + private MelterCore core; + private Decompiler decompiler; + + private Wix.ComponentGroup componentGroup; + private Wix.DirectoryRef primaryDirectoryRef; + private Wix.Fragment fragment; + + private string id; + private string moduleId; + private const string nullGuid = "{00000000-0000-0000-0000-000000000000}"; + + public string Id + { + get { return this.id; } + set { this.id = value; } + } + + public Decompiler Decompiler + { + get { return this.decompiler; } + set { this.decompiler = value; } + } + + /// + /// Creates a new melter object. + /// + /// The decompiler to use during the melting process. + /// The Id to use for the ComponentGroup, DirectoryRef, and WixVariables. If null, defaults to the Module's Id + public Melter(Decompiler decompiler, string id) + { + this.core = new MelterCore(); + + this.componentGroup = new Wix.ComponentGroup(); + this.fragment = new Wix.Fragment(); + this.primaryDirectoryRef = new Wix.DirectoryRef(); + + this.decompiler = decompiler; + this.id = id; + + if (null == this.decompiler) + { + this.core.OnMessage(WixErrors.ExpectedDecompiler("The melting process")); + } + } + + /// + /// Converts a Module wixout into a ComponentGroup. + /// + /// The output object representing the unbound merge module to melt. + /// The converted Module as a ComponentGroup. + public Wix.Wix Melt(Output wixout) + { + this.moduleId = GetModuleId(wixout); + + // Assign the default componentGroupId if none was specified + if (null == this.id) + { + this.id = this.moduleId; + } + + this.componentGroup.Id = this.id; + this.primaryDirectoryRef.Id = this.id; + + PreDecompile(wixout); + + wixout.Type = OutputType.Product; + this.decompiler.TreatProductAsModule = true; + Wix.Wix wix = this.decompiler.Decompile(wixout); + + if (null == wix) + { + return wix; + } + + ConvertModule(wix); + + return wix; + } + + /// + /// Converts a Module to a ComponentGroup and adds all of its relevant elements to the main fragment. + /// + /// The output object representing an unbound merge module. + private void ConvertModule(Wix.Wix wix) + { + Wix.Product product = Melter.GetProduct(wix); + + List customActionsRemoved = new List(); + Dictionary customsToRemove = new Dictionary(); + + foreach (Wix.ISchemaElement child in product.Children) + { + Wix.Directory childDir = child as Wix.Directory; + if (null != childDir) + { + bool isTargetDir = this.WalkDirectory(childDir); + if (isTargetDir) + { + continue; + } + } + else + { + Wix.Dependency childDep = child as Wix.Dependency; + if (null != childDep) + { + this.AddPropertyRef(childDep.RequiredId); + continue; + } + else if (child is Wix.Package) + { + continue; + } + else if (child is Wix.CustomAction) + { + Wix.CustomAction customAction = child as Wix.CustomAction; + string directoryId; + if (StartsWithStandardDirectoryId(customAction.Id, out directoryId) && customAction.Property == customAction.Id) + { + customActionsRemoved.Add(customAction.Id); + continue; + } + } + else if (child is Wix.InstallExecuteSequence) + { + Wix.InstallExecuteSequence installExecuteSequence = child as Wix.InstallExecuteSequence; + + foreach (Wix.ISchemaElement sequenceChild in installExecuteSequence.Children) + { + Wix.Custom custom = sequenceChild as Wix.Custom; + string directoryId; + if (custom != null && StartsWithStandardDirectoryId(custom.Action, out directoryId)) + { + customsToRemove.Add(custom, installExecuteSequence); + } + } + } + } + + this.fragment.AddChild(child); + } + + // For any customaction that we removed, also remove the scheduling of that action. + foreach (Wix.Custom custom in customsToRemove.Keys) + { + if (customActionsRemoved.Contains(custom.Action)) + { + ((Wix.InstallExecuteSequence)customsToRemove[custom]).RemoveChild(custom); + } + } + + AddProperty(this.moduleId, this.id); + + wix.RemoveChild(product); + wix.AddChild(this.fragment); + + this.fragment.AddChild(this.componentGroup); + this.fragment.AddChild(this.primaryDirectoryRef); + } + + /// + /// Gets the module from the Wix object. + /// + /// The Wix object. + /// The Module in the Wix object, null if no Module was found + private static Wix.Product GetProduct(Wix.Wix wix) + { + foreach (Wix.ISchemaElement element in wix.Children) + { + Wix.Product productElement = element as Wix.Product; + if (null != productElement) + { + return productElement; + } + } + return null; + } + + /// + /// Adds a PropertyRef to the main Fragment. + /// + /// Id of the PropertyRef. + private void AddPropertyRef(string propertyRefId) + { + Wix.PropertyRef propertyRef = new Wix.PropertyRef(); + propertyRef.Id = propertyRefId; + this.fragment.AddChild(propertyRef); + } + + /// + /// Adds a Property to the main Fragment. + /// + /// Id of the Property. + /// Value of the Property. + private void AddProperty(string propertyId, string value) + { + Wix.Property property = new Wix.Property(); + property.Id = propertyId; + property.Value = value; + this.fragment.AddChild(property); + } + + /// + /// Walks a directory structure obtaining Component Id's and Standard Directory Id's. + /// + /// The Directory to walk. + /// true if the directory is TARGETDIR. + private bool WalkDirectory(Wix.Directory directory) + { + bool isTargetDir = false; + if ("TARGETDIR" == directory.Id) + { + isTargetDir = true; + } + + string standardDirectoryId = null; + if (Melter.StartsWithStandardDirectoryId(directory.Id, out standardDirectoryId) && !isTargetDir) + { + this.AddSetPropertyCustomAction(directory.Id, String.Format(CultureInfo.InvariantCulture, "[{0}]", standardDirectoryId)); + } + + foreach (Wix.ISchemaElement child in directory.Children) + { + Wix.Directory childDir = child as Wix.Directory; + if (null != childDir) + { + if (isTargetDir) + { + this.primaryDirectoryRef.AddChild(child); + } + this.WalkDirectory(childDir); + } + else + { + Wix.Component childComponent = child as Wix.Component; + if (null != childComponent) + { + if (isTargetDir) + { + this.primaryDirectoryRef.AddChild(child); + } + this.AddComponentRef(childComponent); + } + } + } + + return isTargetDir; + } + + /// + /// Gets the module Id out of the Output object. + /// + /// The output object. + /// The module Id from the Output object. + private string GetModuleId(Output wixout) + { + // get the moduleId from the wixout + Table moduleSignatureTable = wixout.Tables["ModuleSignature"]; + if (null == moduleSignatureTable || 0 >= moduleSignatureTable.Rows.Count) + { + this.core.OnMessage(WixErrors.ExpectedTableInMergeModule("ModuleSignature")); + } + return moduleSignatureTable.Rows[0].Fields[0].Data.ToString(); + } + + /// + /// Determines if the directory Id starts with a standard directory id. + /// + /// The directory id. + /// The standard directory id. + /// true if the directory starts with a standard directory id. + private static bool StartsWithStandardDirectoryId(string directoryId, out string standardDirectoryId) + { + standardDirectoryId = null; + foreach (string id in WindowsInstallerStandard.GetStandardDirectories()) + { + if (directoryId.StartsWith(id, StringComparison.Ordinal)) + { + standardDirectoryId = id; + return true; + } + } + return false; + } + + /// + /// Adds a ComponentRef to the main ComponentGroup. + /// + /// The component to add. + private void AddComponentRef(Wix.Component component) + { + Wix.ComponentRef componentRef = new Wix.ComponentRef(); + componentRef.Id = component.Id; + this.componentGroup.AddChild(componentRef); + } + + /// + /// Adds a SetProperty CA for a Directory. + /// + /// The Id of the Property to set. + /// The value to set the Property to. + private void AddSetPropertyCustomAction(string propertyId, string value) + { + // Add the action + Wix.CustomAction customAction = new Wix.CustomAction(); + customAction.Id = propertyId; + customAction.Property = propertyId; + customAction.Value = value; + this.fragment.AddChild(customAction); + + // Schedule the action + Wix.InstallExecuteSequence installExecuteSequence = new Wix.InstallExecuteSequence(); + Wix.Custom custom = new Wix.Custom(); + custom.Action = customAction.Id; + custom.Before = "CostInitialize"; + installExecuteSequence.AddChild(custom); + this.fragment.AddChild(installExecuteSequence); + } + + /// + /// Does any operations to the wixout that would need to be done before decompiling. + /// + /// The output object representing the unbound merge module. + private void PreDecompile(Output wixout) + { + string wixVariable = String.Format(CultureInfo.InvariantCulture, "!(wix.{0}", this.id); + + foreach (Table table in wixout.Tables) + { + // Determine if the table has a feature foreign key + bool hasFeatureForeignKey = false; + foreach (ColumnDefinition columnDef in table.Definition.Columns) + { + if (null != columnDef.KeyTable) + { + string[] keyTables = columnDef.KeyTable.Split(';'); + foreach (string keyTable in keyTables) + { + if ("Feature" == keyTable) + { + hasFeatureForeignKey = true; + break; + } + } + } + } + + // If this table has no foreign keys to the feature table, skip it. + if (!hasFeatureForeignKey) + { + continue; + } + + // Go through all the rows and replace the null guid with the wix variable + // for columns that are foreign keys into the feature table. + foreach (Row row in table.Rows) + { + foreach (Field field in row.Fields) + { + if (null != field.Column.KeyTable) + { + string[] keyTables = field.Column.KeyTable.Split(';'); + foreach (string keyTable in keyTables) + { + if ("Feature" == keyTable) + { + field.Data = field.Data.ToString().Replace(nullGuid, wixVariable); + break; + } + } + } + } + } + } + } +#endif + } +} -- cgit v1.2.3-55-g6feb