From 49f1209035aac1fcfad5dbbe25f7b2306d3be86c Mon Sep 17 00:00:00 2001 From: Rob Mensching Date: Thu, 7 Dec 2017 14:19:05 -0800 Subject: Support MSI backends creating custom tables and remove WixToolset.Data.WindowsInstaller --- .../Bind/BindDatabaseCommand.cs | 18 +- .../Bind/BindTransformCommand.cs | 1 + .../Bind/CopyTransformDataCommand.cs | 9 +- .../Bind/CreateCabinetsCommand.cs | 3 +- .../Bind/CreateIdtFileCommand.cs | 228 +++ .../Bind/CreateOutputFromIRCommand.cs | 25 +- .../Bind/GenerateDatabaseCommand.cs | 55 +- .../Bind/MergeModulesCommand.cs | 7 +- .../Bind/ModularaizeCommand.cs | 238 +++ .../Bind/UpdateControlTextCommand.cs | 3 +- .../Bind/UpdateFileFacadesCommand.cs | 1 + .../Bind/UpdateMediaSequencesCommand.cs | 4 +- .../Data/actions.xml | 76 + .../Data/tables.xml | 1962 ++++++++++++++++++++ src/WixToolset.Core.WindowsInstaller/Differ.cs | 6 +- .../Inscribe/InscribeMsiPackageCommand.cs | 12 +- .../Msi/Database.cs | 65 +- .../Msi/WixInvalidIdtException.cs | 33 + src/WixToolset.Core.WindowsInstaller/MsiBackend.cs | 18 +- src/WixToolset.Core.WindowsInstaller/MsmBackend.cs | 18 +- src/WixToolset.Core.WindowsInstaller/Patch.cs | 7 +- .../RowDictionary.cs | 84 + .../Rows/WixActionRowCollection.cs | 328 ++++ .../Unbind/ExtractCabinetsCommand.cs | 3 +- .../Unbind/UnbindDatabaseCommand.cs | 31 +- .../Unbind/UnbindTranformCommand.cs | 1 + src/WixToolset.Core.WindowsInstaller/Validator.cs | 9 +- .../ValidatorExtension.cs | 1 + .../WindowsInstallerStandardInternal.cs | 64 + .../WixToolset.Core.WindowsInstaller.csproj | 7 +- 30 files changed, 3191 insertions(+), 126 deletions(-) create mode 100644 src/WixToolset.Core.WindowsInstaller/Bind/CreateIdtFileCommand.cs create mode 100644 src/WixToolset.Core.WindowsInstaller/Bind/ModularaizeCommand.cs create mode 100644 src/WixToolset.Core.WindowsInstaller/Data/actions.xml create mode 100644 src/WixToolset.Core.WindowsInstaller/Data/tables.xml create mode 100644 src/WixToolset.Core.WindowsInstaller/Msi/WixInvalidIdtException.cs create mode 100644 src/WixToolset.Core.WindowsInstaller/RowDictionary.cs create mode 100644 src/WixToolset.Core.WindowsInstaller/Rows/WixActionRowCollection.cs create mode 100644 src/WixToolset.Core.WindowsInstaller/WindowsInstallerStandardInternal.cs (limited to 'src/WixToolset.Core.WindowsInstaller') diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs index 012998e6..9e30aed2 100644 --- a/src/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs +++ b/src/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs @@ -10,6 +10,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind using WixToolset.Data; using WixToolset.Data.Bind; using WixToolset.Data.Tuples; + using WixToolset.Data.WindowsInstaller; using WixToolset.Extensibility; using WixToolset.Extensibility.Services; @@ -21,7 +22,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind // As outlined in RFC 4122, this is our namespace for generating name-based (version 3) UUIDs. internal static readonly Guid WixComponentGuidNamespace = new Guid("{3064E5C6-FB63-4FE9-AC49-E446A792EFA5}"); - public BindDatabaseCommand(IBindContext context, Validator validator) + public BindDatabaseCommand(IBindContext context, IEnumerable backendExtension, Validator validator) { this.TableDefinitions = WindowsInstallerStandardInternal.GetTableDefinitions(); @@ -40,7 +41,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind this.Validator = validator; this.WixVariableResolver = context.WixVariableResolver; - this.BackendExtensions = context.ExtensionManager.Create(); + this.BackendExtensions = backendExtension; } private IEnumerable BindPaths { get; } @@ -298,7 +299,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind // Try to put as much above here as possible, updating the IR is better. Output output; { - var command = new CreateOutputFromIRCommand(section, this.TableDefinitions); + var command = new CreateOutputFromIRCommand(section, this.TableDefinitions, this.BackendExtensions); command.Execute(); output = command.Output; @@ -313,13 +314,8 @@ namespace WixToolset.Core.WindowsInstaller.Bind // Modularize identifiers and add tables with real streams to the import tables. if (OutputType.Module == output.Type) { - // Gather all the suppress modularization identifiers - var suppressModularizationIdentifiers = new HashSet(section.Tuples.OfType().Select(s => s.WixSuppressModularization)); - - foreach (var table in output.Tables) - { - table.Modularize(modularizationGuid, suppressModularizationIdentifiers); - } + var command = new ModularaizeCommand(output, modularizationGuid, section.Tuples.OfType()); + command.Execute(); } #if TODO_FINISH_UPDATE @@ -897,7 +893,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind { Dictionary componentGuidConditions = new Dictionary(componentTable.Rows.Count); - foreach (Data.Rows.ComponentRow row in componentTable.Rows) + foreach (Data.WindowsInstaller.Rows.ComponentRow row in componentTable.Rows) { // we don't care about unmanaged components and if there's a * GUID remaining, // there's already an error that prevented it from being replaced with a real GUID. diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/BindTransformCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/BindTransformCommand.cs index b4027834..49440cea 100644 --- a/src/WixToolset.Core.WindowsInstaller/Bind/BindTransformCommand.cs +++ b/src/WixToolset.Core.WindowsInstaller/Bind/BindTransformCommand.cs @@ -10,6 +10,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind using WixToolset.Extensibility; using WixToolset.Msi; using WixToolset.Core.Native; + using WixToolset.Data.WindowsInstaller; internal class BindTransformCommand { diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/CopyTransformDataCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/CopyTransformDataCommand.cs index 0dcddb99..559d440c 100644 --- a/src/WixToolset.Core.WindowsInstaller/Bind/CopyTransformDataCommand.cs +++ b/src/WixToolset.Core.WindowsInstaller/Bind/CopyTransformDataCommand.cs @@ -5,12 +5,13 @@ namespace WixToolset.Core.WindowsInstaller.Bind using System; using System.Collections.Generic; using System.Diagnostics; - using WixToolset.Data; - using WixToolset.Data.Rows; - using WixToolset.Extensibility; - using WixToolset.Core.Native; using WixToolset.Core.Bind; + using WixToolset.Core.Native; + using WixToolset.Data; using WixToolset.Data.Tuples; + using WixToolset.Data.WindowsInstaller; + using WixToolset.Data.WindowsInstaller.Rows; + using WixToolset.Extensibility; internal class CopyTransformDataCommand { diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/CreateCabinetsCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/CreateCabinetsCommand.cs index a449397d..0aa50bd9 100644 --- a/src/WixToolset.Core.WindowsInstaller/Bind/CreateCabinetsCommand.cs +++ b/src/WixToolset.Core.WindowsInstaller/Bind/CreateCabinetsCommand.cs @@ -12,8 +12,9 @@ namespace WixToolset.Core.WindowsInstaller.Bind using WixToolset.Core.Bind; using WixToolset.Data; using WixToolset.Data.Bind; - using WixToolset.Data.Rows; using WixToolset.Data.Tuples; + using WixToolset.Data.WindowsInstaller; + using WixToolset.Data.WindowsInstaller.Rows; using WixToolset.Extensibility; /// diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/CreateIdtFileCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/CreateIdtFileCommand.cs new file mode 100644 index 00000000..1fc7d068 --- /dev/null +++ b/src/WixToolset.Core.WindowsInstaller/Bind/CreateIdtFileCommand.cs @@ -0,0 +1,228 @@ +// 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.Bind +{ + using System; + using System.Globalization; + using System.IO; + using System.Text; + using WixToolset.Data; + using WixToolset.Data.WindowsInstaller; + + internal class CreateIdtFileCommand + { + public CreateIdtFileCommand(Table table, int codepage, string intermediateFolder, bool keepAddedColumns) + { + this.Table = table; + this.Codepage = codepage; + this.IntermediateFolder = intermediateFolder; + this.KeepAddedColumns = keepAddedColumns; + } + + private Table Table { get; } + + private int Codepage { get; set; } + + private string IntermediateFolder { get; } + + private bool KeepAddedColumns { get; } + + public string IdtPath { get; private set; } + + public void Execute() + { + // write out the table to an IDT file + Encoding encoding; + + // If UTF8 encoding, use the UTF8-specific constructor to avoid writing + // the byte order mark at the beginning of the file + if (this.Codepage == Encoding.UTF8.CodePage) + { + encoding = new UTF8Encoding(false, true); + } + else + { + if (this.Codepage == 0) + { + this.Codepage = Encoding.ASCII.CodePage; + } + + encoding = Encoding.GetEncoding(this.Codepage, new EncoderExceptionFallback(), new DecoderExceptionFallback()); + } + + this.IdtPath = Path.Combine(this.IntermediateFolder, String.Concat(this.Table.Name, ".idt")); + + using (var idtWriter = new StreamWriter(this.IdtPath, false, encoding)) + { + this.TableToIdtDefinition(this.Table, idtWriter, this.KeepAddedColumns); + } + } + + private void TableToIdtDefinition(Table table, StreamWriter writer, bool keepAddedColumns) + { + if (table.Definition.Unreal) + { + return; + } + + if (TableDefinition.MaxColumnsInRealTable < table.Definition.Columns.Count) + { + throw new WixException(WixDataErrors.TooManyColumnsInRealTable(table.Definition.Name, table.Definition.Columns.Count, TableDefinition.MaxColumnsInRealTable)); + } + + // Tack on the table header, and flush before we start writing bytes directly to the stream. + var header = this.TableDefinitionToIdtDefinition(table.Definition, keepAddedColumns); + writer.Write(header); + writer.Flush(); + + using (var binary = new BinaryWriter(writer.BaseStream, writer.Encoding, true)) + { + // Create an encoding that replaces characters with question marks, and doesn't throw. We'll + // use this in case of errors + Encoding convertEncoding = Encoding.GetEncoding(writer.Encoding.CodePage); + + foreach (Row row in table.Rows) + { + if (row.Redundant) + { + continue; + } + + string rowString = this.RowToIdtDefinition(row, keepAddedColumns); + byte[] rowBytes; + + try + { + // GetBytes will throw an exception if any character doesn't match our current encoding + rowBytes = writer.Encoding.GetBytes(rowString); + } + catch (EncoderFallbackException) + { + Messaging.Instance.OnMessage(WixDataErrors.InvalidStringForCodepage(row.SourceLineNumbers, Convert.ToString(writer.Encoding.WindowsCodePage, CultureInfo.InvariantCulture))); + + rowBytes = convertEncoding.GetBytes(rowString); + } + + binary.Write(rowBytes, 0, rowBytes.Length); + } + } + } + + private string TableDefinitionToIdtDefinition(TableDefinition definition, bool keepAddedColumns) + { + var first = true; + var columnString = new StringBuilder(); + var dataString = new StringBuilder(); + var tableString = new StringBuilder(); + + tableString.Append(definition.Name); + foreach (var column in definition.Columns) + { + // conditionally keep columns added in a transform; otherwise, + // break because columns can only be added at the end + if (column.Added && !keepAddedColumns) + { + break; + } + + if (!first) + { + columnString.Append('\t'); + dataString.Append('\t'); + } + + columnString.Append(column.Name); + dataString.Append(ColumnIdtType(column)); + + if (column.PrimaryKey) + { + tableString.AppendFormat("\t{0}", column.Name); + } + + first = false; + } + columnString.Append("\r\n"); + columnString.Append(dataString); + columnString.Append("\r\n"); + columnString.Append(tableString); + columnString.Append("\r\n"); + + return columnString.ToString(); + } + + private string RowToIdtDefinition(Row row, bool keepAddedColumns) + { + var first = true; + var sb = new StringBuilder(); + + foreach (var field in row.Fields) + { + // Conditionally keep columns added in a transform; otherwise, + // break because columns can only be added at the end. + if (field.Column.Added && !keepAddedColumns) + { + break; + } + + if (first) + { + first = false; + } + else + { + sb.Append('\t'); + } + + sb.Append(this.FieldToIdtValue(field)); + } + sb.Append("\r\n"); + + return sb.ToString(); + } + + private string FieldToIdtValue(Field field) + { + var data = field.AsString(); + + if (String.IsNullOrEmpty(data)) + { + return data; + } + + // Special field value idt-specific escaping. + return data.Replace('\t', '\x10') + .Replace('\r', '\x11') + .Replace('\n', '\x19'); + } + + + /// + /// Gets the type of the column in IDT format. + /// + /// IDT format for column type. + private static string ColumnIdtType(ColumnDefinition column) + { + char typeCharacter; + switch (column.Type) + { + case ColumnType.Number: + typeCharacter = column.Nullable ? 'I' : 'i'; + break; + case ColumnType.Preserved: + case ColumnType.String: + typeCharacter = column.Nullable ? 'S' : 's'; + break; + case ColumnType.Localized: + typeCharacter = column.Nullable ? 'L' : 'l'; + break; + case ColumnType.Object: + typeCharacter = column.Nullable ? 'V' : 'v'; + break; + default: + throw new InvalidOperationException(String.Format(CultureInfo.CurrentUICulture, WixDataStrings.EXP_UnknownColumnType, column.Type)); + } + + return String.Concat(typeCharacter, column.Length); + } + } +} diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/CreateOutputFromIRCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/CreateOutputFromIRCommand.cs index a19a53f1..4e053c12 100644 --- a/src/WixToolset.Core.WindowsInstaller/Bind/CreateOutputFromIRCommand.cs +++ b/src/WixToolset.Core.WindowsInstaller/Bind/CreateOutputFromIRCommand.cs @@ -3,20 +3,26 @@ namespace WixToolset.Core.WindowsInstaller.Bind { using System; + using System.Collections.Generic; using System.Linq; using WixToolset.Core.Native; using WixToolset.Data; - using WixToolset.Data.Rows; using WixToolset.Data.Tuples; + using WixToolset.Data.WindowsInstaller; + using WixToolset.Data.WindowsInstaller.Rows; + using WixToolset.Extensibility; internal class CreateOutputFromIRCommand { - public CreateOutputFromIRCommand(IntermediateSection section, TableDefinitionCollection tableDefinitions) + public CreateOutputFromIRCommand(IntermediateSection section, TableDefinitionCollection tableDefinitions, IEnumerable backendExtensions) { this.Section = section; this.TableDefinitions = tableDefinitions; + this.BackendExtensions = backendExtensions; } + private IEnumerable BackendExtensions { get; } + private TableDefinitionCollection TableDefinitions { get; } private IntermediateSection Section { get; } @@ -60,6 +66,10 @@ namespace WixToolset.Core.WindowsInstaller.Bind // Ignored. break; + case TupleDefinitionType.MustBeFromAnExtension: + this.AddTupleFromExtension(tuple, output); + break; + default: this.AddTupleDefaultly(tuple, output); break; @@ -206,6 +216,17 @@ namespace WixToolset.Core.WindowsInstaller.Bind } } + private void AddTupleFromExtension(IntermediateTuple tuple, Output output) + { + foreach (var extension in this.BackendExtensions) + { + if (extension.TryAddTupleToOutput(tuple, output)) + { + break; + } + } + } + private void AddTupleDefaultly(IntermediateTuple tuple, Output output) { if (!this.TableDefinitions.TryGet(tuple.Definition.Name, out var tableDefinition)) diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/GenerateDatabaseCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/GenerateDatabaseCommand.cs index a3d3ecf7..e4e66559 100644 --- a/src/WixToolset.Core.WindowsInstaller/Bind/GenerateDatabaseCommand.cs +++ b/src/WixToolset.Core.WindowsInstaller/Bind/GenerateDatabaseCommand.cs @@ -12,6 +12,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind using WixToolset.Extensibility; using WixToolset.Msi; using WixToolset.Core.Native; + using WixToolset.Data.WindowsInstaller; internal class GenerateDatabaseCommand { @@ -44,14 +45,56 @@ namespace WixToolset.Core.WindowsInstaller.Bind // Add the _Validation rows. if (!this.SuppressAddingValidationRows) { - Table validationTable = this.Output.EnsureTable(this.TableDefinitions["_Validation"]); + var validationTable = this.Output.EnsureTable(this.TableDefinitions["_Validation"]); - foreach (Table table in this.Output.Tables) + foreach (var table in this.Output.Tables) { if (!table.Definition.Unreal) { // Add the validation rows for this table. - table.Definition.AddValidationRows(validationTable); + foreach (ColumnDefinition columnDef in table.Definition.Columns) + { + var row = validationTable.CreateRow(null); + + row[0] = table.Name; + + row[1] = columnDef.Name; + + if (columnDef.Nullable) + { + row[2] = "Y"; + } + else + { + row[2] = "N"; + } + + if (columnDef.MinValue.HasValue) + { + row[3] = columnDef.MinValue.Value; + } + + if (columnDef.MaxValue.HasValue) + { + row[4] = columnDef.MaxValue.Value; + } + + row[5] = columnDef.KeyTable; + + if (columnDef.KeyColumn.HasValue) + { + row[6] = columnDef.KeyColumn.Value; + } + + if (ColumnCategory.Unknown != columnDef.Category) + { + row[7] = columnDef.Category.ToString(); + } + + row[8] = columnDef.Possibilities; + + row[9] = columnDef.Description; + } } } } @@ -133,7 +176,11 @@ namespace WixToolset.Core.WindowsInstaller.Bind { try { - db.ImportTable(this.Output.Codepage, importTable, baseDirectory, this.KeepAddedColumns); + //db.ImportTable(this.Output.Codepage, importTable, baseDirectory, this.KeepAddedColumns); + var command = new CreateIdtFileCommand(importTable, this.Output.Codepage, baseDirectory, this.KeepAddedColumns); + command.Execute(); + + db.Import(command.IdtPath); } catch (WixInvalidIdtException) { diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/MergeModulesCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/MergeModulesCommand.cs index dcf67c05..32a05d93 100644 --- a/src/WixToolset.Core.WindowsInstaller/Bind/MergeModulesCommand.cs +++ b/src/WixToolset.Core.WindowsInstaller/Bind/MergeModulesCommand.cs @@ -8,12 +8,13 @@ namespace WixToolset.Core.WindowsInstaller.Bind using System.IO; using System.Runtime.InteropServices; using System.Text; + using WixToolset.Core.Bind; + using WixToolset.Core.Native; using WixToolset.Data; - using WixToolset.Data.Rows; + using WixToolset.Data.WindowsInstaller; + using WixToolset.Data.WindowsInstaller.Rows; using WixToolset.MergeMod; using WixToolset.Msi; - using WixToolset.Core.Native; - using WixToolset.Core.Bind; /// /// Update file information. diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/ModularaizeCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/ModularaizeCommand.cs new file mode 100644 index 00000000..03538fc3 --- /dev/null +++ b/src/WixToolset.Core.WindowsInstaller/Bind/ModularaizeCommand.cs @@ -0,0 +1,238 @@ +// 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.Bind +{ + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Globalization; + using System.Linq; + using System.Text; + using System.Text.RegularExpressions; + using WixToolset.Data; + using WixToolset.Data.Tuples; + using WixToolset.Data.WindowsInstaller; + + internal class ModularaizeCommand + { + public ModularaizeCommand(Output output, string modularizationGuid, IEnumerable suppressTuples) + { + this.Output = output; + this.ModularizationGuid = modularizationGuid; + + // Gather all the unique suppress modularization identifiers. + this.SuppressModularizationIdentifiers = new HashSet(suppressTuples.Select(s => s.WixSuppressModularization)); + } + + private Output Output { get; } + + private string ModularizationGuid { get; } + + private HashSet SuppressModularizationIdentifiers { get; } + + public void Execute() + { + foreach (var table in this.Output.Tables) + { + this.ModularizeTable(table); + } + } + + /// + /// Modularize the table. + /// + /// String containing the GUID of the Merge Module, if appropriate. + /// Optional collection of identifiers that should not be modularized. + public void ModularizeTable(Table table) + { + var modularizedColumns = new List(); + + // find the modularized columns + for (var i = 0; i < table.Definition.Columns.Count; ++i) + { + if (ColumnModularizeType.None != table.Definition.Columns[i].ModularizeType) + { + modularizedColumns.Add(i); + } + } + + if (0 < modularizedColumns.Count) + { + foreach (var row in table.Rows) + { + foreach (var modularizedColumn in modularizedColumns) + { + var field = row.Fields[modularizedColumn]; + + if (field.Data != null) + { + field.Data = this.ModularizedRowFieldValue(row, field); + } + } + } + } + } + + private string ModularizedRowFieldValue(Row row, Field field) + { + var fieldData = field.AsString(); + + if (!(WindowsInstallerStandard.IsStandardAction(fieldData) || WindowsInstallerStandard.IsStandardProperty(fieldData))) + { + ColumnModularizeType modularizeType = field.Column.ModularizeType; + + // special logic for the ControlEvent table's Argument column + // this column requires different modularization methods depending upon the value of the Event column + if (ColumnModularizeType.ControlEventArgument == field.Column.ModularizeType) + { + switch (row[2].ToString()) + { + case "CheckExistingTargetPath": // redirectable property name + case "CheckTargetPath": + case "DoAction": // custom action name + case "NewDialog": // dialog name + case "SelectionBrowse": + case "SetTargetPath": + case "SpawnDialog": + case "SpawnWaitDialog": + if (Common.IsIdentifier(fieldData)) + { + modularizeType = ColumnModularizeType.Column; + } + else + { + modularizeType = ColumnModularizeType.Property; + } + break; + default: // formatted + modularizeType = ColumnModularizeType.Property; + break; + } + } + else if (ColumnModularizeType.ControlText == field.Column.ModularizeType) + { + // icons are stored in the Binary table, so they get column-type modularization + if (("Bitmap" == row[2].ToString() || "Icon" == row[2].ToString()) && Common.IsIdentifier(fieldData)) + { + modularizeType = ColumnModularizeType.Column; + } + else + { + modularizeType = ColumnModularizeType.Property; + } + } + + switch (modularizeType) + { + case ColumnModularizeType.Column: + // ensure the value is an identifier (otherwise it shouldn't be modularized this way) + if (!Common.IsIdentifier(fieldData)) + { + throw new InvalidOperationException(String.Format(CultureInfo.CurrentUICulture, WixDataStrings.EXP_CannotModularizeIllegalID, fieldData)); + } + + // if we're not supposed to suppress modularization of this identifier + if (!this.SuppressModularizationIdentifiers.Contains(fieldData)) + { + fieldData = String.Concat(fieldData, ".", this.ModularizationGuid); + } + break; + + case ColumnModularizeType.Property: + case ColumnModularizeType.Condition: + Regex regex; + if (ColumnModularizeType.Property == modularizeType) + { + regex = new Regex(@"\[(?[#$!]?[a-zA-Z_][a-zA-Z0-9_\.]*)]", RegexOptions.Singleline | RegexOptions.ExplicitCapture); + } + else + { + Debug.Assert(ColumnModularizeType.Condition == modularizeType); + + // This heinous looking regular expression is actually quite an elegant way + // to shred the entire condition into the identifiers that need to be + // modularized. Let's break it down piece by piece: + // + // 1. Look for the operators: NOT, EQV, XOR, OR, AND, IMP (plus a space). Note that the + // regular expression is case insensitive so we don't have to worry about + // all the permutations of these strings. + // 2. Look for quoted strings. Quoted strings are just text and are ignored + // outright. + // 3. Look for environment variables. These look like identifiers we might + // otherwise be interested in but start with a percent sign. Like quoted + // strings these enviroment variable references are ignored outright. + // 4. Match all identifiers that are things that need to be modularized. Note + // the special characters (!, $, ?, &) that denote Component and Feature states. + regex = new Regex(@"NOT\s|EQV\s|XOR\s|OR\s|AND\s|IMP\s|"".*?""|%[a-zA-Z_][a-zA-Z0-9_\.]*|(?[!$\?&]?[a-zA-Z_][a-zA-Z0-9_\.]*)", RegexOptions.Singleline | RegexOptions.IgnoreCase | RegexOptions.ExplicitCapture); + + // less performant version of the above with captures showing where everything lives + // regex = new Regex(@"(?NOT|EQV|XOR|OR|AND|IMP)|(?"".*?"")|(?%[a-zA-Z_][a-zA-Z0-9_\.]*)|(?[!$\?&]?[a-zA-Z_][a-zA-Z0-9_\.]*)",RegexOptions.Singleline | RegexOptions.IgnoreCase | RegexOptions.ExplicitCapture); + } + + var matches = regex.Matches(fieldData); + + var sb = new StringBuilder(fieldData); + + // Notice how this code walks backward through the list + // because it modifies the string as we through it. + for (var i = matches.Count - 1; 0 <= i; i--) + { + var group = matches[i].Groups["identifier"]; + if (group.Success) + { + var identifier = group.Value; + if (!WindowsInstallerStandard.IsStandardProperty(identifier) && !this.SuppressModularizationIdentifiers.Contains(identifier)) + { + sb.Insert(group.Index + group.Length, '.'); + sb.Insert(group.Index + group.Length + 1, this.ModularizationGuid); + } + } + } + + fieldData = sb.ToString(); + break; + + case ColumnModularizeType.CompanionFile: + // if we're not supposed to ignore this identifier and the value does not start with + // a digit, we must have a companion file so modularize it + if (!this.SuppressModularizationIdentifiers.Contains(fieldData) && + 0 < fieldData.Length && !Char.IsDigit(fieldData, 0)) + { + fieldData = String.Concat(fieldData, ".", this.ModularizationGuid); + } + break; + + case ColumnModularizeType.Icon: + if (!this.SuppressModularizationIdentifiers.Contains(fieldData)) + { + var start = fieldData.LastIndexOf(".", StringComparison.Ordinal); + if (-1 == start) + { + fieldData = String.Concat(fieldData, ".", this.ModularizationGuid); + } + else + { + fieldData = String.Concat(fieldData.Substring(0, start), ".", this.ModularizationGuid, fieldData.Substring(start)); + } + } + break; + + case ColumnModularizeType.SemicolonDelimited: + var keys = fieldData.Split(';'); + for (var i = 0; i < keys.Length; ++i) + { + if (!String.IsNullOrEmpty(keys[i])) + { + keys[i] = String.Concat(keys[i], ".", this.ModularizationGuid); + } + } + + fieldData = String.Join(";", keys); + break; + } + } + + return fieldData; + } + } +} diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/UpdateControlTextCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/UpdateControlTextCommand.cs index 9579e0f8..dddc9380 100644 --- a/src/WixToolset.Core.WindowsInstaller/Bind/UpdateControlTextCommand.cs +++ b/src/WixToolset.Core.WindowsInstaller/Bind/UpdateControlTextCommand.cs @@ -5,7 +5,8 @@ namespace WixToolset.Core.WindowsInstaller.Bind using System; using System.IO; using WixToolset.Data; - using WixToolset.Data.Rows; + using WixToolset.Data.WindowsInstaller; + using WixToolset.Data.WindowsInstaller.Rows; internal class UpdateControlTextCommand { diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/UpdateFileFacadesCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/UpdateFileFacadesCommand.cs index a9eb2a8f..cf620e72 100644 --- a/src/WixToolset.Core.WindowsInstaller/Bind/UpdateFileFacadesCommand.cs +++ b/src/WixToolset.Core.WindowsInstaller/Bind/UpdateFileFacadesCommand.cs @@ -14,6 +14,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind using WixToolset.Core.Bind; using WixToolset.Data; using WixToolset.Data.Tuples; + using WixToolset.Data.WindowsInstaller; using WixToolset.Msi; /// diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/UpdateMediaSequencesCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/UpdateMediaSequencesCommand.cs index db74eda5..0767adb0 100644 --- a/src/WixToolset.Core.WindowsInstaller/Bind/UpdateMediaSequencesCommand.cs +++ b/src/WixToolset.Core.WindowsInstaller/Bind/UpdateMediaSequencesCommand.cs @@ -2,13 +2,13 @@ namespace WixToolset.Core.WindowsInstaller.Bind { - using System; using System.Collections.Generic; using System.Linq; using WixToolset.Core.Bind; using WixToolset.Data; - using WixToolset.Data.Rows; using WixToolset.Data.Tuples; + using WixToolset.Data.WindowsInstaller; + using WixToolset.Data.WindowsInstaller.Rows; internal class UpdateMediaSequencesCommand { diff --git a/src/WixToolset.Core.WindowsInstaller/Data/actions.xml b/src/WixToolset.Core.WindowsInstaller/Data/actions.xml new file mode 100644 index 00000000..f65b792d --- /dev/null +++ b/src/WixToolset.Core.WindowsInstaller/Data/actions.xml @@ -0,0 +1,76 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/WixToolset.Core.WindowsInstaller/Data/tables.xml b/src/WixToolset.Core.WindowsInstaller/Data/tables.xml new file mode 100644 index 00000000..e4b5e954 --- /dev/null +++ b/src/WixToolset.Core.WindowsInstaller/Data/tables.xml @@ -0,0 +1,1962 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/WixToolset.Core.WindowsInstaller/Differ.cs b/src/WixToolset.Core.WindowsInstaller/Differ.cs index bdd06d32..9bbde302 100644 --- a/src/WixToolset.Core.WindowsInstaller/Differ.cs +++ b/src/WixToolset.Core.WindowsInstaller/Differ.cs @@ -1,14 +1,14 @@ // 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 +namespace WixToolset.Core.WindowsInstaller { using System; using System.Collections; using System.Collections.Generic; using System.Globalization; - using WixToolset.Core; using WixToolset.Data; - using WixToolset.Data.Rows; + using WixToolset.Data.WindowsInstaller; + using WixToolset.Data.WindowsInstaller.Rows; using WixToolset.Extensibility; using WixToolset.Msi; diff --git a/src/WixToolset.Core.WindowsInstaller/Inscribe/InscribeMsiPackageCommand.cs b/src/WixToolset.Core.WindowsInstaller/Inscribe/InscribeMsiPackageCommand.cs index 58384325..5c56d9aa 100644 --- a/src/WixToolset.Core.WindowsInstaller/Inscribe/InscribeMsiPackageCommand.cs +++ b/src/WixToolset.Core.WindowsInstaller/Inscribe/InscribeMsiPackageCommand.cs @@ -9,7 +9,9 @@ namespace WixToolset.Core.WindowsInstaller.Inscribe using System.Runtime.InteropServices; using System.Security.Cryptography.X509Certificates; using WixToolset.Core.Native; + using WixToolset.Core.WindowsInstaller.Bind; using WixToolset.Data; + using WixToolset.Data.WindowsInstaller; using WixToolset.Extensibility; using WixToolset.Msi; @@ -250,13 +252,19 @@ namespace WixToolset.Core.WindowsInstaller.Inscribe if (digitalCertificateTable.Rows.Count > 0) { - database.ImportTable(codepage, digitalCertificateTable, this.Context.IntermediateFolder, true); + var command = new CreateIdtFileCommand(digitalCertificateTable, codepage, this.Context.IntermediateFolder, true); + command.Execute(); + + database.Import(command.IdtPath); shouldCommit = true; } if (digitalSignatureTable.Rows.Count > 0) { - database.ImportTable(codepage, digitalSignatureTable, this.Context.IntermediateFolder, true); + var command = new CreateIdtFileCommand(digitalSignatureTable, codepage, this.Context.IntermediateFolder, true); + command.Execute(); + + database.Import(command.IdtPath); shouldCommit = true; } diff --git a/src/WixToolset.Core.WindowsInstaller/Msi/Database.cs b/src/WixToolset.Core.WindowsInstaller/Msi/Database.cs index 801ebdde..ccb0e6cf 100644 --- a/src/WixToolset.Core.WindowsInstaller/Msi/Database.cs +++ b/src/WixToolset.Core.WindowsInstaller/Msi/Database.cs @@ -3,13 +3,11 @@ namespace WixToolset.Msi { using System; - using System.ComponentModel; using System.Globalization; using System.IO; - using System.Text; using System.Threading; - using WixToolset.Data; using WixToolset.Core.Native; + using WixToolset.Data; /// /// Wrapper class for managing MSI API database handles. @@ -25,8 +23,7 @@ namespace WixToolset.Msi /// Persist mode to use when opening the database. public Database(string path, OpenDatabase type) { - uint handle = 0; - int error = MsiInterop.MsiOpenDatabase(path, new IntPtr((int)type), out handle); + int error = MsiInterop.MsiOpenDatabase(path, new IntPtr((int)type), out var handle); if (0 != error) { throw new MsiException(error); @@ -153,9 +150,9 @@ namespace WixToolset.Msi /// Specifies the name of the exported table archive file. public void Export(string tableName, string folderPath, string fileName) { - if (null == folderPath || 0 == folderPath.Length) + if (String.IsNullOrEmpty(folderPath)) { - folderPath = System.Environment.CurrentDirectory; + folderPath = Environment.CurrentDirectory; } int error = MsiInterop.MsiDatabaseExport(this.Handle, tableName, folderPath, fileName); @@ -241,63 +238,13 @@ namespace WixToolset.Msi /// primary key columns for a specified table. public Record PrimaryKeys(string tableName) { - uint recordHandle; - int error = MsiInterop.MsiDatabaseGetPrimaryKeys(this.Handle, tableName, out recordHandle); - if (0 != error) + var error = MsiInterop.MsiDatabaseGetPrimaryKeys(this.Handle, tableName, out var recordHandle); + if (error != 0) { throw new MsiException(error); } return new Record(recordHandle); } - - /// - /// Imports a table into the database. - /// - /// Codepage of the database to import table to. - /// Table to import into database. - /// The base directory where intermediate files are created. - /// Whether to keep columns added in a transform. - public void ImportTable(int codepage, Table table, string baseDirectory, bool keepAddedColumns) - { - // write out the table to an IDT file - string idtPath = Path.Combine(baseDirectory, String.Concat(table.Name, ".idt")); - Encoding encoding; - - // If UTF8 encoding, use the UTF8-specific constructor to avoid writing - // the byte order mark at the beginning of the file - if (Encoding.UTF8.CodePage == codepage) - { - encoding = new UTF8Encoding(false, true); - } - else - { - if (0 == codepage) - { - codepage = Encoding.ASCII.CodePage; - } - - encoding = Encoding.GetEncoding(codepage, new EncoderExceptionFallback(), new DecoderExceptionFallback()); - } - - using (StreamWriter idtWriter = new StreamWriter(idtPath, false, encoding)) - { - table.ToIdtDefinition(idtWriter, keepAddedColumns); - } - - // try to import the table into the MSI - try - { - this.Import(idtPath); - } - catch (WixInvalidIdtException) - { - table.ValidateRows(); - - // If ValidateRows finds anything it doesn't like, it throws. Otherwise, we'll - // throw WixInvalidIdtException here which is caught in light and turns off tidy. - throw new WixInvalidIdtException(idtPath, table.Name); - } - } } } diff --git a/src/WixToolset.Core.WindowsInstaller/Msi/WixInvalidIdtException.cs b/src/WixToolset.Core.WindowsInstaller/Msi/WixInvalidIdtException.cs new file mode 100644 index 00000000..a603d5a7 --- /dev/null +++ b/src/WixToolset.Core.WindowsInstaller/Msi/WixInvalidIdtException.cs @@ -0,0 +1,33 @@ +// 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.Msi +{ + using System; + using WixToolset.Data; + + /// + /// WiX invalid idt exception. + /// + [Serializable] + public sealed class WixInvalidIdtException : WixException + { + /// + /// Instantiate a new WixInvalidIdtException. + /// + /// The invalid idt file. + public WixInvalidIdtException(string idtFile) : + base(WixDataErrors.InvalidIdt(new SourceLineNumber(idtFile), idtFile)) + { + } + + /// + /// Instantiate a new WixInvalidIdtException. + /// + /// The invalid idt file. + /// The table name of the invalid idt file. + public WixInvalidIdtException(string idtFile, string tableName) : + base(WixDataErrors.InvalidIdt(new SourceLineNumber(idtFile), idtFile, tableName)) + { + } + } +} diff --git a/src/WixToolset.Core.WindowsInstaller/MsiBackend.cs b/src/WixToolset.Core.WindowsInstaller/MsiBackend.cs index 2590c14f..4753677a 100644 --- a/src/WixToolset.Core.WindowsInstaller/MsiBackend.cs +++ b/src/WixToolset.Core.WindowsInstaller/MsiBackend.cs @@ -14,12 +14,26 @@ namespace WixToolset.Core.WindowsInstaller { public BindResult Bind(IBindContext context) { + var backendExtensions = context.ExtensionManager.Create(); + + foreach (var extension in backendExtensions) + { + extension.PreBackendBind(context); + } + var validator = Validator.CreateFromContext(context, "darice.cub"); - var command = new BindDatabaseCommand(context, validator); + var command = new BindDatabaseCommand(context, backendExtensions, validator); command.Execute(); - return new BindResult(command.FileTransfers, command.ContentFilePaths); + var result = new BindResult(command.FileTransfers, command.ContentFilePaths); + + foreach (var extension in backendExtensions) + { + extension.PostBackendBind(result); + } + + return result; } public bool Inscribe(IInscribeContext context) diff --git a/src/WixToolset.Core.WindowsInstaller/MsmBackend.cs b/src/WixToolset.Core.WindowsInstaller/MsmBackend.cs index e39eb883..2323f8dd 100644 --- a/src/WixToolset.Core.WindowsInstaller/MsmBackend.cs +++ b/src/WixToolset.Core.WindowsInstaller/MsmBackend.cs @@ -13,12 +13,26 @@ namespace WixToolset.Core.WindowsInstaller { public BindResult Bind(IBindContext context) { + var backendExtensions = context.ExtensionManager.Create(); + + foreach (var extension in backendExtensions) + { + extension.PreBackendBind(context); + } + var validator = Validator.CreateFromContext(context, "mergemod.cub"); - var command = new BindDatabaseCommand(context, validator); + var command = new BindDatabaseCommand(context, backendExtensions, validator); command.Execute(); - return new BindResult(command.FileTransfers, command.ContentFilePaths); + var result = new BindResult(command.FileTransfers, command.ContentFilePaths); + + foreach (var extension in backendExtensions) + { + extension.PostBackendBind(result); + } + + return result; } public bool Inscribe(IInscribeContext context) diff --git a/src/WixToolset.Core.WindowsInstaller/Patch.cs b/src/WixToolset.Core.WindowsInstaller/Patch.cs index 8e617fdb..24a54859 100644 --- a/src/WixToolset.Core.WindowsInstaller/Patch.cs +++ b/src/WixToolset.Core.WindowsInstaller/Patch.cs @@ -3,14 +3,11 @@ namespace WixToolset.Data { using System; - using System.Collections; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; - using System.Globalization; - using WixToolset.Data.Rows; + using WixToolset.Core.WindowsInstaller; + using WixToolset.Data.WindowsInstaller; using WixToolset.Extensibility; - using WixToolset.Core.Native; - using WixToolset.Msi; /// /// Contains output tables and logic for building an MSP package. diff --git a/src/WixToolset.Core.WindowsInstaller/RowDictionary.cs b/src/WixToolset.Core.WindowsInstaller/RowDictionary.cs new file mode 100644 index 00000000..101ebefd --- /dev/null +++ b/src/WixToolset.Core.WindowsInstaller/RowDictionary.cs @@ -0,0 +1,84 @@ +// 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 +{ + using System; + using System.Collections.Generic; + using WixToolset.Data.WindowsInstaller; + + /// + /// A dictionary of rows. Unlike the this + /// will throw when multiple rows with the same key are added. + /// + public sealed class RowDictionary : Dictionary where T : Row + { + /// + /// Creates an empty . + /// + public RowDictionary() + : base(StringComparer.InvariantCulture) + { + } + + /// + /// Creates and populates a with the rows from the given enumerator. + /// + /// Rows to add. + public RowDictionary(IEnumerable rows) + : this() + { + foreach (T row in rows) + { + this.Add(row); + } + } + + /// + /// Creates and populates a with the rows from the given . + /// + /// The table to index. + /// + /// Rows added to the index are not automatically added to the given . + /// + public RowDictionary(Table table) + : this() + { + if (null != table) + { + foreach (T row in table.Rows) + { + this.Add(row); + } + } + } + + /// + /// Adds a row to the dictionary using the row key. + /// + /// Row to add to the dictionary. + public void Add(T row) + { + this.Add(row.GetKey(), row); + } + + /// + /// Gets the row by integer key. + /// + /// Integer key to look up. + /// Row or null if key is not found. + public T Get(int key) + { + return this.Get(key.ToString()); + } + + /// + /// Gets the row by string key. + /// + /// String key to look up. + /// Row or null if key is not found. + public T Get(string key) + { + return this.TryGetValue(key, out var result) ? result : null; + } + } +} diff --git a/src/WixToolset.Core.WindowsInstaller/Rows/WixActionRowCollection.cs b/src/WixToolset.Core.WindowsInstaller/Rows/WixActionRowCollection.cs new file mode 100644 index 00000000..d72198ee --- /dev/null +++ b/src/WixToolset.Core.WindowsInstaller/Rows/WixActionRowCollection.cs @@ -0,0 +1,328 @@ +// 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.Rows +{ + using System; + using System.Collections; + using System.Diagnostics; + using System.Globalization; + using System.Xml; + using WixToolset.Data.Tuples; + using WixToolset.Data.WindowsInstaller.Rows; + + /// + /// A collection of action rows sorted by their sequence table and action name. + /// + internal sealed class WixActionRowCollection : ICollection + { + private SortedList collection; + + /// + /// Creates a new action table object. + /// + public WixActionRowCollection() + { + this.collection = new SortedList(); + } + + /// + /// Gets the number of items in the collection. + /// + /// Number of items in collection. + public int Count + { + get { return this.collection.Count; } + } + + /// + /// Gets if the collection has been synchronized. + /// + /// True if the collection has been synchronized. + public bool IsSynchronized + { + get { return this.collection.IsSynchronized; } + } + + /// + /// Gets the object used to synchronize the collection. + /// + /// Oject used the synchronize the collection. + public object SyncRoot + { + get { return this; } + } + + /// + /// Get an ActionRow by its sequence table and action name. + /// + /// The sequence table of the ActionRow. + /// The action name of the ActionRow. + public WixActionRow this[SequenceTable sequenceTable, string action] + { + get { return (WixActionRow)this.collection[GetKey(sequenceTable, action)]; } + } + + /// + /// Add an ActionRow to the collection. + /// + /// The ActionRow to add. + /// true to overwrite an existing ActionRow; false otherwise. + public void Add(WixActionRow actionRow, bool overwrite) + { + string key = GetKey(actionRow.SequenceTable, actionRow.Action); + + if (overwrite) + { + this.collection[key] = actionRow; + } + else + { + this.collection.Add(key, actionRow); + } + } + + /// + /// Add an ActionRow to the collection. + /// + /// The ActionRow to add. + public void Add(WixActionRow actionRow) + { + this.Add(actionRow, false); + } + + /// + /// Determines if the collection contains an ActionRow with a specific sequence table and name. + /// + /// The sequence table of the ActionRow. + /// The action name of the ActionRow. + /// true if the ActionRow was found; false otherwise. + public bool Contains(SequenceTable sequenceTable, string action) + { + return this.collection.Contains(GetKey(sequenceTable, action)); + } + + /// + /// Copies the collection into an array. + /// + /// Array to copy the collection into. + /// Index to start copying from. + public void CopyTo(System.Array array, int index) + { + this.collection.Values.CopyTo(array, index); + } + + /// + /// Gets the enumerator for the collection. + /// + /// The enumerator for the collection. + public IEnumerator GetEnumerator() + { + return this.collection.Values.GetEnumerator(); + } + + /// + /// Remove an ActionRow from the collection. + /// + /// The sequence table of the ActionRow. + /// The action name of the ActionRow. + public void Remove(SequenceTable sequenceTable, string action) + { + this.collection.Remove(GetKey(sequenceTable, action)); + } + + /// + /// Load an action table from an XmlReader. + /// + /// Reader to get data from. + /// The ActionRowCollection represented by the xml. + internal static WixActionRowCollection Load(XmlReader reader) + { + reader.MoveToContent(); + + return Parse(reader); + } + + /// + /// Creates a new action table object and populates it from an Xml reader. + /// + /// Reader to get data from. + /// The parsed ActionTable. + private static WixActionRowCollection Parse(XmlReader reader) + { + if (!reader.LocalName.Equals("actions")) + { + throw new XmlException(); + } + + WixActionRowCollection actionRows = new WixActionRowCollection(); + bool empty = reader.IsEmptyElement; + + while (reader.MoveToNextAttribute()) + { + } + + if (!empty) + { + bool done = false; + + // loop through all the fields in a row + while (!done && reader.Read()) + { + switch (reader.NodeType) + { + case XmlNodeType.Element: + switch (reader.LocalName) + { + case "action": + WixActionRow[] parsedActionRows = ParseActions(reader); + + foreach (WixActionRow actionRow in parsedActionRows) + { + actionRows.Add(actionRow); + } + break; + default: + throw new XmlException(); + } + break; + case XmlNodeType.EndElement: + done = true; + break; + } + } + + if (!done) + { + throw new XmlException(); + } + } + + return actionRows; + } + + /// + /// Get the key for storing an ActionRow. + /// + /// The sequence table of the ActionRow. + /// The action name of the ActionRow. + /// The string key. + private static string GetKey(SequenceTable sequenceTable, string action) + { + return GetKey(sequenceTable.ToString(), action); + } + + /// + /// Get the key for storing an ActionRow. + /// + /// The sequence table of the ActionRow. + /// The action name of the ActionRow. + /// The string key. + private static string GetKey(string sequenceTable, string action) + { + return String.Concat(sequenceTable, '/', action); + } + + /// + /// Parses ActionRows from the Xml reader. + /// + /// Xml reader that contains serialized ActionRows. + /// The parsed ActionRows. + internal static WixActionRow[] ParseActions(XmlReader reader) + { + Debug.Assert("action" == reader.LocalName); + + string id = null; + string condition = null; + bool empty = reader.IsEmptyElement; + int sequence = int.MinValue; + int sequenceCount = 0; + SequenceTable[] sequenceTables = new SequenceTable[Enum.GetValues(typeof(SequenceTable)).Length]; + + while (reader.MoveToNextAttribute()) + { + switch (reader.Name) + { + case "name": + id = reader.Value; + break; + case "AdminExecuteSequence": + if (reader.Value.Equals("yes")) + { + sequenceTables[sequenceCount] = SequenceTable.AdminExecuteSequence; + ++sequenceCount; + } + break; + case "AdminUISequence": + if (reader.Value.Equals("yes")) + { + sequenceTables[sequenceCount] = SequenceTable.AdminUISequence; + ++sequenceCount; + } + break; + case "AdvtExecuteSequence": + if (reader.Value.Equals("yes")) + { + sequenceTables[sequenceCount] = SequenceTable.AdvtExecuteSequence; + ++sequenceCount; + } + break; + case "condition": + condition = reader.Value; + break; + case "InstallExecuteSequence": + if (reader.Value.Equals("yes")) + { + sequenceTables[sequenceCount] = SequenceTable.InstallExecuteSequence; + ++sequenceCount; + } + break; + case "InstallUISequence": + if (reader.Value.Equals("yes")) + { + sequenceTables[sequenceCount] = SequenceTable.InstallUISequence; + ++sequenceCount; + } + break; + case "sequence": + sequence = Convert.ToInt32(reader.Value, CultureInfo.InvariantCulture); + break; + } + } + + if (null == id) + { + throw new XmlException(); + } + + if (int.MinValue == sequence) + { + throw new XmlException(); + } + else if (1 > sequence) + { + throw new XmlException(); + } + + if (0 == sequenceCount) + { + throw new XmlException(); + } + + if (!empty && reader.Read() && XmlNodeType.EndElement != reader.MoveToContent()) + { + throw new XmlException(); + } + + // create the actions + WixActionRow[] actionRows = new WixActionRow[sequenceCount]; + for (int i = 0; i < sequenceCount; i++) + { + //WixActionRow actionRow = new WixActionRow(sequenceTables[i], id, condition, sequence); + //actionRows[i] = actionRow; + throw new NotImplementedException(); + } + + return actionRows; + } + } +} diff --git a/src/WixToolset.Core.WindowsInstaller/Unbind/ExtractCabinetsCommand.cs b/src/WixToolset.Core.WindowsInstaller/Unbind/ExtractCabinetsCommand.cs index 7985c120..1757e06f 100644 --- a/src/WixToolset.Core.WindowsInstaller/Unbind/ExtractCabinetsCommand.cs +++ b/src/WixToolset.Core.WindowsInstaller/Unbind/ExtractCabinetsCommand.cs @@ -9,7 +9,8 @@ namespace WixToolset.Core.WindowsInstaller.Unbind using System.IO; using WixToolset.Core.Native; using WixToolset.Data; - using WixToolset.Data.Rows; + using WixToolset.Data.WindowsInstaller; + using WixToolset.Data.WindowsInstaller.Rows; using WixToolset.Msi; internal class ExtractCabinetsCommand diff --git a/src/WixToolset.Core.WindowsInstaller/Unbind/UnbindDatabaseCommand.cs b/src/WixToolset.Core.WindowsInstaller/Unbind/UnbindDatabaseCommand.cs index 9cd7b775..72e0c3c8 100644 --- a/src/WixToolset.Core.WindowsInstaller/Unbind/UnbindDatabaseCommand.cs +++ b/src/WixToolset.Core.WindowsInstaller/Unbind/UnbindDatabaseCommand.cs @@ -10,7 +10,8 @@ namespace WixToolset.Core.WindowsInstaller.Unbind using System.Text.RegularExpressions; using WixToolset.Core.Native; using WixToolset.Data; - using WixToolset.Data.Rows; + using WixToolset.Data.WindowsInstaller; + using WixToolset.Data.WindowsInstaller.Rows; using WixToolset.Msi; internal class UnbindDatabaseCommand @@ -155,13 +156,10 @@ namespace WixToolset.Core.WindowsInstaller.Unbind ColumnCategory columnCategory = ColumnCategory.Unknown; ColumnModularizeType columnModularizeType = ColumnModularizeType.None; bool primary = tablePrimaryKeys.Contains(columnName); - bool minValueSet = false; - int minValue = -1; - bool maxValueSet = false; - int maxValue = -1; + int? minValue = null; + int? maxValue = null; string keyTable = null; - bool keyColumnSet = false; - int keyColumn = -1; + int? keyColumn = null; string category = null; string set = null; string description = null; @@ -205,16 +203,13 @@ namespace WixToolset.Core.WindowsInstaller.Unbind if (null != validationRecord) { string validationNullable = validationRecord.GetString(3); - minValueSet = !validationRecord.IsNull(4); - minValue = (minValueSet ? validationRecord.GetInteger(4) : -1); - maxValueSet = !validationRecord.IsNull(5); - maxValue = (maxValueSet ? validationRecord.GetInteger(5) : -1); - keyTable = (!validationRecord.IsNull(6) ? validationRecord.GetString(6) : null); - keyColumnSet = !validationRecord.IsNull(7); - keyColumn = (keyColumnSet ? validationRecord.GetInteger(7) : -1); - category = (!validationRecord.IsNull(8) ? validationRecord.GetString(8) : null); - set = (!validationRecord.IsNull(9) ? validationRecord.GetString(9) : null); - description = (!validationRecord.IsNull(10) ? validationRecord.GetString(10) : null); + minValue = validationRecord.IsNull(4) ? null : (int?)validationRecord.GetInteger(4); + maxValue = validationRecord.IsNull(5) ? null : (int?)validationRecord.GetInteger(5); + keyTable = validationRecord.IsNull(6) ? null : validationRecord.GetString(6); + keyColumn = validationRecord.IsNull(7) ? null : (int?)validationRecord.GetInteger(7); + category = validationRecord.IsNull(8) ? null : validationRecord.GetString(8); + set = validationRecord.IsNull(9) ? null : validationRecord.GetString(9); + description = validationRecord.IsNull(10) ? null : validationRecord.GetString(10); // check the validation nullable value against the column definition if (null == validationNullable) @@ -264,7 +259,7 @@ namespace WixToolset.Core.WindowsInstaller.Unbind columnModularizeType = ColumnModularizeType.Column; } - columns.Add(new ColumnDefinition(columnName, columnType, length, primary, nullable, columnModularizeType, (ColumnType.Localized == columnType), minValueSet, minValue, maxValueSet, maxValue, keyTable, keyColumnSet, keyColumn, columnCategory, set, description, true, true)); + columns.Add(new ColumnDefinition(columnName, columnType, length, primary, nullable, columnCategory, minValue, maxValue, keyTable, keyColumn, set, description, columnModularizeType, (ColumnType.Localized == columnType), true)); } } diff --git a/src/WixToolset.Core.WindowsInstaller/Unbind/UnbindTranformCommand.cs b/src/WixToolset.Core.WindowsInstaller/Unbind/UnbindTranformCommand.cs index 7eb81ac7..70f751f5 100644 --- a/src/WixToolset.Core.WindowsInstaller/Unbind/UnbindTranformCommand.cs +++ b/src/WixToolset.Core.WindowsInstaller/Unbind/UnbindTranformCommand.cs @@ -11,6 +11,7 @@ namespace WixToolset.Core.WindowsInstaller.Unbind using WixToolset.Core.Native; using WixToolset.Core.WindowsInstaller.Bind; using WixToolset.Data; + using WixToolset.Data.WindowsInstaller; using WixToolset.Extensibility; using WixToolset.Msi; diff --git a/src/WixToolset.Core.WindowsInstaller/Validator.cs b/src/WixToolset.Core.WindowsInstaller/Validator.cs index 652a8a07..eb17d8af 100644 --- a/src/WixToolset.Core.WindowsInstaller/Validator.cs +++ b/src/WixToolset.Core.WindowsInstaller/Validator.cs @@ -9,14 +9,15 @@ namespace WixToolset.Core.WindowsInstaller using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.IO; + using System.Linq; + using System.Reflection; using System.Threading; + using WixToolset.Core.Native; using WixToolset.Data; + using WixToolset.Data.WindowsInstaller; using WixToolset.Extensibility; - using WixToolset.Core.Native; - using WixToolset.Msi; - using System.Linq; - using System.Reflection; using WixToolset.Extensibility.Services; + using WixToolset.Msi; /// /// Runs internal consistency evaluators (ICEs) from cub files against a database. diff --git a/src/WixToolset.Core.WindowsInstaller/ValidatorExtension.cs b/src/WixToolset.Core.WindowsInstaller/ValidatorExtension.cs index 44ec3106..67f5962c 100644 --- a/src/WixToolset.Core.WindowsInstaller/ValidatorExtension.cs +++ b/src/WixToolset.Core.WindowsInstaller/ValidatorExtension.cs @@ -5,6 +5,7 @@ namespace WixToolset.Extensibility using System; using System.Collections; using WixToolset.Data; + using WixToolset.Data.WindowsInstaller; /// /// Base class for creating a validator extension. This default implementation diff --git a/src/WixToolset.Core.WindowsInstaller/WindowsInstallerStandardInternal.cs b/src/WixToolset.Core.WindowsInstaller/WindowsInstallerStandardInternal.cs new file mode 100644 index 00000000..3b4721a6 --- /dev/null +++ b/src/WixToolset.Core.WindowsInstaller/WindowsInstallerStandardInternal.cs @@ -0,0 +1,64 @@ +// 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 +{ + using System; + using System.Reflection; + using System.Xml; + using WixToolset.Core.WindowsInstaller.Rows; + using WixToolset.Data.WindowsInstaller; + + /// + /// Represents the Windows Installer standard objects. + /// + internal static class WindowsInstallerStandardInternal + { + private static readonly object lockObject = new object(); + + private static TableDefinitionCollection tableDefinitions; + private static WixActionRowCollection standardActions; + + /// + /// Gets the table definitions stored in this assembly. + /// + /// Table definition collection for tables stored in this assembly. + public static TableDefinitionCollection GetTableDefinitions() + { + lock (lockObject) + { + if (null == WindowsInstallerStandardInternal.tableDefinitions) + { + using (XmlReader reader = XmlReader.Create(Assembly.GetExecutingAssembly().GetManifestResourceStream("WixToolset.Core.WindowsInstaller.Data.tables.xml"))) + { + WindowsInstallerStandardInternal.tableDefinitions = TableDefinitionCollection.Load(reader); + } + } + } + + return WindowsInstallerStandardInternal.tableDefinitions; + } + + /// + /// Gets the standard actions stored in this assembly. + /// + /// Collection of standard actions in this assembly. + public static WixActionRowCollection GetStandardActionRows() + { +#if REVISIT_FOR_PATCHING + lock (lockObject) + { + if (null == WindowsInstallerStandardInternal.standardActions) + { + using (XmlReader reader = XmlReader.Create(Assembly.GetExecutingAssembly().GetManifestResourceStream("WixToolset.Core.WindowsInstaller.Data.actions.xml"))) + { + WindowsInstallerStandardInternal.standardActions = WixActionRowCollection.Load(reader); + } + } + } + + return WindowsInstallerStandardInternal.standardActions; +#endif + throw new NotImplementedException(); + } + } +} diff --git a/src/WixToolset.Core.WindowsInstaller/WixToolset.Core.WindowsInstaller.csproj b/src/WixToolset.Core.WindowsInstaller/WixToolset.Core.WindowsInstaller.csproj index da49f31c..4a26d031 100644 --- a/src/WixToolset.Core.WindowsInstaller/WixToolset.Core.WindowsInstaller.csproj +++ b/src/WixToolset.Core.WindowsInstaller/WixToolset.Core.WindowsInstaller.csproj @@ -12,6 +12,11 @@ NU1701 + + + + + @@ -22,8 +27,6 @@ - - -- cgit v1.2.3-55-g6feb