// 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.Harvesters
{
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Globalization;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;
using WixToolset.Harvesters.Data;
using WixToolset.Harvesters.Extensibility;
using Wix = WixToolset.Harvesters.Serialize;
///
/// The finalize harvester mutator for the WiX Toolset Utility Extension.
///
public sealed class UtilFinalizeHarvesterMutator : BaseMutatorExtension
{
private ArrayList components;
private ArrayList directories;
private SortedList directoryPaths;
private Hashtable filePaths;
private ArrayList files;
private ArrayList registryValues;
private List payloads;
private bool suppressCOMElements;
private bool suppressVB6COMElements;
private string preprocessorVariable;
///
/// Instantiate a new UtilFinalizeHarvesterMutator.
///
public UtilFinalizeHarvesterMutator()
{
this.components = new ArrayList();
this.directories = new ArrayList();
this.directoryPaths = new SortedList();
this.filePaths = new Hashtable();
this.files = new ArrayList();
this.registryValues = new ArrayList();
this.payloads = new List();
}
///
/// Gets or sets the preprocessor variable for substitution.
///
/// The preprocessor variable for substitution.
public string PreprocessorVariable
{
get { return this.preprocessorVariable; }
set { this.preprocessorVariable = value; }
}
///
/// Gets the sequence of the extension.
///
/// The sequence of the extension.
public override int Sequence
{
get { return 2000; }
}
///
/// Gets or sets the option to suppress COM elements.
///
/// The option to suppress COM elements.
public bool SuppressCOMElements
{
get { return this.suppressCOMElements; }
set { this.suppressCOMElements = value; }
}
///
/// Gets or sets the option to suppress VB6 COM elements.
///
/// The option to suppress VB6 COM elements.
public bool SuppressVB6COMElements
{
get { return this.suppressVB6COMElements; }
set { this.suppressVB6COMElements = value; }
}
///
/// Mutate a WiX document.
///
/// The Wix document element.
public override void Mutate(Wix.Wix wix)
{
this.components.Clear();
this.directories.Clear();
this.directoryPaths.Clear();
this.filePaths.Clear();
this.files.Clear();
this.registryValues.Clear();
this.payloads.Clear();
// index elements in this wix document
this.IndexElement(wix);
this.MutateDirectories();
this.MutateFiles();
this.MutateRegistryValues();
this.MutatePayloads();
// must occur after all the registry values have been formatted
this.MutateComponents();
}
///
/// Index an element.
///
/// The element to index.
private void IndexElement(Wix.ISchemaElement element)
{
if (element is Wix.Component)
{
// Component elements only need to be indexed if COM registry values will be strongly typed
if (!this.suppressCOMElements)
{
this.components.Add(element);
}
}
else if (element is Wix.Directory)
{
this.directories.Add(element);
}
else if (element is Wix.File)
{
this.files.Add(element);
}
else if (element is Wix.RegistryValue)
{
this.registryValues.Add(element);
}
else if (element is Wix.Payload payloadElement)
{
this.payloads.Add(payloadElement);
}
// index the child elements
if (element is Wix.IParentElement)
{
foreach (Wix.ISchemaElement childElement in ((Wix.IParentElement)element).Children)
{
this.IndexElement(childElement);
}
}
}
///
/// Mutate the components.
///
private void MutateComponents()
{
if (this.suppressVB6COMElements)
{
// Search for VB6 specific COM registrations
foreach (Wix.Component component in this.components)
{
ArrayList vb6RegistryValues = new ArrayList();
foreach (Wix.RegistryValue registryValue in component[typeof(Wix.RegistryValue)])
{
if (Wix.RegistryValue.ActionType.write == registryValue.Action && Wix.RegistryRootType.HKCR == registryValue.Root)
{
string[] parts = registryValue.Key.Split('\\');
if (String.Equals(parts[0], "CLSID", StringComparison.OrdinalIgnoreCase))
{
// Search for the VB6 CLSID {D5DE8D20-5BB8-11D1-A1E3-00A0C90F2731}
if (2 <= parts.Length)
{
if (String.Equals(parts[1], "{D5DE8D20-5BB8-11D1-A1E3-00A0C90F2731}", StringComparison.OrdinalIgnoreCase))
{
if (!vb6RegistryValues.Contains(registryValue))
{
vb6RegistryValues.Add(registryValue);
}
}
}
}
else if (String.Equals(parts[0], "TypeLib", StringComparison.OrdinalIgnoreCase))
{
// Search for the VB6 TypeLibs {EA544A21-C82D-11D1-A3E4-00A0C90AEA82} or {000204EF-0000-0000-C000-000000000046}
if (2 <= parts.Length)
{
if (String.Equals(parts[1], "{EA544A21-C82D-11D1-A3E4-00A0C90AEA82}", StringComparison.OrdinalIgnoreCase) ||
String.Equals(parts[1], "{000204EF-0000-0000-C000-000000000046}", StringComparison.OrdinalIgnoreCase))
{
if (!vb6RegistryValues.Contains(registryValue))
{
vb6RegistryValues.Add(registryValue);
}
}
}
}
else if (String.Equals(parts[0], "Interface", StringComparison.OrdinalIgnoreCase))
{
// Search for any Interfaces that reference the VB6 TypeLibs {EA544A21-C82D-11D1-A3E4-00A0C90AEA82} or {000204EF-0000-0000-C000-000000000046}
if (3 <= parts.Length)
{
if (String.Equals(parts[2], "TypeLib", StringComparison.OrdinalIgnoreCase))
{
if (String.Equals(registryValue.Value, "{EA544A21-C82D-11D1-A3E4-00A0C90AEA82}", StringComparison.OrdinalIgnoreCase) ||
String.Equals(registryValue.Value, "{000204EF-0000-0000-C000-000000000046}", StringComparison.OrdinalIgnoreCase))
{
// Having found a match we have to loop through again finding the matching Interface entries
foreach (Wix.RegistryValue regValue in component[typeof(Wix.RegistryValue)])
{
if (Wix.RegistryValue.ActionType.write == regValue.Action && Wix.RegistryRootType.HKCR == regValue.Root)
{
string[] rvparts = regValue.Key.Split('\\');
if (String.Equals(rvparts[0], "Interface", StringComparison.OrdinalIgnoreCase))
{
if (2 <= rvparts.Length)
{
if (String.Equals(rvparts[1], parts[1], StringComparison.OrdinalIgnoreCase))
{
if (!vb6RegistryValues.Contains(regValue))
{
vb6RegistryValues.Add(regValue);
}
}
}
}
}
}
}
}
}
}
}
}
// Remove all the VB6 specific COM registry values
foreach (Object entry in vb6RegistryValues)
{
component.RemoveChild((Wix.RegistryValue)entry);
}
}
}
foreach (Wix.Component component in this.components)
{
SortedList indexedElements = CollectionsUtil.CreateCaseInsensitiveSortedList();
SortedList indexedRegistryValues = CollectionsUtil.CreateCaseInsensitiveSortedList();
List duplicateRegistryValues = new List();
// index all the File elements
foreach (Wix.File file in component[typeof(Wix.File)])
{
indexedElements.Add(String.Concat("file/", file.Id), file);
}
// group all the registry values by the COM element they would correspond to and
// create a COM element for each group
foreach (Wix.RegistryValue registryValue in component[typeof(Wix.RegistryValue)])
{
if (!String.IsNullOrEmpty(registryValue.Key) && Wix.RegistryValue.ActionType.write == registryValue.Action && Wix.RegistryRootType.HKCR == registryValue.Root && Wix.RegistryValue.TypeType.@string == registryValue.Type)
{
string index = null;
string[] parts = registryValue.Key.Split('\\');
// create a COM element for COM registration and index it
if (1 <= parts.Length)
{
if (String.Equals(parts[0], "AppID", StringComparison.OrdinalIgnoreCase))
{
// only work with GUID AppIds here
if (2 <= parts.Length && parts[1].StartsWith("{", StringComparison.Ordinal) && parts[1].EndsWith("}", StringComparison.Ordinal))
{
index = String.Concat(parts[0], '/', parts[1]);
if (!indexedElements.Contains(index))
{
Wix.AppId appId = new Wix.AppId();
appId.Id = parts[1].ToUpper(CultureInfo.InvariantCulture);
indexedElements.Add(index, appId);
}
}
}
else if (String.Equals(parts[0], "CLSID", StringComparison.OrdinalIgnoreCase))
{
if (2 <= parts.Length)
{
index = String.Concat(parts[0], '/', parts[1]);
if (!indexedElements.Contains(index))
{
Wix.Class wixClass = new Wix.Class();
wixClass.Id = parts[1].ToUpper(CultureInfo.InvariantCulture);
indexedElements.Add(index, wixClass);
}
}
}
else if (String.Equals(parts[0], "Component Categories", StringComparison.OrdinalIgnoreCase))
{
// If this is the .NET Component Category it should not end up in the authoring. Therefore, add
// the registry key to the duplicate list to ensure it gets removed later.
if (String.Equals(parts[1], "{62C8FE65-4EBB-45e7-B440-6E39B2CDBF29}", StringComparison.OrdinalIgnoreCase))
{
duplicateRegistryValues.Add(registryValue);
}
else
{
// TODO: add support for Component Categories to the compiler.
}
}
else if (String.Equals(parts[0], "Interface", StringComparison.OrdinalIgnoreCase))
{
if (2 <= parts.Length)
{
index = String.Concat(parts[0], '/', parts[1]);
if (!indexedElements.Contains(index))
{
Wix.Interface wixInterface = new Wix.Interface();
wixInterface.Id = parts[1].ToUpper(CultureInfo.InvariantCulture);
indexedElements.Add(index, wixInterface);
}
}
}
else if (String.Equals(parts[0], "TypeLib", StringComparison.Ordinal))
{
if (3 <= parts.Length)
{
// use a special index to ensure progIds are processed before classes
index = String.Concat(".typelib/", parts[1], '/', parts[2]);
if (!indexedElements.Contains(index))
{
Version version = TypeLibraryHarvester.ParseHexVersion(parts[2]);
if (version != null)
{
Wix.TypeLib typeLib = new Wix.TypeLib();
typeLib.Id = parts[1].ToUpper(CultureInfo.InvariantCulture);
typeLib.MajorVersion = version.Major;
typeLib.MinorVersion = version.Minor;
indexedElements.Add(index, typeLib);
}
else // not a valid type library registry value
{
index = null;
}
}
}
}
else if (parts[0].StartsWith(".", StringComparison.Ordinal))
{
// extension
}
else // ProgId (hopefully)
{
// use a special index to ensure progIds are processed before classes
index = String.Concat(".progid/", parts[0]);
if (!indexedElements.Contains(index))
{
Wix.ProgId progId = new Wix.ProgId();
progId.Id = parts[0];
indexedElements.Add(index, progId);
}
}
}
// index the RegistryValue element according to the COM element it corresponds to
if (null != index)
{
SortedList registryValues = (SortedList)indexedRegistryValues[index];
if (null == registryValues)
{
registryValues = CollectionsUtil.CreateCaseInsensitiveSortedList();
indexedRegistryValues.Add(index, registryValues);
}
try
{
registryValues.Add(String.Concat(registryValue.Key, '/', registryValue.Name), registryValue);
}
catch (ArgumentException)
{
duplicateRegistryValues.Add(registryValue);
if (String.IsNullOrEmpty(registryValue.Value))
{
this.Core.Messaging.Write(HarvesterWarnings.DuplicateDllRegistryEntry(String.Concat(registryValue.Key, '/', registryValue.Name), component.Id));
}
else
{
this.Core.Messaging.Write(HarvesterWarnings.DuplicateDllRegistryEntry(String.Concat(registryValue.Key, '/', registryValue.Name), registryValue.Value, component.Id));
}
}
}
}
}
foreach (Wix.RegistryValue removeRegistryEntry in duplicateRegistryValues)
{
component.RemoveChild(removeRegistryEntry);
}
// set various values on the COM elements from their corresponding registry values
Hashtable indexedProcessedRegistryValues = new Hashtable();
foreach (DictionaryEntry entry in indexedRegistryValues)
{
Wix.ISchemaElement element = (Wix.ISchemaElement)indexedElements[entry.Key];
string parentIndex = null;
SortedList registryValues = (SortedList)entry.Value;
// element-specific variables (for really tough situations)
string classAppId = null;
bool threadingModelSet = false;
foreach (Wix.RegistryValue registryValue in registryValues.Values)
{
string[] parts = registryValue.Key.ToLower(CultureInfo.InvariantCulture).Split('\\');
bool processed = false;
if (element is Wix.AppId)
{
Wix.AppId appId = (Wix.AppId)element;
if (2 == parts.Length)
{
if (null == registryValue.Name)
{
appId.Description = registryValue.Value;
processed = true;
}
}
}
else if (element is Wix.Class)
{
Wix.Class wixClass = (Wix.Class)element;
if (2 == parts.Length)
{
if (null == registryValue.Name)
{
wixClass.Description = registryValue.Value;
processed = true;
}
else if (String.Equals(registryValue.Name, "AppID", StringComparison.OrdinalIgnoreCase))
{
classAppId = registryValue.Value;
processed = true;
}
}
else if (3 == parts.Length)
{
Wix.Class.ContextType contextType = Wix.Class.ContextType.None;
switch (parts[2])
{
case "control":
wixClass.Control = Wix.YesNoType.yes;
processed = true;
break;
case "inprochandler":
if (null == registryValue.Name)
{
if (null == wixClass.Handler)
{
wixClass.Handler = "1";
processed = true;
}
else if ("2" == wixClass.Handler)
{
wixClass.Handler = "3";
processed = true;
}
}
break;
case "inprochandler32":
if (null == registryValue.Name)
{
if (null == wixClass.Handler)
{
wixClass.Handler = "2";
processed = true;
}
else if ("1" == wixClass.Handler)
{
wixClass.Handler = "3";
processed = true;
}
}
break;
case "inprocserver":
contextType = Wix.Class.ContextType.InprocServer;
break;
case "inprocserver32":
contextType = Wix.Class.ContextType.InprocServer32;
break;
case "insertable":
wixClass.Insertable = Wix.YesNoType.yes;
processed = true;
break;
case "localserver":
contextType = Wix.Class.ContextType.LocalServer;
break;
case "localserver32":
contextType = Wix.Class.ContextType.LocalServer32;
break;
case "progid":
if (null == registryValue.Name)
{
Wix.ProgId progId = (Wix.ProgId)indexedElements[String.Concat(".progid/", registryValue.Value)];
// verify that the versioned ProgId appears under this Class element
// if not, toss the entire element
if (null == progId || wixClass != progId.ParentElement)
{
element = null;
}
else
{
processed = true;
}
}
break;
case "programmable":
wixClass.Programmable = Wix.YesNoType.yes;
processed = true;
break;
case "typelib":
if (null == registryValue.Name)
{
foreach (DictionaryEntry indexedEntry in indexedElements)
{
string key = (string)indexedEntry.Key;
Wix.ISchemaElement possibleTypeLib = (Wix.ISchemaElement)indexedEntry.Value;
if (key.StartsWith(".typelib/", StringComparison.Ordinal) &&
0 == String.Compare(key, 9, registryValue.Value, 0, registryValue.Value.Length, StringComparison.OrdinalIgnoreCase))
{
// ensure the TypeLib is nested under the same thing we want the Class under
if (null == parentIndex || indexedElements[parentIndex] == possibleTypeLib.ParentElement)
{
parentIndex = key;
processed = true;
}
}
}
}
break;
case "version":
if (null == registryValue.Name)
{
wixClass.Version = registryValue.Value;
processed = true;
}
break;
case "versionindependentprogid":
if (null == registryValue.Name)
{
Wix.ProgId progId = (Wix.ProgId)indexedElements[String.Concat(".progid/", registryValue.Value)];
// verify that the version independent ProgId appears somewhere
// under this Class element - if not, toss the entire element
if (null == progId || wixClass != progId.ParentElement)
{
// check the parent of the parent
if (null == progId || null == progId.ParentElement || wixClass != progId.ParentElement.ParentElement)
{
element = null;
}
}
processed = true;
}
break;
}
if (Wix.Class.ContextType.None != contextType)
{
wixClass.Context |= contextType;
if (null == registryValue.Name)
{
if ((registryValue.Value.StartsWith("[!", StringComparison.Ordinal) || registryValue.Value.StartsWith("[#", StringComparison.Ordinal))
&& registryValue.Value.EndsWith("]", StringComparison.Ordinal))
{
parentIndex = String.Concat("file/", registryValue.Value.Substring(2, registryValue.Value.Length - 3));
processed = true;
}
else if (String.Equals(Path.GetFileName(registryValue.Value), "mscoree.dll", StringComparison.OrdinalIgnoreCase))
{
wixClass.ForeignServer = "mscoree.dll";
processed = true;
}
else if (String.Equals(Path.GetFileName(registryValue.Value), "msvbvm60.dll", StringComparison.OrdinalIgnoreCase))
{
wixClass.ForeignServer = "msvbvm60.dll";
processed = true;
}
else
{
// Some servers are specifying relative paths (which the above code doesn't find)
// If there's any ambiguity leave it alone and let the developer figure it out when it breaks in the compiler
bool possibleDuplicate = false;
string possibleParentIndex = null;
foreach (Wix.File file in this.files)
{
if (String.Equals(registryValue.Value, Path.GetFileName(file.Source), StringComparison.OrdinalIgnoreCase))
{
if (null == possibleParentIndex)
{
possibleParentIndex = String.Concat("file/", file.Id);
}
else
{
possibleDuplicate = true;
break;
}
}
}
if (!possibleDuplicate)
{
if (null == possibleParentIndex)
{
wixClass.ForeignServer = registryValue.Value;
processed = true;
}
else
{
parentIndex = possibleParentIndex;
wixClass.RelativePath = Wix.YesNoType.yes;
processed = true;
}
}
}
}
else if (String.Equals(registryValue.Name, "ThreadingModel", StringComparison.OrdinalIgnoreCase))
{
Wix.Class.ThreadingModelType threadingModel;
if (String.Equals(registryValue.Value, "apartment", StringComparison.OrdinalIgnoreCase))
{
threadingModel = Wix.Class.ThreadingModelType.apartment;
processed = true;
}
else if (String.Equals(registryValue.Value, "both", StringComparison.OrdinalIgnoreCase))
{
threadingModel = Wix.Class.ThreadingModelType.both;
processed = true;
}
else if (String.Equals(registryValue.Value, "free", StringComparison.OrdinalIgnoreCase))
{
threadingModel = Wix.Class.ThreadingModelType.free;
processed = true;
}
else if (String.Equals(registryValue.Value, "neutral", StringComparison.OrdinalIgnoreCase))
{
threadingModel = Wix.Class.ThreadingModelType.neutral;
processed = true;
}
else if (String.Equals(registryValue.Value, "rental", StringComparison.OrdinalIgnoreCase))
{
threadingModel = Wix.Class.ThreadingModelType.rental;
processed = true;
}
else if (String.Equals(registryValue.Value, "single", StringComparison.OrdinalIgnoreCase))
{
threadingModel = Wix.Class.ThreadingModelType.single;
processed = true;
}
else
{
continue;
}
if (!threadingModelSet || wixClass.ThreadingModel == threadingModel)
{
wixClass.ThreadingModel = threadingModel;
threadingModelSet = true;
}
else
{
element = null;
break;
}
}
}
}
else if (4 == parts.Length)
{
if (String.Equals(parts[2], "implemented categories", StringComparison.Ordinal))
{
switch (parts[3])
{
case "{7dd95801-9882-11cf-9fa9-00aa006c42c4}":
wixClass.SafeForScripting = Wix.YesNoType.yes;
processed = true;
break;
case "{7dd95802-9882-11cf-9fa9-00aa006c42c4}":
wixClass.SafeForInitializing = Wix.YesNoType.yes;
processed = true;
break;
}
}
}
}
else if (element is Wix.Interface)
{
Wix.Interface wixInterface = (Wix.Interface)element;
if (2 == parts.Length && null == registryValue.Name)
{
wixInterface.Name = registryValue.Value;
processed = true;
}
else if (3 == parts.Length)
{
switch (parts[2])
{
case "proxystubclsid":
if (null == registryValue.Name)
{
wixInterface.ProxyStubClassId = registryValue.Value.ToUpper(CultureInfo.InvariantCulture);
processed = true;
}
break;
case "proxystubclsid32":
if (null == registryValue.Name)
{
wixInterface.ProxyStubClassId32 = registryValue.Value.ToUpper(CultureInfo.InvariantCulture);
processed = true;
}
break;
case "nummethods":
if (null == registryValue.Name)
{
wixInterface.NumMethods = Convert.ToInt32(registryValue.Value, CultureInfo.InvariantCulture);
processed = true;
}
break;
case "typelib":
if (String.Equals("Version", registryValue.Name, StringComparison.OrdinalIgnoreCase))
{
parentIndex = String.Concat(parentIndex, registryValue.Value);
processed = true;
}
else if (null == registryValue.Name) // TypeLib guid
{
parentIndex = String.Concat(".typelib/", registryValue.Value, '/', parentIndex);
processed = true;
}
break;
}
}
}
else if (element is Wix.ProgId)
{
Wix.ProgId progId = (Wix.ProgId)element;
if (null == registryValue.Name)
{
if (1 == parts.Length)
{
progId.Description = registryValue.Value;
processed = true;
}
else if (2 == parts.Length)
{
if (String.Equals(parts[1], "CLSID", StringComparison.OrdinalIgnoreCase))
{
parentIndex = String.Concat("CLSID/", registryValue.Value);
processed = true;
}
else if (String.Equals(parts[1], "CurVer", StringComparison.OrdinalIgnoreCase))
{
// If a progId points to its own ProgId with CurVer, it isn't meaningful, so ignore it
if (!String.Equals(progId.Id, registryValue.Value, StringComparison.OrdinalIgnoreCase))
{
// this registry value should usually be processed second so the
// version independent ProgId should be under the versioned one
parentIndex = String.Concat(".progid/", registryValue.Value);
processed = true;
}
}
}
}
}
else if (element is Wix.TypeLib)
{
Wix.TypeLib typeLib = (Wix.TypeLib)element;
if (null == registryValue.Name)
{
if (3 == parts.Length)
{
typeLib.Description = registryValue.Value;
processed = true;
}
else if (4 == parts.Length)
{
if (String.Equals(parts[3], "flags", StringComparison.OrdinalIgnoreCase))
{
int flags = Convert.ToInt32(registryValue.Value, CultureInfo.InvariantCulture);
if (0x1 == (flags & 0x1))
{
typeLib.Restricted = Wix.YesNoType.yes;
}
if (0x2 == (flags & 0x2))
{
typeLib.Control = Wix.YesNoType.yes;
}
if (0x4 == (flags & 0x4))
{
typeLib.Hidden = Wix.YesNoType.yes;
}
if (0x8 == (flags & 0x8))
{
typeLib.HasDiskImage = Wix.YesNoType.yes;
}
processed = true;
}
else if (String.Equals(parts[3], "helpdir", StringComparison.OrdinalIgnoreCase))
{
if (registryValue.Value.StartsWith("[", StringComparison.Ordinal) && (registryValue.Value.EndsWith("]", StringComparison.Ordinal)
|| registryValue.Value.EndsWith("]\\", StringComparison.Ordinal)))
{
typeLib.HelpDirectory = registryValue.Value.Substring(1, registryValue.Value.LastIndexOf(']') - 1);
}
else if (0 == String.Compare(registryValue.Value, Environment.SystemDirectory, StringComparison.OrdinalIgnoreCase)) // VB6 DLLs register their help directory as SystemFolder
{
typeLib.HelpDirectory = "SystemFolder";
}
else if (null != component.Directory) // -sfrag has not been specified
{
typeLib.HelpDirectory = component.Directory;
}
else if (component.ParentElement is Wix.Directory) // -sfrag has been specified
{
typeLib.HelpDirectory = ((Wix.Directory)component.ParentElement).Id;
}
else if (component.ParentElement is Wix.DirectoryRef) // -sfrag has been specified
{
typeLib.HelpDirectory = ((Wix.DirectoryRef)component.ParentElement).Id;
}
//If the helpdir has not matched a known directory, drop it because it cannot be resolved.
processed = true;
}
}
else if (5 == parts.Length && (String.Equals("win32", parts[4], StringComparison.OrdinalIgnoreCase) || String.Equals("win64", parts[4], StringComparison.OrdinalIgnoreCase)))
{
typeLib.Language = Convert.ToInt32(parts[3], CultureInfo.InvariantCulture);
if ((registryValue.Value.StartsWith("[!", StringComparison.Ordinal) || registryValue.Value.StartsWith("[#", StringComparison.Ordinal))
&& registryValue.Value.EndsWith("]", StringComparison.Ordinal))
{
parentIndex = String.Concat("file/", registryValue.Value.Substring(2, registryValue.Value.Length - 3));
}
processed = true;
}
}
}
// index the processed registry values by their corresponding COM element
if (processed)
{
indexedProcessedRegistryValues.Add(registryValue, element);
}
}
// parent the COM element
if (null != element)
{
if (null != parentIndex)
{
Wix.IParentElement parentElement = (Wix.IParentElement)indexedElements[parentIndex];
if (null != parentElement)
{
parentElement.AddChild(element);
}
}
else if (0 < indexedProcessedRegistryValues.Count)
{
component.AddChild(element);
}
// special handling for AppID since it doesn't fit the general model
if (null != classAppId)
{
Wix.AppId appId = (Wix.AppId)indexedElements[String.Concat("AppID/", classAppId)];
// move the Class element under the AppId (and put the AppId under its old parent)
if (null != appId)
{
// move the AppId element
((Wix.IParentElement)appId.ParentElement).RemoveChild(appId);
((Wix.IParentElement)element.ParentElement).AddChild(appId);
// move the Class element
((Wix.IParentElement)element.ParentElement).RemoveChild(element);
appId.AddChild(element);
}
}
}
}
// remove the RegistryValue elements which were converted into COM elements
// that were successfully nested under the Component element
foreach (DictionaryEntry entry in indexedProcessedRegistryValues)
{
Wix.ISchemaElement element = (Wix.ISchemaElement)entry.Value;
Wix.RegistryValue registryValue = (Wix.RegistryValue)entry.Key;
while (null != element)
{
if (element == component)
{
((Wix.IParentElement)registryValue.ParentElement).RemoveChild(registryValue);
break;
}
element = element.ParentElement;
}
}
}
}
///
/// Mutate the directories.
///
private void MutateDirectories()
{
foreach (Wix.Directory directory in this.directories)
{
string path = directory.FileSource;
// create a new directory element without the FileSource attribute
if (null != path)
{
Wix.Directory newDirectory = new Wix.Directory();
newDirectory.Id = directory.Id;
newDirectory.Name = directory.Name;
foreach (Wix.ISchemaElement element in directory.Children)
{
newDirectory.AddChild(element);
}
((Wix.IParentElement)directory.ParentElement).AddChild(newDirectory);
((Wix.IParentElement)directory.ParentElement).RemoveChild(directory);
if (null != newDirectory.Id)
{
this.directoryPaths[path.ToLower(CultureInfo.InvariantCulture)] = String.Concat("[", newDirectory.Id, "]");
}
}
}
}
///
/// Mutate the files.
///
private void MutateFiles()
{
string sourceDirSubstitution = this.preprocessorVariable;
if (sourceDirSubstitution != null)
{
string prefix = "$(";
if (sourceDirSubstitution.StartsWith("wix.", StringComparison.Ordinal))
{
prefix = "!(";
}
sourceDirSubstitution = String.Concat(prefix, sourceDirSubstitution, ")");
}
foreach (Wix.File file in this.files)
{
if (null != file.Id && null != file.Source)
{
string fileSource = this.Core.ResolveFilePath(file.Source);
// index the long path
this.filePaths[fileSource.ToLower(CultureInfo.InvariantCulture)] = String.Concat("[#", file.Id, "]");
// index the long path as a URL for assembly harvesting
Uri fileUri = new Uri(fileSource);
this.filePaths[fileUri.ToString().ToLower(CultureInfo.InvariantCulture)] = String.Concat("file:///[#", file.Id, "]");
// index the short path
string shortPath = NativeMethods.GetShortPathName(fileSource);
this.filePaths[shortPath.ToLower(CultureInfo.InvariantCulture)] = String.Concat("[!", file.Id, "]");
// escape literal $ characters
file.Source = file.Source.Replace("$", "$$");
if (null != sourceDirSubstitution && file.Source.StartsWith("SourceDir\\", StringComparison.Ordinal))
{
file.Source = file.Source.Substring(9).Insert(0, sourceDirSubstitution);
}
}
}
}
///
/// Mutate the payloads.
///
private void MutatePayloads()
{
string sourceDirSubstitution = this.preprocessorVariable;
if (sourceDirSubstitution == null)
{
return;
}
string prefix = "$(";
if (sourceDirSubstitution.StartsWith("wix.", StringComparison.Ordinal))
{
prefix = "!(";
}
sourceDirSubstitution = String.Concat(prefix, sourceDirSubstitution, ")");
foreach (var payload in this.payloads)
{
if (payload.SourceFile != null && payload.SourceFile.StartsWith("SourceDir\\", StringComparison.Ordinal))
{
payload.SourceFile = payload.SourceFile.Substring(9).Insert(0, sourceDirSubstitution);
}
}
}
///
/// Mutate an individual registry string, according to a collection of replacement items.
///
/// The string to mutate.
/// The collection of items to replace within the string.
/// The mutated registry string.
private string MutateRegistryString(string value, ICollection replace)
{
int index;
string lowercaseValue = value.ToLower(CultureInfo.InvariantCulture);
foreach (DictionaryEntry entry in replace)
{
while (0 <= (index = lowercaseValue.IndexOf((string)entry.Key, StringComparison.Ordinal)))
{
value = value.Remove(index, ((string)entry.Key).Length);
value = value.Insert(index, (string)entry.Value);
lowercaseValue = value.ToLower(CultureInfo.InvariantCulture);
}
}
return value;
}
///
/// Mutate the registry values.
///
private void MutateRegistryValues()
{
if (this.SuppressVB6COMElements && this.SuppressCOMElements)
{
var vb6RegistryValues = new List();
foreach (Wix.RegistryValue registryValue in this.registryValues)
{
if (IsVb6RegistryValue(registryValue))
{
if (!vb6RegistryValues.Contains(registryValue))
{
vb6RegistryValues.Add(registryValue);
}
}
}
// Remove all the VB6 specific COM registry values
foreach (var reg in vb6RegistryValues)
{
if (reg.ParentElement is Wix.Component component)
{
component.RemoveChild(reg);
}
this.registryValues.Remove(reg);
}
}
ArrayList reversedDirectoryPaths = new ArrayList();
// reverse the indexed directory paths to ensure the longest paths are found first
foreach (DictionaryEntry entry in this.directoryPaths)
{
reversedDirectoryPaths.Insert(0, entry);
}
foreach (Wix.RegistryValue registryValue in this.registryValues)
{
// Multi-string values are stored as children - their "Value" member is null
if (Wix.RegistryValue.TypeType.multiString == registryValue.Type)
{
foreach (Wix.MultiStringValue multiStringValue in registryValue.Children)
{
// first replace file paths with their MSI tokens
multiStringValue.Value = this.MutateRegistryString(multiStringValue.Value, (ICollection)this.filePaths);
// next replace directory paths with their MSI tokens
multiStringValue.Value = this.MutateRegistryString(multiStringValue.Value, (ICollection)reversedDirectoryPaths);
}
}
else
{
// first replace file paths with their MSI tokens
registryValue.Value = this.MutateRegistryString(registryValue.Value, (ICollection)this.filePaths);
// next replace directory paths with their MSI tokens
registryValue.Value = this.MutateRegistryString(registryValue.Value, (ICollection)reversedDirectoryPaths);
}
}
}
private static bool IsVb6RegistryValue(Wix.RegistryValue registryValue)
{
if (Wix.RegistryValue.ActionType.write == registryValue.Action && Wix.RegistryRootType.HKCR == registryValue.Root)
{
string[] parts = registryValue.Key.Split('\\');
if (String.Equals(parts[0], "CLSID", StringComparison.OrdinalIgnoreCase))
{
// Search for the VB6 CLSID {D5DE8D20-5BB8-11D1-A1E3-00A0C90F2731}
if (2 <= parts.Length)
{
if (String.Equals(parts[1], "{D5DE8D20-5BB8-11D1-A1E3-00A0C90F2731}", StringComparison.OrdinalIgnoreCase))
{
return true;
}
}
}
else if (String.Equals(parts[0], "TypeLib", StringComparison.OrdinalIgnoreCase))
{
// Search for the VB6 TypeLibs {EA544A21-C82D-11D1-A3E4-00A0C90AEA82} or {000204EF-0000-0000-C000-000000000046}
if (2 <= parts.Length)
{
if (String.Equals(parts[1], "{EA544A21-C82D-11D1-A3E4-00A0C90AEA82}", StringComparison.OrdinalIgnoreCase) ||
String.Equals(parts[1], "{000204EF-0000-0000-C000-000000000046}", StringComparison.OrdinalIgnoreCase))
{
return true;
}
}
}
else if (String.Equals(parts[0], "Interface", StringComparison.OrdinalIgnoreCase))
{
// Search for any Interfaces that reference the VB6 TypeLibs {EA544A21-C82D-11D1-A3E4-00A0C90AEA82} or {000204EF-0000-0000-C000-000000000046}
if (3 <= parts.Length)
{
if (String.Equals(parts[2], "TypeLib", StringComparison.OrdinalIgnoreCase))
{
if (String.Equals(registryValue.Value, "{EA544A21-C82D-11D1-A3E4-00A0C90AEA82}", StringComparison.OrdinalIgnoreCase) ||
String.Equals(registryValue.Value, "{000204EF-0000-0000-C000-000000000046}", StringComparison.OrdinalIgnoreCase))
{
return true;
}
}
}
}
}
return false;
}
///
/// The native methods for grabbing machine-specific short file paths.
///
private class NativeMethods
{
///
/// Gets the short name for a file.
///
/// Fullpath to file on disk.
/// Short name for file.
internal static string GetShortPathName(string fullPath)
{
var bufferSize = (int)GetShortPathName(fullPath, null, 0);
if (0 == bufferSize)
{
int err = System.Runtime.InteropServices.Marshal.GetLastWin32Error();
throw new System.Runtime.InteropServices.COMException(String.Concat("Failed to get short path buffer size for file: ", fullPath), err);
}
bufferSize += 1;
var shortPath = new StringBuilder(bufferSize, bufferSize);
uint result = GetShortPathName(fullPath, shortPath, bufferSize);
if (0 == result)
{
int err = System.Runtime.InteropServices.Marshal.GetLastWin32Error();
throw new System.Runtime.InteropServices.COMException(String.Concat("Failed to get short path name for file: ", fullPath), err);
}
return shortPath.ToString();
}
///
/// Gets the short name for a file.
///
/// Long path to convert to short path.
/// Short path from long path.
/// Size of short path.
/// zero if success.
[DllImport("kernel32.dll", EntryPoint = "GetShortPathNameW", CharSet = CharSet.Unicode, ExactSpelling = true, SetLastError = true)]
internal static extern uint GetShortPathName(string longPath, StringBuilder shortPath, [MarshalAs(UnmanagedType.U4)]int buffer);
}
}
}