// 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.WindowsInstaller.Decompile
{
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Xml.Linq;
using WixToolset.Data;
using WixToolset.Data.Symbols;
using WixToolset.Data.WindowsInstaller;
using WixToolset.Data.WindowsInstaller.Rows;
using WixToolset.Extensibility;
using WixToolset.Extensibility.Services;
///
/// Decompiles an msi database into WiX source.
///
internal class Decompiler
{
private static readonly Regex NullSplitter = new Regex(@"\[~]");
// NameToBit arrays
private static readonly string[] TextControlAttributes = { "Transparent", "NoPrefix", "NoWrap", "FormatSize", "UserLanguage" };
private static readonly string[] HyperlinkControlAttributes = { "Transparent" };
private static readonly string[] EditControlAttributes = { "Multiline", null, null, null, null, "Password" };
private static readonly string[] ProgressControlAttributes = { "ProgressBlocks" };
private static readonly string[] VolumeControlAttributes = { "Removable", "Fixed", "Remote", "CDROM", "RAMDisk", "Floppy", "ShowRollbackCost" };
private static readonly string[] ListboxControlAttributes = { "Sorted", null, null, null, "UserLanguage" };
private static readonly string[] ListviewControlAttributes = { "Sorted", null, null, null, "FixedSize", "Icon16", "Icon32" };
private static readonly string[] ComboboxControlAttributes = { "Sorted", "ComboList", null, null, "UserLanguage" };
private static readonly string[] RadioControlAttributes = { "Image", "PushLike", "Bitmap", "Icon", "FixedSize", "Icon16", "Icon32", null, "HasBorder" };
private static readonly string[] ButtonControlAttributes = { "Image", null, "Bitmap", "Icon", "FixedSize", "Icon16", "Icon32", "ElevationShield" };
private static readonly string[] IconControlAttributes = { "Image", null, null, null, "FixedSize", "Icon16", "Icon32" };
private static readonly string[] BitmapControlAttributes = { "Image", null, null, null, "FixedSize" };
private static readonly string[] CheckboxControlAttributes = { null, "PushLike", "Bitmap", "Icon", "FixedSize", "Icon16", "Icon32" };
private XElement uiElement;
///
/// Creates a new decompiler object with a default set of table definitions.
///
public Decompiler(IMessaging messaging, IBackendHelper backendHelper, IEnumerable extensions, string baseSourcePath, bool suppressCustomTables, bool suppressDroppingEmptyTables, bool suppressUI, bool treatProductAsModule)
{
this.Messaging = messaging;
this.BackendHelper = backendHelper;
this.Extensions = extensions;
this.BaseSourcePath = baseSourcePath ?? "SourceDir";
this.SuppressCustomTables = suppressCustomTables;
this.SuppressDroppingEmptyTables = suppressDroppingEmptyTables;
this.SuppressUI = suppressUI;
this.TreatProductAsModule = treatProductAsModule;
this.ExtensionsByTableName = new Dictionary();
this.StandardActions = WindowsInstallerStandard.StandardActions().ToDictionary(a => a.Id.Id);
this.TableDefinitions = new TableDefinitionCollection();
}
private IMessaging Messaging { get; }
private IBackendHelper BackendHelper { get; }
private IEnumerable Extensions { get; }
private Dictionary ExtensionsByTableName { get; }
private string BaseSourcePath { get; }
private bool SuppressCustomTables { get; }
private bool SuppressDroppingEmptyTables { get; }
private bool SuppressRelativeActionSequencing { get; }
private bool SuppressUI { get; }
private bool TreatProductAsModule { get; }
private OutputType OutputType { get; set; }
private Dictionary StandardActions { get; }
private bool Compressed { get; set; }
private XElement RootElement { get; set; }
private TableDefinitionCollection TableDefinitions { get; }
private bool ShortNames { get; set; }
private string ModularizationGuid { get; set; }
private XElement UIElement
{
get
{
if (null == this.uiElement)
{
this.uiElement = new XElement(Names.UIElement);
this.RootElement.Add(this.uiElement);
}
return this.uiElement;
}
}
private Dictionary Singletons { get; } = new Dictionary();
private Dictionary IndexedElements { get; } = new Dictionary();
private Dictionary PatchTargetFiles { get; } = new Dictionary();
///
/// Decompile the database file.
///
/// The output to decompile.
/// The serialized WiX source code.
public XDocument Decompile(WindowsInstallerData output)
{
if (null == output)
{
throw new ArgumentNullException(nameof(output));
}
this.OutputType = output.Type;
// collect the table definitions from the output
this.TableDefinitions.Clear();
foreach (var table in output.Tables)
{
this.TableDefinitions.Add(table.Definition);
}
// add any missing standard and wix-specific table definitions
foreach (var tableDefinition in WindowsInstallerTableDefinitions.All)
{
if (!this.TableDefinitions.Contains(tableDefinition.Name))
{
this.TableDefinitions.Add(tableDefinition);
}
}
// add any missing extension table definitions
#if TODO_DECOMPILER_EXTENSIONS
foreach (var extension in this.Extensions)
{
this.AddExtension(extension);
}
#endif
switch (this.OutputType)
{
case OutputType.Module:
this.RootElement = new XElement(Names.ModuleElement);
break;
case OutputType.PatchCreation:
this.RootElement = new XElement(Names.PatchCreationElement);
break;
case OutputType.Product:
this.RootElement = new XElement(Names.PackageElement);
break;
default:
throw new InvalidOperationException("Unknown output type.");
}
var xWix = new XElement(Names.WixElement, this.RootElement);
// try to decompile the database file
// stop processing if an error previously occurred
if (this.Messaging.EncounteredError)
{
return null;
}
this.InitializeDecompile(output.Tables, output.Codepage);
// stop processing if an error previously occurred
if (this.Messaging.EncounteredError)
{
return null;
}
// decompile the tables
this.DecompileTables(output);
// finalize the decompiler and its extensions
this.FinalizeDecompile(output.Tables);
// return the XML document only if decompilation completed successfully
var document = new XDocument(xWix);
return this.Messaging.EncounteredError ? null : document;
}
#if TODO_DECOMPILER_EXTENSIONS
private void AddExtension(IWindowsInstallerBackendDecompilerExtension extension)
{
if (null != extension.TableDefinitions)
{
foreach (TableDefinition tableDefinition in extension.TableDefinitions)
{
if (!this.ExtensionsByTableName.ContainsKey(tableDefinition.Name))
{
this.ExtensionsByTableName.Add(tableDefinition.Name, extension);
}
else
{
this.Messaging.Write(ErrorMessages.DuplicateExtensionTable(extension.GetType().ToString(), tableDefinition.Name));
}
}
}
}
#endif
internal static Platform? GetPlatformFromTemplateSummaryInformation(string[] template)
{
if (null != template && 1 < template.Length && null != template[0] && 0 < template[0].Length)
{
switch (template[0])
{
case "Intel":
return Platform.X86;
case "x64":
return Platform.X64;
case "Arm64":
return Platform.ARM64;
}
}
return null;
}
///
/// Gets the element corresponding to the row it came from.
///
/// The row corresponding to the element.
/// The indexed element.
private XElement GetIndexedElement(WixToolset.Data.WindowsInstaller.Row row) => this.GetIndexedElement(row.TableDefinition.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter));
///
/// Gets the element corresponding to the primary key of the given table.
///
/// The table corresponding to the element.
/// The primary key corresponding to the element.
/// The indexed element.
private XElement GetIndexedElement(string table, params string[] primaryKey) => this.IndexedElements[String.Concat(table, ':', String.Join(DecompilerConstants.PrimaryKeyDelimiterString, primaryKey))];
///
/// Tries to get the element corresponding to the primary key of the given table.
///
/// The table corresponding to the element.
/// The indexed element.
/// Whether the element was found.
private bool TryGetIndexedElement(WixToolset.Data.WindowsInstaller.Row row, out XElement xElement) => this.TryGetIndexedElement(row.TableDefinition.Name, out xElement, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter));
///
/// Tries to get the element corresponding to the primary key of the given table.
///
/// The table corresponding to the element.
/// The indexed element.
/// The primary key corresponding to the element.
/// Whether the element was found.
private bool TryGetIndexedElement(string table, out XElement xElement, params string[] primaryKey) => this.IndexedElements.TryGetValue(String.Concat(table, ':', String.Join(DecompilerConstants.PrimaryKeyDelimiterString, primaryKey)), out xElement);
///
/// Index an element by its corresponding row.
///
/// The row corresponding to the element.
/// The element to index.
private void IndexElement(WixToolset.Data.WindowsInstaller.Row row, XElement element)
{
this.IndexedElements.Add(String.Concat(row.TableDefinition.Name, ':', row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter)), element);
}
///
/// Index an element by its corresponding row.
///
/// The element to index.
///
///
private void IndexElement(XElement element, string table, params string[] primaryKey)
{
this.IndexedElements.Add(String.Concat(table, ':', String.Join(DecompilerConstants.PrimaryKeyDelimiterString, primaryKey)), element);
}
private Dictionary> IndexTableOneToMany(IEnumerable rows, int column = 0)
{
return rows
.ToLookup(row => row.FieldAsString(column), row => this.GetIndexedElement(row))
.ToDictionary(lookup => lookup.Key, lookup => lookup.ToList());
}
private Dictionary> IndexTableOneToMany(TableIndexedCollection tables, string tableName, int column = 0) => this.IndexTableOneToMany(tables[tableName]?.Rows ?? Enumerable.Empty(), column);
private Dictionary> IndexTableOneToMany(Table table, int column = 0) => this.IndexTableOneToMany(table?.Rows ?? Enumerable.Empty(), column);
private void AddChildToParent(string parentName, XElement xChild, Row row, int column)
{
var key = row.FieldAsString(column);
if (this.TryGetIndexedElement(parentName, out var xParent, key))
{
xParent.Add(xChild);
}
else
{
this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, row.Table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), row.Fields[column].Column.Name, key, parentName));
}
}
private static XAttribute XAttributeIfNotNull(string attributeName, Row row, int column) => row.IsColumnNull(column) ? null : new XAttribute(attributeName, row.FieldAsString(column));
private static void SetAttributeIfNotNull(XElement xElement, string attributeName, string value)
{
if (!String.IsNullOrEmpty(value))
{
xElement.SetAttributeValue(attributeName, value);
}
}
private static void SetAttributeIfNotNull(XElement xElement, string attributeName, int? value)
{
if (value.HasValue)
{
xElement.SetAttributeValue(attributeName, value);
}
}
///
/// Convert an Int32 into a DateTime.
///
/// The Int32 value.
/// The DateTime.
private static DateTime ConvertIntegerToDateTime(int value)
{
var date = value / 65536;
var time = value % 65536;
return new DateTime(1980 + (date / 512), (date % 512) / 32, date % 32, time / 2048, (time % 2048) / 32, (time % 32) * 2);
}
///
/// Set the common control attributes in a control element.
///
/// The control attributes.
/// The control element.
private static void SetControlAttributes(int attributes, XElement xControl)
{
if (0 == (attributes & WindowsInstallerConstants.MsidbControlAttributesEnabled))
{
xControl.SetAttributeValue("Disabled", "yes");
}
if (WindowsInstallerConstants.MsidbControlAttributesIndirect == (attributes & WindowsInstallerConstants.MsidbControlAttributesIndirect))
{
xControl.SetAttributeValue("Indirect", "yes");
}
if (WindowsInstallerConstants.MsidbControlAttributesInteger == (attributes & WindowsInstallerConstants.MsidbControlAttributesInteger))
{
xControl.SetAttributeValue("Integer", "yes");
}
if (WindowsInstallerConstants.MsidbControlAttributesLeftScroll == (attributes & WindowsInstallerConstants.MsidbControlAttributesLeftScroll))
{
xControl.SetAttributeValue("LeftScroll", "yes");
}
if (WindowsInstallerConstants.MsidbControlAttributesRightAligned == (attributes & WindowsInstallerConstants.MsidbControlAttributesRightAligned))
{
xControl.SetAttributeValue("RightAligned", "yes");
}
if (WindowsInstallerConstants.MsidbControlAttributesRTLRO == (attributes & WindowsInstallerConstants.MsidbControlAttributesRTLRO))
{
xControl.SetAttributeValue("RightToLeft", "yes");
}
if (WindowsInstallerConstants.MsidbControlAttributesSunken == (attributes & WindowsInstallerConstants.MsidbControlAttributesSunken))
{
xControl.SetAttributeValue("Sunken", "yes");
}
if (0 == (attributes & WindowsInstallerConstants.MsidbControlAttributesVisible))
{
xControl.SetAttributeValue("Hidden", "yes");
}
}
///
/// Creates an action element.
///
/// The action from which the element should be created.
private void CreateActionElement(WixActionSymbol actionSymbol)
{
XElement xAction;
if (this.TryGetIndexedElement("CustomAction", out var _, actionSymbol.Action)) // custom action
{
xAction = new XElement(Names.CustomElement,
new XAttribute("Action", actionSymbol.Action),
String.IsNullOrEmpty(actionSymbol.Condition) ? null : new XAttribute("Condition", actionSymbol.Condition));
switch (actionSymbol.Sequence)
{
case (-4):
xAction.SetAttributeValue("OnExit", "suspend");
break;
case (-3):
xAction.SetAttributeValue("OnExit", "error");
break;
case (-2):
xAction.SetAttributeValue("OnExit", "cancel");
break;
case (-1):
xAction.SetAttributeValue("OnExit", "success");
break;
default:
if (null != actionSymbol.Before)
{
xAction.SetAttributeValue("Before", actionSymbol.Before);
}
else if (null != actionSymbol.After)
{
xAction.SetAttributeValue("After", actionSymbol.After);
}
else if (actionSymbol.Sequence.HasValue)
{
xAction.SetAttributeValue("Sequence", actionSymbol.Sequence.Value);
}
break;
}
}
else if (this.TryGetIndexedElement("Dialog", out var _, actionSymbol.Action)) // dialog
{
xAction = new XElement(Names.CustomElement,
new XAttribute("Dialog", actionSymbol.Action),
new XAttribute("Condition", actionSymbol.Condition));
switch (actionSymbol.Sequence)
{
case (-4):
xAction.SetAttributeValue("OnExit", "suspend");
break;
case (-3):
xAction.SetAttributeValue("OnExit", "error");
break;
case (-2):
xAction.SetAttributeValue("OnExit", "cancel");
break;
case (-1):
xAction.SetAttributeValue("OnExit", "success");
break;
default:
SetAttributeIfNotNull(xAction, "Before", actionSymbol.Before);
SetAttributeIfNotNull(xAction, "After", actionSymbol.After);
SetAttributeIfNotNull(xAction, "Sequence", actionSymbol.Sequence);
break;
}
}
else // possibly a standard action without suggested sequence information
{
xAction = this.CreateStandardActionElement(actionSymbol);
}
// add the action element to the appropriate sequence element
if (null != xAction)
{
var sequenceTable = actionSymbol.SequenceTable.ToString();
if (!this.Singletons.TryGetValue(sequenceTable, out var xSequence))
{
xSequence = new XElement(Names.WxsNamespace + sequenceTable);
this.RootElement.Add(xSequence);
this.Singletons.Add(sequenceTable, xSequence);
}
try
{
xSequence.Add(xAction);
}
catch (ArgumentException) // action/dialog is not valid for this sequence
{
this.Messaging.Write(WarningMessages.IllegalActionInSequence(actionSymbol.SourceLineNumbers, actionSymbol.SequenceTable.ToString(), actionSymbol.Action));
}
}
}
///
/// Creates a standard action element.
///
/// The action row from which the element should be created.
/// The created element.
private XElement CreateStandardActionElement(WixActionSymbol actionSymbol)
{
XElement xStandardAction = null;
switch (actionSymbol.Action)
{
case "AllocateRegistrySpace":
case "BindImage":
case "CostFinalize":
case "CostInitialize":
case "CreateFolders":
case "CreateShortcuts":
case "DeleteServices":
case "DuplicateFiles":
case "ExecuteAction":
case "FileCost":
case "InstallAdminPackage":
case "InstallFiles":
case "InstallFinalize":
case "InstallInitialize":
case "InstallODBC":
case "InstallServices":
case "InstallValidate":
case "IsolateComponents":
case "MigrateFeatureStates":
case "MoveFiles":
case "MsiPublishAssemblies":
case "MsiUnpublishAssemblies":
case "PatchFiles":
case "ProcessComponents":
case "PublishComponents":
case "PublishFeatures":
case "PublishProduct":
case "RegisterClassInfo":
case "RegisterComPlus":
case "RegisterExtensionInfo":
case "RegisterFonts":
case "RegisterMIMEInfo":
case "RegisterProduct":
case "RegisterProgIdInfo":
case "RegisterTypeLibraries":
case "RegisterUser":
case "RemoveDuplicateFiles":
case "RemoveEnvironmentStrings":
case "RemoveFiles":
case "RemoveFolders":
case "RemoveIniValues":
case "RemoveODBC":
case "RemoveRegistryValues":
case "RemoveShortcuts":
case "SelfRegModules":
case "SelfUnregModules":
case "SetODBCFolders":
case "StartServices":
case "StopServices":
case "UnpublishComponents":
case "UnpublishFeatures":
case "UnregisterClassInfo":
case "UnregisterComPlus":
case "UnregisterExtensionInfo":
case "UnregisterFonts":
case "UnregisterMIMEInfo":
case "UnregisterProgIdInfo":
case "UnregisterTypeLibraries":
case "ValidateProductID":
case "WriteEnvironmentStrings":
case "WriteIniValues":
case "WriteRegistryValues":
xStandardAction = new XElement(Names.WxsNamespace + actionSymbol.Action);
break;
case "AppSearch":
this.StandardActions.TryGetValue(actionSymbol.Id.Id, out var appSearchActionRow);
if (null != actionSymbol.Before || null != actionSymbol.After || (null != appSearchActionRow && actionSymbol.Sequence != appSearchActionRow.Sequence))
{
xStandardAction = new XElement(Names.AppSearchElement);
SetAttributeIfNotNull(xStandardAction, "Condition", actionSymbol.Condition);
SetAttributeIfNotNull(xStandardAction, "Before", actionSymbol.Before);
SetAttributeIfNotNull(xStandardAction, "After", actionSymbol.After);
SetAttributeIfNotNull(xStandardAction, "Sequence", actionSymbol.Sequence);
return xStandardAction;
}
break;
case "CCPSearch":
case "DisableRollback":
case "FindRelatedProducts":
case "ForceReboot":
case "InstallExecute":
case "InstallExecuteAgain":
case "LaunchConditions":
case "RemoveExistingProducts":
case "ResolveSource":
case "RMCCPSearch":
case "ScheduleReboot":
xStandardAction = new XElement(Names.WxsNamespace + actionSymbol.Action);
Decompiler.SequenceRelativeAction(actionSymbol, xStandardAction);
return xStandardAction;
default:
this.Messaging.Write(WarningMessages.UnknownAction(actionSymbol.SourceLineNumbers, actionSymbol.SequenceTable.ToString(), actionSymbol.Action));
return null;
}
if (xStandardAction != null)
{
this.SequenceStandardAction(actionSymbol, xStandardAction);
}
return xStandardAction;
}
///
/// Applies the condition and sequence to a standard action element based on the action symbol data.
///
/// Action data from the database.
/// Element to be sequenced.
private void SequenceStandardAction(WixActionSymbol actionSymbol, XElement xAction)
{
xAction.SetAttributeValue("Condition", actionSymbol.Condition);
if ((null != actionSymbol.Before || null != actionSymbol.After) && 0 == actionSymbol.Sequence)
{
this.Messaging.Write(WarningMessages.DecompiledStandardActionRelativelyScheduledInModule(actionSymbol.SourceLineNumbers, actionSymbol.SequenceTable.ToString(), actionSymbol.Action));
}
else if (actionSymbol.Sequence.HasValue)
{
xAction.SetAttributeValue("Sequence", actionSymbol.Sequence.Value);
}
}
///
/// Applies the condition and relative sequence to an action element based on the action row data.
///
/// Action data from the database.
/// Element to be sequenced.
private static void SequenceRelativeAction(WixActionSymbol actionSymbol, XElement xAction)
{
SetAttributeIfNotNull(xAction, "Condition", actionSymbol.Condition);
SetAttributeIfNotNull(xAction, "Before", actionSymbol.Before);
SetAttributeIfNotNull(xAction, "After", actionSymbol.After);
SetAttributeIfNotNull(xAction, "Sequence", actionSymbol.Sequence);
}
///
/// Ensure that a particular property exists in the decompiled output.
///
/// The identifier of the property.
/// The property element.
private XElement EnsureProperty(string id)
{
XElement xProperty;
if (!this.TryGetIndexedElement("Property", out xProperty, id))
{
xProperty = new XElement(Names.PropertyElement, new XAttribute("Id", id));
this.RootElement.Add(xProperty);
this.IndexElement(xProperty, "Property", id);
}
return xProperty;
}
///
/// Finalize decompilation.
///
/// The collection of all tables.
private void FinalizeDecompile(TableIndexedCollection tables)
{
if (OutputType.PatchCreation == this.OutputType)
{
this.FinalizeFamilyFileRangesTable(tables);
}
else
{
this.FinalizeSummaryInformationStream(tables);
this.FinalizeCheckBoxTable(tables);
this.FinalizeComponentTable(tables);
this.FinalizeDialogTable(tables);
this.FinalizeDuplicateMoveFileTables(tables);
this.FinalizeFeatureComponentsTable(tables);
this.FinalizeFileTable(tables);
this.FinalizeMIMETable(tables);
this.FinalizeMsiLockPermissionsExTable(tables);
this.FinalizeLockPermissionsTable(tables);
this.FinalizeProgIdTable(tables);
this.FinalizePropertyTable(tables);
this.FinalizeRemoveFileTable(tables);
this.FinalizeSearchTables(tables);
this.FinalizeShortcutTable(tables);
this.FinalizeUpgradeTable(tables);
this.FinalizeSequenceTables(tables);
this.FinalizeVerbTable(tables);
}
}
///
/// Finalize the CheckBox table.
///
/// The collection of all tables.
///
/// Enumerates through all the Control rows, looking for controls of type "CheckBox" with
/// a value in the Property column. This is then possibly matched up with a CheckBox row
/// to retrieve a CheckBoxValue. There is no foreign key from the Control to CheckBox table.
///
private void FinalizeCheckBoxTable(TableIndexedCollection tables)
{
// if the user has requested to suppress the UI elements, we have nothing to do
if (this.SuppressUI)
{
return;
}
var checkBoxTable = tables["CheckBox"];
var controlTable = tables["Control"];
var checkBoxes = checkBoxTable?.Rows.ToDictionary(row => row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter));
var checkBoxProperties = checkBoxTable?.Rows.ToDictionary(row => row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), row => false);
// enumerate through the Control table, adding CheckBox values where appropriate
if (null != controlTable)
{
foreach (var row in controlTable.Rows)
{
var xControl = this.GetIndexedElement(row);
if ("CheckBox" == row.FieldAsString(2))
{
var property = row.FieldAsString(8);
if (!String.IsNullOrEmpty(property) && checkBoxes.TryGetValue(property, out var checkBoxRow))
{
// if we've seen this property already, create a reference to it
if (checkBoxProperties.TryGetValue(property, out var seen) && seen)
{
xControl.SetAttributeValue("CheckBoxPropertyRef", property);
}
else
{
xControl.SetAttributeValue("Property", property);
checkBoxProperties[property] = true;
}
xControl.SetAttributeValue("CheckBoxValue", checkBoxRow.FieldAsString(1));
}
else
{
this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, "Control", row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Property", row.FieldAsString(8), "CheckBox"));
}
}
}
}
}
///
/// Finalize the Component table.
///
/// The collection of all tables.
///
/// Set the keypaths for each component.
///
private void FinalizeComponentTable(TableIndexedCollection tables)
{
var componentTable = tables["Component"];
var fileTable = tables["File"];
var odbcDataSourceTable = tables["ODBCDataSource"];
var registryTable = tables["Registry"];
// set the component keypaths
if (null != componentTable)
{
foreach (var row in componentTable.Rows)
{
var attributes = row.FieldAsInteger(3);
var keyPath = row.FieldAsString(5);
if (String.IsNullOrEmpty(keyPath))
{
var xComponent = this.GetIndexedElement("Component", row.FieldAsString(0));
xComponent.SetAttributeValue("KeyPath", "yes");
}
else if (WindowsInstallerConstants.MsidbComponentAttributesRegistryKeyPath == (attributes & WindowsInstallerConstants.MsidbComponentAttributesRegistryKeyPath))
{
if (this.TryGetIndexedElement("Registry", out var xRegistry, keyPath))
{
if (xRegistry.Name.LocalName == "RegistryValue")
{
xRegistry.SetAttributeValue("KeyPath", "yes");
}
else
{
this.Messaging.Write(WarningMessages.IllegalRegistryKeyPath(row.SourceLineNumbers, "Component", keyPath));
}
}
else
{
this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, "Component", row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "KeyPath", keyPath, "Registry"));
}
}
else if (WindowsInstallerConstants.MsidbComponentAttributesODBCDataSource == (attributes & WindowsInstallerConstants.MsidbComponentAttributesODBCDataSource))
{
if (this.TryGetIndexedElement("ODBCDataSource", out var xOdbcDataSource, keyPath))
{
xOdbcDataSource.SetAttributeValue("KeyPath", "yes");
}
else
{
this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, "Component", row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "KeyPath", keyPath, "ODBCDataSource"));
}
}
else
{
if (this.TryGetIndexedElement("File", out var xFile, keyPath))
{
xFile.SetAttributeValue("KeyPath", "yes");
}
else
{
this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, "Component", row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "KeyPath", keyPath, "File"));
}
}
}
}
// add the File children elements
if (null != fileTable)
{
foreach (FileRow fileRow in fileTable.Rows)
{
if (this.TryGetIndexedElement("Component", out var xComponent, fileRow.Component)
&& this.TryGetIndexedElement(fileRow, out var xFile))
{
xComponent.Add(xFile);
}
else
{
this.Messaging.Write(WarningMessages.ExpectedForeignRow(fileRow.SourceLineNumbers, "File", fileRow.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Component_", fileRow.Component, "Component"));
}
}
}
// add the ODBCDataSource children elements
if (null != odbcDataSourceTable)
{
foreach (var row in odbcDataSourceTable.Rows)
{
if (this.TryGetIndexedElement("Component", out var xComponent, row.FieldAsString(1))
&& this.TryGetIndexedElement(row, out var xOdbcDataSource))
{
xComponent.Add(xOdbcDataSource);
}
else
{
this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, "ODBCDataSource", row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Component_", row.FieldAsString(1), "Component"));
}
}
}
// add the Registry children elements
if (null != registryTable)
{
foreach (var row in registryTable.Rows)
{
if (this.TryGetIndexedElement("Component", out var xComponent, row.FieldAsString(5))
&& this.TryGetIndexedElement(row, out var xRegistry))
{
xComponent.Add(xRegistry);
}
else
{
this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, "Registry", row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Component_", row.FieldAsString(5), "Component"));
}
}
}
}
///
/// Finalize the Dialog table.
///
/// The collection of all tables.
///
/// Sets the first, default, and cancel control for each dialog and adds all child control
/// elements to the dialog.
///
private void FinalizeDialogTable(TableIndexedCollection tables)
{
// if the user has requested to suppress the UI elements, we have nothing to do
if (this.SuppressUI)
{
return;
}
var addedControls = new HashSet();
var controlTable = tables["Control"];
var controlRows = controlTable?.Rows.ToDictionary(row => row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter));
var dialogTable = tables["Dialog"];
if (null != dialogTable)
{
foreach (var dialogRow in dialogTable.Rows)
{
var xDialog = this.GetIndexedElement(dialogRow);
var dialogId = dialogRow.FieldAsString(0);
if (!this.TryGetIndexedElement("Control", out var xControl, dialogId, dialogRow.FieldAsString(7)))
{
this.Messaging.Write(WarningMessages.ExpectedForeignRow(dialogRow.SourceLineNumbers, "Dialog", dialogRow.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Dialog", dialogId, "Control_First", dialogRow.FieldAsString(7), "Control"));
}
// add tabbable controls
while (null != xControl)
{
var controlId = xControl.Attribute("Id");
var controlRow = controlRows[String.Concat(dialogId, DecompilerConstants.PrimaryKeyDelimiter, controlId)];
xControl.SetAttributeValue("TabSkip", "no");
xDialog.Add(xControl);
addedControls.Add(xControl);
var controlNext = controlRow.FieldAsString(10);
if (!String.IsNullOrEmpty(controlNext))
{
if (this.TryGetIndexedElement("Control", out xControl, dialogId, controlNext))
{
// looped back to the first control in the dialog
if (addedControls.Contains(xControl))
{
xControl = null;
}
}
else
{
this.Messaging.Write(WarningMessages.ExpectedForeignRow(controlRow.SourceLineNumbers, "Control", controlRow.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Dialog_", dialogId, "Control_Next", controlNext, "Control"));
}
}
else
{
xControl = null;
}
}
// set default control
var controlDefault = dialogRow.FieldAsString(8);
if (!String.IsNullOrEmpty(controlDefault))
{
if (this.TryGetIndexedElement("Control", out var xDefaultControl, dialogId, controlDefault))
{
xDefaultControl.SetAttributeValue("Default", "yes");
}
else
{
this.Messaging.Write(WarningMessages.ExpectedForeignRow(dialogRow.SourceLineNumbers, "Dialog", dialogRow.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Dialog", dialogId, "Control_Default", Convert.ToString(dialogRow[8]), "Control"));
}
}
// set cancel control
var controlCancel = dialogRow.FieldAsString(8);
if (!String.IsNullOrEmpty(controlCancel))
{
if (this.TryGetIndexedElement("Control", out var xCancelControl, dialogId, controlCancel))
{
xCancelControl.SetAttributeValue("Cancel", "yes");
}
else
{
this.Messaging.Write(WarningMessages.ExpectedForeignRow(dialogRow.SourceLineNumbers, "Dialog", dialogRow.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Dialog", dialogId, "Control_Cancel", Convert.ToString(dialogRow[9]), "Control"));
}
}
}
}
// add the non-tabbable controls to the dialog
if (null != controlTable)
{
foreach (var controlRow in controlTable.Rows)
{
var dialogId = controlRow.FieldAsString(0);
if (!this.TryGetIndexedElement("Dialog", out var xDialog, dialogId))
{
this.Messaging.Write(WarningMessages.ExpectedForeignRow(controlRow.SourceLineNumbers, "Control", controlRow.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Dialog_", dialogId, "Dialog"));
continue;
}
var xControl = this.GetIndexedElement(controlRow);
if (!addedControls.Contains(xControl))
{
xControl.SetAttributeValue("TabSkip", "yes");
xDialog.Add(xControl);
}
}
}
}
///
/// Finalize the DuplicateFile and MoveFile tables.
///
/// The collection of all tables.
///
/// Sets the source/destination property/directory for each DuplicateFile or
/// MoveFile row.
///
private void FinalizeDuplicateMoveFileTables(TableIndexedCollection tables)
{
var duplicateFileTable = tables["DuplicateFile"];
if (null != duplicateFileTable)
{
foreach (var row in duplicateFileTable.Rows)
{
var xCopyFile = this.GetIndexedElement(row);
var destination = row.FieldAsString(4);
if (!String.IsNullOrEmpty(destination))
{
if (this.TryGetIndexedElement("Directory", out var _, destination))
{
xCopyFile.SetAttributeValue("DestinationDirectory", destination);
}
else
{
xCopyFile.SetAttributeValue("DestinationProperty", destination);
}
}
}
}
var moveFileTable = tables["MoveFile"];
if (null != moveFileTable)
{
foreach (var row in moveFileTable.Rows)
{
var xCopyFile = this.GetIndexedElement(row);
var source = row.FieldAsString(4);
if (!String.IsNullOrEmpty(source))
{
if (this.TryGetIndexedElement("Directory", out var _, source))
{
xCopyFile.SetAttributeValue("SourceDirectory", source);
}
else
{
xCopyFile.SetAttributeValue("SourceProperty", source);
}
}
var destination = row.FieldAsString(5);
if (this.TryGetIndexedElement("Directory", out var _, destination))
{
xCopyFile.SetAttributeValue("DestinationDirectory", destination);
}
else
{
xCopyFile.SetAttributeValue("DestinationProperty", destination);
}
}
}
}
///
/// Finalize the FamilyFileRanges table.
///
/// The collection of all tables.
private void FinalizeFamilyFileRangesTable(TableIndexedCollection tables)
{
var familyFileRangesTable = tables["FamilyFileRanges"];
if (null != familyFileRangesTable)
{
foreach (var row in familyFileRangesTable.Rows)
{
var xProtectRange = new XElement(Names.ProtectRangeElement);
if (!row.IsColumnNull(2) && !row.IsColumnNull(3))
{
var retainOffsets = row.FieldAsString(2).Split(',');
var retainLengths = row.FieldAsString(3).Split(',');
if (retainOffsets.Length == retainLengths.Length)
{
for (var i = 0; i < retainOffsets.Length; i++)
{
if (retainOffsets[i].StartsWith("0x", StringComparison.Ordinal))
{
xProtectRange.SetAttributeValue("Offset", Convert.ToInt32(retainOffsets[i].Substring(2), 16));
}
else
{
xProtectRange.SetAttributeValue("Offset", Convert.ToInt32(retainOffsets[i], CultureInfo.InvariantCulture));
}
if (retainLengths[i].StartsWith("0x", StringComparison.Ordinal))
{
xProtectRange.SetAttributeValue("Length", Convert.ToInt32(retainLengths[i].Substring(2), 16));
}
else
{
xProtectRange.SetAttributeValue("Length", Convert.ToInt32(retainLengths[i], CultureInfo.InvariantCulture));
}
}
}
else
{
// TODO: warn
}
}
else if (!row.IsColumnNull(2) || !row.IsColumnNull(3))
{
// TODO: warn about mismatch between columns
}
this.IndexElement(row, xProtectRange);
}
}
var usedProtectRanges = new HashSet();
var externalFilesTable = tables["ExternalFiles"];
if (null != externalFilesTable)
{
foreach (var row in externalFilesTable.Rows)
{
if (this.TryGetIndexedElement(row, out var xExternalFile)
&& this.TryGetIndexedElement("FamilyFileRanges", out var xProtectRange, row.FieldAsString(0), row.FieldAsString(0)))
{
xExternalFile.Add(xProtectRange);
usedProtectRanges.Add(xProtectRange);
}
}
}
var targetFiles_OptionalDataTable = tables["TargetFiles_OptionalData"];
if (null != targetFiles_OptionalDataTable)
{
var targetImagesTable = tables["TargetImages"];
var targetImageRows = targetImagesTable?.Rows.ToDictionary(row => row.FieldAsString(0));
var upgradedImagesTable = tables["UpgradedImages"];
var upgradedImagesRows = upgradedImagesTable?.Rows.ToDictionary(row => row.FieldAsString(0));
foreach (var row in targetFiles_OptionalDataTable.Rows)
{
var xTargetFile = this.PatchTargetFiles[row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter)];
if (!targetImageRows.TryGetValue(row.FieldAsString(0), out var targetImageRow))
{
this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, targetFiles_OptionalDataTable.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Target", row.FieldAsString(0), "TargetImages"));
continue;
}
if (!upgradedImagesRows.TryGetValue(row.FieldAsString(3), out var upgradedImagesRow))
{
this.Messaging.Write(WarningMessages.ExpectedForeignRow(targetImageRow.SourceLineNumbers, targetImageRow.Table.Name, targetImageRow.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Upgraded", row.FieldAsString(3), "UpgradedImages"));
continue;
}
if (this.TryGetIndexedElement("FamilyFileRanges", out var xProtectRange, upgradedImagesRow.FieldAsString(4), row.FieldAsString(1)))
{
xTargetFile.Add(xProtectRange);
usedProtectRanges.Add(xProtectRange);
}
}
}
if (null != familyFileRangesTable)
{
foreach (var row in familyFileRangesTable.Rows)
{
var xProtectRange = this.GetIndexedElement(row);
if (!usedProtectRanges.Contains(xProtectRange))
{
var xProtectFile = new XElement(Names.ProtectFileElement, new XAttribute("File", row.FieldAsString(1)));
xProtectFile.Add(xProtectRange);
this.AddChildToParent("ImageFamilies", xProtectFile, row, 0);
}
}
}
}
///
/// Finalize the FeatureComponents table.
///
/// The collection of all tables.
///
/// Since tables specifying references to the FeatureComponents table have references to
/// the Feature and Component table separately, but not the FeatureComponents table specifically,
/// the FeatureComponents table and primary features must be decompiled during finalization.
///
private void FinalizeFeatureComponentsTable(TableIndexedCollection tables)
{
var classTable = tables["Class"];
if (null != classTable)
{
foreach (var row in classTable.Rows)
{
this.SetPrimaryFeature(row, 11, 2);
}
}
var extensionTable = tables["Extension"];
if (null != extensionTable)
{
foreach (var row in extensionTable.Rows)
{
this.SetPrimaryFeature(row, 4, 1);
}
}
var msiAssemblyTable = tables["MsiAssembly"];
if (null != msiAssemblyTable)
{
foreach (var row in msiAssemblyTable.Rows)
{
this.SetPrimaryFeature(row, 1, 0);
}
}
var publishComponentTable = tables["PublishComponent"];
if (null != publishComponentTable)
{
foreach (var row in publishComponentTable.Rows)
{
this.SetPrimaryFeature(row, 4, 2);
}
}
var typeLibTable = tables["TypeLib"];
if (null != typeLibTable)
{
foreach (var row in typeLibTable.Rows)
{
this.SetPrimaryFeature(row, 6, 2);
}
}
}
///
/// Finalize the File table.
///
/// The collection of all tables.
///
/// Sets the source, diskId, and assembly information for each file.
///
private void FinalizeFileTable(TableIndexedCollection tables)
{
// index the media table by media id
var mediaTable = tables["Media"];
var mediaRows = new RowDictionary(mediaTable);
// set the disk identifiers and sources for files
foreach (var fileRow in tables["File"]?.Rows.Cast() ?? Enumerable.Empty())
{
var xFile = this.GetIndexedElement("File", fileRow.File);
// Don't bother processing files that are orphaned (and won't show up in the output anyway)
if (null != xFile.Parent)
{
// set the diskid
if (null != mediaTable)
{
foreach (MediaRow mediaRow in mediaTable.Rows)
{
if (fileRow.Sequence <= mediaRow.LastSequence && mediaRow.DiskId != 1)
{
xFile.SetAttributeValue("DiskId", mediaRow.DiskId);
break;
}
}
}
var fileId = xFile?.Attribute("Id")?.Value;
var fileCompressed = xFile?.Attribute("Compressed")?.Value;
var fileShortName = xFile?.Attribute("ShortName")?.Value;
var fileName = xFile?.Attribute("Name")?.Value;
// set the source (done here because it requires information from the Directory table)
if (OutputType.Module == this.OutputType)
{
xFile.SetAttributeValue("Source", String.Concat(this.BaseSourcePath, Path.DirectorySeparatorChar, "File", Path.DirectorySeparatorChar, fileId, '.', this.ModularizationGuid.Substring(1, 36).Replace('-', '_')));
}
else if (fileCompressed == "yes" || (fileCompressed != "no" && this.Compressed) || (OutputType.Product == this.OutputType && this.TreatProductAsModule))
{
xFile.SetAttributeValue("Source", String.Concat(this.BaseSourcePath, Path.DirectorySeparatorChar, "File", Path.DirectorySeparatorChar, fileId));
}
else // uncompressed
{
var name = (!this.ShortNames && !String.IsNullOrEmpty(fileName)) ? fileName : fileShortName ?? fileName;
if (this.Compressed) // uncompressed at the root of the source image
{
xFile.SetAttributeValue("Source", String.Concat("SourceDir", Path.DirectorySeparatorChar, name));
}
else
{
var sourcePath = this.GetSourcePath(xFile);
xFile.SetAttributeValue("Source", Path.Combine(sourcePath, name));
}
}
}
}
// set the file assemblies and manifests
foreach (var row in tables["MsiAssembly"]?.Rows ?? Enumerable.Empty())
{
if (this.TryGetIndexedElement("Component", out var xComponent, row.FieldAsString(0)))
{
foreach (var xFile in xComponent.Elements(Names.FileElement).Where(x => x.Attribute("KeyPath")?.Value == "yes"))
{
xFile.SetAttributeValue("AssemblyManifest", row.FieldAsString(2));
xFile.SetAttributeValue("AssemblyApplication", row.FieldAsString(3));
xFile.SetAttributeValue("Assembly", row.FieldAsInteger(4) == 0 ? ".net" : "win32");
}
}
else
{
this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, "MsiAssembly", row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Component_", row.FieldAsString(0), "Component"));
}
}
// nest the TypeLib elements
foreach (var row in tables["TypeLib"]?.Rows ?? Enumerable.Empty())
{
var xComponent = this.GetIndexedElement("Component", row.FieldAsString(2));
var xTypeLib = this.GetIndexedElement(row);
foreach (var xFile in xComponent.Elements(Names.FileElement).Where(x => x.Attribute("KeyPath")?.Value == "yes"))
{
xFile.Add(xTypeLib);
}
}
}
///
/// Finalize the MIME table.
///
/// The collection of all tables.
///
/// There is a foreign key shared between the MIME and Extension
/// tables so either one would be valid to be decompiled first, so
/// the only safe way to nest the MIME elements is to do it during finalize.
///
private void FinalizeMIMETable(TableIndexedCollection tables)
{
var extensionRows = tables["Extension"]?.Rows ?? Enumerable.Empty();
foreach (var row in extensionRows)
{
// set the default MIME element for this extension
var mimeRef = row.FieldAsString(3);
if (null != mimeRef)
{
if (this.TryGetIndexedElement("MIME", out var xMime, mimeRef))
{
xMime.SetAttributeValue("Default", "yes");
}
else
{
this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, "Extension", row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "MIME_", row.FieldAsString(3), "MIME"));
}
}
}
var extensionsByExtensionId = this.IndexTableOneToMany(extensionRows);
foreach (var row in tables["MIME"]?.Rows ?? Enumerable.Empty())
{
var xMime = this.GetIndexedElement(row);
if (extensionsByExtensionId.TryGetValue(row.FieldAsString(1), out var xExtensions))
{
foreach (var extension in xExtensions)
{
extension.Add(xMime);
}
}
else
{
this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, "MIME", row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Extension_", row.FieldAsString(1), "Extension"));
}
}
}
///
/// Finalize the ProgId table.
///
/// The collection of all tables.
///
/// Enumerates through all the Class rows, looking for child ProgIds (these are the
/// default ProgIds for a given Class). Then go through the ProgId table and add any
/// remaining ProgIds for each Class. This happens during finalize because there is
/// a circular dependency between the Class and ProgId tables.
///
private void FinalizeProgIdTable(TableIndexedCollection tables)
{
// add the default ProgIds for each class (and index the class table)
var classRows = tables["Class"]?.Rows?.Where(row => row.FieldAsString(3) != null) ?? Enumerable.Empty();
var classesByCLSID = this.IndexTableOneToMany(classRows);
var addedProgIds = new Dictionary();
foreach (var row in classRows)
{
var clsid = row.FieldAsString(0);
var xClass = this.GetIndexedElement(row);
if (this.TryGetIndexedElement("ProgId", out var xProgId, row.FieldAsString(3)))
{
if (addedProgIds.TryGetValue(xProgId, out var progid))
{
this.Messaging.Write(WarningMessages.TooManyProgIds(row.SourceLineNumbers, row.FieldAsString(0), row.FieldAsString(3), progid));
}
else
{
xClass.Add(xProgId);
addedProgIds.Add(xProgId, clsid);
}
}
else
{
this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, "Class", row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "ProgId_Default", row.FieldAsString(3), "ProgId"));
}
}
// add the remaining non-default ProgId entries for each class
foreach (var row in tables["ProgId"]?.Rows ?? Enumerable.Empty())
{
var clsid = row.FieldAsString(2);
var xProgId = this.GetIndexedElement(row);
if (!addedProgIds.ContainsKey(xProgId) && null != clsid && null == xProgId.Parent)
{
if (classesByCLSID.TryGetValue(clsid, out var xClasses))
{
foreach (var xClass in xClasses)
{
xClass.Add(xProgId);
addedProgIds.Add(xProgId, clsid);
}
}
else
{
this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, "ProgId", row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Class_", row.FieldAsString(2), "Class"));
}
}
}
// Check for any progIds that are not hooked up to a class and hook them up to the component specified by the extension
var componentsById = this.IndexTableOneToMany(tables, "Component");
foreach (var row in tables["Extension"]?.Rows?.Where(row => row.FieldAsString(2) != null) ?? Enumerable.Empty())
{
var xProgId = this.GetIndexedElement("ProgId", row.FieldAsString(2));
// Haven't added the progId yet and it doesn't have a parent progId
if (!addedProgIds.ContainsKey(xProgId) && null == xProgId.Parent)
{
if (componentsById.TryGetValue(row.FieldAsString(1), out var xComponents))
{
foreach (var xComponent in xComponents)
{
xComponent.Add(xProgId);
}
}
else
{
this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, "Extension", row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Component_", row.FieldAsString(1), "Component"));
}
}
}
}
///
/// Finalize the Property table.
///
/// The collection of all tables.
///
/// Removes properties that are generated from other entries.
///
private void FinalizePropertyTable(TableIndexedCollection tables)
{
foreach (var row in tables["CustomAction"]?.Rows ?? Enumerable.Empty())
{
// If no other fields on the property are set we must have created it in the backend.
var bits = row.FieldAsInteger(1);
if (WindowsInstallerConstants.MsidbCustomActionTypeHideTarget == (bits & WindowsInstallerConstants.MsidbCustomActionTypeHideTarget)
&& WindowsInstallerConstants.MsidbCustomActionTypeInScript == (bits & WindowsInstallerConstants.MsidbCustomActionTypeInScript)
&& this.TryGetIndexedElement("Property", out var xProperty, row.FieldAsString(0))
&& String.IsNullOrEmpty(xProperty.Attribute("Value")?.Value)
&& xProperty.Attribute("Secure")?.Value != "yes"
&& xProperty.Attribute("SuppressModularization")?.Value != "yes")
{
xProperty.Remove();
}
}
}
///
/// Finalize the RemoveFile table.
///
/// The collection of all tables.
///
/// Sets the directory/property for each RemoveFile row.
///
private void FinalizeRemoveFileTable(TableIndexedCollection tables)
{
foreach (var row in tables["RemoveFile"]?.Rows ?? Enumerable.Empty())
{
var xRemove = this.GetIndexedElement(row);
var property = row.FieldAsString(3);
if (this.TryGetIndexedElement("Directory", out var _, property))
{
xRemove.SetAttributeValue("Directory", property);
}
else
{
xRemove.SetAttributeValue("Property", property);
}
}
}
///
/// Finalize the LockPermissions or MsiLockPermissionsEx table.
///
/// The collection of all tables.
/// Which table to finalize.
///
/// Nests the Permission elements below their parent elements. There are no declared foreign
/// keys for the parents of the LockPermissions table.
///
private void FinalizePermissionsTable(TableIndexedCollection tables, string tableName)
{
var createFoldersById = this.IndexTableOneToMany(tables, tableName);
foreach (var row in tables[tableName]?.Rows ?? Enumerable.Empty())
{
var id = row.FieldAsString(0);
var table = row.FieldAsString(1);
var xPermission = this.GetIndexedElement(row);
if ("CreateFolder" == table)
{
if (createFoldersById.TryGetValue(id, out var xCreateFolders))
{
foreach (var xCreateFolder in xCreateFolders)
{
xCreateFolder.Add(xPermission);
}
}
else
{
this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, tableName, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "LockObject", id, table));
}
}
else
{
if (this.TryGetIndexedElement(table, out var xParent, id))
{
xParent.Add(xPermission);
}
else
{
this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, tableName, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "LockObject", id, table));
}
}
}
}
///
/// Finalize the LockPermissions table.
///
/// The collection of all tables.
///
/// Nests the Permission elements below their parent elements. There are no declared foreign
/// keys for the parents of the LockPermissions table.
///
private void FinalizeLockPermissionsTable(TableIndexedCollection tables) => this.FinalizePermissionsTable(tables, "LockPermissions");
///
/// Finalize the MsiLockPermissionsEx table.
///
/// The collection of all tables.
///
/// Nests the PermissionEx elements below their parent elements. There are no declared foreign
/// keys for the parents of the MsiLockPermissionsEx table.
///
private void FinalizeMsiLockPermissionsExTable(TableIndexedCollection tables) => this.FinalizePermissionsTable(tables, "MsiLockPermissionsEx");
private static Dictionary> IndexTable(Table table, int keyColumn, int? dataColumn)
{
if (table == null)
{
return new Dictionary>();
}
return table.Rows
.ToLookup(row => row.FieldAsString(keyColumn), row => dataColumn.HasValue ? row.FieldAsString(dataColumn.Value) : null)
.ToDictionary(lookup => lookup.Key, lookup => lookup.ToList());
}
private static XElement FindComplianceDrive(XElement xSearch)
{
var xComplianceDrive = xSearch.Element(Names.ComplianceDriveElement);
if (null == xComplianceDrive)
{
xComplianceDrive = new XElement(Names.ComplianceDriveElement);
xSearch.Add(xComplianceDrive);
}
return xComplianceDrive;
}
///
/// Finalize the search tables.
///
/// The collection of all tables.
/// Does all the complex linking required for the search tables.
private void FinalizeSearchTables(TableIndexedCollection tables)
{
var appSearches = IndexTable(tables["AppSearch"], keyColumn: 1, dataColumn: 0);
var ccpSearches = IndexTable(tables["CCPSearch"], keyColumn: 0, dataColumn: null);
var drLocators = tables["DrLocator"]?.Rows.ToDictionary(row => this.GetIndexedElement(row), row => row);
var xComplianceCheck = new XElement(Names.ComplianceCheckElement);
if (ccpSearches.Keys.Any(ccpSignature => !appSearches.ContainsKey(ccpSignature)))
{
this.RootElement.Add(xComplianceCheck);
}
// index the locator tables by their signatures
var locators =
new[] { "CompLocator", "RegLocator", "IniLocator", "DrLocator", "Signature" }
.SelectMany(table => tables[table]?.Rows ?? Enumerable.Empty())
.ToLookup(row => row.FieldAsString(0), row => row)
.ToDictionary(lookup => lookup.Key, lookup => lookup.ToList());
// move the DrLocator rows with a parent of CCP_DRIVE first to ensure they get FileSearch children (not FileSearchRef)
foreach (var locatorRows in locators.Values)
{
var firstDrLocator = -1;
for (var i = 0; i < locatorRows.Count; i++)
{
var locatorRow = (Row)locatorRows[i];
if ("DrLocator" == locatorRow.TableDefinition.Name)
{
if (-1 == firstDrLocator)
{
firstDrLocator = i;
}
if ("CCP_DRIVE" == Convert.ToString(locatorRow[1]))
{
locatorRows.RemoveAt(i);
locatorRows.Insert(firstDrLocator, locatorRow);
break;
}
}
}
}
var xUsedSearches = new HashSet();
var xUnusedSearches = new Dictionary();
foreach (var signature in locators.Keys)
{
var locatorRows = locators[signature];
var xSignatureSearches = new List();
foreach (var locatorRow in locatorRows)
{
var used = true;
var xSearch = this.GetIndexedElement(locatorRow);
if ("Signature" == locatorRow.TableDefinition.Name && 0 < xSignatureSearches.Count)
{
foreach (var xSearchParent in xSignatureSearches)
{
if (!xUsedSearches.Contains(xSearch))
{
xSearchParent.Add(xSearch);
xUsedSearches.Add(xSearch);
}
else
{
var xFileSearchRef = new XElement(Names.FileSearchRefElement,
new XAttribute("Id", signature));
xSearchParent.Add(xFileSearchRef);
}
}
}
else if ("DrLocator" == locatorRow.TableDefinition.Name && !locatorRow.IsColumnNull(1))
{
var parentSignature = locatorRow.FieldAsString(1);
if ("CCP_DRIVE" == parentSignature)
{
if (appSearches.ContainsKey(signature)
&& appSearches.TryGetValue(signature, out var appSearchPropertyIds))
{
foreach (var propertyId in appSearchPropertyIds)
{
var xProperty = this.EnsureProperty(propertyId);
if (ccpSearches.ContainsKey(signature))
{
xProperty.SetAttributeValue("ComplianceCheck", "yes");
}
var xComplianceDrive = FindComplianceDrive(xProperty);
if (!xUsedSearches.Contains(xSearch))
{
xComplianceDrive.Add(xSearch);
xUsedSearches.Add(xSearch);
}
else
{
var directorySearchRef = new XElement(Names.DirectorySearchRefElement,
new XAttribute("Id", signature),
XAttributeIfNotNull("Parent", locatorRow, 1),
XAttributeIfNotNull("Path", locatorRow, 2));
xComplianceDrive.Add(directorySearchRef);
xSignatureSearches.Add(directorySearchRef);
}
}
}
else if (ccpSearches.ContainsKey(signature))
{
var xComplianceDrive = FindComplianceDrive(xComplianceCheck);
if (!xUsedSearches.Contains(xSearch))
{
xComplianceDrive.Add(xSearch);
xUsedSearches.Add(xSearch);
}
else
{
var directorySearchRef = new XElement(Names.DirectorySearchRefElement,
new XAttribute("Id", signature),
XAttributeIfNotNull("Parent", locatorRow, 1),
XAttributeIfNotNull("Path", locatorRow, 2));
xComplianceDrive.Add(directorySearchRef);
xSignatureSearches.Add(directorySearchRef);
}
}
}
else
{
var usedDrLocator = false;
if (locators.TryGetValue(parentSignature, out var parentLocatorRows))
{
foreach (var parentLocatorRow in parentLocatorRows)
{
if ("DrLocator" == parentLocatorRow.TableDefinition.Name)
{
var xParentSearch = this.GetIndexedElement(parentLocatorRow);
if (xParentSearch.HasElements)
{
var parentDrLocatorRow = drLocators[xParentSearch];
var xDirectorySearchRef = new XElement(Names.DirectorySearchRefElement,
new XAttribute("Id", parentSignature),
XAttributeIfNotNull("Parent", parentDrLocatorRow, 1),
XAttributeIfNotNull("Path", parentDrLocatorRow, 2));
xParentSearch = xDirectorySearchRef;
xUnusedSearches.Add(parentSignature, xDirectorySearchRef);
}
if (!xUsedSearches.Contains(xSearch))
{
xParentSearch.Add(xSearch);
xUsedSearches.Add(xSearch);
usedDrLocator = true;
}
else
{
var xDirectorySearchRef = new XElement(Names.DirectorySearchRefElement,
new XAttribute("Id", signature),
new XAttribute("Parent", parentSignature),
XAttributeIfNotNull("Path", locatorRow, 2));
xParentSearch.Add(xSearch);
usedDrLocator = true;
}
}
else if ("RegLocator" == parentLocatorRow.TableDefinition.Name)
{
var xParentSearch = this.GetIndexedElement(parentLocatorRow);
xParentSearch.Add(xSearch);
xUsedSearches.Add(xSearch);
usedDrLocator = true;
}
}
// keep track of unused DrLocator rows
if (!usedDrLocator)
{
xUnusedSearches.Add(xSearch.Attribute("Id").Value, xSearch);
}
}
else
{
// TODO: warn
}
}
}
else if (appSearches.ContainsKey(signature)
&& appSearches.TryGetValue(signature, out var appSearchPropertyIds))
{
foreach (var propertyId in appSearchPropertyIds)
{
var xProperty = this.EnsureProperty(propertyId);
if (ccpSearches.ContainsKey(signature))
{
xProperty.SetAttributeValue("ComplianceCheck", "yes");
}
if (!xUsedSearches.Contains(xSearch))
{
xProperty.Add(xSearch);
xUsedSearches.Add(xSearch);
}
else if ("RegLocator" == locatorRow.TableDefinition.Name)
{
var xRegistrySearchRef = new XElement(Names.RegistrySearchRefElement,
new XAttribute("Id", signature));
xProperty.Add(xRegistrySearchRef);
xSignatureSearches.Add(xRegistrySearchRef);
}
else
{
// TODO: warn about unavailable Ref element
}
}
}
else if (ccpSearches.ContainsKey(signature))
{
if (!xUsedSearches.Contains(xSearch))
{
xComplianceCheck.Add(xSearch);
xUsedSearches.Add(xSearch);
}
else if ("RegLocator" == locatorRow.TableDefinition.Name)
{
var xRegistrySearchRef = new XElement(Names.RegistrySearchRefElement,
new XAttribute("Id", signature));
xComplianceCheck.Add(xRegistrySearchRef);
xSignatureSearches.Add(xRegistrySearchRef);
}
else
{
// TODO: warn about unavailable Ref element
}
}
else
{
if (xSearch.Name.LocalName == "DirectorySearch" || xSearch.Name.LocalName == "RegistrySearch")
{
xUnusedSearches.Add(xSearch.Attribute("Id").Value, xSearch);
}
else
{
// TODO: warn
used = false;
}
}
// keep track of the search elements for this signature so that nested searches go in the proper parents
if (used)
{
xSignatureSearches.Add(xSearch);
}
}
}
// Iterate through the unused elements through a sorted list of their ids so the output is deterministic.
foreach (var unusedSearch in xUnusedSearches.OrderBy(kvp => kvp.Key))
{
var used = false;
XElement xLeafDirectorySearch = null;
var xUnusedSearch = unusedSearch.Value;
var xParent = xUnusedSearch;
var updatedLeaf = true;
while (updatedLeaf)
{
updatedLeaf = false;
var xDirectorySearch = xParent.Element(Names.DirectorySearchElement);
if (xDirectorySearch != null)
{
xParent = xLeafDirectorySearch = xDirectorySearch;
updatedLeaf = true;
}
}
if (xLeafDirectorySearch != null)
{
var leafDirectorySearchId = xLeafDirectorySearch.Attribute("Id").Value;
if (appSearches.TryGetValue(leafDirectorySearchId, out var appSearchPropertyIds))
{
var xProperty = this.EnsureProperty(appSearchPropertyIds[0]);
xProperty.Add(xUnusedSearch);
used = true;
}
else if (ccpSearches.ContainsKey(leafDirectorySearchId))
{
xComplianceCheck.Add(xUnusedSearch);
used = true;
}
else
{
// TODO: warn
}
}
if (!used)
{
// TODO: warn
}
}
}
///
/// Finalize the Shortcut table.
///
/// The collection of all tables.
///
/// Sets Advertise to yes if Target points to a Feature.
/// Occurs during finalization because it has to check against every feature row.
///
private void FinalizeShortcutTable(TableIndexedCollection tables)
{
var shortcutTable = tables["Shortcut"];
if (null == shortcutTable)
{
return;
}
foreach (var row in shortcutTable.Rows)
{
var xShortcut = this.GetIndexedElement(row);
var target = row.FieldAsString(4);
if (this.TryGetIndexedElement("Feature", out var _, target))
{
xShortcut.SetAttributeValue("Advertise", "yes");
this.SetPrimaryFeature(row, 4, 3);
}
else
{
// TODO: use this value to do a "more-correct" nesting under the indicated File or CreateDirectory element
xShortcut.SetAttributeValue("Target", target);
}
}
}
///
/// Finalize the sequence tables.
///
/// The collection of all tables.
///
/// Creates the sequence elements. Occurs during finalization because its
/// not known if sequences refer to custom actions or dialogs during decompilation.
///
private void FinalizeSequenceTables(TableIndexedCollection tables)
{
// finalize the normal sequence tables
if (OutputType.Product == this.OutputType && !this.TreatProductAsModule)
{
foreach (SequenceTable sequenceTable in Enum.GetValues(typeof(SequenceTable)))
{
var sequenceTableName = sequenceTable.WindowsInstallerTableName();
// if suppressing UI elements, skip UI-related sequence tables
if (this.SuppressUI && ("AdminUISequence" == sequenceTableName || "InstallUISequence" == sequenceTableName))
{
continue;
}
var table = tables[sequenceTableName];
if (null != table)
{
var actionSymbols = new List();
var needAbsoluteScheduling = this.SuppressRelativeActionSequencing;
var nonSequencedActionRows = new Dictionary();
var suppressedRelativeActionRows = new Dictionary();
// create a sorted array of actions in this table
foreach (var row in table.Rows)
{
var action = row.FieldAsString(0);
var actionSymbol = new WixActionSymbol(null, new Identifier(AccessModifier.Global, sequenceTable, action));
actionSymbol.Action = action;
if (!row.IsColumnNull(1))
{
actionSymbol.Condition = row.FieldAsString(1);
}
actionSymbol.Sequence = row.FieldAsInteger(2);
actionSymbol.SequenceTable = sequenceTable;
actionSymbols.Add(actionSymbol);
}
actionSymbols = actionSymbols.OrderBy(t => t.Sequence).ToList();
for (var i = 0; i < actionSymbols.Count && !needAbsoluteScheduling; i++)
{
var actionSymbol = actionSymbols[i];
this.StandardActions.TryGetValue(actionSymbol.Id.Id, out var standardActionRow);
// create actions for custom actions, dialogs, AppSearch when its moved, and standard actions with non-standard conditions
if ("AppSearch" == actionSymbol.Action || null == standardActionRow || actionSymbol.Condition != standardActionRow.Condition)
{
WixActionSymbol previousActionSymbol = null;
WixActionSymbol nextActionSymbol = null;
// find the previous action row if there is one
if (0 <= i - 1)
{
previousActionSymbol = actionSymbols[i - 1];
}
// find the next action row if there is one
if (actionSymbols.Count > i + 1)
{
nextActionSymbol = actionSymbols[i + 1];
}
// the logic for setting the before or after attribute for an action:
// 1. If more than one action shares the same sequence number, everything must be absolutely sequenced.
// 2. If the next action is a standard action and is 1 sequence number higher, this action occurs before it.
// 3. If the previous action is a standard action and is 1 sequence number lower, this action occurs after it.
// 4. If this action is not standard and the previous action is 1 sequence number lower and does not occur before this action, this action occurs after it.
// 5. If this action is not standard and the previous action does not have the same sequence number and the next action is 1 sequence number higher, this action occurs before it.
// 6. If this action is AppSearch and has all standard information, ignore it.
// 7. If this action is standard and has a non-standard condition, create the action without any scheduling information.
// 8. Everything must be absolutely sequenced.
if ((null != previousActionSymbol && actionSymbol.Sequence == previousActionSymbol.Sequence) || (null != nextActionSymbol && actionSymbol.Sequence == nextActionSymbol.Sequence))
{
needAbsoluteScheduling = true;
}
else if (null != nextActionSymbol && this.StandardActions.ContainsKey(nextActionSymbol.Id.Id) && actionSymbol.Sequence + 1 == nextActionSymbol.Sequence)
{
actionSymbol.Before = nextActionSymbol.Action;
}
else if (null != previousActionSymbol && this.StandardActions.ContainsKey(previousActionSymbol.Id.Id) && actionSymbol.Sequence - 1 == previousActionSymbol.Sequence)
{
actionSymbol.After = previousActionSymbol.Action;
}
else if (null == standardActionRow && null != previousActionSymbol && actionSymbol.Sequence - 1 == previousActionSymbol.Sequence && previousActionSymbol.Before != actionSymbol.Action)
{
actionSymbol.After = previousActionSymbol.Action;
}
else if (null == standardActionRow && null != previousActionSymbol && actionSymbol.Sequence != previousActionSymbol.Sequence && null != nextActionSymbol && actionSymbol.Sequence + 1 == nextActionSymbol.Sequence)
{
actionSymbol.Before = nextActionSymbol.Action;
}
else if ("AppSearch" == actionSymbol.Action && null != standardActionRow && actionSymbol.Sequence == standardActionRow.Sequence && actionSymbol.Condition == standardActionRow.Condition)
{
// ignore an AppSearch row which has the WiX standard sequence and a standard condition
}
else if (null != standardActionRow && actionSymbol.Condition != standardActionRow.Condition) // standard actions get their standard sequence numbers
{
nonSequencedActionRows.Add(actionSymbol.Id.Id, actionSymbol);
}
else if (0 < actionSymbol.Sequence)
{
needAbsoluteScheduling = true;
}
}
else
{
suppressedRelativeActionRows.Add(actionSymbol.Id.Id, actionSymbol);
}
}
// create the actions now that we know if they must be absolutely or relatively scheduled
foreach (var actionRow in actionSymbols)
{
var key = actionRow.Id.Id;
if (needAbsoluteScheduling)
{
// remove any before/after information to ensure this is absolutely sequenced
actionRow.Before = null;
actionRow.After = null;
}
else if (nonSequencedActionRows.ContainsKey(key))
{
// clear the sequence attribute to ensure this action is scheduled without a sequence number (or before/after)
actionRow.Sequence = 0;
}
else if (suppressedRelativeActionRows.ContainsKey(key))
{
// skip the suppressed relatively scheduled action rows
continue;
}
// create the action element
this.CreateActionElement(actionRow);
}
}
}
}
else if (OutputType.Module == this.OutputType || this.TreatProductAsModule) // finalize the Module sequence tables
{
foreach (SequenceTable sequenceTable in Enum.GetValues(typeof(SequenceTable)))
{
var sequenceTableName = sequenceTable.WindowsInstallerTableName();
// if suppressing UI elements, skip UI-related sequence tables
if (this.SuppressUI && ("AdminUISequence" == sequenceTableName || "InstallUISequence" == sequenceTableName))
{
continue;
}
var table = tables[String.Concat("Module", sequenceTableName)];
if (null != table)
{
foreach (var row in table.Rows)
{
var actionRow = new WixActionSymbol(null, new Identifier(AccessModifier.Global, sequenceTable, row.FieldAsString(0)));
actionRow.Action = row.FieldAsString(0);
if (!row.IsColumnNull(1))
{
actionRow.Sequence = row.FieldAsInteger(1);
}
if (!row.IsColumnNull(2) && !row.IsColumnNull(3))
{
switch (row.FieldAsInteger(3))
{
case 0:
actionRow.Before = row.FieldAsString(2);
break;
case 1:
actionRow.After = row.FieldAsString(2);
break;
default:
this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[3].Column.Name, row[3]));
break;
}
}
if (!row.IsColumnNull(4))
{
actionRow.Condition = row.FieldAsString(4);
}
actionRow.SequenceTable = sequenceTable;
// create action elements for non-standard actions
if (!this.StandardActions.ContainsKey(actionRow.Id.Id) || null != actionRow.After || null != actionRow.Before)
{
this.CreateActionElement(actionRow);
}
}
}
}
}
}
///
/// Finalize the Upgrade table.
///
/// The collection of all tables.
///
/// Decompile the rows from the Upgrade and LaunchCondition tables
/// created by the MajorUpgrade element.
///
private void FinalizeUpgradeTable(TableIndexedCollection tables)
{
var launchConditionTable = tables["LaunchCondition"];
var upgradeTable = tables["Upgrade"];
string downgradeErrorMessage = null;
string disallowUpgradeErrorMessage = null;
// find the DowngradePreventedCondition launch condition message
if (null != launchConditionTable && 0 < launchConditionTable.Rows.Count)
{
foreach (var launchRow in launchConditionTable.Rows)
{
if (WixUpgradeConstants.DowngradePreventedCondition == Convert.ToString(launchRow[0]))
{
downgradeErrorMessage = Convert.ToString(launchRow[1]);
}
else if (WixUpgradeConstants.UpgradePreventedCondition == Convert.ToString(launchRow[0]))
{
disallowUpgradeErrorMessage = Convert.ToString(launchRow[1]);
}
}
}
if (null != upgradeTable && 0 < upgradeTable.Rows.Count)
{
XElement xMajorUpgrade = null;
foreach (UpgradeRow upgradeRow in upgradeTable.Rows)
{
if (WixUpgradeConstants.UpgradeDetectedProperty == upgradeRow.ActionProperty)
{
var attr = upgradeRow.Attributes;
var removeFeatures = upgradeRow.Remove;
xMajorUpgrade = xMajorUpgrade ?? new XElement(Names.MajorUpgradeElement);
if (WindowsInstallerConstants.MsidbUpgradeAttributesVersionMaxInclusive == (attr & WindowsInstallerConstants.MsidbUpgradeAttributesVersionMaxInclusive))
{
xMajorUpgrade.SetAttributeValue("AllowSameVersionUpgrades", "yes");
}
if (WindowsInstallerConstants.MsidbUpgradeAttributesMigrateFeatures != (attr & WindowsInstallerConstants.MsidbUpgradeAttributesMigrateFeatures))
{
xMajorUpgrade.SetAttributeValue("MigrateFeatures", "no");
}
if (WindowsInstallerConstants.MsidbUpgradeAttributesIgnoreRemoveFailure == (attr & WindowsInstallerConstants.MsidbUpgradeAttributesIgnoreRemoveFailure))
{
xMajorUpgrade.SetAttributeValue("IgnoreRemoveFailure", "yes");
}
if (!String.IsNullOrEmpty(removeFeatures))
{
xMajorUpgrade.SetAttributeValue("RemoveFeatures", removeFeatures);
}
}
else if (WixUpgradeConstants.DowngradeDetectedProperty == upgradeRow.ActionProperty)
{
xMajorUpgrade = xMajorUpgrade ?? new XElement(Names.MajorUpgradeElement);
xMajorUpgrade.SetAttributeValue("DowngradeErrorMessage", downgradeErrorMessage);
}
}
if (xMajorUpgrade != null)
{
if (String.IsNullOrEmpty(downgradeErrorMessage))
{
xMajorUpgrade.SetAttributeValue("AllowDowngrades", "yes");
}
if (!String.IsNullOrEmpty(disallowUpgradeErrorMessage))
{
xMajorUpgrade.SetAttributeValue("Disallow", "yes");
xMajorUpgrade.SetAttributeValue("DisallowUpgradeErrorMessage", disallowUpgradeErrorMessage);
}
var scheduledType = DetermineMajorUpgradeScheduling(tables);
if (scheduledType != "afterInstallValidate")
{
xMajorUpgrade.SetAttributeValue("Schedule", scheduledType);
}
this.RootElement.Add(xMajorUpgrade);
}
}
}
///
/// Finalize the Verb table.
///
/// The collection of all tables.
///
/// The Extension table is a foreign table for the Verb table, but the
/// foreign key is only part of the primary key of the Extension table,
/// so it needs special logic to be nested properly.
///
private void FinalizeVerbTable(TableIndexedCollection tables)
{
var xExtensions = this.IndexTableOneToMany(tables["Extension"]);
var verbTable = tables["Verb"];
if (null != verbTable)
{
foreach (var row in verbTable.Rows)
{
if (xExtensions.TryGetValue(row.FieldAsString(0), out var xVerbExtensions))
{
var xVerb = this.GetIndexedElement(row);
foreach (var xVerbExtension in xVerbExtensions)
{
xVerbExtension.Add(xVerb);
}
}
else
{
this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, verbTable.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Extension_", row.FieldAsString(0), "Extension"));
}
}
}
}
///
/// Get the path to a file in the source image.
///
/// The file.
/// The path to the file in the source image.
private string GetSourcePath(XElement xFile)
{
var sourcePath = new StringBuilder();
var component = xFile.Parent;
for (var xDirectory = component.Parent; null != xDirectory && xDirectory.Name.LocalName == "Directory"; xDirectory = xDirectory.Parent)
{
string name;
var dirSourceName = xDirectory.Attribute("SourceName")?.Value;
var dirShortSourceName = xDirectory.Attribute("ShortSourceName")?.Value;
var dirShortName = xDirectory.Attribute("ShortName")?.Value;
var dirName = xDirectory.Attribute("Name")?.Value;
if (!this.ShortNames && null != dirSourceName)
{
name = dirSourceName;
}
else if (null != dirShortSourceName)
{
name = dirShortSourceName;
}
else if (!this.ShortNames || null == dirShortName)
{
name = dirName;
}
else
{
name = dirShortName;
}
if (0 == sourcePath.Length)
{
sourcePath.Append(name);
}
else
{
sourcePath.Insert(0, Path.DirectorySeparatorChar);
sourcePath.Insert(0, name);
}
}
return sourcePath.ToString();
}
///
/// Resolve the dependencies for a table (this is a helper method for GetSortedTableNames).
///
/// The name of the table to resolve.
/// The unsorted table names.
/// The sorted table names.
private void ResolveTableDependencies(string tableName, List unsortedTableNames, HashSet sortedTableNames)
{
unsortedTableNames.Remove(tableName);
foreach (var columnDefinition in this.TableDefinitions[tableName].Columns)
{
// no dependency to resolve because this column doesn't reference another table
if (null == columnDefinition.KeyTable)
{
continue;
}
foreach (var keyTable in columnDefinition.KeyTable.Split(';'))
{
if (tableName == keyTable)
{
continue; // self-referencing dependency
}
else if (sortedTableNames.Contains(keyTable))
{
continue; // dependent table has already been sorted
}
else if (!this.TableDefinitions.Contains(keyTable))
{
this.Messaging.Write(ErrorMessages.MissingTableDefinition(keyTable));
}
else if (unsortedTableNames.Contains(keyTable))
{
this.ResolveTableDependencies(keyTable, unsortedTableNames, sortedTableNames);
}
else
{
// found a circular dependency, so ignore it (this assumes that the tables will
// use a finalize method to nest their elements since the ordering will not be
// deterministic
}
}
}
sortedTableNames.Add(tableName);
}
///
/// Get the names of the tables to process in the order they should be processed, according to their dependencies.
///
/// A StringCollection containing the ordered table names.
private HashSet GetOrderedTableNames()
{
var orderedTableNames = new HashSet();
var unsortedTableNames = new List(this.TableDefinitions.Select(t => t.Name));
// resolve the dependencies for each table
while (0 < unsortedTableNames.Count)
{
this.ResolveTableDependencies(unsortedTableNames[0], unsortedTableNames, orderedTableNames);
}
return orderedTableNames;
}
///
/// Initialize decompilation.
///
/// The collection of all tables.
///
private void InitializeDecompile(TableIndexedCollection tables, int codepage)
{
// reset all the state information
this.Compressed = false;
this.ShortNames = false;
this.Singletons.Clear();
this.IndexedElements.Clear();
this.PatchTargetFiles.Clear();
// set the codepage if its not neutral (0)
if (0 != codepage)
{
this.RootElement.SetAttributeValue("Codepage", codepage);
}
if (this.OutputType == OutputType.Module)
{
var table = tables["_SummaryInformation"];
var row = table.Rows.SingleOrDefault(r => r.FieldAsInteger(0) == 9);
this.ModularizationGuid = row?.FieldAsString(1);
}
// index the rows from the extension libraries
var indexedExtensionTables = new Dictionary>();
#if TODO_DECOMPILER_EXTENSIONS
foreach (IDecompilerExtension extension in this.extensions)
{
// Get the optional library from the extension with the rows to be removed.
Library library = extension.GetLibraryToRemove(this.tableDefinitions);
if (null != library)
{
foreach (var section in library.Sections)
{
foreach (Table table in section.Tables)
{
foreach (Row row in table.Rows)
{
string primaryKey;
string tableName;
// the Actions table needs to be handled specially
if ("WixAction" == table.Name)
{
primaryKey = row.FieldAsString(1);
if (OutputType.Module == this.outputType)
{
tableName = String.Concat("Module", row.FieldAsString(0));
}
else
{
tableName = row.FieldAsString(0);
}
}
else
{
primaryKey = row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter);
tableName = table.Name;
}
if (null != primaryKey)
{
HashSet indexedExtensionRows;
if (!indexedExtensionTables.TryGetValue(tableName, out indexedExtensionRows))
{
indexedExtensionRows = new HashSet();
indexedExtensionTables.Add(tableName, indexedExtensionRows);
}
indexedExtensionRows.Add(primaryKey);
}
}
}
}
}
}
#endif
// remove the rows from the extension libraries (to allow full round-tripping)
foreach (var kvp in indexedExtensionTables)
{
var tableName = kvp.Key;
var indexedExtensionRows = kvp.Value;
var table = tables[tableName];
if (null != table)
{
var originalRows = new RowDictionary(table);
// remove the original rows so that they can be added back if they should remain
table.Rows.Clear();
foreach (var row in originalRows.Values)
{
if (!indexedExtensionRows.Contains(row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter)))
{
table.Rows.Add(row);
}
}
}
}
}
///
/// Decompile the tables.
///
/// The output being decompiled.
private void DecompileTables(WindowsInstallerData output)
{
var orderedTableNames = this.GetOrderedTableNames();
foreach (var tableName in orderedTableNames)
{
var table = output.Tables[tableName];
// table does not exist in this database or should not be decompiled
if (null == table || !this.DecompilableTable(output, tableName))
{
continue;
}
this.Messaging.Write(VerboseMessages.DecompilingTable(table.Name));
// empty tables may be kept with EnsureTable if the user set the proper option
if (0 == table.Rows.Count && this.SuppressDroppingEmptyTables)
{
this.RootElement.Add(new XElement(Names.EnsureTableElement, new XAttribute("Id", table.Name)));
}
switch (table.Name)
{
case "_SummaryInformation":
// handled in FinalizeDecompile
break;
case "AdminExecuteSequence":
case "AdminUISequence":
case "AdvtExecuteSequence":
case "InstallExecuteSequence":
case "InstallUISequence":
case "ModuleAdminExecuteSequence":
case "ModuleAdminUISequence":
case "ModuleAdvtExecuteSequence":
case "ModuleInstallExecuteSequence":
case "ModuleInstallUISequence":
// handled in FinalizeSequenceTables
break;
case "ActionText":
this.DecompileActionTextTable(table);
break;
case "AdvtUISequence":
this.Messaging.Write(WarningMessages.DeprecatedTable(table.Name));
break;
case "AppId":
this.DecompileAppIdTable(table);
break;
case "AppSearch":
// handled in FinalizeSearchTables
break;
case "BBControl":
this.DecompileBBControlTable(table);
break;
case "Billboard":
this.DecompileBillboardTable(table);
break;
case "Binary":
this.DecompileBinaryTable(table);
break;
case "BindImage":
this.DecompileBindImageTable(table);
break;
case "CCPSearch":
// handled in FinalizeSearchTables
break;
case "CheckBox":
// handled in FinalizeCheckBoxTable
break;
case "Class":
this.DecompileClassTable(table);
break;
case "ComboBox":
this.DecompileComboBoxTable(table);
break;
case "Control":
this.DecompileControlTable(table);
break;
case "ControlCondition":
this.DecompileControlConditionTable(table);
break;
case "ControlEvent":
this.DecompileControlEventTable(table);
break;
case "CreateFolder":
this.DecompileCreateFolderTable(table);
break;
case "CustomAction":
this.DecompileCustomActionTable(table);
break;
case "CompLocator":
this.DecompileCompLocatorTable(table);
break;
case "Complus":
this.DecompileComplusTable(table);
break;
case "Component":
this.DecompileComponentTable(table);
break;
case "Condition":
this.DecompileConditionTable(table);
break;
case "Dialog":
this.DecompileDialogTable(table);
break;
case "Directory":
this.DecompileDirectoryTable(table);
break;
case "DrLocator":
this.DecompileDrLocatorTable(table);
break;
case "DuplicateFile":
this.DecompileDuplicateFileTable(table);
break;
case "Environment":
this.DecompileEnvironmentTable(table);
break;
case "Error":
this.DecompileErrorTable(table);
break;
case "EventMapping":
this.DecompileEventMappingTable(table);
break;
case "Extension":
this.DecompileExtensionTable(table);
break;
case "ExternalFiles":
this.DecompileExternalFilesTable(table);
break;
case "FamilyFileRanges":
// handled in FinalizeFamilyFileRangesTable
break;
case "Feature":
this.DecompileFeatureTable(table);
break;
case "FeatureComponents":
this.DecompileFeatureComponentsTable(table);
break;
case "File":
this.DecompileFileTable(table);
break;
case "FileSFPCatalog":
this.DecompileFileSFPCatalogTable(table);
break;
case "Font":
this.DecompileFontTable(table);
break;
case "Icon":
this.DecompileIconTable(table);
break;
case "ImageFamilies":
this.DecompileImageFamiliesTable(table);
break;
case "IniFile":
this.DecompileIniFileTable(table);
break;
case "IniLocator":
this.DecompileIniLocatorTable(table);
break;
case "IsolatedComponent":
this.DecompileIsolatedComponentTable(table);
break;
case "LaunchCondition":
this.DecompileLaunchConditionTable(table);
break;
case "ListBox":
this.DecompileListBoxTable(table);
break;
case "ListView":
this.DecompileListViewTable(table);
break;
case "LockPermissions":
this.DecompileLockPermissionsTable(table);
break;
case "Media":
this.DecompileMediaTable(table);
break;
case "MIME":
this.DecompileMIMETable(table);
break;
case "ModuleAdvtUISequence":
this.Messaging.Write(WarningMessages.DeprecatedTable(table.Name));
break;
case "ModuleComponents":
// handled by DecompileComponentTable (since the ModuleComponents table
// rows are created by nesting components under the Module element)
break;
case "ModuleConfiguration":
this.DecompileModuleConfigurationTable(table);
break;
case "ModuleDependency":
this.DecompileModuleDependencyTable(table);
break;
case "ModuleExclusion":
this.DecompileModuleExclusionTable(table);
break;
case "ModuleIgnoreTable":
this.DecompileModuleIgnoreTableTable(table);
break;
case "ModuleSignature":
this.DecompileModuleSignatureTable(table);
break;
case "ModuleSubstitution":
this.DecompileModuleSubstitutionTable(table);
break;
case "MoveFile":
this.DecompileMoveFileTable(table);
break;
case "MsiAssembly":
// handled in FinalizeFileTable
break;
case "MsiDigitalCertificate":
this.DecompileMsiDigitalCertificateTable(table);
break;
case "MsiDigitalSignature":
this.DecompileMsiDigitalSignatureTable(table);
break;
case "MsiEmbeddedChainer":
this.DecompileMsiEmbeddedChainerTable(table);
break;
case "MsiEmbeddedUI":
this.DecompileMsiEmbeddedUITable(table);
break;
case "MsiLockPermissionsEx":
this.DecompileMsiLockPermissionsExTable(table);
break;
case "MsiPackageCertificate":
this.DecompileMsiPackageCertificateTable(table);
break;
case "MsiPatchCertificate":
this.DecompileMsiPatchCertificateTable(table);
break;
case "MsiShortcutProperty":
this.DecompileMsiShortcutPropertyTable(table);
break;
case "ODBCAttribute":
this.DecompileODBCAttributeTable(table);
break;
case "ODBCDataSource":
this.DecompileODBCDataSourceTable(table);
break;
case "ODBCDriver":
this.DecompileODBCDriverTable(table);
break;
case "ODBCSourceAttribute":
this.DecompileODBCSourceAttributeTable(table);
break;
case "ODBCTranslator":
this.DecompileODBCTranslatorTable(table);
break;
case "PatchMetadata":
this.DecompilePatchMetadataTable(table);
break;
case "PatchSequence":
this.DecompilePatchSequenceTable(table);
break;
case "ProgId":
this.DecompileProgIdTable(table);
break;
case "Properties":
this.DecompilePropertiesTable(table);
break;
case "Property":
this.DecompilePropertyTable(table);
break;
case "PublishComponent":
this.DecompilePublishComponentTable(table);
break;
case "RadioButton":
this.DecompileRadioButtonTable(table);
break;
case "Registry":
this.DecompileRegistryTable(table);
break;
case "RegLocator":
this.DecompileRegLocatorTable(table);
break;
case "RemoveFile":
this.DecompileRemoveFileTable(table);
break;
case "RemoveIniFile":
this.DecompileRemoveIniFileTable(table);
break;
case "RemoveRegistry":
this.DecompileRemoveRegistryTable(table);
break;
case "ReserveCost":
this.DecompileReserveCostTable(table);
break;
case "SelfReg":
this.DecompileSelfRegTable(table);
break;
case "ServiceControl":
this.DecompileServiceControlTable(table);
break;
case "ServiceInstall":
this.DecompileServiceInstallTable(table);
break;
case "SFPCatalog":
this.DecompileSFPCatalogTable(table);
break;
case "Shortcut":
this.DecompileShortcutTable(table);
break;
case "Signature":
this.DecompileSignatureTable(table);
break;
case "TargetFiles_OptionalData":
this.DecompileTargetFiles_OptionalDataTable(table);
break;
case "TargetImages":
this.DecompileTargetImagesTable(table);
break;
case "TextStyle":
this.DecompileTextStyleTable(table);
break;
case "TypeLib":
this.DecompileTypeLibTable(table);
break;
case "Upgrade":
this.DecompileUpgradeTable(table);
break;
case "UpgradedFiles_OptionalData":
this.DecompileUpgradedFiles_OptionalDataTable(table);
break;
case "UpgradedFilesToIgnore":
this.DecompileUpgradedFilesToIgnoreTable(table);
break;
case "UpgradedImages":
this.DecompileUpgradedImagesTable(table);
break;
case "UIText":
this.DecompileUITextTable(table);
break;
case "Verb":
this.DecompileVerbTable(table);
break;
default:
#if TODO_DECOMPILER_EXTENSIONS
if (this.ExtensionsByTableName.TryGetValue(table.Name, out var extension)
{
extension.DecompileTable(table);
}
else
#endif
if (!this.SuppressCustomTables)
{
this.DecompileCustomTable(table);
}
break;
}
}
}
///
/// Determine if a particular table should be decompiled with the current settings.
///
/// The output being decompiled.
/// The name of a table.
/// true if the table should be decompiled; false otherwise.
private bool DecompilableTable(WindowsInstallerData output, string tableName)
{
switch (tableName)
{
case "ActionText":
case "BBControl":
case "Billboard":
case "CheckBox":
case "Control":
case "ControlCondition":
case "ControlEvent":
case "Dialog":
case "Error":
case "EventMapping":
case "RadioButton":
case "TextStyle":
case "UIText":
return !this.SuppressUI;
case "ModuleAdminExecuteSequence":
case "ModuleAdminUISequence":
case "ModuleAdvtExecuteSequence":
case "ModuleAdvtUISequence":
case "ModuleComponents":
case "ModuleConfiguration":
case "ModuleDependency":
case "ModuleIgnoreTable":
case "ModuleInstallExecuteSequence":
case "ModuleInstallUISequence":
case "ModuleExclusion":
case "ModuleSignature":
case "ModuleSubstitution":
if (OutputType.Module != output.Type)
{
this.Messaging.Write(WarningMessages.SkippingMergeModuleTable(output.SourceLineNumbers, tableName));
return false;
}
else
{
return true;
}
case "ExternalFiles":
case "FamilyFileRanges":
case "ImageFamilies":
case "PatchMetadata":
case "PatchSequence":
case "Properties":
case "TargetFiles_OptionalData":
case "TargetImages":
case "UpgradedFiles_OptionalData":
case "UpgradedFilesToIgnore":
case "UpgradedImages":
if (OutputType.PatchCreation != output.Type)
{
this.Messaging.Write(WarningMessages.SkippingPatchCreationTable(output.SourceLineNumbers, tableName));
return false;
}
else
{
return true;
}
case "MsiPatchHeaders":
case "MsiPatchMetadata":
case "MsiPatchOldAssemblyName":
case "MsiPatchOldAssemblyFile":
case "MsiPatchSequence":
case "Patch":
case "PatchPackage":
this.Messaging.Write(WarningMessages.PatchTable(output.SourceLineNumbers, tableName));
return false;
case "_SummaryInformation":
return true;
case "_Validation":
case "MsiAssemblyName":
case "MsiFileHash":
return false;
default: // all other tables are allowed in any output except for a patch creation package
if (OutputType.PatchCreation == output.Type)
{
this.Messaging.Write(WarningMessages.IllegalPatchCreationTable(output.SourceLineNumbers, tableName));
return false;
}
else
{
return true;
}
}
}
///
/// Decompile the _SummaryInformation table.
///
/// The tables to decompile.
private void FinalizeSummaryInformationStream(TableIndexedCollection tables)
{
var table = tables["_SummaryInformation"];
if (OutputType.Module == this.OutputType || OutputType.Product == this.OutputType)
{
var xSummaryInformation = new XElement(Names.SummaryInformationElement);
foreach (var row in table.Rows)
{
var value = row.FieldAsString(1);
if (!String.IsNullOrEmpty(value))
{
switch (row.FieldAsInteger(0))
{
case 1:
if ("1252" != value)
{
xSummaryInformation.SetAttributeValue("Codepage", value);
}
break;
case 3:
{
var productName = this.RootElement.Attribute("Name")?.Value;
if (value != productName)
{
xSummaryInformation.SetAttributeValue("Description", value);
}
break;
}
case 4:
{
var productManufacturer = this.RootElement.Attribute("Manufacturer")?.Value;
if (value != productManufacturer)
{
xSummaryInformation.SetAttributeValue("Manufacturer", value);
}
break;
}
case 5:
if ("Installer" != value)
{
xSummaryInformation.SetAttributeValue("Keywords", value);
}
break;
case 7:
var template = value.Split(';');
if (0 < template.Length && 0 < template[template.Length - 1].Length)
{
this.RootElement.SetAttributeValue("Language", template[template.Length - 1]);
}
break;
case 14:
var installerVersion = row.FieldAsInteger(1);
// Default InstallerVersion.
if (installerVersion != 500)
{
this.RootElement.SetAttributeValue("InstallerVersion", installerVersion);
}
break;
case 15:
var wordCount = row.FieldAsInteger(1);
if (0x1 == (wordCount & 0x1))
{
this.ShortNames = true;
if (OutputType.Product == this.OutputType)
{
this.RootElement.SetAttributeValue("ShortNames", "yes");
}
}
if (0x2 == (wordCount & 0x2))
{
this.Compressed = true;
if (OutputType.Product == this.OutputType)
{
this.RootElement.SetAttributeValue("Compressed", "yes");
}
}
if (OutputType.Product == this.OutputType)
{
if (0x8 == (wordCount & 0x8))
{
this.RootElement.SetAttributeValue("Scope", "perUser");
}
else
{
var xAllUsers = this.RootElement.Elements(Names.PropertyElement).SingleOrDefault(p => p.Attribute("Id")?.Value == "ALLUSERS");
if (xAllUsers?.Attribute("Value")?.Value == "1")
{
xAllUsers?.Remove();
}
}
}
break;
}
}
}
if (xSummaryInformation.HasAttributes)
{
this.RootElement.Add(xSummaryInformation);
}
}
else
{
var xPatchInformation = new XElement(Names.PatchInformationElement);
foreach (var row in table.Rows)
{
var propertyId = row.FieldAsInteger(0);
var value = row.FieldAsString(1);
if (!String.IsNullOrEmpty(value))
{
switch (propertyId)
{
case 1:
if ("1252" != value)
{
xPatchInformation.SetAttributeValue("SummaryCodepage", value);
}
break;
case 3:
xPatchInformation.SetAttributeValue("Description", value);
break;
case 4:
xPatchInformation.SetAttributeValue("Manufacturer", value);
break;
case 5:
if ("Installer,Patching,PCP,Database" != value)
{
xPatchInformation.SetAttributeValue("Keywords", value);
}
break;
case 6:
xPatchInformation.SetAttributeValue("Comments", value);
break;
case 19:
var security = Convert.ToInt32(value, CultureInfo.InvariantCulture);
switch (security)
{
case 0:
xPatchInformation.SetAttributeValue("ReadOnly", "no");
break;
case 4:
xPatchInformation.SetAttributeValue("ReadOnly", "yes");
break;
}
break;
}
}
}
this.RootElement.Add(xPatchInformation);
}
}
///
/// Decompile the ActionText table.
///
/// The table to decompile.
private void DecompileActionTextTable(Table table)
{
foreach (var row in table.Rows)
{
var progressText = new XElement(Names.ProgressTextElement,
new XAttribute("Action", row.FieldAsString(0)),
row.IsColumnNull(1) ? null : new XAttribute("Message", row.FieldAsString(1)),
row.IsColumnNull(2) ? null : new XAttribute("Template", row.FieldAsString(2)));
this.UIElement.Add(progressText);
}
}
///
/// Decompile the AppId table.
///
/// The table to decompile.
private void DecompileAppIdTable(Table table)
{
foreach (var row in table.Rows)
{
var appId = new XElement(Names.AppIdElement,
new XAttribute("Advertise", "yes"),
new XAttribute("Id", row.FieldAsString(0)),
row.IsColumnNull(1) ? null : new XAttribute("RemoteServerName", row.FieldAsString(1)),
row.IsColumnNull(2) ? null : new XAttribute("LocalService", row.FieldAsString(2)),
row.IsColumnNull(3) ? null : new XAttribute("ServiceParameters", row.FieldAsString(3)),
row.IsColumnNull(4) ? null : new XAttribute("DllSurrogate", row.FieldAsString(4)),
row.IsColumnNull(5) || row.FieldAsInteger(5) != 1 ? null : new XAttribute("ActivateAtStorage", "yes"),
row.IsColumnNull(6) || row.FieldAsInteger(6) != 1 ? null : new XAttribute("RunAsInteractiveUser", "yes"));
this.RootElement.Add(appId);
this.IndexElement(row, appId);
}
}
///
/// Decompile the BBControl table.
///
/// The table to decompile.
private void DecompileBBControlTable(Table table)
{
foreach (BBControlRow bbControlRow in table.Rows)
{
var xControl = new XElement(Names.ControlElement,
new XAttribute("Id", bbControlRow.BBControl),
new XAttribute("Type", bbControlRow.Type),
new XAttribute("X", bbControlRow.X),
new XAttribute("Y", bbControlRow.Y),
new XAttribute("Width", bbControlRow.Width),
new XAttribute("Height", bbControlRow.Height),
null == bbControlRow.Text ? null : new XAttribute("Text", bbControlRow.Text));
if (null != bbControlRow[7])
{
SetControlAttributes(bbControlRow.Attributes, xControl);
}
if (this.TryGetIndexedElement("Billboard", out var xBillboard, bbControlRow.Billboard))
{
xBillboard.Add(xControl);
}
else
{
this.Messaging.Write(WarningMessages.ExpectedForeignRow(bbControlRow.SourceLineNumbers, table.Name, bbControlRow.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Billboard_", bbControlRow.Billboard, "Billboard"));
}
}
}
///
/// Decompile the Billboard table.
///
/// The table to decompile.
private void DecompileBillboardTable(Table table)
{
var billboards = new SortedList();
foreach (var row in table.Rows)
{
var xBillboard = new XElement(Names.BillboardElement,
new XAttribute("Id", row.FieldAsString(0)),
new XAttribute("Feature", row.FieldAsString(1)));
this.IndexElement(row, xBillboard);
billboards.Add(String.Format(CultureInfo.InvariantCulture, "{0}|{1:0000000000}", row[0], row[3]), row);
}
var billboardActions = new Dictionary();
foreach (var row in billboards.Values)
{
var xBillboard = this.GetIndexedElement(row);
if (!billboardActions.TryGetValue(row.FieldAsString(2), out var xBillboardAction))
{
xBillboardAction = new XElement(Names.BillboardActionElement,
new XAttribute("Id", row.FieldAsString(2)));
this.UIElement.Add(xBillboardAction);
billboardActions.Add(row.FieldAsString(2), xBillboardAction);
}
xBillboardAction.Add(xBillboard);
}
}
///
/// Decompile the Binary table.
///
/// The table to decompile.
private void DecompileBinaryTable(Table table)
{
foreach (var row in table.Rows)
{
var xBinary = new XElement(Names.BinaryElement,
new XAttribute("Id", row.FieldAsString(0)),
new XAttribute("SourceFile", row.FieldAsString(1)));
this.RootElement.Add(xBinary);
}
}
///
/// Decompile the BindImage table.
///
/// The table to decompile.
private void DecompileBindImageTable(Table table)
{
foreach (var row in table.Rows)
{
if (this.TryGetIndexedElement("File", out var xFile, row.FieldAsString(0)))
{
xFile.SetAttributeValue("BindPath", row.FieldAsString(1));
}
else
{
this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "File_", row.FieldAsString(0), "File"));
}
}
}
///
/// Decompile the Class table.
///
/// The table to decompile.
private void DecompileClassTable(Table table)
{
foreach (var row in table.Rows)
{
var xClass = new XElement(Names.ClassElement,
new XAttribute("Id", row.FieldAsString(0)),
new XAttribute("Advertise", "yes"),
new XAttribute("Context", row.FieldAsString(1)),
row.IsColumnNull(4) ? null : new XAttribute("Description", row.FieldAsString(4)),
row.IsColumnNull(5) ? null : new XAttribute("AppId", row.FieldAsString(5)),
row.IsColumnNull(7) ? null : new XAttribute("Icon", row.FieldAsString(7)),
row.IsColumnNull(8) ? null : new XAttribute("IconIndex", row.FieldAsString(8)),
row.IsColumnNull(9) ? null : new XAttribute("Handler", row.FieldAsString(9)),
row.IsColumnNull(10) ? null : new XAttribute("Argument", row.FieldAsString(10)));
if (!row.IsColumnNull(6))
{
var fileTypeMaskStrings = row.FieldAsString(6).Split(';');
try
{
foreach (var fileTypeMaskString in fileTypeMaskStrings)
{
var fileTypeMaskParts = fileTypeMaskString.Split(',');
if (4 == fileTypeMaskParts.Length)
{
var xFileTypeMask = new XElement(Names.FileTypeMaskElement,
new XAttribute("Offset", Convert.ToInt32(fileTypeMaskParts[0], CultureInfo.InvariantCulture)),
new XAttribute("Mask", fileTypeMaskParts[2]),
new XAttribute("Value", fileTypeMaskParts[3]));
xClass.Add(xFileTypeMask);
}
else
{
// TODO: warn
}
}
}
catch (FormatException)
{
this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[6].Column.Name, row[6]));
}
catch (OverflowException)
{
this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[6].Column.Name, row[6]));
}
}
if (!row.IsColumnNull(12))
{
if (1 == row.FieldAsInteger(12))
{
xClass.SetAttributeValue("RelativePath", "yes");
}
else
{
this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[12].Column.Name, row[12]));
}
}
this.AddChildToParent("Component", xClass, row, 2);
this.IndexElement(row, xClass);
}
}
///
/// Decompile the ComboBox table.
///
/// The table to decompile.
private void DecompileComboBoxTable(Table table)
{
// sort the combo boxes by their property and order
var comboBoxRows = table.Rows.Select(row => row).OrderBy(row => String.Format("{0}|{1:0000000000}", row.FieldAsString(0), row.FieldAsInteger(1)));
XElement xComboBox = null;
string property = null;
foreach (var row in comboBoxRows)
{
if (null == xComboBox || row.FieldAsString(0) != property)
{
property = row.FieldAsString(0);
xComboBox = new XElement(Names.ComboBoxElement,
new XAttribute("Property", property));
this.UIElement.Add(xComboBox);
}
var xListItem = new XElement(Names.ListItemElement,
new XAttribute("Value", row.FieldAsString(2)),
row.IsColumnNull(3) ? null : new XAttribute("Text", row.FieldAsString(3)));
xComboBox.Add(xListItem);
}
}
///
/// Decompile the Control table.
///
/// The table to decompile.
private void DecompileControlTable(Table table)
{
foreach (ControlRow controlRow in table.Rows)
{
var xControl = new XElement(Names.ControlElement,
new XAttribute("Id", controlRow.Control),
new XAttribute("Type", controlRow.Type),
new XAttribute("X", controlRow.X),
new XAttribute("Y", controlRow.Y),
new XAttribute("Width", controlRow.Width),
new XAttribute("Height", controlRow.Height),
new XAttribute("Text", controlRow.Text));
if (!controlRow.IsColumnNull(7))
{
string[] specialAttributes;
// sets various common attributes like Disabled, Indirect, Integer, ...
SetControlAttributes(controlRow.Attributes, xControl);
switch (controlRow.Type)
{
case "Bitmap":
specialAttributes = BitmapControlAttributes;
break;
case "CheckBox":
specialAttributes = CheckboxControlAttributes;
break;
case "ComboBox":
specialAttributes = ComboboxControlAttributes;
break;
case "DirectoryCombo":
specialAttributes = VolumeControlAttributes;
break;
case "Edit":
specialAttributes = EditControlAttributes;
break;
case "Icon":
specialAttributes = IconControlAttributes;
break;
case "ListBox":
specialAttributes = ListboxControlAttributes;
break;
case "ListView":
specialAttributes = ListviewControlAttributes;
break;
case "MaskedEdit":
specialAttributes = EditControlAttributes;
break;
case "PathEdit":
specialAttributes = EditControlAttributes;
break;
case "ProgressBar":
specialAttributes = ProgressControlAttributes;
break;
case "PushButton":
specialAttributes = ButtonControlAttributes;
break;
case "RadioButtonGroup":
specialAttributes = RadioControlAttributes;
break;
case "Text":
specialAttributes = TextControlAttributes;
break;
case "VolumeCostList":
specialAttributes = VolumeControlAttributes;
break;
case "VolumeSelectCombo":
specialAttributes = VolumeControlAttributes;
break;
default:
specialAttributes = null;
break;
}
if (null != specialAttributes)
{
var iconSizeSet = false;
for (var i = 16; 32 > i; i++)
{
if (1 == ((controlRow.Attributes >> i) & 1))
{
string attribute = null;
if (specialAttributes.Length > (i - 16))
{
attribute = specialAttributes[i - 16];
}
// unknown attribute
if (null == attribute)
{
this.Messaging.Write(WarningMessages.IllegalColumnValue(controlRow.SourceLineNumbers, table.Name, controlRow.Fields[7].Column.Name, controlRow.Attributes));
continue;
}
switch (attribute)
{
case "Bitmap":
xControl.SetAttributeValue("Bitmap", "yes");
break;
case "CDROM":
xControl.SetAttributeValue("CDROM", "yes");
break;
case "ComboList":
xControl.SetAttributeValue("ComboList", "yes");
break;
case "ElevationShield":
xControl.SetAttributeValue("ElevationShield", "yes");
break;
case "Fixed":
xControl.SetAttributeValue("Fixed", "yes");
break;
case "FixedSize":
xControl.SetAttributeValue("FixedSize", "yes");
break;
case "Floppy":
xControl.SetAttributeValue("Floppy", "yes");
break;
case "FormatSize":
xControl.SetAttributeValue("FormatSize", "yes");
break;
case "HasBorder":
xControl.SetAttributeValue("HasBorder", "yes");
break;
case "Icon":
xControl.SetAttributeValue("Icon", "yes");
break;
case "Icon16":
if (iconSizeSet)
{
xControl.SetAttributeValue("IconSize", "48");
}
else
{
iconSizeSet = true;
xControl.SetAttributeValue("IconSize", "16");
}
break;
case "Icon32":
if (iconSizeSet)
{
xControl.SetAttributeValue("IconSize", "48");
}
else
{
iconSizeSet = true;
xControl.SetAttributeValue("IconSize", "32");
}
break;
case "Image":
xControl.SetAttributeValue("Image", "yes");
break;
case "Multiline":
xControl.SetAttributeValue("Multiline", "yes");
break;
case "NoPrefix":
xControl.SetAttributeValue("NoPrefix", "yes");
break;
case "NoWrap":
xControl.SetAttributeValue("NoWrap", "yes");
break;
case "Password":
xControl.SetAttributeValue("Password", "yes");
break;
case "ProgressBlocks":
xControl.SetAttributeValue("ProgressBlocks", "yes");
break;
case "PushLike":
xControl.SetAttributeValue("PushLike", "yes");
break;
case "RAMDisk":
xControl.SetAttributeValue("RAMDisk", "yes");
break;
case "Remote":
xControl.SetAttributeValue("Remote", "yes");
break;
case "Removable":
xControl.SetAttributeValue("Removable", "yes");
break;
case "ShowRollbackCost":
xControl.SetAttributeValue("ShowRollbackCost", "yes");
break;
case "Sorted":
xControl.SetAttributeValue("Sorted", "yes");
break;
case "Transparent":
xControl.SetAttributeValue("Transparent", "yes");
break;
case "UserLanguage":
xControl.SetAttributeValue("UserLanguage", "yes");
break;
default:
throw new InvalidOperationException($"Unknown control attribute: '{attribute}'.");
}
}
}
}
else if (0 < (controlRow.Attributes & 0xFFFF0000))
{
this.Messaging.Write(WarningMessages.IllegalColumnValue(controlRow.SourceLineNumbers, table.Name, controlRow.Fields[7].Column.Name, controlRow.Attributes));
}
}
// FinalizeCheckBoxTable adds Control/@Property|@CheckBoxPropertyRef
if (null != controlRow.Property && 0 != String.CompareOrdinal("CheckBox", controlRow.Type))
{
xControl.SetAttributeValue("Property", controlRow.Property);
}
if (null != controlRow.Help)
{
var help = controlRow.Help.Split('|');
if (2 == help.Length)
{
if (0 < help[0].Length)
{
xControl.SetAttributeValue("ToolTip", help[0]);
}
if (0 < help[1].Length)
{
xControl.SetAttributeValue("Help", help[1]);
}
}
}
this.IndexElement(controlRow, xControl);
}
}
///
/// Decompile the ControlCondition table.
///
/// The table to decompile.
private void DecompileControlConditionTable(Table table)
{
foreach (var row in table.Rows)
{
if (this.TryGetIndexedElement("Control", out var xControl, row.FieldAsString(0), row.FieldAsString(1)))
{
switch (row.FieldAsString(2))
{
case "Default":
xControl.SetAttributeValue("DefaultCondition", row.FieldAsString(3));
break;
case "Disable":
xControl.SetAttributeValue("DisableCondition", row.FieldAsString(3));
break;
case "Enable":
xControl.SetAttributeValue("EnableCondition", row.FieldAsString(3));
break;
case "Hide":
xControl.SetAttributeValue("HideCondition", row.FieldAsString(3));
break;
case "Show":
xControl.SetAttributeValue("ShowCondition", row.FieldAsString(3));
break;
default:
this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[2].Column.Name, row[2]));
break;
}
}
else
{
this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Dialog_", row.FieldAsString(0), "Control_", row.FieldAsString(1), "Control"));
}
}
}
///
/// Decompile the ControlEvent table.
///
/// The table to decompile.
private void DecompileControlEventTable(Table table)
{
var controlEvents = new SortedList();
foreach (var row in table.Rows)
{
var xPublish = new XElement(Names.PublishElement,
new XAttribute("Condition", row.FieldAsString(4)));
var publishEvent = row.FieldAsString(2);
if (publishEvent.StartsWith("[", StringComparison.Ordinal) && publishEvent.EndsWith("]", StringComparison.Ordinal))
{
xPublish.SetAttributeValue("Property", publishEvent.Substring(1, publishEvent.Length - 2));
if ("{}" != row.FieldAsString(3))
{
xPublish.SetAttributeValue("Value", row.FieldAsString(3));
}
}
else
{
xPublish.SetAttributeValue("Event", publishEvent);
xPublish.SetAttributeValue("Value", row.FieldAsString(3));
}
controlEvents.Add(String.Format(CultureInfo.InvariantCulture, "{0}|{1}|{2:0000000000}|{3}|{4}|{5}", row.FieldAsString(0), row.FieldAsString(1), row.FieldAsNullableInteger(5) ?? 0, row.FieldAsString(2), row.FieldAsString(3), row.FieldAsString(4)), row);
this.IndexElement(row, xPublish);
}
foreach (var row in controlEvents.Values)
{
if (this.TryGetIndexedElement("Control", out var xControl, row.FieldAsString(0), row.FieldAsString(1)))
{
var xPublish = this.GetIndexedElement(row);
xControl.Add(xPublish);
}
else
{
this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Dialog_", row.FieldAsString(0), "Control_", row.FieldAsString(1), "Control"));
}
}
}
///
/// Decompile a custom table.
///
/// The table to decompile.
private void DecompileCustomTable(Table table)
{
if (0 < table.Rows.Count || this.SuppressDroppingEmptyTables)
{
this.Messaging.Write(WarningMessages.DecompilingAsCustomTable(table.Rows[0].SourceLineNumbers, table.Name));
var xCustomTable = new XElement(Names.CustomTableElement,
new XAttribute("Id", table.Name));
foreach (var columnDefinition in table.Definition.Columns)
{
var xColumn = new XElement(Names.ColumnElement,
new XAttribute("Id", columnDefinition.Name),
columnDefinition.Description == null ? null : new XAttribute("Description", columnDefinition.Description),
columnDefinition.KeyTable == null ? null : new XAttribute("KeyTable", columnDefinition.KeyTable),
!columnDefinition.KeyColumn.HasValue ? null : new XAttribute("KeyColumn", columnDefinition.KeyColumn.Value),
!columnDefinition.IsLocalizable ? null : new XAttribute("Localizable", "yes"),
!columnDefinition.MaxValue.HasValue ? null : new XAttribute("MaxValue", columnDefinition.MaxValue.Value),
!columnDefinition.MinValue.HasValue ? null : new XAttribute("MinValue", columnDefinition.MinValue.Value),
!columnDefinition.Nullable ? null : new XAttribute("Nullable", "yes"),
!columnDefinition.PrimaryKey ? null : new XAttribute("PrimaryKey", "yes"),
columnDefinition.Possibilities == null ? null : new XAttribute("Possibilities", "yes"),
new XAttribute("Width", columnDefinition.Length));
if (ColumnCategory.Unknown != columnDefinition.Category)
{
switch (columnDefinition.Category)
{
case ColumnCategory.Text:
xColumn.SetAttributeValue("Category", "text");
break;
case ColumnCategory.UpperCase:
xColumn.SetAttributeValue("Category", "upperCase");
break;
case ColumnCategory.LowerCase:
xColumn.SetAttributeValue("Category", "lowerCase");
break;
case ColumnCategory.Integer:
xColumn.SetAttributeValue("Category", "integer");
break;
case ColumnCategory.DoubleInteger:
xColumn.SetAttributeValue("Category", "doubleInteger");
break;
case ColumnCategory.TimeDate:
xColumn.SetAttributeValue("Category", "timeDate");
break;
case ColumnCategory.Identifier:
xColumn.SetAttributeValue("Category", "identifier");
break;
case ColumnCategory.Property:
xColumn.SetAttributeValue("Category", "property");
break;
case ColumnCategory.Filename:
xColumn.SetAttributeValue("Category", "filename");
break;
case ColumnCategory.WildCardFilename:
xColumn.SetAttributeValue("Category", "wildCardFilename");
break;
case ColumnCategory.Path:
xColumn.SetAttributeValue("Category", "path");
break;
case ColumnCategory.Paths:
xColumn.SetAttributeValue("Category", "paths");
break;
case ColumnCategory.AnyPath:
xColumn.SetAttributeValue("Category", "anyPath");
break;
case ColumnCategory.DefaultDir:
xColumn.SetAttributeValue("Category", "defaultDir");
break;
case ColumnCategory.RegPath:
xColumn.SetAttributeValue("Category", "regPath");
break;
case ColumnCategory.Formatted:
xColumn.SetAttributeValue("Category", "formatted");
break;
case ColumnCategory.FormattedSDDLText:
xColumn.SetAttributeValue("Category", "formattedSddl");
break;
case ColumnCategory.Template:
xColumn.SetAttributeValue("Category", "template");
break;
case ColumnCategory.Condition:
xColumn.SetAttributeValue("Category", "condition");
break;
case ColumnCategory.Guid:
xColumn.SetAttributeValue("Category", "guid");
break;
case ColumnCategory.Version:
xColumn.SetAttributeValue("Category", "version");
break;
case ColumnCategory.Language:
xColumn.SetAttributeValue("Category", "language");
break;
case ColumnCategory.Binary:
xColumn.SetAttributeValue("Category", "binary");
break;
case ColumnCategory.CustomSource:
xColumn.SetAttributeValue("Category", "customSource");
break;
case ColumnCategory.Cabinet:
xColumn.SetAttributeValue("Category", "cabinet");
break;
case ColumnCategory.Shortcut:
xColumn.SetAttributeValue("Category", "shortcut");
break;
default:
throw new InvalidOperationException($"Unknown custom column category '{columnDefinition.Category.ToString()}'.");
}
}
if (ColumnModularizeType.None != columnDefinition.ModularizeType)
{
switch (columnDefinition.ModularizeType)
{
case ColumnModularizeType.Column:
xColumn.SetAttributeValue("Modularize", "Column");
break;
case ColumnModularizeType.Condition:
xColumn.SetAttributeValue("Modularize", "Condition");
break;
case ColumnModularizeType.Icon:
xColumn.SetAttributeValue("Modularize", "Icon");
break;
case ColumnModularizeType.Property:
xColumn.SetAttributeValue("Modularize", "Property");
break;
case ColumnModularizeType.SemicolonDelimited:
xColumn.SetAttributeValue("Modularize", "SemicolonDelimited");
break;
default:
throw new InvalidOperationException($"Unknown custom column modularization type '{columnDefinition.ModularizeType.ToString()}'.");
}
}
if (ColumnType.Unknown != columnDefinition.Type)
{
switch (columnDefinition.Type)
{
case ColumnType.Localized:
xColumn.SetAttributeValue("Localizable", "yes");
xColumn.SetAttributeValue("Type", "string");
break;
case ColumnType.Number:
xColumn.SetAttributeValue("Type", "int");
break;
case ColumnType.Object:
xColumn.SetAttributeValue("Type", "binary");
break;
case ColumnType.Preserved:
case ColumnType.String:
xColumn.SetAttributeValue("Type", "string");
break;
default:
throw new InvalidOperationException($"Unknown custom column type '{columnDefinition.Type}'.");
}
}
xCustomTable.Add(xColumn);
}
foreach (var row in table.Rows)
{
var xRow = new XElement(Names.RowElement);
foreach (var field in row.Fields.Where(f => f.Data != null))
{
var xData = new XElement(Names.DataElement,
new XAttribute("Column", field.Column.Name),
new XAttribute("Value", field.AsString()));
xRow.Add(xData);
}
xCustomTable.Add(xRow);
}
this.RootElement.Add(xCustomTable);
}
}
///
/// Decompile the CreateFolder table.
///
/// The table to decompile.
private void DecompileCreateFolderTable(Table table)
{
foreach (var row in table.Rows)
{
var xCreateFolder = new XElement(Names.CreateFolderElement,
new XAttribute("Directory", row.FieldAsString(0)));
this.AddChildToParent("Component", xCreateFolder, row, 1);
this.IndexElement(row, xCreateFolder);
}
}
///
/// Decompile the CustomAction table.
///
/// The table to decompile.
private void DecompileCustomActionTable(Table table)
{
foreach (var row in table.Rows)
{
var xCustomAction = new XElement(Names.CustomActionElement,
new XAttribute("Id", row.FieldAsString(0)));
var type = row.FieldAsInteger(1);
if (WindowsInstallerConstants.MsidbCustomActionTypeHideTarget == (type & WindowsInstallerConstants.MsidbCustomActionTypeHideTarget))
{
xCustomAction.SetAttributeValue("HideTarget", "yes");
}
if (WindowsInstallerConstants.MsidbCustomActionTypeNoImpersonate == (type & WindowsInstallerConstants.MsidbCustomActionTypeNoImpersonate))
{
xCustomAction.SetAttributeValue("Impersonate", "no");
}
if (WindowsInstallerConstants.MsidbCustomActionTypeTSAware == (type & WindowsInstallerConstants.MsidbCustomActionTypeTSAware))
{
xCustomAction.SetAttributeValue("TerminalServerAware", "yes");
}
if (WindowsInstallerConstants.MsidbCustomActionType64BitScript == (type & WindowsInstallerConstants.MsidbCustomActionType64BitScript))
{
xCustomAction.SetAttributeValue("Bitness", "always64");
}
else if (WindowsInstallerConstants.MsidbCustomActionTypeVBScript == (type & WindowsInstallerConstants.MsidbCustomActionTypeVBScript) ||
WindowsInstallerConstants.MsidbCustomActionTypeJScript == (type & WindowsInstallerConstants.MsidbCustomActionTypeJScript))
{
xCustomAction.SetAttributeValue("Bitness", "always32");
}
switch (type & WindowsInstallerConstants.MsidbCustomActionTypeExecuteBits)
{
case 0:
// this is the default value
break;
case WindowsInstallerConstants.MsidbCustomActionTypeFirstSequence:
xCustomAction.SetAttributeValue("Execute", "firstSequence");
break;
case WindowsInstallerConstants.MsidbCustomActionTypeOncePerProcess:
xCustomAction.SetAttributeValue("Execute", "oncePerProcess");
break;
case WindowsInstallerConstants.MsidbCustomActionTypeClientRepeat:
xCustomAction.SetAttributeValue("Execute", "secondSequence");
break;
case WindowsInstallerConstants.MsidbCustomActionTypeInScript:
xCustomAction.SetAttributeValue("Execute", "deferred");
break;
case WindowsInstallerConstants.MsidbCustomActionTypeInScript + WindowsInstallerConstants.MsidbCustomActionTypeRollback:
xCustomAction.SetAttributeValue("Execute", "rollback");
break;
case WindowsInstallerConstants.MsidbCustomActionTypeInScript + WindowsInstallerConstants.MsidbCustomActionTypeCommit:
xCustomAction.SetAttributeValue("Execute", "commit");
break;
default:
this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[1].Column.Name, row[1]));
break;
}
switch (type & WindowsInstallerConstants.MsidbCustomActionTypeReturnBits)
{
case 0:
// this is the default value
break;
case WindowsInstallerConstants.MsidbCustomActionTypeContinue:
xCustomAction.SetAttributeValue("Return", "ignore");
break;
case WindowsInstallerConstants.MsidbCustomActionTypeAsync:
xCustomAction.SetAttributeValue("Return", "asyncWait");
break;
case WindowsInstallerConstants.MsidbCustomActionTypeAsync + WindowsInstallerConstants.MsidbCustomActionTypeContinue:
xCustomAction.SetAttributeValue("Return", "asyncNoWait");
break;
default:
this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[1].Column.Name, row[1]));
break;
}
var source = type & WindowsInstallerConstants.MsidbCustomActionTypeSourceBits;
switch (source)
{
case WindowsInstallerConstants.MsidbCustomActionTypeBinaryData:
xCustomAction.SetAttributeValue("BinaryRef", row.FieldAsString(2));
break;
case WindowsInstallerConstants.MsidbCustomActionTypeSourceFile:
if (!row.IsColumnNull(2))
{
xCustomAction.SetAttributeValue("FileRef", row.FieldAsString(2));
}
break;
case WindowsInstallerConstants.MsidbCustomActionTypeDirectory:
if (!row.IsColumnNull(2))
{
xCustomAction.SetAttributeValue("Directory", row.FieldAsString(2));
}
break;
case WindowsInstallerConstants.MsidbCustomActionTypeProperty:
xCustomAction.SetAttributeValue("Property", row.FieldAsString(2));
break;
default:
this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[1].Column.Name, row[1]));
break;
}
switch (type & WindowsInstallerConstants.MsidbCustomActionTypeTargetBits)
{
case WindowsInstallerConstants.MsidbCustomActionTypeDll:
xCustomAction.SetAttributeValue("DllEntry", row.FieldAsString(3));
break;
case WindowsInstallerConstants.MsidbCustomActionTypeExe:
xCustomAction.SetAttributeValue("ExeCommand", row.FieldAsString(3));
break;
case WindowsInstallerConstants.MsidbCustomActionTypeTextData:
if (WindowsInstallerConstants.MsidbCustomActionTypeSourceFile == source)
{
xCustomAction.SetAttributeValue("Error", row.FieldAsString(3));
}
else
{
xCustomAction.SetAttributeValue("Value", row.FieldAsString(3));
}
break;
case WindowsInstallerConstants.MsidbCustomActionTypeJScript:
if (WindowsInstallerConstants.MsidbCustomActionTypeDirectory == source)
{
xCustomAction.SetAttributeValue("Script", "jscript");
// TODO: Extract to @ScriptFile?
// xCustomAction.Content = row.FieldAsString(3);
}
else
{
xCustomAction.SetAttributeValue("JScriptCall", row.FieldAsString(3));
}
break;
case WindowsInstallerConstants.MsidbCustomActionTypeVBScript:
if (WindowsInstallerConstants.MsidbCustomActionTypeDirectory == source)
{
xCustomAction.SetAttributeValue("Script", "vbscript");
// TODO: Extract to @ScriptFile?
// xCustomAction.Content = row.FieldAsString(3);
}
else
{
xCustomAction.SetAttributeValue("VBScriptCall", row.FieldAsString(3));
}
break;
case WindowsInstallerConstants.MsidbCustomActionTypeInstall:
this.Messaging.Write(WarningMessages.NestedInstall(row.SourceLineNumbers, table.Name, row.Fields[1].Column.Name, row[1]));
continue;
default:
this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[1].Column.Name, row[1]));
break;
}
var extype = 4 < row.Fields.Length && !row.IsColumnNull(4) ? row.FieldAsInteger(4) : 0;
if (WindowsInstallerConstants.MsidbCustomActionTypePatchUninstall == (extype & WindowsInstallerConstants.MsidbCustomActionTypePatchUninstall))
{
xCustomAction.SetAttributeValue("PatchUninstall", "yes");
}
this.RootElement.Add(xCustomAction);
this.IndexElement(row, xCustomAction);
}
}
///
/// Decompile the CompLocator table.
///
/// The table to decompile.
private void DecompileCompLocatorTable(Table table)
{
foreach (var row in table.Rows)
{
var xComponentSearch = new XElement(Names.ComponentSearchElement,
new XAttribute("Id", row.FieldAsString(0)),
new XAttribute("Guid", row.FieldAsString(1)));
if (!row.IsColumnNull(2))
{
switch (row.FieldAsInteger(2))
{
case WindowsInstallerConstants.MsidbLocatorTypeDirectory:
xComponentSearch.SetAttributeValue("Type", "directory");
break;
case WindowsInstallerConstants.MsidbLocatorTypeFileName:
// this is the default value
break;
default:
this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[2].Column.Name, row[2]));
break;
}
}
this.IndexElement(row, xComponentSearch);
}
}
///
/// Decompile the Complus table.
///
/// The table to decompile.
private void DecompileComplusTable(Table table)
{
foreach (var row in table.Rows)
{
if (!row.IsColumnNull(1))
{
if (this.TryGetIndexedElement("Component", out var xComponent, row.FieldAsString(0)))
{
xComponent.SetAttributeValue("ComPlusFlags", row.FieldAsInteger(1));
}
else
{
this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Component_", row.FieldAsString(0), "Component"));
}
}
}
}
///
/// Decompile the Component table.
///
/// The table to decompile.
private void DecompileComponentTable(Table table)
{
foreach (var row in table.Rows)
{
var xComponent = new XElement(Names.ComponentElement,
new XAttribute("Id", row.FieldAsString(0)),
new XAttribute("Guid", row.FieldAsString(1) ?? String.Empty));
var attributes = row.FieldAsInteger(3);
if (WindowsInstallerConstants.MsidbComponentAttributesSourceOnly == (attributes & WindowsInstallerConstants.MsidbComponentAttributesSourceOnly))
{
xComponent.SetAttributeValue("Location", "source");
}
else if (WindowsInstallerConstants.MsidbComponentAttributesOptional == (attributes & WindowsInstallerConstants.MsidbComponentAttributesOptional))
{
xComponent.SetAttributeValue("Location", "either");
}
if (WindowsInstallerConstants.MsidbComponentAttributesSharedDllRefCount == (attributes & WindowsInstallerConstants.MsidbComponentAttributesSharedDllRefCount))
{
xComponent.SetAttributeValue("SharedDllRefCount", "yes");
}
if (WindowsInstallerConstants.MsidbComponentAttributesPermanent == (attributes & WindowsInstallerConstants.MsidbComponentAttributesPermanent))
{
xComponent.SetAttributeValue("Permanent", "yes");
}
if (WindowsInstallerConstants.MsidbComponentAttributesTransitive == (attributes & WindowsInstallerConstants.MsidbComponentAttributesTransitive))
{
xComponent.SetAttributeValue("Transitive", "yes");
}
if (WindowsInstallerConstants.MsidbComponentAttributesNeverOverwrite == (attributes & WindowsInstallerConstants.MsidbComponentAttributesNeverOverwrite))
{
xComponent.SetAttributeValue("NeverOverwrite", "yes");
}
if (WindowsInstallerConstants.MsidbComponentAttributes64bit == (attributes & WindowsInstallerConstants.MsidbComponentAttributes64bit))
{
xComponent.SetAttributeValue("Bitness", "always64");
}
else
{
xComponent.SetAttributeValue("Bitness", "always32");
}
if (WindowsInstallerConstants.MsidbComponentAttributesDisableRegistryReflection == (attributes & WindowsInstallerConstants.MsidbComponentAttributesDisableRegistryReflection))
{
xComponent.SetAttributeValue("DisableRegistryReflection", "yes");
}
if (WindowsInstallerConstants.MsidbComponentAttributesUninstallOnSupersedence == (attributes & WindowsInstallerConstants.MsidbComponentAttributesUninstallOnSupersedence))
{
xComponent.SetAttributeValue("UninstallWhenSuperseded", "yes");
}
if (WindowsInstallerConstants.MsidbComponentAttributesShared == (attributes & WindowsInstallerConstants.MsidbComponentAttributesShared))
{
xComponent.SetAttributeValue("Shared", "yes");
}
if (!row.IsColumnNull(4))
{
xComponent.SetAttributeValue("Condition", row.FieldAsString(4));
}
this.AddChildToParent("Directory", xComponent, row, 2);
this.IndexElement(row, xComponent);
}
}
///
/// Decompile the Condition table.
///
/// The table to decompile.
private void DecompileConditionTable(Table table)
{
foreach (var row in table.Rows)
{
if (this.TryGetIndexedElement("Feature", out var xFeature, row.FieldAsString(0)))
{
var xLevel = new XElement(Names.LevelElement,
row.IsColumnNull(2) ? null : new XAttribute("Condition", row.FieldAsString(2)),
new XAttribute("Level", row.FieldAsInteger(1)));
xFeature.Add(xLevel);
}
else
{
this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Feature_", row.FieldAsString(0), "Feature"));
}
}
}
///
/// Decompile the Dialog table.
///
/// The table to decompile.
private void DecompileDialogTable(Table table)
{
foreach (var row in table.Rows)
{
var attributes = row.FieldAsNullableInteger(5) ?? 0;
var xDialog = new XElement(Names.DialogElement,
new XAttribute("Id", row.FieldAsString(0)),
new XAttribute("X", row.FieldAsString(1)),
new XAttribute("Y", row.FieldAsString(2)),
new XAttribute("Width", row.FieldAsString(3)),
new XAttribute("Height", row.FieldAsString(4)),
0 == (attributes & WindowsInstallerConstants.MsidbDialogAttributesVisible) ? new XAttribute("Hidden", "yes") : null,
0 == (attributes & WindowsInstallerConstants.MsidbDialogAttributesModal) ? new XAttribute("Modeless", "yes") : null,
0 == (attributes & WindowsInstallerConstants.MsidbDialogAttributesMinimize) ? new XAttribute("NoMinimize", "yes") : null,
0 == (attributes & WindowsInstallerConstants.MsidbDialogAttributesMinimize) ? new XAttribute("NoMinimize", "yes") : null,
WindowsInstallerConstants.MsidbDialogAttributesSysModal == (attributes & WindowsInstallerConstants.MsidbDialogAttributesSysModal) ? new XAttribute("SystemModal", "yes") : null,
WindowsInstallerConstants.MsidbDialogAttributesKeepModeless == (attributes & WindowsInstallerConstants.MsidbDialogAttributesKeepModeless) ? new XAttribute("KeepModeless", "yes") : null,
WindowsInstallerConstants.MsidbDialogAttributesTrackDiskSpace == (attributes & WindowsInstallerConstants.MsidbDialogAttributesTrackDiskSpace) ? new XAttribute("TrackDiskSpace", "yes") : null,
WindowsInstallerConstants.MsidbDialogAttributesUseCustomPalette == (attributes & WindowsInstallerConstants.MsidbDialogAttributesUseCustomPalette) ? new XAttribute("CustomPalette", "yes") : null,
WindowsInstallerConstants.MsidbDialogAttributesLeftScroll == (attributes & WindowsInstallerConstants.MsidbDialogAttributesLeftScroll) ? new XAttribute("LeftScroll", "yes") : null,
WindowsInstallerConstants.MsidbDialogAttributesError == (attributes & WindowsInstallerConstants.MsidbDialogAttributesError) ? new XAttribute("ErrorDialog", "yes") : null,
!row.IsColumnNull(6) ? new XAttribute("Title", row.FieldAsString(6)) : null);
this.UIElement.Add(xDialog);
this.IndexElement(row, xDialog);
}
}
///
/// Decompile the Directory table.
///
/// The table to decompile.
private void DecompileDirectoryTable(Table table)
{
foreach (var row in table.Rows)
{
var id = row.FieldAsString(0);
var elementName = WindowsInstallerStandard.IsStandardDirectory(id) ? Names.StandardDirectoryElement : Names.DirectoryElement;
var xDirectory = new XElement(elementName,
new XAttribute("Id", id));
if (!WindowsInstallerStandard.IsStandardDirectory(id))
{
var names = this.BackendHelper.SplitMsiFileName(row.FieldAsString(2));
if (id == "TARGETDIR" && names[0] != "SourceDir")
{
this.Messaging.Write(WarningMessages.TargetDirCorrectedDefaultDir());
xDirectory.SetAttributeValue("Name", "SourceDir");
}
else
{
if (null != names[0] && "." != names[0])
{
if (null != names[1])
{
xDirectory.SetAttributeValue("ShortName", names[0]);
}
else
{
xDirectory.SetAttributeValue("Name", names[0]);
}
}
if (null != names[1])
{
xDirectory.SetAttributeValue("Name", names[1]);
}
}
if (null != names[2])
{
if (null != names[3])
{
xDirectory.SetAttributeValue("ShortSourceName", names[2]);
}
else
{
xDirectory.SetAttributeValue("SourceName", names[2]);
}
}
if (null != names[3])
{
xDirectory.SetAttributeValue("SourceName", names[3]);
}
}
this.IndexElement(row, xDirectory);
}
// nest the directories
foreach (var row in table.Rows)
{
var xDirectory = this.GetIndexedElement(row);
var id = row.FieldAsString(0);
if (id == "TARGETDIR")
{
// Skip TARGETDIR.
}
else if (row.IsColumnNull(1) || WindowsInstallerStandard.IsStandardDirectory(id))
{
this.RootElement.Add(xDirectory);
}
else
{
if (!this.TryGetIndexedElement("Directory", out var xParentDirectory, row.FieldAsString(1)))
{
this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Directory_Parent", row.FieldAsString(1), "Directory"));
}
else if (xParentDirectory == xDirectory) // another way to specify a root directory
{
this.RootElement.Add(xDirectory);
}
else
{
xParentDirectory.Add(xDirectory);
}
}
}
}
///
/// Decompile the DrLocator table.
///
/// The table to decompile.
private void DecompileDrLocatorTable(Table table)
{
foreach (var row in table.Rows)
{
var xDirectorySearch = new XElement(Names.DirectorySearchElement,
new XAttribute("Id", row.FieldAsString(0)),
XAttributeIfNotNull("Path", row, 2),
XAttributeIfNotNull("Depth", row, 3));
this.IndexElement(row, xDirectorySearch);
}
}
///
/// Decompile the DuplicateFile table.
///
/// The table to decompile.
private void DecompileDuplicateFileTable(Table table)
{
foreach (var row in table.Rows)
{
var xCopyFile = new XElement(Names.CopyFileElement,
new XAttribute("Id", row.FieldAsString(0)),
new XAttribute("FileId", row.FieldAsString(2)));
if (!row.IsColumnNull(3))
{
var names = this.BackendHelper.SplitMsiFileName(row.FieldAsString(3));
if (null != names[0] && null != names[1])
{
xCopyFile.SetAttributeValue("DestinationShortName", names[0]);
xCopyFile.SetAttributeValue("DestinationName", names[1]);
}
else if (null != names[0])
{
xCopyFile.SetAttributeValue("DestinationName", names[0]);
}
}
// destination directory/property is set in FinalizeDuplicateMoveFileTables
this.AddChildToParent("Component", xCopyFile, row, 1);
this.IndexElement(row, xCopyFile);
}
}
///
/// Decompile the Environment table.
///
/// The table to decompile.
private void DecompileEnvironmentTable(Table table)
{
foreach (var row in table.Rows)
{
var xEnvironment = new XElement(Names.EnvironmentElement,
new XAttribute("Id", row.FieldAsString(0)));
var done = false;
var permanent = true;
var name = row.FieldAsString(1);
for (var i = 0; i < name.Length && !done; i++)
{
switch (name[i])
{
case '=':
xEnvironment.SetAttributeValue("Action", "set");
break;
case '+':
xEnvironment.SetAttributeValue("Action", "create");
break;
case '-':
permanent = false;
break;
case '!':
xEnvironment.SetAttributeValue("Action", "remove");
break;
case '*':
xEnvironment.SetAttributeValue("System", "yes");
break;
default:
xEnvironment.SetAttributeValue("Name", name.Substring(i));
done = true;
break;
}
}
if (permanent)
{
xEnvironment.SetAttributeValue("Permanent", "yes");
}
if (!row.IsColumnNull(2))
{
var value = row.FieldAsString(2);
if (value.StartsWith("[~]", StringComparison.Ordinal))
{
xEnvironment.SetAttributeValue("Part", "last");
if (3 < value.Length)
{
xEnvironment.SetAttributeValue("Separator", value.Substring(3, 1));
xEnvironment.SetAttributeValue("Value", value.Substring(4));
}
}
else if (value.EndsWith("[~]", StringComparison.Ordinal))
{
xEnvironment.SetAttributeValue("Part", "first");
if (3 < value.Length)
{
xEnvironment.SetAttributeValue("Separator", value.Substring(value.Length - 4, 1));
xEnvironment.SetAttributeValue("Value", value.Substring(0, value.Length - 4));
}
}
else
{
xEnvironment.SetAttributeValue("Value", value);
}
}
this.AddChildToParent("Component", xEnvironment, row, 3);
}
}
///
/// Decompile the Error table.
///
/// The table to decompile.
private void DecompileErrorTable(Table table)
{
foreach (var row in table.Rows)
{
var xError = new XElement(Names.ErrorElement,
new XAttribute("Id", row.FieldAsString(0)),
new XAttribute("Message", row.FieldAsString(1)));
this.UIElement.Add(xError);
}
}
///
/// Decompile the EventMapping table.
///
/// The table to decompile.
private void DecompileEventMappingTable(Table table)
{
foreach (var row in table.Rows)
{
var xSubscribe = new XElement(Names.SubscribeElement,
new XAttribute("Event", row.FieldAsString(2)),
new XAttribute("Attribute", row.FieldAsString(3)));
if (this.TryGetIndexedElement("Control", out var xControl, row.FieldAsString(0), row.FieldAsString(1)))
{
xControl.Add(xSubscribe);
}
else
{
this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Dialog_", row.FieldAsString(0), "Control_", row.FieldAsString(1), "Control"));
}
}
}
///
/// Decompile the Extension table.
///
/// The table to decompile.
private void DecompileExtensionTable(Table table)
{
foreach (var row in table.Rows)
{
var xExtension = new XElement(Names.ExtensionElement,
new XAttribute("Id", row.FieldAsString(0)),
new XAttribute("Advertise", "yes"));
if (!row.IsColumnNull(3))
{
if (this.TryGetIndexedElement("MIME", out var xMime, row.FieldAsString(3)))
{
xMime.SetAttributeValue("Default", "yes");
}
else
{
this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "MIME_", row.FieldAsString(3), "MIME"));
}
}
if (!row.IsColumnNull(2))
{
this.AddChildToParent("ProgId", xExtension, row, 2);
}
else
{
this.AddChildToParent("Component", xExtension, row, 1);
}
this.IndexElement(row, xExtension);
}
}
///
/// Decompile the ExternalFiles table.
///
/// The table to decompile.
private void DecompileExternalFilesTable(Table table)
{
foreach (var row in table.Rows)
{
var xExternalFile = new XElement(Names.ExternalFileElement,
new XAttribute("File", row.FieldAsString(1)),
new XAttribute("Source", row.FieldAsString(2)));
AddSymbolPaths(row, 3, xExternalFile);
if (!row.IsColumnNull(4) && !row.IsColumnNull(5))
{
var ignoreOffsets = row.FieldAsString(4).Split(',');
var ignoreLengths = row.FieldAsString(5).Split(',');
if (ignoreOffsets.Length == ignoreLengths.Length)
{
for (var i = 0; i < ignoreOffsets.Length; i++)
{
var xIgnoreRange = new XElement(Names.IgnoreRangeElement);
if (ignoreOffsets[i].StartsWith("0x", StringComparison.Ordinal))
{
xIgnoreRange.SetAttributeValue("Offset", Convert.ToInt32(ignoreOffsets[i].Substring(2), 16));
}
else
{
xIgnoreRange.SetAttributeValue("Offset", Convert.ToInt32(ignoreOffsets[i], CultureInfo.InvariantCulture));
}
if (ignoreLengths[i].StartsWith("0x", StringComparison.Ordinal))
{
xIgnoreRange.SetAttributeValue("Length", Convert.ToInt32(ignoreLengths[i].Substring(2), 16));
}
else
{
xIgnoreRange.SetAttributeValue("Length", Convert.ToInt32(ignoreLengths[i], CultureInfo.InvariantCulture));
}
xExternalFile.Add(xIgnoreRange);
}
}
else
{
// TODO: warn
}
}
else if (!row.IsColumnNull(4) || !row.IsColumnNull(5))
{
// TODO: warn about mismatch between columns
}
// the RetainOffsets column is handled in FinalizeFamilyFileRangesTable
if (!row.IsColumnNull(7))
{
xExternalFile.SetAttributeValue("Order", row.FieldAsInteger(7));
}
this.AddChildToParent("ImageFamilies", xExternalFile, row, 0);
this.IndexElement(row, xExternalFile);
}
}
///
/// Decompile the Feature table.
///
/// The table to decompile.
private void DecompileFeatureTable(Table table)
{
var sortedFeatures = new SortedList();
foreach (var row in table.Rows)
{
var feature = new XElement(Names.FeatureElement,
new XAttribute("Id", row.FieldAsString(0)),
row.IsColumnNull(2) ? null : new XAttribute("Title", row.FieldAsString(2)),
row.IsColumnNull(3) ? null : new XAttribute("Description", row.FieldAsString(3)),
new XAttribute("Level", row.FieldAsInteger(5)),
row.IsColumnNull(6) ? null : new XAttribute("ConfigurableDirectory", row.FieldAsString(6)));
if (row.IsColumnNull(4))
{
feature.SetAttributeValue("Display", "hidden");
}
else
{
var display = row.FieldAsInteger(4);
if (0 == display)
{
feature.SetAttributeValue("Display", "hidden");
}
else if (1 == display % 2)
{
feature.SetAttributeValue("Display", "expand");
}
}
var attributes = row.FieldAsInteger(7);
if (WindowsInstallerConstants.MsidbFeatureAttributesFavorSource == (attributes & WindowsInstallerConstants.MsidbFeatureAttributesFavorSource) && WindowsInstallerConstants.MsidbFeatureAttributesFollowParent == (attributes & WindowsInstallerConstants.MsidbFeatureAttributesFollowParent))
{
// TODO: display a warning for setting favor local and follow parent together
}
else if (WindowsInstallerConstants.MsidbFeatureAttributesFavorSource == (attributes & WindowsInstallerConstants.MsidbFeatureAttributesFavorSource))
{
feature.SetAttributeValue("InstallDefault", "source");
}
else if (WindowsInstallerConstants.MsidbFeatureAttributesFollowParent == (attributes & WindowsInstallerConstants.MsidbFeatureAttributesFollowParent))
{
feature.SetAttributeValue("InstallDefault", "followParent");
}
if (WindowsInstallerConstants.MsidbFeatureAttributesFavorAdvertise == (attributes & WindowsInstallerConstants.MsidbFeatureAttributesFavorAdvertise))
{
feature.SetAttributeValue("InstallDefault", "advertise");
}
if (WindowsInstallerConstants.MsidbFeatureAttributesDisallowAdvertise == (attributes & WindowsInstallerConstants.MsidbFeatureAttributesDisallowAdvertise) &&
WindowsInstallerConstants.MsidbFeatureAttributesNoUnsupportedAdvertise == (attributes & WindowsInstallerConstants.MsidbFeatureAttributesNoUnsupportedAdvertise))
{
this.Messaging.Write(WarningMessages.InvalidAttributeCombination(row.SourceLineNumbers, "msidbFeatureAttributesDisallowAdvertise", "msidbFeatureAttributesNoUnsupportedAdvertise", "Feature.AllowAdvertiseType", "no"));
feature.SetAttributeValue("AllowAdvertise", "no");
}
else if (WindowsInstallerConstants.MsidbFeatureAttributesDisallowAdvertise == (attributes & WindowsInstallerConstants.MsidbFeatureAttributesDisallowAdvertise))
{
feature.SetAttributeValue("AllowAdvertise", "no");
}
else if (WindowsInstallerConstants.MsidbFeatureAttributesNoUnsupportedAdvertise == (attributes & WindowsInstallerConstants.MsidbFeatureAttributesNoUnsupportedAdvertise))
{
feature.SetAttributeValue("AllowAdvertise", "system");
}
if (WindowsInstallerConstants.MsidbFeatureAttributesUIDisallowAbsent == (attributes & WindowsInstallerConstants.MsidbFeatureAttributesUIDisallowAbsent))
{
feature.SetAttributeValue("Absent", "disallow");
}
this.IndexElement(row, feature);
// sort the features by their display column (and append the identifier to ensure unique keys)
sortedFeatures.Add(String.Format(CultureInfo.InvariantCulture, "{0:00000}|{1}", row.FieldAsInteger(4), row[0]), row);
}
// nest the features
foreach (var row in sortedFeatures.Values)
{
var xFeature = this.GetIndexedElement("Feature", row.FieldAsString(0));
if (row.IsColumnNull(1))
{
this.RootElement.Add(xFeature);
}
else
{
if (this.TryGetIndexedElement("Feature", out var xParentFeature, row.FieldAsString(1)))
{
if (xParentFeature == xFeature)
{
// TODO: display a warning about self-nesting
}
else
{
xParentFeature.Add(xFeature);
}
}
else
{
this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Feature_Parent", row.FieldAsString(1), "Feature"));
}
}
}
}
///
/// Decompile the FeatureComponents table.
///
/// The table to decompile.
private void DecompileFeatureComponentsTable(Table table)
{
foreach (var row in table.Rows)
{
var xComponentRef = new XElement(Names.ComponentRefElement,
new XAttribute("Id", row.FieldAsString(1)));
this.AddChildToParent("Feature", xComponentRef, row, 0);
this.IndexElement(row, xComponentRef);
}
}
///
/// Decompile the File table.
///
/// The table to decompile.
private void DecompileFileTable(Table table)
{
foreach (FileRow fileRow in table.Rows)
{
var xFile = new XElement(Names.FileElement,
new XAttribute("Id", fileRow.File),
WindowsInstallerConstants.MsidbFileAttributesReadOnly == (fileRow.Attributes & WindowsInstallerConstants.MsidbFileAttributesReadOnly) ? new XAttribute("ReadOnly", "yes") : null,
WindowsInstallerConstants.MsidbFileAttributesHidden == (fileRow.Attributes & WindowsInstallerConstants.MsidbFileAttributesHidden) ? new XAttribute("Hidden", "yes") : null,
WindowsInstallerConstants.MsidbFileAttributesSystem == (fileRow.Attributes & WindowsInstallerConstants.MsidbFileAttributesSystem) ? new XAttribute("System", "yes") : null,
WindowsInstallerConstants.MsidbFileAttributesChecksum == (fileRow.Attributes & WindowsInstallerConstants.MsidbFileAttributesChecksum) ? new XAttribute("Checksum", "yes") : null,
WindowsInstallerConstants.MsidbFileAttributesVital != (fileRow.Attributes & WindowsInstallerConstants.MsidbFileAttributesVital) ? new XAttribute("Vital", "no") : null,
null != fileRow.Version && 0 < fileRow.Version.Length && !Char.IsDigit(fileRow.Version[0]) ? new XAttribute("CompanionFile", fileRow.Version) : null);
var names = this.BackendHelper.SplitMsiFileName(fileRow.FileName);
if (null != names[0] && null != names[1])
{
xFile.SetAttributeValue("ShortName", names[0]);
xFile.SetAttributeValue("Name", names[1]);
}
else if (null != names[0])
{
xFile.SetAttributeValue("Name", names[0]);
}
if (WindowsInstallerConstants.MsidbFileAttributesNoncompressed == (fileRow.Attributes & WindowsInstallerConstants.MsidbFileAttributesNoncompressed) &&
WindowsInstallerConstants.MsidbFileAttributesCompressed == (fileRow.Attributes & WindowsInstallerConstants.MsidbFileAttributesCompressed))
{
// TODO: error
}
else if (WindowsInstallerConstants.MsidbFileAttributesNoncompressed == (fileRow.Attributes & WindowsInstallerConstants.MsidbFileAttributesNoncompressed))
{
xFile.SetAttributeValue("Compressed", "no");
}
else if (WindowsInstallerConstants.MsidbFileAttributesCompressed == (fileRow.Attributes & WindowsInstallerConstants.MsidbFileAttributesCompressed))
{
xFile.SetAttributeValue("Compressed", "yes");
}
this.IndexElement(fileRow, xFile);
}
}
///
/// Decompile the FileSFPCatalog table.
///
/// The table to decompile.
private void DecompileFileSFPCatalogTable(Table table)
{
foreach (var row in table.Rows)
{
var xSfpFile = new XElement(Names.SFPFileElement,
new XAttribute("Id", row.FieldAsString(0)));
this.AddChildToParent("SFPCatalog", xSfpFile, row, 1);
}
}
///
/// Decompile the Font table.
///
/// The table to decompile.
private void DecompileFontTable(Table table)
{
foreach (var row in table.Rows)
{
if (this.TryGetIndexedElement("File", out var xFile, row.FieldAsString(0)))
{
if (!row.IsColumnNull(1))
{
xFile.SetAttributeValue("FontTitle", row.FieldAsString(1));
}
else
{
xFile.SetAttributeValue("TrueType", "yes");
}
}
else
{
this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "File_", row.FieldAsString(0), "File"));
}
}
}
///
/// Decompile the Icon table.
///
/// The table to decompile.
private void DecompileIconTable(Table table)
{
foreach (var row in table.Rows)
{
var icon = new XElement(Names.IconElement,
new XAttribute("Id", row.FieldAsString(0)),
new XAttribute("SourceFile", row.FieldAsString(1)));
this.RootElement.Add(icon);
}
}
///
/// Decompile the ImageFamilies table.
///
/// The table to decompile.
private void DecompileImageFamiliesTable(Table table)
{
foreach (var row in table.Rows)
{
var family = new XElement(Names.FamilyElement,
new XAttribute("Name", row.FieldAsString(0)),
row.IsColumnNull(1) ? null : new XAttribute("MediaSrcProp", row.FieldAsString(1)),
row.IsColumnNull(2) ? null : new XAttribute("DiskId", row.FieldAsString(2)),
row.IsColumnNull(3) ? null : new XAttribute("SequenceStart", row.FieldAsString(3)),
row.IsColumnNull(4) ? null : new XAttribute("DiskPrompt", row.FieldAsString(4)),
row.IsColumnNull(5) ? null : new XAttribute("VolumeLabel", row.FieldAsString(5)));
this.RootElement.Add(family);
this.IndexElement(row, family);
}
}
///
/// Decompile the IniFile table.
///
/// The table to decompile.
private void DecompileIniFileTable(Table table)
{
foreach (var row in table.Rows)
{
var xIniFile = new XElement(Names.IniFileElement,
new XAttribute("Id", row.FieldAsString(0)),
new XAttribute("Section", row.FieldAsString(3)),
new XAttribute("Key", row.FieldAsString(4)),
new XAttribute("Value", row.FieldAsString(5)),
row.IsColumnNull(2) ? null : new XAttribute("Directory", row.FieldAsString(2)));
var names = this.BackendHelper.SplitMsiFileName(row.FieldAsString(1));
if (null != names[0])
{
if (null == names[1])
{
xIniFile.SetAttributeValue("Name", names[0]);
}
else
{
xIniFile.SetAttributeValue("ShortName", names[0]);
}
}
if (null != names[1])
{
xIniFile.SetAttributeValue("Name", names[1]);
}
switch (row.FieldAsInteger(6))
{
case WindowsInstallerConstants.MsidbIniFileActionAddLine:
xIniFile.SetAttributeValue("Action", "addLine");
break;
case WindowsInstallerConstants.MsidbIniFileActionCreateLine:
xIniFile.SetAttributeValue("Action", "createLine");
break;
case WindowsInstallerConstants.MsidbIniFileActionAddTag:
xIniFile.SetAttributeValue("Action", "addTag");
break;
default:
this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[6].Column.Name, row[6]));
break;
}
this.AddChildToParent("Component", xIniFile, row, 7);
}
}
///
/// Decompile the IniLocator table.
///
/// The table to decompile.
private void DecompileIniLocatorTable(Table table)
{
foreach (var row in table.Rows)
{
var xIniFileSearch = new XElement(Names.IniFileSearchElement,
new XAttribute("Id", row.FieldAsString(0)),
new XAttribute("Section", row.FieldAsString(2)),
new XAttribute("Key", row.FieldAsString(3)),
row.IsColumnNull(4) || row.FieldAsInteger(4) == 0 ? null : new XAttribute("Field", row.FieldAsInteger(4)));
var names = this.BackendHelper.SplitMsiFileName(row.FieldAsString(1));
if (null != names[0] && null != names[1])
{
xIniFileSearch.SetAttributeValue("ShortName", names[0]);
xIniFileSearch.SetAttributeValue("Name", names[1]);
}
else if (null != names[0])
{
xIniFileSearch.SetAttributeValue("Name", names[0]);
}
if (!row.IsColumnNull(5))
{
switch (row.FieldAsInteger(5))
{
case WindowsInstallerConstants.MsidbLocatorTypeDirectory:
xIniFileSearch.SetAttributeValue("Type", "directory");
break;
case WindowsInstallerConstants.MsidbLocatorTypeFileName:
// this is the default value
break;
case WindowsInstallerConstants.MsidbLocatorTypeRawValue:
xIniFileSearch.SetAttributeValue("Type", "raw");
break;
default:
this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[5].Column.Name, row[5]));
break;
}
}
this.IndexElement(row, xIniFileSearch);
}
}
///
/// Decompile the IsolatedComponent table.
///
/// The table to decompile.
private void DecompileIsolatedComponentTable(Table table)
{
foreach (var row in table.Rows)
{
var xIsolateComponent = new XElement(Names.IsolateComponentElement,
new XAttribute("Shared", row.FieldAsString(0)));
this.AddChildToParent("Component", xIsolateComponent, row, 1);
}
}
///
/// Decompile the LaunchCondition table.
///
/// The table to decompile.
private void DecompileLaunchConditionTable(Table table)
{
foreach (var row in table.Rows)
{
if (WixUpgradeConstants.DowngradePreventedCondition == row.FieldAsString(0) || WixUpgradeConstants.UpgradePreventedCondition == row.FieldAsString(0))
{
continue; // MajorUpgrade rows processed in FinalizeUpgradeTable
}
var condition = new XElement(Names.LaunchElement,
new XAttribute("Condition", row.FieldAsString(0)),
new XAttribute("Message", row.FieldAsString(1)));
this.RootElement.Add(condition);
}
}
///
/// Decompile the ListBox table.
///
/// The table to decompile.
private void DecompileListBoxTable(Table table)
{
// sort the list boxes by their property and order
var listBoxRows = table.Rows.OrderBy(row => row.FieldAsString(0)).ThenBy(row => row.FieldAsInteger(1)).ToList();
XElement xListBox = null;
foreach (Row row in listBoxRows)
{
if (null == xListBox || row.FieldAsString(0) != xListBox.Attribute("Property")?.Value)
{
xListBox = new XElement(Names.ListBoxElement,
new XAttribute("Property", row.FieldAsString(0)));
this.UIElement.Add(xListBox);
}
var listItem = new XElement(Names.ListItemElement,
new XAttribute("Value", row.FieldAsString(2)),
row.IsColumnNull(3) ? null : new XAttribute("Text", row.FieldAsString(3)));
xListBox.Add(listItem);
}
}
///
/// Decompile the ListView table.
///
/// The table to decompile.
private void DecompileListViewTable(Table table)
{
// sort the list views by their property and order
var listViewRows = table.Rows.OrderBy(row => row.FieldAsString(0)).ThenBy(row => row.FieldAsInteger(1)).ToList();
XElement xListView = null;
foreach (var row in listViewRows)
{
if (null == xListView || row.FieldAsString(0) != xListView.Attribute("Property")?.Value)
{
xListView = new XElement(Names.ListViewElement,
new XAttribute("Property", row.FieldAsString(0)));
this.UIElement.Add(xListView);
}
var listItem = new XElement(Names.ListItemElement,
new XAttribute("Value", row.FieldAsString(2)),
row.IsColumnNull(3) ? null : new XAttribute("Text", row.FieldAsString(3)),
row.IsColumnNull(4) ? null : new XAttribute("Icon", row.FieldAsString(4)));
xListView.Add(listItem);
}
}
///
/// Decompile the LockPermissions table.
///
/// The table to decompile.
private void DecompileLockPermissionsTable(Table table)
{
foreach (var row in table.Rows)
{
var xPermission = new XElement(Names.PermissionElement,
row.IsColumnNull(2) ? null : new XAttribute("Domain", row.FieldAsString(2)),
new XAttribute("User", row.FieldAsString(3)));
string[] specialPermissions;
switch (row.FieldAsString(1))
{
case "CreateFolder":
specialPermissions = LockPermissionConstants.FolderPermissions;
break;
case "File":
specialPermissions = LockPermissionConstants.FilePermissions;
break;
case "Registry":
specialPermissions = LockPermissionConstants.RegistryPermissions;
break;
default:
this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, row.Table.Name, row.Fields[1].Column.Name, row[1]));
return;
}
var permissionBits = row.FieldAsInteger(4);
for (var i = 0; i < 32; i++)
{
if (0 != ((permissionBits >> i) & 1))
{
string name = null;
if (specialPermissions.Length > i)
{
name = specialPermissions[i];
}
else if (16 > i && specialPermissions.Length <= i)
{
name = "SpecificRightsAll";
}
else if (28 > i && LockPermissionConstants.StandardPermissions.Length > (i - 16))
{
name = LockPermissionConstants.StandardPermissions[i - 16];
}
else if (0 <= (i - 28) && LockPermissionConstants.GenericPermissions.Length > (i - 28))
{
name = LockPermissionConstants.GenericPermissions[i - 28];
}
if (null == name)
{
this.Messaging.Write(WarningMessages.UnknownPermission(row.SourceLineNumbers, row.Table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), i));
}
else
{
switch (name)
{
case "Append":
case "ChangePermission":
case "CreateChild":
case "CreateFile":
case "CreateLink":
case "CreateSubkeys":
case "Delete":
case "DeleteChild":
case "EnumerateSubkeys":
case "Execute":
case "FileAllRights":
case "GenericAll":
case "GenericExecute":
case "GenericRead":
case "GenericWrite":
case "Notify":
case "Read":
case "ReadAttributes":
case "ReadExtendedAttributes":
case "ReadPermission":
case "SpecificRightsAll":
case "Synchronize":
case "TakeOwnership":
case "Traverse":
case "Write":
case "WriteAttributes":
case "WriteExtendedAttributes":
xPermission.SetAttributeValue(name, "yes");
break;
default:
throw new InvalidOperationException($"Unknown permission attribute '{name}'.");
}
}
}
}
this.IndexElement(row, xPermission);
}
}
///
/// Decompile the Media table.
///
/// The table to decompile.
private void DecompileMediaTable(Table table)
{
foreach (MediaRow mediaRow in table.Rows)
{
var xMedia = new XElement(Names.MediaElement,
new XAttribute("Id", mediaRow.DiskId),
mediaRow.DiskPrompt == null ? null : new XAttribute("DiskPrompt", mediaRow.DiskPrompt),
mediaRow.VolumeLabel == null ? null : new XAttribute("VolumeLabel", mediaRow.VolumeLabel));
if (null != mediaRow.Cabinet)
{
var cabinet = mediaRow.Cabinet;
if (cabinet.StartsWith("#", StringComparison.Ordinal))
{
xMedia.SetAttributeValue("EmbedCab", "yes");
cabinet = cabinet.Substring(1);
}
xMedia.SetAttributeValue("Cabinet", cabinet);
}
this.RootElement.Add(xMedia);
this.IndexElement(mediaRow, xMedia);
}
}
///
/// Decompile the MIME table.
///
/// The table to decompile.
private void DecompileMIMETable(Table table)
{
foreach (var row in table.Rows)
{
var mime = new XElement(Names.MIMEElement,
new XAttribute("ContentType", row.FieldAsString(0)),
row.IsColumnNull(2) ? null : new XAttribute("Class", row.FieldAsString(2)));
this.IndexElement(row, mime);
}
}
///
/// Decompile the ModuleConfiguration table.
///
/// The table to decompile.
private void DecompileModuleConfigurationTable(Table table)
{
foreach (var row in table.Rows)
{
var configuration = new XElement(Names.ConfigurationElement,
new XAttribute("Name", row.FieldAsString(0)),
XAttributeIfNotNull("Type", row, 2),
XAttributeIfNotNull("ContextData", row, 3),
XAttributeIfNotNull("DefaultValue", row, 4),
XAttributeIfNotNull("DisplayName", row, 6),
XAttributeIfNotNull("Description", row, 7),
XAttributeIfNotNull("HelpLocation", row, 8),
XAttributeIfNotNull("HelpKeyword", row, 9));
switch (row.FieldAsInteger(1))
{
case 0:
configuration.SetAttributeValue("Format", "Text");
break;
case 1:
configuration.SetAttributeValue("Format", "Key");
break;
case 2:
configuration.SetAttributeValue("Format", "Integer");
break;
case 3:
configuration.SetAttributeValue("Format", "Bitfield");
break;
default:
this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[1].Column.Name, row[1]));
break;
}
if (!row.IsColumnNull(5))
{
var attributes = row.FieldAsInteger(5);
if (WindowsInstallerConstants.MsidbMsmConfigurableOptionKeyNoOrphan == (attributes & WindowsInstallerConstants.MsidbMsmConfigurableOptionKeyNoOrphan))
{
configuration.SetAttributeValue("KeyNoOrphan", "yes");
}
if (WindowsInstallerConstants.MsidbMsmConfigurableOptionNonNullable == (attributes & WindowsInstallerConstants.MsidbMsmConfigurableOptionNonNullable))
{
configuration.SetAttributeValue("NonNullable", "yes");
}
if (3 < attributes)
{
this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[5].Column.Name, row[5]));
}
}
this.RootElement.Add(configuration);
}
}
///
/// Decompile the ModuleDependency table.
///
/// The table to decompile.
private void DecompileModuleDependencyTable(Table table)
{
foreach (var row in table.Rows)
{
var xDependency = new XElement(Names.DependencyElement,
new XAttribute("RequiredId", row.FieldAsString(2)),
new XAttribute("RequiredLanguage", row.FieldAsString(3)),
XAttributeIfNotNull("RequiredVersion", row, 4));
this.RootElement.Add(xDependency);
}
}
///
/// Decompile the ModuleExclusion table.
///
/// The table to decompile.
private void DecompileModuleExclusionTable(Table table)
{
foreach (var row in table.Rows)
{
var xExclusion = new XElement(Names.ExclusionElement,
new XAttribute("ExcludedId", row.FieldAsString(2)),
XAttributeIfNotNull("ExcludedMinVersion", row, 4),
XAttributeIfNotNull("ExcludedMaxVersion", row, 5));
var excludedLanguage = row.FieldAsInteger(3);
if (0 < excludedLanguage)
{
xExclusion.SetAttributeValue("ExcludeLanguage", excludedLanguage);
}
else if (0 > excludedLanguage)
{
xExclusion.SetAttributeValue("ExcludeExceptLanguage", -excludedLanguage);
}
this.RootElement.Add(xExclusion);
}
}
///
/// Decompile the ModuleIgnoreTable table.
///
/// The table to decompile.
private void DecompileModuleIgnoreTableTable(Table table)
{
foreach (var row in table.Rows)
{
var tableName = row.FieldAsString(0);
// the linker automatically adds a ModuleIgnoreTable row for some tables
if ("ModuleConfiguration" != tableName && "ModuleSubstitution" != tableName)
{
var xIgnoreTable = new XElement(Names.IgnoreTableElement,
new XAttribute("Id", tableName));
this.RootElement.Add(xIgnoreTable);
}
}
}
///
/// Decompile the ModuleSignature table.
///
/// The table to decompile.
private void DecompileModuleSignatureTable(Table table)
{
if (1 == table.Rows.Count)
{
var row = table.Rows[0];
this.RootElement.SetAttributeValue("Id", row.FieldAsString(0));
// support Language columns that are treated as integers as well as strings (the WiX default, to support localizability)
this.RootElement.SetAttributeValue("Language", row.FieldAsString(1));
this.RootElement.SetAttributeValue("Version", row.FieldAsString(2));
}
else
{
// TODO: warn
}
}
///
/// Decompile the ModuleSubstitution table.
///
/// The table to decompile.
private void DecompileModuleSubstitutionTable(Table table)
{
foreach (var row in table.Rows)
{
var xSubstitution = new XElement(Names.SubstitutionElement,
new XAttribute("Table", row.FieldAsString(0)),
new XAttribute("Row", row.FieldAsString(1)),
new XAttribute("Column", row.FieldAsString(2)),
XAttributeIfNotNull("Value", row, 3));
this.RootElement.Add(xSubstitution);
}
}
///
/// Decompile the MoveFile table.
///
/// The table to decompile.
private void DecompileMoveFileTable(Table table)
{
foreach (var row in table.Rows)
{
var xCopyFile = new XElement(Names.CopyFileElement,
new XAttribute("Id", row.FieldAsString(0)),
XAttributeIfNotNull("SourceName", row, 2));
if (!row.IsColumnNull(3))
{
var names = this.BackendHelper.SplitMsiFileName(row.FieldAsString(3));
if (null != names[0] && null != names[1])
{
xCopyFile.SetAttributeValue("DestinationShortName", names[0]);
xCopyFile.SetAttributeValue("DestinationName", names[1]);
}
else if (null != names[0])
{
xCopyFile.SetAttributeValue("DestinationName", names[0]);
}
}
// source/destination directory/property is set in FinalizeDuplicateMoveFileTables
switch (row.FieldAsInteger(6))
{
case 0:
break;
case WindowsInstallerConstants.MsidbMoveFileOptionsMove:
xCopyFile.SetAttributeValue("Delete", "yes");
break;
default:
this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[6].Column.Name, row[6]));
break;
}
this.AddChildToParent("Component", xCopyFile, row, 1);
this.IndexElement(row, xCopyFile);
}
}
///
/// Decompile the MsiDigitalCertificate table.
///
/// The table to decompile.
private void DecompileMsiDigitalCertificateTable(Table table)
{
foreach (var row in table.Rows)
{
var xDigitalCertificate = new XElement(Names.DigitalCertificateElement,
new XAttribute("Id", row.FieldAsString(0)),
new XAttribute("SourceFile", row.FieldAsString(1)));
this.IndexElement(row, xDigitalCertificate);
}
}
///
/// Decompile the MsiDigitalSignature table.
///
/// The table to decompile.
private void DecompileMsiDigitalSignatureTable(Table table)
{
foreach (var row in table.Rows)
{
var xDigitalSignature = new XElement(Names.DigitalSignatureElement,
XAttributeIfNotNull("SourceFile", row, 3));
this.AddChildToParent("MsiDigitalCertificate", xDigitalSignature, row, 2);
if (this.TryGetIndexedElement(row.FieldAsString(0), out var xParentElement, row.FieldAsString(1)))
{
xParentElement.Add(xDigitalSignature);
}
else
{
this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "SignObject", row.FieldAsString(1), row.FieldAsString(0)));
}
}
}
///
/// Decompile the MsiEmbeddedChainer table.
///
/// The table to decompile.
private void DecompileMsiEmbeddedChainerTable(Table table)
{
foreach (var row in table.Rows)
{
var xEmbeddedChainer = new XElement(Names.EmbeddedChainerElement,
new XAttribute("Id", row.FieldAsString(0)),
new XAttribute("Condition", row.FieldAsString(1)),
XAttributeIfNotNull("CommandLine", row, 2));
switch (row.FieldAsInteger(4))
{
case WindowsInstallerConstants.MsidbCustomActionTypeExe + WindowsInstallerConstants.MsidbCustomActionTypeBinaryData:
xEmbeddedChainer.SetAttributeValue("BinarySource", row.FieldAsString(3));
break;
case WindowsInstallerConstants.MsidbCustomActionTypeExe + WindowsInstallerConstants.MsidbCustomActionTypeSourceFile:
xEmbeddedChainer.SetAttributeValue("FileSource", row.FieldAsString(3));
break;
case WindowsInstallerConstants.MsidbCustomActionTypeExe + WindowsInstallerConstants.MsidbCustomActionTypeProperty:
xEmbeddedChainer.SetAttributeValue("PropertySource", row.FieldAsString(3));
break;
default:
this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[4].Column.Name, row[4]));
break;
}
this.RootElement.Add(xEmbeddedChainer);
}
}
///
/// Decompile the MsiEmbeddedUI table.
///
/// The table to decompile.
private void DecompileMsiEmbeddedUITable(Table table)
{
var xEmbeddedUI = new XElement(Names.EmbeddedUIElement);
var foundEmbeddedUI = false;
var foundEmbeddedResources = false;
foreach (var row in table.Rows)
{
var attributes = row.FieldAsInteger(2);
if (WindowsInstallerConstants.MsidbEmbeddedUI == (attributes & WindowsInstallerConstants.MsidbEmbeddedUI))
{
if (foundEmbeddedUI)
{
this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[2].Column.Name, row[2]));
}
else
{
xEmbeddedUI.SetAttributeValue("Id", row.FieldAsString(0));
xEmbeddedUI.SetAttributeValue("Name", row.FieldAsString(1));
var messageFilter = row.FieldAsInteger(3);
if (0 == (messageFilter & WindowsInstallerConstants.INSTALLLOGMODE_FATALEXIT))
{
xEmbeddedUI.SetAttributeValue("IgnoreFatalExit", "yes");
}
if (0 == (messageFilter & WindowsInstallerConstants.INSTALLLOGMODE_ERROR))
{
xEmbeddedUI.SetAttributeValue("IgnoreError", "yes");
}
if (0 == (messageFilter & WindowsInstallerConstants.INSTALLLOGMODE_WARNING))
{
xEmbeddedUI.SetAttributeValue("IgnoreWarning", "yes");
}
if (0 == (messageFilter & WindowsInstallerConstants.INSTALLLOGMODE_USER))
{
xEmbeddedUI.SetAttributeValue("IgnoreUser", "yes");
}
if (0 == (messageFilter & WindowsInstallerConstants.INSTALLLOGMODE_INFO))
{
xEmbeddedUI.SetAttributeValue("IgnoreInfo", "yes");
}
if (0 == (messageFilter & WindowsInstallerConstants.INSTALLLOGMODE_FILESINUSE))
{
xEmbeddedUI.SetAttributeValue("IgnoreFilesInUse", "yes");
}
if (0 == (messageFilter & WindowsInstallerConstants.INSTALLLOGMODE_RESOLVESOURCE))
{
xEmbeddedUI.SetAttributeValue("IgnoreResolveSource", "yes");
}
if (0 == (messageFilter & WindowsInstallerConstants.INSTALLLOGMODE_OUTOFDISKSPACE))
{
xEmbeddedUI.SetAttributeValue("IgnoreOutOfDiskSpace", "yes");
}
if (0 == (messageFilter & WindowsInstallerConstants.INSTALLLOGMODE_ACTIONSTART))
{
xEmbeddedUI.SetAttributeValue("IgnoreActionStart", "yes");
}
if (0 == (messageFilter & WindowsInstallerConstants.INSTALLLOGMODE_ACTIONDATA))
{
xEmbeddedUI.SetAttributeValue("IgnoreActionData", "yes");
}
if (0 == (messageFilter & WindowsInstallerConstants.INSTALLLOGMODE_PROGRESS))
{
xEmbeddedUI.SetAttributeValue("IgnoreProgress", "yes");
}
if (0 == (messageFilter & WindowsInstallerConstants.INSTALLLOGMODE_COMMONDATA))
{
xEmbeddedUI.SetAttributeValue("IgnoreCommonData", "yes");
}
if (0 == (messageFilter & WindowsInstallerConstants.INSTALLLOGMODE_INITIALIZE))
{
xEmbeddedUI.SetAttributeValue("IgnoreInitialize", "yes");
}
if (0 == (messageFilter & WindowsInstallerConstants.INSTALLLOGMODE_TERMINATE))
{
xEmbeddedUI.SetAttributeValue("IgnoreTerminate", "yes");
}
if (0 == (messageFilter & WindowsInstallerConstants.INSTALLLOGMODE_SHOWDIALOG))
{
xEmbeddedUI.SetAttributeValue("IgnoreShowDialog", "yes");
}
if (0 == (messageFilter & WindowsInstallerConstants.INSTALLLOGMODE_RMFILESINUSE))
{
xEmbeddedUI.SetAttributeValue("IgnoreRMFilesInUse", "yes");
}
if (0 == (messageFilter & WindowsInstallerConstants.INSTALLLOGMODE_INSTALLSTART))
{
xEmbeddedUI.SetAttributeValue("IgnoreInstallStart", "yes");
}
if (0 == (messageFilter & WindowsInstallerConstants.INSTALLLOGMODE_INSTALLEND))
{
xEmbeddedUI.SetAttributeValue("IgnoreInstallEnd", "yes");
}
if (WindowsInstallerConstants.MsidbEmbeddedHandlesBasic == (attributes & WindowsInstallerConstants.MsidbEmbeddedHandlesBasic))
{
xEmbeddedUI.SetAttributeValue("SupportBasicUI", "yes");
}
xEmbeddedUI.SetAttributeValue("SourceFile", row.FieldAsString(4));
this.UIElement.Add(xEmbeddedUI);
foundEmbeddedUI = true;
}
}
else
{
var xEmbeddedResource = new XElement(Names.EmbeddedUIResourceElement,
new XAttribute("Id", row.FieldAsString(0)),
new XAttribute("Name", row.FieldAsString(1)),
new XAttribute("SourceFile", row.FieldAsString(4)));
xEmbeddedUI.Add(xEmbeddedResource);
foundEmbeddedResources = true;
}
}
if (!foundEmbeddedUI && foundEmbeddedResources)
{
// TODO: warn
}
}
///
/// Decompile the MsiLockPermissionsEx table.
///
/// The table to decompile.
private void DecompileMsiLockPermissionsExTable(Table table)
{
foreach (var row in table.Rows)
{
var xPermissionEx = new XElement(Names.PermissionExElement,
new XAttribute("Id", row.FieldAsString(0)),
new XAttribute("Sddl", row.FieldAsString(3)),
XAttributeIfNotNull("Condition", row, 4));
switch (row.FieldAsString(2))
{
case "CreateFolder":
case "File":
case "Registry":
case "ServiceInstall":
break;
default:
this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, row.Table.Name, row.Fields[1].Column.Name, row[1]));
return;
}
this.IndexElement(row, xPermissionEx);
}
}
///
/// Decompile the MsiPackageCertificate table.
///
/// The table to decompile.
private void DecompileMsiPackageCertificateTable(Table table)
{
if (0 < table.Rows.Count)
{
var xPackageCertificates = new XElement(Names.PatchCertificatesElement);
this.RootElement.Add(xPackageCertificates);
this.AddCertificates(table, xPackageCertificates);
}
}
///
/// Decompile the MsiPatchCertificate table.
///
/// The table to decompile.
private void DecompileMsiPatchCertificateTable(Table table)
{
if (0 < table.Rows.Count)
{
var xPatchCertificates = new XElement(Names.PatchCertificatesElement);
this.RootElement.Add(xPatchCertificates);
this.AddCertificates(table, xPatchCertificates);
}
}
///
/// Insert DigitalCertificate records associated with passed msiPackageCertificate or msiPatchCertificate table.
///
/// The table being decompiled.
/// DigitalCertificate parent
private void AddCertificates(Table table, XElement parent)
{
foreach (var row in table.Rows)
{
if (this.TryGetIndexedElement("MsiDigitalCertificate", out var xDigitalCertificate, row.FieldAsString(1)))
{
parent.Add(xDigitalCertificate);
}
else
{
this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "DigitalCertificate_", row.FieldAsString(1), "MsiDigitalCertificate"));
}
}
}
///
/// Decompile the MsiShortcutProperty table.
///
/// The table to decompile.
private void DecompileMsiShortcutPropertyTable(Table table)
{
foreach (var row in table.Rows)
{
var xProperty = new XElement(Names.ShortcutPropertyElement,
new XAttribute("Id", row.FieldAsString(0)),
new XAttribute("Key", row.FieldAsString(2)),
new XAttribute("Value", row.FieldAsString(3)));
this.AddChildToParent("Shortcut", xProperty, row, 1);
}
}
///
/// Decompile the ODBCAttribute table.
///
/// The table to decompile.
private void DecompileODBCAttributeTable(Table table)
{
foreach (var row in table.Rows)
{
var xProperty = new XElement(Names.PropertyElement,
new XAttribute("Id", row.FieldAsString(1)),
row.IsColumnNull(2) ? null : new XAttribute("Value", row.FieldAsString(2)));
this.AddChildToParent("ODBCDriver", xProperty, row, 0);
}
}
///
/// Decompile the ODBCDataSource table.
///
/// The table to decompile.
private void DecompileODBCDataSourceTable(Table table)
{
foreach (var row in table.Rows)
{
var xOdbcDataSource = new XElement(Names.ODBCDataSourceElement,
new XAttribute("Id", row.FieldAsString(0)),
new XAttribute("Name", row.FieldAsString(2)),
new XAttribute("DriverName", row.FieldAsString(3)));
switch (row.FieldAsInteger(4))
{
case WindowsInstallerConstants.MsidbODBCDataSourceRegistrationPerMachine:
xOdbcDataSource.SetAttributeValue("Registration", "machine");
break;
case WindowsInstallerConstants.MsidbODBCDataSourceRegistrationPerUser:
xOdbcDataSource.SetAttributeValue("Registration", "user");
break;
default:
this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[4].Column.Name, row[4]));
break;
}
this.IndexElement(row, xOdbcDataSource);
}
}
///
/// Decompile the ODBCDriver table.
///
/// The table to decompile.
private void DecompileODBCDriverTable(Table table)
{
foreach (var row in table.Rows)
{
var xOdbcDriver = new XElement(Names.ODBCDriverElement,
new XAttribute("Id", row.FieldAsString(0)),
new XAttribute("Name", row.FieldAsString(2)),
new XAttribute("File", row.FieldAsString(3)),
XAttributeIfNotNull("SetupFile", row, 4));
this.AddChildToParent("Component", xOdbcDriver, row, 1);
this.IndexElement(row, xOdbcDriver);
}
}
///
/// Decompile the ODBCSourceAttribute table.
///
/// The table to decompile.
private void DecompileODBCSourceAttributeTable(Table table)
{
foreach (var row in table.Rows)
{
var xProperty = new XElement(Names.PropertyElement,
new XAttribute("Id", row.FieldAsString(1)),
XAttributeIfNotNull("Value", row, 2));
this.AddChildToParent("ODBCDataSource", xProperty, row, 0);
}
}
///
/// Decompile the ODBCTranslator table.
///
/// The table to decompile.
private void DecompileODBCTranslatorTable(Table table)
{
foreach (var row in table.Rows)
{
var xOdbcTranslator = new XElement(Names.ODBCTranslatorElement,
new XAttribute("Id", row.FieldAsString(0)),
new XAttribute("Name", row.FieldAsString(2)),
new XAttribute("File", row.FieldAsString(3)),
XAttributeIfNotNull("SetupFile", row, 4));
this.AddChildToParent("Component", xOdbcTranslator, row, 1);
}
}
///
/// Decompile the PatchMetadata table.
///
/// The table to decompile.
private void DecompilePatchMetadataTable(Table table)
{
if (0 < table.Rows.Count)
{
var xPatchMetadata = new XElement(Names.PatchMetadataElement);
foreach (var row in table.Rows)
{
var value = row.FieldAsString(2);
switch (row.FieldAsString(1))
{
case "AllowRemoval":
if ("1" == value)
{
xPatchMetadata.SetAttributeValue("AllowRemoval", "yes");
}
break;
case "Classification":
if (null != value)
{
xPatchMetadata.SetAttributeValue("Classification", value);
}
break;
case "CreationTimeUTC":
if (null != value)
{
xPatchMetadata.SetAttributeValue("CreationTimeUTC", value);
}
break;
case "Description":
if (null != value)
{
xPatchMetadata.SetAttributeValue("Description", value);
}
break;
case "DisplayName":
if (null != value)
{
xPatchMetadata.SetAttributeValue("DisplayName", value);
}
break;
case "ManufacturerName":
if (null != value)
{
xPatchMetadata.SetAttributeValue("ManufacturerName", value);
}
break;
case "MinorUpdateTargetRTM":
if (null != value)
{
xPatchMetadata.SetAttributeValue("MinorUpdateTargetRTM", value);
}
break;
case "MoreInfoURL":
if (null != value)
{
xPatchMetadata.SetAttributeValue("MoreInfoURL", value);
}
break;
case "OptimizeCA":
var xOptimizeCustomActions = new XElement(Names.OptimizeCustomActionsElement);
var optimizeCA = Int32.Parse(value, CultureInfo.InvariantCulture);
if (0 != (Convert.ToInt32(OptimizeCAFlags.SkipAssignment) & optimizeCA))
{
xOptimizeCustomActions.SetAttributeValue("SkipAssignment", "yes");
}
if (0 != (Convert.ToInt32(OptimizeCAFlags.SkipImmediate) & optimizeCA))
{
xOptimizeCustomActions.SetAttributeValue("SkipImmediate", "yes");
}
if (0 != (Convert.ToInt32(OptimizeCAFlags.SkipDeferred) & optimizeCA))
{
xOptimizeCustomActions.SetAttributeValue("SkipDeferred", "yes");
}
xPatchMetadata.Add(xOptimizeCustomActions);
break;
case "OptimizedInstallMode":
if ("1" == value)
{
xPatchMetadata.SetAttributeValue("OptimizedInstallMode", "yes");
}
break;
case "TargetProductName":
if (null != value)
{
xPatchMetadata.SetAttributeValue("TargetProductName", value);
}
break;
default:
var xCustomProperty = new XElement(Names.CustomPropertyElement,
XAttributeIfNotNull("Company", row, 0),
XAttributeIfNotNull("Property", row, 1),
XAttributeIfNotNull("Value", row, 2));
xPatchMetadata.Add(xCustomProperty);
break;
}
}
this.RootElement.Add(xPatchMetadata);
}
}
///
/// Decompile the PatchSequence table.
///
/// The table to decompile.
private void DecompilePatchSequenceTable(Table table)
{
foreach (var row in table.Rows)
{
var patchSequence = new XElement(Names.PatchSequenceElement,
new XAttribute("PatchFamily", row.FieldAsString(0)));
if (!row.IsColumnNull(1))
{
try
{
var guid = new Guid(row.FieldAsString(1));
patchSequence.SetAttributeValue("ProductCode", row.FieldAsString(1));
}
catch // non-guid value
{
patchSequence.SetAttributeValue("TargetImage", row.FieldAsString(1));
}
}
if (!row.IsColumnNull(2))
{
patchSequence.SetAttributeValue("Sequence", row.FieldAsString(2));
}
if (!row.IsColumnNull(3) && 0x1 == row.FieldAsInteger(3))
{
patchSequence.SetAttributeValue("Supersede", "yes");
}
this.RootElement.Add(patchSequence);
}
}
///
/// Decompile the ProgId table.
///
/// The table to decompile.
private void DecompileProgIdTable(Table table)
{
foreach (var row in table.Rows)
{
var xProgId = new XElement(Names.ProgIdElement,
new XAttribute("Advertise", "yes"),
new XAttribute("Id", row.FieldAsString(0)),
XAttributeIfNotNull("Description", row, 3),
XAttributeIfNotNull("Icon", row, 4),
XAttributeIfNotNull("IconIndex", row, 5));
this.IndexElement(row, xProgId);
}
// nest the ProgIds
foreach (var row in table.Rows)
{
var xProgId = this.GetIndexedElement(row);
if (!row.IsColumnNull(1))
{
this.AddChildToParent("ProgId", xProgId, row, 1);
}
else if (!row.IsColumnNull(2))
{
// nesting is handled in FinalizeProgIdTable
}
else
{
// TODO: warn for orphaned ProgId
}
}
}
///
/// Decompile the Properties table.
///
/// The table to decompile.
private void DecompilePropertiesTable(Table table)
{
foreach (var row in table.Rows)
{
var name = row.FieldAsString(0);
var value = row.FieldAsString(1);
switch (name)
{
case "AllowProductCodeMismatches":
if ("1" == value)
{
this.RootElement.SetAttributeValue("AllowProductCodeMismatches", "yes");
}
break;
case "AllowProductVersionMajorMismatches":
if ("1" == value)
{
this.RootElement.SetAttributeValue("AllowMajorVersionMismatches", "yes");
}
break;
case "ApiPatchingSymbolFlags":
if (null != value)
{
try
{
// remove the leading "0x" if its present
if (value.StartsWith("0x", StringComparison.Ordinal))
{
value = value.Substring(2);
}
this.RootElement.SetAttributeValue("SymbolFlags", Convert.ToInt32(value, 16));
}
catch
{
this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[1].Column.Name, row[1]));
}
}
break;
case "DontRemoveTempFolderWhenFinished":
if ("1" == value)
{
this.RootElement.SetAttributeValue("CleanWorkingFolder", "no");
}
break;
case "IncludeWholeFilesOnly":
if ("1" == value)
{
this.RootElement.SetAttributeValue("WholeFilesOnly", "yes");
}
break;
case "ListOfPatchGUIDsToReplace":
if (null != value)
{
var guidRegex = new Regex(@"\{[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}\}");
var guidMatches = guidRegex.Matches(value);
foreach (Match guidMatch in guidMatches)
{
var xReplacePatch = new XElement(Names.ReplacePatchElement,
new XAttribute("Id", guidMatch.Value));
this.RootElement.Add(xReplacePatch);
}
}
break;
case "ListOfTargetProductCodes":
if (null != value)
{
var targetProductCodes = value.Split(';');
foreach (var targetProductCodeString in targetProductCodes)
{
var xTargetProductCode = new XElement(Names.TargetProductCodeElement,
new XAttribute("Id", targetProductCodeString));
this.RootElement.Add(xTargetProductCode);
}
}
break;
case "PatchGUID":
this.RootElement.SetAttributeValue("Id", value);
break;
case "PatchSourceList":
this.RootElement.SetAttributeValue("SourceList", value);
break;
case "PatchOutputPath":
this.RootElement.SetAttributeValue("OutputPath", value);
break;
default:
var patchProperty = new XElement(Names.PatchPropertyElement,
new XAttribute("Name", name),
new XAttribute("Value", value));
this.RootElement.Add(patchProperty);
break;
}
}
}
///
/// Decompile the Property table.
///
/// The table to decompile.
private void DecompilePropertyTable(Table table)
{
foreach (var row in table.Rows)
{
var id = row.FieldAsString(0);
var value = row.FieldAsString(1);
if ("AdminProperties" == id || "MsiHiddenProperties" == id || "SecureCustomProperties" == id)
{
if (0 < value.Length)
{
foreach (var propertyId in value.Split(';'))
{
if (WixUpgradeConstants.DowngradeDetectedProperty == propertyId || WixUpgradeConstants.UpgradeDetectedProperty == propertyId)
{
continue;
}
var property = propertyId;
var suppressModulularization = false;
if (OutputType.Module == this.OutputType)
{
if (propertyId.EndsWith(this.ModularizationGuid.Substring(1, 36).Replace('-', '_'), StringComparison.Ordinal))
{
property = propertyId.Substring(0, propertyId.Length - this.ModularizationGuid.Length + 1);
}
else
{
suppressModulularization = true;
}
}
var xSpecialProperty = this.EnsureProperty(property);
if (suppressModulularization)
{
xSpecialProperty.SetAttributeValue("SuppressModularization", "yes");
}
switch (id)
{
case "AdminProperties":
xSpecialProperty.SetAttributeValue("Admin", "yes");
break;
case "MsiHiddenProperties":
xSpecialProperty.SetAttributeValue("Hidden", "yes");
break;
case "SecureCustomProperties":
xSpecialProperty.SetAttributeValue("Secure", "yes");
break;
}
}
}
continue;
}
else if (OutputType.Product == this.OutputType)
{
switch (id)
{
case "Manufacturer":
this.RootElement.SetAttributeValue("Manufacturer", value);
continue;
case "ProductCode":
this.RootElement.SetAttributeValue("ProductCode", value.ToUpper(CultureInfo.InvariantCulture));
continue;
case "ProductLanguage":
this.RootElement.SetAttributeValue("Language", value);
continue;
case "ProductName":
this.RootElement.SetAttributeValue("Name", value);
continue;
case "ProductVersion":
this.RootElement.SetAttributeValue("Version", value);
continue;
case "UpgradeCode":
this.RootElement.SetAttributeValue("UpgradeCode", value);
continue;
}
}
if (!this.SuppressUI || "ErrorDialog" != id)
{
var xProperty = this.EnsureProperty(id);
xProperty.SetAttributeValue("Value", value);
}
}
}
///
/// Decompile the PublishComponent table.
///
/// The table to decompile.
private void DecompilePublishComponentTable(Table table)
{
foreach (var row in table.Rows)
{
var category = new XElement(Names.CategoryElement,
new XAttribute("Id", row.FieldAsString(0)),
new XAttribute("Qualifier", row.FieldAsString(1)),
XAttributeIfNotNull("AppData", row, 3));
this.AddChildToParent("Component", category, row, 2);
}
}
///
/// Decompile the RadioButton table.
///
/// The table to decompile.
private void DecompileRadioButtonTable(Table table)
{
foreach (var row in table.Rows)
{
var radioButton = new XElement(Names.RadioButtonElement,
new XAttribute("Value", row.FieldAsString(2)),
new XAttribute("X", row.FieldAsInteger(3)),
new XAttribute("Y", row.FieldAsInteger(4)),
new XAttribute("Width", row.FieldAsInteger(5)),
new XAttribute("Height", row.FieldAsInteger(6)),
XAttributeIfNotNull("Text", row, 7));
if (!row.IsColumnNull(8))
{
var help = (row.FieldAsString(8)).Split('|');
if (2 == help.Length)
{
if (0 < help[0].Length)
{
radioButton.SetAttributeValue("ToolTip", help[0]);
}
if (0 < help[1].Length)
{
radioButton.SetAttributeValue("Help", help[1]);
}
}
}
this.IndexElement(row, radioButton);
}
// nest the radio buttons
var xRadioButtonGroups = new Dictionary();
foreach (var row in table.Rows.OrderBy(row => row.FieldAsString(0)).ThenBy(row => row.FieldAsInteger(1)))
{
var xRadioButton = this.GetIndexedElement(row);
if (!xRadioButtonGroups.TryGetValue(row.FieldAsString(0), out var xRadioButtonGroup))
{
xRadioButtonGroup = new XElement(Names.RadioButtonGroupElement,
new XAttribute("Property", row.FieldAsString(0)));
this.UIElement.Add(xRadioButtonGroup);
xRadioButtonGroups.Add(row.FieldAsString(0), xRadioButtonGroup);
}
xRadioButtonGroup.Add(xRadioButton);
}
}
///
/// Decompile the Registry table.
///
/// The table to decompile.
private void DecompileRegistryTable(Table table)
{
foreach (var row in table.Rows)
{
if (("-" == row.FieldAsString(3) || "+" == row.FieldAsString(3) || "*" == row.FieldAsString(3)) && row.IsColumnNull(4))
{
var xRegistryKey = new XElement(Names.RegistryKeyElement,
new XAttribute("Id", row.FieldAsString(0)),
new XAttribute("Key", row.FieldAsString(2)));
if (this.GetRegistryRootType(row.SourceLineNumbers, table.Name, row.Fields[1], out var registryRootType))
{
xRegistryKey.SetAttributeValue("Root", registryRootType);
}
switch (row.FieldAsString(3))
{
case "+":
xRegistryKey.SetAttributeValue("ForceCreateOnInstall", "yes");
break;
case "-":
xRegistryKey.SetAttributeValue("ForceDeleteOnUninstall", "yes");
break;
case "*":
xRegistryKey.SetAttributeValue("ForceCreateOnInstall", "yes");
xRegistryKey.SetAttributeValue("ForceDeleteOnUninstall", "yes");
break;
}
this.IndexElement(row, xRegistryKey);
}
else
{
var xRegistryValue = new XElement(Names.RegistryValueElement,
new XAttribute("Id", row.FieldAsString(0)),
new XAttribute("Key", row.FieldAsString(2)),
XAttributeIfNotNull("Name", row, 3));
if (this.GetRegistryRootType(row.SourceLineNumbers, table.Name, row.Fields[1], out var registryRootType))
{
xRegistryValue.SetAttributeValue("Root", registryRootType);
}
if (!row.IsColumnNull(4))
{
var value = row.FieldAsString(4);
if (value.StartsWith("#x", StringComparison.Ordinal))
{
xRegistryValue.SetAttributeValue("Type", "binary");
xRegistryValue.SetAttributeValue("Value", value.Substring(2));
}
else if (value.StartsWith("#%", StringComparison.Ordinal))
{
xRegistryValue.SetAttributeValue("Type", "expandable");
xRegistryValue.SetAttributeValue("Value", value.Substring(2));
}
else if (value.StartsWith("#", StringComparison.Ordinal) && !value.StartsWith("##", StringComparison.Ordinal))
{
xRegistryValue.SetAttributeValue("Type", "integer");
xRegistryValue.SetAttributeValue("Value", value.Substring(1));
}
else
{
if (value.StartsWith("##", StringComparison.Ordinal))
{
value = value.Substring(1);
}
if (0 <= value.IndexOf("[~]", StringComparison.Ordinal))
{
xRegistryValue.SetAttributeValue("Type", "multiString");
if ("[~]" == value)
{
value = String.Empty;
}
else if (value.StartsWith("[~]", StringComparison.Ordinal) && value.EndsWith("[~]", StringComparison.Ordinal))
{
value = value.Substring(3, value.Length - 6);
}
else if (value.StartsWith("[~]", StringComparison.Ordinal))
{
xRegistryValue.SetAttributeValue("Action", "append");
value = value.Substring(3);
}
else if (value.EndsWith("[~]", StringComparison.Ordinal))
{
xRegistryValue.SetAttributeValue("Action", "prepend");
value = value.Substring(0, value.Length - 3);
}
var multiValues = NullSplitter.Split(value);
foreach (var multiValue in multiValues)
{
var xMultiStringValue = new XElement(Names.MultiStringElement,
new XAttribute("Value", multiValue));
xRegistryValue.Add(xMultiStringValue);
}
}
else
{
xRegistryValue.SetAttributeValue("Type", "string");
xRegistryValue.SetAttributeValue("Value", value);
}
}
}
else
{
xRegistryValue.SetAttributeValue("Type", "string");
xRegistryValue.SetAttributeValue("Value", String.Empty);
}
this.IndexElement(row, xRegistryValue);
}
}
}
///
/// Decompile the RegLocator table.
///
/// The table to decompile.
private void DecompileRegLocatorTable(Table table)
{
foreach (var row in table.Rows)
{
var xRegistrySearch = new XElement(Names.RegistrySearchElement,
new XAttribute("Id", row.FieldAsString(0)),
new XAttribute("Key", row.FieldAsString(2)),
XAttributeIfNotNull("Name", row, 3));
switch (row.FieldAsInteger(1))
{
case WindowsInstallerConstants.MsidbRegistryRootClassesRoot:
xRegistrySearch.SetAttributeValue("Root", "HKCR");
break;
case WindowsInstallerConstants.MsidbRegistryRootCurrentUser:
xRegistrySearch.SetAttributeValue("Root", "HKCU");
break;
case WindowsInstallerConstants.MsidbRegistryRootLocalMachine:
xRegistrySearch.SetAttributeValue("Root", "HKLM");
break;
case WindowsInstallerConstants.MsidbRegistryRootUsers:
xRegistrySearch.SetAttributeValue("Root", "HKU");
break;
default:
this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[1].Column.Name, row[1]));
break;
}
if (row.IsColumnNull(4))
{
xRegistrySearch.SetAttributeValue("Type", "file");
}
else
{
var type = row.FieldAsInteger(4);
if (WindowsInstallerConstants.MsidbLocatorType64bit == (type & WindowsInstallerConstants.MsidbLocatorType64bit))
{
xRegistrySearch.SetAttributeValue("Bitness", "always64");
type &= ~WindowsInstallerConstants.MsidbLocatorType64bit;
}
else
{
xRegistrySearch.SetAttributeValue("Bitness", "always32");
}
switch (type)
{
case WindowsInstallerConstants.MsidbLocatorTypeDirectory:
xRegistrySearch.SetAttributeValue("Type", "directory");
break;
case WindowsInstallerConstants.MsidbLocatorTypeFileName:
xRegistrySearch.SetAttributeValue("Type", "file");
break;
case WindowsInstallerConstants.MsidbLocatorTypeRawValue:
xRegistrySearch.SetAttributeValue("Type", "raw");
break;
default:
this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[4].Column.Name, row[4]));
break;
}
}
this.IndexElement(row, xRegistrySearch);
}
}
///
/// Decompile the RemoveFile table.
///
/// The table to decompile.
private void DecompileRemoveFileTable(Table table)
{
foreach (var row in table.Rows)
{
if (row.IsColumnNull(2))
{
var xRemoveFolder = new XElement(Names.RemoveFolderElement,
new XAttribute("Id", row.FieldAsString(0)));
// directory/property is set in FinalizeDecompile
switch (row.FieldAsInteger(4))
{
case WindowsInstallerConstants.MsidbRemoveFileInstallModeOnInstall:
xRemoveFolder.SetAttributeValue("On", "install");
break;
case WindowsInstallerConstants.MsidbRemoveFileInstallModeOnRemove:
xRemoveFolder.SetAttributeValue("On", "uninstall");
break;
case WindowsInstallerConstants.MsidbRemoveFileInstallModeOnBoth:
xRemoveFolder.SetAttributeValue("On", "both");
break;
default:
this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[4].Column.Name, row[4]));
break;
}
this.AddChildToParent("Component", xRemoveFolder, row, 1);
this.IndexElement(row, xRemoveFolder);
}
else
{
var xRemoveFile = new XElement(Names.RemoveFileElement,
new XAttribute("Id", row.FieldAsString(0)));
var names = this.BackendHelper.SplitMsiFileName(row.FieldAsString(2));
if (null != names[0] && null != names[1])
{
xRemoveFile.SetAttributeValue("ShortName", names[0]);
xRemoveFile.SetAttributeValue("Name", names[1]);
}
else if (null != names[0])
{
xRemoveFile.SetAttributeValue("Name", names[0]);
}
// directory/property is set in FinalizeDecompile
switch (row.FieldAsInteger(4))
{
case WindowsInstallerConstants.MsidbRemoveFileInstallModeOnInstall:
xRemoveFile.SetAttributeValue("On", "install");
break;
case WindowsInstallerConstants.MsidbRemoveFileInstallModeOnRemove:
xRemoveFile.SetAttributeValue("On", "uninstall");
break;
case WindowsInstallerConstants.MsidbRemoveFileInstallModeOnBoth:
xRemoveFile.SetAttributeValue("On", "both");
break;
default:
this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[4].Column.Name, row[4]));
break;
}
this.AddChildToParent("Component", xRemoveFile, row, 1);
this.IndexElement(row, xRemoveFile);
}
}
}
///
/// Decompile the RemoveIniFile table.
///
/// The table to decompile.
private void DecompileRemoveIniFileTable(Table table)
{
foreach (var row in table.Rows)
{
var xIniFile = new XElement(Names.IniFileElement,
new XAttribute("Id", row.FieldAsString(0)),
XAttributeIfNotNull("Directory", row, 2),
new XAttribute("Section", row.FieldAsString(3)),
new XAttribute("Key", row.FieldAsString(4)),
XAttributeIfNotNull("Value", row, 5));
var names = this.BackendHelper.SplitMsiFileName(row.FieldAsString(1));
if (null != names[0] && null != names[1])
{
xIniFile.SetAttributeValue("ShortName", names[0]);
xIniFile.SetAttributeValue("Name", names[1]);
}
else if (null != names[0])
{
xIniFile.SetAttributeValue("Name", names[0]);
}
switch (row.FieldAsInteger(6))
{
case WindowsInstallerConstants.MsidbIniFileActionRemoveLine:
xIniFile.SetAttributeValue("Action", "removeLine");
break;
case WindowsInstallerConstants.MsidbIniFileActionRemoveTag:
xIniFile.SetAttributeValue("Action", "removeTag");
break;
default:
this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[6].Column.Name, row[6]));
break;
}
this.AddChildToParent("Component", xIniFile, row, 7);
}
}
///
/// Decompile the RemoveRegistry table.
///
/// The table to decompile.
private void DecompileRemoveRegistryTable(Table table)
{
foreach (var row in table.Rows)
{
if ("-" == row.FieldAsString(3))
{
var xRemoveRegistryKey = new XElement(Names.RemoveRegistryKeyElement,
new XAttribute("Id", row.FieldAsString(0)),
new XAttribute("Key", row.FieldAsString(2)),
new XAttribute("Action", "removeOnInstall"));
if (this.GetRegistryRootType(row.SourceLineNumbers, table.Name, row.Fields[1], out var registryRootType))
{
xRemoveRegistryKey.SetAttributeValue("Root", registryRootType);
}
this.AddChildToParent("Component", xRemoveRegistryKey, row, 4);
}
else
{
var xRemoveRegistryValue = new XElement(Names.RemoveRegistryValueElement,
new XAttribute("Id", row.FieldAsString(0)),
new XAttribute("Key", row.FieldAsString(2)),
XAttributeIfNotNull("Name", row, 3));
if (this.GetRegistryRootType(row.SourceLineNumbers, table.Name, row.Fields[1], out var registryRootType))
{
xRemoveRegistryValue.SetAttributeValue("Root", registryRootType);
}
this.AddChildToParent("Component", xRemoveRegistryValue, row, 4);
}
}
}
///
/// Decompile the ReserveCost table.
///
/// The table to decompile.
private void DecompileReserveCostTable(Table table)
{
foreach (var row in table.Rows)
{
var xReserveCost = new XElement(Names.ReserveCostElement,
new XAttribute("Id", row.FieldAsString(0)),
XAttributeIfNotNull("Directory", row, 2),
new XAttribute("RunLocal", row.FieldAsString(3)),
new XAttribute("RunFromSource", row.FieldAsString(4)));
this.AddChildToParent("Component", xReserveCost, row, 4);
}
}
///
/// Decompile the SelfReg table.
///
/// The table to decompile.
private void DecompileSelfRegTable(Table table)
{
foreach (var row in table.Rows)
{
if (this.TryGetIndexedElement("File", out var xFile, row.FieldAsString(0)))
{
xFile.SetAttributeValue("SelfRegCost", row.IsColumnNull(1) ? 0 : row.FieldAsInteger(1));
}
else
{
this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "File_", row.FieldAsString(0), "File"));
}
}
}
///
/// Decompile the ServiceControl table.
///
/// The table to decompile.
private void DecompileServiceControlTable(Table table)
{
foreach (var row in table.Rows)
{
var xServiceControl = new XElement(Names.ServiceControlElement,
new XAttribute("Id", row.FieldAsString(0)),
new XAttribute("Name", row.FieldAsString(1)));
var eventValue = row.FieldAsInteger(2);
if (WindowsInstallerConstants.MsidbServiceControlEventStart == (eventValue & WindowsInstallerConstants.MsidbServiceControlEventStart) &&
WindowsInstallerConstants.MsidbServiceControlEventUninstallStart == (eventValue & WindowsInstallerConstants.MsidbServiceControlEventUninstallStart))
{
xServiceControl.SetAttributeValue("Start", "both");
}
else if (WindowsInstallerConstants.MsidbServiceControlEventStart == (eventValue & WindowsInstallerConstants.MsidbServiceControlEventStart))
{
xServiceControl.SetAttributeValue("Start", "install");
}
else if (WindowsInstallerConstants.MsidbServiceControlEventUninstallStart == (eventValue & WindowsInstallerConstants.MsidbServiceControlEventUninstallStart))
{
xServiceControl.SetAttributeValue("Start", "uninstall");
}
if (WindowsInstallerConstants.MsidbServiceControlEventStop == (eventValue & WindowsInstallerConstants.MsidbServiceControlEventStop) &&
WindowsInstallerConstants.MsidbServiceControlEventUninstallStop == (eventValue & WindowsInstallerConstants.MsidbServiceControlEventUninstallStop))
{
xServiceControl.SetAttributeValue("Stop", "both");
}
else if (WindowsInstallerConstants.MsidbServiceControlEventStop == (eventValue & WindowsInstallerConstants.MsidbServiceControlEventStop))
{
xServiceControl.SetAttributeValue("Stop", "install");
}
else if (WindowsInstallerConstants.MsidbServiceControlEventUninstallStop == (eventValue & WindowsInstallerConstants.MsidbServiceControlEventUninstallStop))
{
xServiceControl.SetAttributeValue("Stop", "uninstall");
}
if (WindowsInstallerConstants.MsidbServiceControlEventDelete == (eventValue & WindowsInstallerConstants.MsidbServiceControlEventDelete) &&
WindowsInstallerConstants.MsidbServiceControlEventUninstallDelete == (eventValue & WindowsInstallerConstants.MsidbServiceControlEventUninstallDelete))
{
xServiceControl.SetAttributeValue("Remove", "both");
}
else if (WindowsInstallerConstants.MsidbServiceControlEventDelete == (eventValue & WindowsInstallerConstants.MsidbServiceControlEventDelete))
{
xServiceControl.SetAttributeValue("Remove", "install");
}
else if (WindowsInstallerConstants.MsidbServiceControlEventUninstallDelete == (eventValue & WindowsInstallerConstants.MsidbServiceControlEventUninstallDelete))
{
xServiceControl.SetAttributeValue("Remove", "uninstall");
}
if (!row.IsColumnNull(3))
{
var arguments = NullSplitter.Split(row.FieldAsString(3));
foreach (var argument in arguments)
{
var xServiceArgument = new XElement(Names.ServiceArgumentElement,
new XAttribute("Value", argument));
xServiceControl.Add(xServiceArgument);
}
}
if (!row.IsColumnNull(4))
{
xServiceControl.SetAttributeValue("Wait", row.FieldAsInteger(4) == 0 ? "no" : "yes");
}
this.AddChildToParent("Component", xServiceControl, row, 5);
}
}
///
/// Decompile the ServiceInstall table.
///
/// The table to decompile.
private void DecompileServiceInstallTable(Table table)
{
foreach (var row in table.Rows)
{
var xServiceInstall = new XElement(Names.ServiceInstallElement,
new XAttribute("Id", row.FieldAsString(0)),
new XAttribute("Name", row.FieldAsString(1)),
XAttributeIfNotNull("DisplayName", row, 2),
XAttributeIfNotNull("LoadOrderGroup", row, 6),
XAttributeIfNotNull("Account", row, 8),
XAttributeIfNotNull("Password", row, 9),
XAttributeIfNotNull("Arguments", row, 10),
XAttributeIfNotNull("Description", row, 12));
var serviceType = row.FieldAsInteger(3);
if (WindowsInstallerConstants.MsidbServiceInstallInteractive == (serviceType & WindowsInstallerConstants.MsidbServiceInstallInteractive))
{
xServiceInstall.SetAttributeValue("Interactive", "yes");
}
if (WindowsInstallerConstants.MsidbServiceInstallOwnProcess == (serviceType & WindowsInstallerConstants.MsidbServiceInstallOwnProcess) &&
WindowsInstallerConstants.MsidbServiceInstallShareProcess == (serviceType & WindowsInstallerConstants.MsidbServiceInstallShareProcess))
{
// TODO: warn
}
else if (WindowsInstallerConstants.MsidbServiceInstallOwnProcess == (serviceType & WindowsInstallerConstants.MsidbServiceInstallOwnProcess))
{
xServiceInstall.SetAttributeValue("Type", "ownProcess");
}
else if (WindowsInstallerConstants.MsidbServiceInstallShareProcess == (serviceType & WindowsInstallerConstants.MsidbServiceInstallShareProcess))
{
xServiceInstall.SetAttributeValue("Type", "shareProcess");
}
var startType = row.FieldAsInteger(4);
if (WindowsInstallerConstants.MsidbServiceInstallDisabled == startType)
{
xServiceInstall.SetAttributeValue("Start", "disabled");
}
else if (WindowsInstallerConstants.MsidbServiceInstallDemandStart == startType)
{
xServiceInstall.SetAttributeValue("Start", "demand");
}
else if (WindowsInstallerConstants.MsidbServiceInstallAutoStart == startType)
{
xServiceInstall.SetAttributeValue("Start", "auto");
}
else
{
this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[4].Column.Name, row[4]));
}
var errorControl = row.FieldAsInteger(5);
if (WindowsInstallerConstants.MsidbServiceInstallErrorCritical == (errorControl & WindowsInstallerConstants.MsidbServiceInstallErrorCritical))
{
xServiceInstall.SetAttributeValue("ErrorControl", "critical");
}
else if (WindowsInstallerConstants.MsidbServiceInstallErrorNormal == (errorControl & WindowsInstallerConstants.MsidbServiceInstallErrorNormal))
{
xServiceInstall.SetAttributeValue("ErrorControl", "normal");
}
else
{
xServiceInstall.SetAttributeValue("ErrorControl", "ignore");
}
if (WindowsInstallerConstants.MsidbServiceInstallErrorControlVital == (errorControl & WindowsInstallerConstants.MsidbServiceInstallErrorControlVital))
{
xServiceInstall.SetAttributeValue("Vital", "yes");
}
if (!row.IsColumnNull(7))
{
var dependencies = NullSplitter.Split(row.FieldAsString(7));
foreach (var dependency in dependencies)
{
if (0 < dependency.Length)
{
var xServiceDependency = new XElement(Names.ServiceDependencyElement);
if (dependency.StartsWith("+", StringComparison.Ordinal))
{
xServiceDependency.SetAttributeValue("Group", "yes");
xServiceDependency.SetAttributeValue("Id", dependency.Substring(1));
}
else
{
xServiceDependency.SetAttributeValue("Id", dependency);
}
xServiceInstall.Add(xServiceDependency);
}
}
}
this.AddChildToParent("Component", xServiceInstall, row, 11);
this.IndexElement(row, xServiceInstall);
}
}
///
/// Decompile the SFPCatalog table.
///
/// The table to decompile.
private void DecompileSFPCatalogTable(Table table)
{
foreach (var row in table.Rows)
{
var xSfpCatalog = new XElement(Names.SFPCatalogElement,
new XAttribute("Name", row.FieldAsString(0)),
new XAttribute("SourceFile", row.FieldAsString(1)));
this.IndexElement(row, xSfpCatalog);
}
// nest the SFPCatalog elements
foreach (var row in table.Rows)
{
var xSfpCatalog = this.GetIndexedElement(row);
if (!row.IsColumnNull(2))
{
if (this.TryGetIndexedElement("SFPCatalog", out var xParentSFPCatalog, row.FieldAsString(2)))
{
xParentSFPCatalog.Add(xSfpCatalog);
}
else
{
xSfpCatalog.SetAttributeValue("Dependency", row.FieldAsString(2));
this.RootElement.Add(xSfpCatalog);
}
}
else
{
this.RootElement.Add(xSfpCatalog);
}
}
}
///
/// Decompile the Shortcut table.
///
/// The table to decompile.
private void DecompileShortcutTable(Table table)
{
foreach (var row in table.Rows)
{
var xShortcut = new XElement(Names.ShortcutElement,
new XAttribute("Id", row.FieldAsString(0)),
new XAttribute("Directory", row.FieldAsString(1)),
XAttributeIfNotNull("Arguments", row, 5),
XAttributeIfNotNull("Description", row, 6),
XAttributeIfNotNull("Hotkey", row, 7),
XAttributeIfNotNull("Icon", row, 8),
XAttributeIfNotNull("IconIndex", row, 9),
XAttributeIfNotNull("WorkingDirectory", row, 11));
var names = this.BackendHelper.SplitMsiFileName(row.FieldAsString(2));
if (null != names[0] && null != names[1])
{
xShortcut.SetAttributeValue("ShortName", names[0]);
xShortcut.SetAttributeValue("Name", names[1]);
}
else if (null != names[0])
{
xShortcut.SetAttributeValue("Name", names[0]);
}
if (!row.IsColumnNull(10))
{
switch (row.FieldAsInteger(10))
{
case 1:
xShortcut.SetAttributeValue("Show", "normal");
break;
case 3:
xShortcut.SetAttributeValue("Show", "maximized");
break;
case 7:
xShortcut.SetAttributeValue("Show", "minimized");
break;
default:
this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[10].Column.Name, row[10]));
break;
}
}
// Only try to read the MSI 4.0-specific columns if they actually exist
if (15 < row.Fields.Length)
{
if (!row.IsColumnNull(12))
{
xShortcut.SetAttributeValue("DisplayResourceDll", row.FieldAsString(12));
}
if (null != row[13])
{
xShortcut.SetAttributeValue("DisplayResourceId", row.FieldAsInteger(13));
}
if (null != row[14])
{
xShortcut.SetAttributeValue("DescriptionResourceDll", row.FieldAsString(14));
}
if (null != row[15])
{
xShortcut.SetAttributeValue("DescriptionResourceId", row.FieldAsInteger(15));
}
}
this.AddChildToParent("Component", xShortcut, row, 3);
this.IndexElement(row, xShortcut);
}
}
///
/// Decompile the Signature table.
///
/// The table to decompile.
private void DecompileSignatureTable(Table table)
{
foreach (var row in table.Rows)
{
var fileSearch = new XElement(Names.FileSearchElement,
new XAttribute("Id", row.FieldAsString(0)),
XAttributeIfNotNull("MinVersion", row, 2),
XAttributeIfNotNull("MaxVersion", row, 3),
XAttributeIfNotNull("MinSize", row, 4),
XAttributeIfNotNull("MaxSize", row, 5),
XAttributeIfNotNull("Languages", row, 8));
var names = this.BackendHelper.SplitMsiFileName(row.FieldAsString(1));
if (null != names[0])
{
// it is permissable to just have a long name
if (!this.BackendHelper.IsValidShortFilename(names[0], false) && null == names[1])
{
fileSearch.SetAttributeValue("Name", names[0]);
}
else
{
fileSearch.SetAttributeValue("ShortName", names[0]);
}
}
if (null != names[1])
{
fileSearch.SetAttributeValue("Name", names[1]);
}
if (!row.IsColumnNull(6))
{
fileSearch.SetAttributeValue("MinDate", ConvertIntegerToDateTime(row.FieldAsInteger(6)));
}
if (!row.IsColumnNull(7))
{
fileSearch.SetAttributeValue("MaxDate", ConvertIntegerToDateTime(row.FieldAsInteger(7)));
}
this.IndexElement(row, fileSearch);
}
}
///
/// Decompile the TargetFiles_OptionalData table.
///
/// The table to decompile.
private void DecompileTargetFiles_OptionalDataTable(Table table)
{
foreach (var row in table.Rows)
{
if (!this.PatchTargetFiles.TryGetValue(row.FieldAsString(0), out var xPatchTargetFile))
{
xPatchTargetFile = new XElement(Names.TargetFileElement,
new XAttribute("Id", row.FieldAsString(1)));
if (this.TryGetIndexedElement("TargetImages", out var xTargetImage, row.FieldAsString(0)))
{
xTargetImage.Add(xPatchTargetFile);
}
else
{
this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Target", row.FieldAsString(0), "TargetImages"));
}
this.PatchTargetFiles.Add(row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), xPatchTargetFile);
}
AddSymbolPaths(row, 2, xPatchTargetFile);
if (!row.IsColumnNull(3) && !row.IsColumnNull(4))
{
var ignoreOffsets = row.FieldAsString(3).Split(',');
var ignoreLengths = row.FieldAsString(4).Split(',');
if (ignoreOffsets.Length == ignoreLengths.Length)
{
for (var i = 0; i < ignoreOffsets.Length; i++)
{
var xIgnoreRange = new XElement(Names.IgnoreRangeElement);
if (ignoreOffsets[i].StartsWith("0x", StringComparison.Ordinal))
{
xIgnoreRange.SetAttributeValue("Offset", Convert.ToInt32(ignoreOffsets[i].Substring(2), 16));
}
else
{
xIgnoreRange.SetAttributeValue("Offset", Convert.ToInt32(ignoreOffsets[i], CultureInfo.InvariantCulture));
}
if (ignoreLengths[i].StartsWith("0x", StringComparison.Ordinal))
{
xIgnoreRange.SetAttributeValue("Length", Convert.ToInt32(ignoreLengths[i].Substring(2), 16));
}
else
{
xIgnoreRange.SetAttributeValue("Length", Convert.ToInt32(ignoreLengths[i], CultureInfo.InvariantCulture));
}
xPatchTargetFile.Add(xIgnoreRange);
}
}
else
{
// TODO: warn
}
}
else if (!row.IsColumnNull(3) || !row.IsColumnNull(4))
{
// TODO: warn about mismatch between columns
}
// the RetainOffsets column is handled in FinalizeFamilyFileRangesTable
}
}
///
/// Decompile the TargetImages table.
///
/// The table to decompile.
private void DecompileTargetImagesTable(Table table)
{
foreach (var row in table.Rows)
{
var xTargetImage = new XElement(Names.TargetImageElement,
new XAttribute("Id", row.FieldAsString(0)),
new XAttribute("SourceFile", row.FieldAsString(1)),
new XAttribute("Order", row.FieldAsInteger(4)),
XAttributeIfNotNull("Validation", row, 5));
AddSymbolPaths(row, 2, xTargetImage);
if (0 != row.FieldAsInteger(6))
{
xTargetImage.SetAttributeValue("IgnoreMissingFiles", "yes");
}
this.AddChildToParent("UpgradedImages", xTargetImage, row, 3);
this.IndexElement(row, xTargetImage);
}
}
///
/// Decompile the TextStyle table.
///
/// The table to decompile.
private void DecompileTextStyleTable(Table table)
{
foreach (var row in table.Rows)
{
var xTextStyle = new XElement(Names.TextStyleElement,
new XAttribute("Id", row.FieldAsString(0)),
new XAttribute("FaceName", row.FieldAsString(1)),
new XAttribute("Size", row.FieldAsString(2)));
if (!row.IsColumnNull(3))
{
var color = row.FieldAsInteger(3);
xTextStyle.SetAttributeValue("Red", color & 0xFF);
xTextStyle.SetAttributeValue("Green", (color & 0xFF00) >> 8);
xTextStyle.SetAttributeValue("Blue", (color & 0xFF0000) >> 16);
}
if (!row.IsColumnNull(4))
{
var styleBits = row.FieldAsInteger(4);
if (WindowsInstallerConstants.MsidbTextStyleStyleBitsBold == (styleBits & WindowsInstallerConstants.MsidbTextStyleStyleBitsBold))
{
xTextStyle.SetAttributeValue("Bold", "yes");
}
if (WindowsInstallerConstants.MsidbTextStyleStyleBitsItalic == (styleBits & WindowsInstallerConstants.MsidbTextStyleStyleBitsItalic))
{
xTextStyle.SetAttributeValue("Italic", "yes");
}
if (WindowsInstallerConstants.MsidbTextStyleStyleBitsUnderline == (styleBits & WindowsInstallerConstants.MsidbTextStyleStyleBitsUnderline))
{
xTextStyle.SetAttributeValue("Underline", "yes");
}
if (WindowsInstallerConstants.MsidbTextStyleStyleBitsStrike == (styleBits & WindowsInstallerConstants.MsidbTextStyleStyleBitsStrike))
{
xTextStyle.SetAttributeValue("Strike", "yes");
}
}
this.UIElement.Add(xTextStyle);
}
}
///
/// Decompile the TypeLib table.
///
/// The table to decompile.
private void DecompileTypeLibTable(Table table)
{
foreach (var row in table.Rows)
{
var id = row.FieldAsString(0);
var xTypeLib = new XElement(Names.TypeLibElement,
new XAttribute("Advertise", "yes"),
new XAttribute("Id", id),
new XAttribute("Language", row.FieldAsInteger(1)),
XAttributeIfNotNull("Description", row, 4),
XAttributeIfNotNull("HelpDirectory", row, 5));
if (!row.IsColumnNull(3))
{
var version = row.FieldAsInteger(3);
if (65536 == version)
{
this.Messaging.Write(WarningMessages.PossiblyIncorrectTypelibVersion(row.SourceLineNumbers, id));
}
xTypeLib.SetAttributeValue("MajorVersion", (version & 0xFFFF00) >> 8);
xTypeLib.SetAttributeValue("MinorVersion", version & 0xFF);
}
if (!row.IsColumnNull(7))
{
xTypeLib.SetAttributeValue("Cost", row.FieldAsInteger(7));
}
// nested under the appropriate File element in FinalizeFileTable
this.IndexElement(row, xTypeLib);
}
}
///
/// Decompile the Upgrade table.
///
/// The table to decompile.
private void DecompileUpgradeTable(Table table)
{
var xUpgrades = new Dictionary();
foreach (UpgradeRow upgradeRow in table.Rows)
{
if (WixUpgradeConstants.UpgradeDetectedProperty == upgradeRow.ActionProperty || WixUpgradeConstants.DowngradeDetectedProperty == upgradeRow.ActionProperty)
{
continue; // MajorUpgrade rows processed in FinalizeUpgradeTable
}
if (!xUpgrades.TryGetValue(upgradeRow.UpgradeCode, out var xUpgrade))
{
xUpgrade = new XElement(Names.UpgradeElement,
new XAttribute("Id", upgradeRow.UpgradeCode));
this.RootElement.Add(xUpgrade);
xUpgrades.Add(upgradeRow.UpgradeCode, xUpgrade);
}
var xUpgradeVersion = new XElement(Names.UpgradeVersionElement,
new XAttribute("Id", upgradeRow.UpgradeCode),
new XAttribute("Property", upgradeRow.ActionProperty));
if (null != upgradeRow.VersionMin)
{
xUpgradeVersion.SetAttributeValue("Minimum", upgradeRow.VersionMin);
}
if (null != upgradeRow.VersionMax)
{
xUpgradeVersion.SetAttributeValue("Maximum", upgradeRow.VersionMax);
}
if (null != upgradeRow.Language)
{
xUpgradeVersion.SetAttributeValue("Language", upgradeRow.Language);
}
if (WindowsInstallerConstants.MsidbUpgradeAttributesMigrateFeatures == (upgradeRow.Attributes & WindowsInstallerConstants.MsidbUpgradeAttributesMigrateFeatures))
{
xUpgradeVersion.SetAttributeValue("MigrateFeatures", "yes");
}
if (WindowsInstallerConstants.MsidbUpgradeAttributesOnlyDetect == (upgradeRow.Attributes & WindowsInstallerConstants.MsidbUpgradeAttributesOnlyDetect))
{
xUpgradeVersion.SetAttributeValue("OnlyDetect", "yes");
}
if (WindowsInstallerConstants.MsidbUpgradeAttributesIgnoreRemoveFailure == (upgradeRow.Attributes & WindowsInstallerConstants.MsidbUpgradeAttributesIgnoreRemoveFailure))
{
xUpgradeVersion.SetAttributeValue("IgnoreRemoveFailure", "yes");
}
if (WindowsInstallerConstants.MsidbUpgradeAttributesVersionMinInclusive == (upgradeRow.Attributes & WindowsInstallerConstants.MsidbUpgradeAttributesVersionMinInclusive))
{
xUpgradeVersion.SetAttributeValue("IncludeMinimum", "yes");
}
if (WindowsInstallerConstants.MsidbUpgradeAttributesVersionMaxInclusive == (upgradeRow.Attributes & WindowsInstallerConstants.MsidbUpgradeAttributesVersionMaxInclusive))
{
xUpgradeVersion.SetAttributeValue("IncludeMaximum", "yes");
}
if (WindowsInstallerConstants.MsidbUpgradeAttributesLanguagesExclusive == (upgradeRow.Attributes & WindowsInstallerConstants.MsidbUpgradeAttributesLanguagesExclusive))
{
xUpgradeVersion.SetAttributeValue("ExcludeLanguages", "yes");
}
if (null != upgradeRow.Remove)
{
xUpgradeVersion.SetAttributeValue("RemoveFeatures", upgradeRow.Remove);
}
xUpgrade.Add(xUpgradeVersion);
}
}
///
/// Decompile the UpgradedFiles_OptionalData table.
///
/// The table to decompile.
private void DecompileUpgradedFiles_OptionalDataTable(Table table)
{
foreach (var row in table.Rows)
{
var xUpgradeFile = new XElement(Names.UpgradeFileElement,
new XAttribute("File", row.FieldAsString(1)),
new XAttribute("Ignore", "no"));
AddSymbolPaths(row, 2, xUpgradeFile);
if (!row.IsColumnNull(3) && 1 == row.FieldAsInteger(3))
{
xUpgradeFile.SetAttributeValue("AllowIgnoreOnError", "yes");
}
if (!row.IsColumnNull(4) && 0 != row.FieldAsInteger(4))
{
xUpgradeFile.SetAttributeValue("WholeFile", "yes");
}
this.AddChildToParent("UpgradedImages", xUpgradeFile, row, 0);
}
}
///
/// Decompile the UpgradedFilesToIgnore table.
///
/// The table to decompile.
private void DecompileUpgradedFilesToIgnoreTable(Table table)
{
foreach (var row in table.Rows)
{
if ("*" != row.FieldAsString(0))
{
var xUpgradeFile = new XElement(Names.UpgradeFileElement,
new XAttribute("File", row.FieldAsString(1)),
new XAttribute("Ignore", "yes"));
this.AddChildToParent("UpgradedImages", xUpgradeFile, row, 0);
}
else
{
this.Messaging.Write(WarningMessages.UnrepresentableColumnValue(row.SourceLineNumbers, table.Name, row.Fields[0].Column.Name, row[0]));
}
}
}
///
/// Decompile the UpgradedImages table.
///
/// The table to decompile.
private void DecompileUpgradedImagesTable(Table table)
{
foreach (var row in table.Rows)
{
var xUpgradeImage = new XElement(Names.UpgradeImageElement,
new XAttribute("Id", row.FieldAsString(0)),
new XAttribute("SourceFile", row.FieldAsString(1)),
XAttributeIfNotNull("SourcePatch", row, 2));
AddSymbolPaths(row, 3, xUpgradeImage);
this.AddChildToParent("ImageFamilies", xUpgradeImage, row, 4);
this.IndexElement(row, xUpgradeImage);
}
}
private static void AddSymbolPaths(Row row, int column, XElement xParent)
{
if (!row.IsColumnNull(column))
{
var symbolPaths = row.FieldAsString(column).Split(';');
foreach (var symbolPath in symbolPaths)
{
var xSymbolPath = new XElement(Names.SymbolPathElement,
new XAttribute("Path", symbolPath));
xParent.Add(xSymbolPath);
}
}
}
///
/// Decompile the UIText table.
///
/// The table to decompile.
private void DecompileUITextTable(Table table)
{
foreach (var row in table.Rows)
{
var xUiText = new XElement(Names.UITextElement,
new XAttribute("Id", row.FieldAsString(0)),
new XAttribute("Value", row.FieldAsString(1)));
this.UIElement.Add(xUiText);
}
}
///
/// Decompile the Verb table.
///
/// The table to decompile.
private void DecompileVerbTable(Table table)
{
foreach (var row in table.Rows)
{
var verb = new XElement(Names.VerbElement,
new XAttribute("Id", row.FieldAsString(1)),
XAttributeIfNotNull("Sequence", row, 2),
XAttributeIfNotNull("Command", row, 3),
XAttributeIfNotNull("Argument", row, 4));
this.IndexElement(row, verb);
}
}
///
/// Gets the RegistryRootType from an integer representation of the root.
///
/// The source line information for the root.
/// The name of the table containing the field.
/// The field containing the root value.
/// The strongly-typed representation of the root.
/// true if the value could be converted; false otherwise.
private bool GetRegistryRootType(SourceLineNumber sourceLineNumbers, string tableName, Field field, out string registryRootType)
{
switch (Convert.ToInt32(field.Data))
{
case (-1):
registryRootType = "HKMU";
return true;
case WindowsInstallerConstants.MsidbRegistryRootClassesRoot:
registryRootType = "HKCR";
return true;
case WindowsInstallerConstants.MsidbRegistryRootCurrentUser:
registryRootType = "HKCU";
return true;
case WindowsInstallerConstants.MsidbRegistryRootLocalMachine:
registryRootType = "HKLM";
return true;
case WindowsInstallerConstants.MsidbRegistryRootUsers:
registryRootType = "HKU";
return true;
default:
this.Messaging.Write(WarningMessages.IllegalColumnValue(sourceLineNumbers, tableName, field.Column.Name, field.Data));
registryRootType = null; // assign anything to satisfy the out parameter
return false;
}
}
///
/// Set the primary feature for a component.
///
/// The row which specifies a primary feature.
/// The index of the column contaning the feature identifier.
/// The index of the column containing the component identifier.
private void SetPrimaryFeature(Row row, int featureColumnIndex, int componentColumnIndex)
{
// only products contain primary features
if (OutputType.Product == this.OutputType)
{
var featureField = row.Fields[featureColumnIndex];
var componentField = row.Fields[componentColumnIndex];
if (this.TryGetIndexedElement("FeatureComponents", out var xComponentRef, Convert.ToString(featureField.Data), Convert.ToString(componentField.Data)))
{
xComponentRef.SetAttributeValue("Primary", "yes");
}
else
{
this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, row.TableDefinition.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), featureField.Column.Name, Convert.ToString(featureField.Data), componentField.Column.Name, Convert.ToString(componentField.Data), "FeatureComponents"));
}
}
}
///
/// Checks the InstallExecuteSequence table to determine where RemoveExistingProducts is scheduled and removes it.
///
/// The collection of all tables.
private static string DetermineMajorUpgradeScheduling(TableIndexedCollection tables)
{
var sequenceRemoveExistingProducts = 0;
var sequenceInstallValidate = 0;
var sequenceInstallInitialize = 0;
var sequenceInstallFinalize = 0;
var sequenceInstallExecute = 0;
var sequenceInstallExecuteAgain = 0;
var installExecuteSequenceTable = tables["InstallExecuteSequence"];
if (null != installExecuteSequenceTable)
{
var removeExistingProductsRow = -1;
for (var i = 0; i < installExecuteSequenceTable.Rows.Count; i++)
{
var row = installExecuteSequenceTable.Rows[i];
var action = row.FieldAsString(0);
var sequence = row.FieldAsInteger(2);
switch (action)
{
case "RemoveExistingProducts":
sequenceRemoveExistingProducts = sequence;
removeExistingProductsRow = i;
break;
case "InstallValidate":
sequenceInstallValidate = sequence;
break;
case "InstallInitialize":
sequenceInstallInitialize = sequence;
break;
case "InstallExecute":
sequenceInstallExecute = sequence;
break;
case "InstallExecuteAgain":
sequenceInstallExecuteAgain = sequence;
break;
case "InstallFinalize":
sequenceInstallFinalize = sequence;
break;
}
}
installExecuteSequenceTable.Rows.RemoveAt(removeExistingProductsRow);
}
if (0 != sequenceInstallValidate && sequenceInstallValidate < sequenceRemoveExistingProducts && sequenceRemoveExistingProducts < sequenceInstallInitialize)
{
return "afterInstallValidate";
}
else if (0 != sequenceInstallInitialize && sequenceInstallInitialize < sequenceRemoveExistingProducts && sequenceRemoveExistingProducts < sequenceInstallExecute)
{
return "afterInstallInitialize";
}
else if (0 != sequenceInstallExecute && sequenceInstallExecute < sequenceRemoveExistingProducts && sequenceRemoveExistingProducts < sequenceInstallExecuteAgain)
{
return "afterInstallExecute";
}
else if (0 != sequenceInstallExecuteAgain && sequenceInstallExecuteAgain < sequenceRemoveExistingProducts && sequenceRemoveExistingProducts < sequenceInstallFinalize)
{
return "afterInstallExecuteAgain";
}
else
{
return "afterInstallFinalize";
}
}
}
}