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