// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. namespace WixToolset { using System; using System.Collections; using System.Collections.Generic; using System.Collections.Specialized; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Linq; using System.Text; using WixToolset.Data; using WixToolset.Data.Rows; using WixToolset.Extensibility; using WixToolset.Link; using WixToolset.Core.Native; /// /// Linker core of the WiX toolset. /// public sealed class Linker : IMessageHandler { private static readonly char[] colonCharacter = ":".ToCharArray(); private static readonly string emptyGuid = Guid.Empty.ToString("B"); private List extensionData; private List inspectorExtensions; private bool sectionIdOnRows; private WixActionRowCollection standardActions; private Output activeOutput; private TableDefinitionCollection tableDefinitions; /// /// Creates a linker. /// public Linker() { this.sectionIdOnRows = true; // TODO: what is the correct value for this? this.standardActions = WindowsInstallerStandard.GetStandardActions(); this.tableDefinitions = new TableDefinitionCollection(WindowsInstallerStandard.GetTableDefinitions()); this.extensionData = new List(); this.inspectorExtensions = new List(); } /// /// Gets or sets the localizer. /// /// The localizer. public Localizer Localizer { get; set; } /// /// Gets or sets the path to output unreferenced symbols to. If null or empty, there is no output. /// /// The path to output the xml file. public string UnreferencedSymbolsFile { get; set; } /// /// Gets or sets the option to show pedantic messages. /// /// The option to show pedantic messages. public bool ShowPedanticMessages { get; set; } /// /// Gets the table definitions used by the linker. /// /// Table definitions used by the linker. public TableDefinitionCollection TableDefinitions { get { return this.tableDefinitions; } } /// /// Gets or sets the Wix variable resolver. /// /// The Wix variable resolver. internal IBindVariableResolver WixVariableResolver { get; set; } /// /// Adds an extension. /// /// The extension to add. public void AddExtensionData(IExtensionData extension) { if (null != extension.TableDefinitions) { foreach (TableDefinition tableDefinition in extension.TableDefinitions) { if (!this.tableDefinitions.Contains(tableDefinition.Name)) { this.tableDefinitions.Add(tableDefinition); } else { throw new WixException(WixErrors.DuplicateExtensionTable(extension.GetType().ToString(), tableDefinition.Name)); } } } // keep track of extension data so the libraries can be loaded from these later once all the table definitions // are loaded; this will allow extensions to have cross table definition dependencies this.extensionData.Add(extension); } /// /// Links a collection of sections into an output. /// /// The collection of sections to link together. /// Expected output type, based on output file extension provided to the linker. /// Output object from the linking. public Output Link(IEnumerable
inputs, OutputType expectedOutputType) { Output output = null; List
sections = new List
(inputs); try { bool containsModuleSubstitution = false; bool containsModuleConfiguration = false; this.activeOutput = null; List actionRows = new List(); List suppressActionRows = new List(); TableDefinitionCollection customTableDefinitions = new TableDefinitionCollection(); List customRows = new List(); StringCollection generatedShortFileNameIdentifiers = new StringCollection(); Hashtable generatedShortFileNames = new Hashtable(); Hashtable multipleFeatureComponents = new Hashtable(); Hashtable wixVariables = new Hashtable(); // verify that modularization types match for foreign key relationships foreach (TableDefinition tableDefinition in this.tableDefinitions) { foreach (ColumnDefinition columnDefinition in tableDefinition.Columns) { if (null != columnDefinition.KeyTable && 0 > columnDefinition.KeyTable.IndexOf(';') && columnDefinition.IsKeyColumnSet) { try { TableDefinition keyTableDefinition = this.tableDefinitions[columnDefinition.KeyTable]; if (0 >= columnDefinition.KeyColumn || keyTableDefinition.Columns.Count < columnDefinition.KeyColumn) { this.OnMessage(WixErrors.InvalidKeyColumn(tableDefinition.Name, columnDefinition.Name, columnDefinition.KeyTable, columnDefinition.KeyColumn)); } else if (keyTableDefinition.Columns[columnDefinition.KeyColumn - 1].ModularizeType != columnDefinition.ModularizeType && ColumnModularizeType.CompanionFile != columnDefinition.ModularizeType) { this.OnMessage(WixErrors.CollidingModularizationTypes(tableDefinition.Name, columnDefinition.Name, columnDefinition.KeyTable, columnDefinition.KeyColumn, columnDefinition.ModularizeType.ToString(), keyTableDefinition.Columns[columnDefinition.KeyColumn - 1].ModularizeType.ToString())); } } catch (WixMissingTableDefinitionException) { // ignore missing table definitions - this error is caught in other places } } } } // Add sections from the extensions with data. foreach (IExtensionData data in this.extensionData) { Library library = data.GetLibrary(this.tableDefinitions); if (null != library) { sections.AddRange(library.Sections); } } // First find the entry section and while processing all sections load all the symbols from all of the sections. // sections.FindEntrySectionAndLoadSymbols(false, this, expectedOutputType, out entrySection, out allSymbols); FindEntrySectionAndLoadSymbolsCommand find = new FindEntrySectionAndLoadSymbolsCommand(sections); find.ExpectedOutputType = expectedOutputType; find.Execute(); // Must have found the entry section by now. if (null == find.EntrySection) { throw new WixException(WixErrors.MissingEntrySection(expectedOutputType.ToString())); } IDictionary allSymbols = find.Symbols; // Add the missing standard action symbols. this.LoadStandardActionSymbols(allSymbols); // now that we know where we're starting from, create the output object output = new Output(null); output.EntrySection = find.EntrySection; // Note: this entry section will get added to the Output.Sections collection later if (null != this.Localizer && -1 != this.Localizer.Codepage) { output.Codepage = this.Localizer.Codepage; } this.activeOutput = output; // Resolve the symbol references to find the set of sections we care about for linking. // Of course, we start with the entry section (that's how it got its name after all). ResolveReferencesCommand resolve = new ResolveReferencesCommand(output.EntrySection, allSymbols); resolve.BuildingMergeModule = (OutputType.Module == output.Type); resolve.Execute(); if (Messaging.Instance.EncounteredError) { return null; } // Add the resolved sections to the output then flatten the complex // references that particpate in groups. foreach (Section section in resolve.ResolvedSections) { output.Sections.Add(section); } this.FlattenSectionsComplexReferences(output.Sections); if (Messaging.Instance.EncounteredError) { return null; } // The hard part in linking is processing the complex references. HashSet referencedComponents = new HashSet(); ConnectToFeatureCollection componentsToFeatures = new ConnectToFeatureCollection(); ConnectToFeatureCollection featuresToFeatures = new ConnectToFeatureCollection(); ConnectToFeatureCollection modulesToFeatures = new ConnectToFeatureCollection(); this.ProcessComplexReferences(output, output.Sections, referencedComponents, componentsToFeatures, featuresToFeatures, modulesToFeatures); if (Messaging.Instance.EncounteredError) { return null; } // Display an error message for Components that were not referenced by a Feature. foreach (Symbol symbol in resolve.ReferencedSymbols.Where(s => "Component".Equals(s.Row.TableDefinition.Name, StringComparison.Ordinal))) { if (!referencedComponents.Contains(symbol.Name)) { this.OnMessage(WixErrors.OrphanedComponent(symbol.Row.SourceLineNumbers, (string)symbol.Row[0])); } } // Report duplicates that would ultimately end up being primary key collisions. ReportConflictingSymbolsCommand reportDupes = new ReportConflictingSymbolsCommand(find.PossiblyConflictingSymbols, resolve.ResolvedSections); reportDupes.Execute(); if (Messaging.Instance.EncounteredError) { return null; } // resolve the feature to feature connects this.ResolveFeatureToFeatureConnects(featuresToFeatures, allSymbols); // start generating OutputTables and OutputRows for all the sections in the output List ensureTableRows = new List(); int sectionCount = 0; foreach (Section section in output.Sections) { sectionCount++; string sectionId = section.Id; if (null == sectionId && this.sectionIdOnRows) { sectionId = "wix.section." + sectionCount.ToString(CultureInfo.InvariantCulture); } foreach (Table table in section.Tables) { bool copyRows = true; // by default, copy rows. // handle special tables switch (table.Name) { case "AppSearch": this.activeOutput.EnsureTable(this.tableDefinitions["Signature"]); break; case "Class": if (OutputType.Product == output.Type) { this.ResolveFeatures(table.Rows, 2, 11, componentsToFeatures, multipleFeatureComponents); } break; case "CustomAction": if (OutputType.Module == this.activeOutput.Type) { this.activeOutput.EnsureTable(this.tableDefinitions["AdminExecuteSequence"]); this.activeOutput.EnsureTable(this.tableDefinitions["AdminUISequence"]); this.activeOutput.EnsureTable(this.tableDefinitions["AdvtExecuteSequence"]); this.activeOutput.EnsureTable(this.tableDefinitions["InstallExecuteSequence"]); this.activeOutput.EnsureTable(this.tableDefinitions["InstallUISequence"]); } break; case "Dialog": this.activeOutput.EnsureTable(this.tableDefinitions["ListBox"]); break; case "Directory": foreach (Row row in table.Rows) { if (OutputType.Module == this.activeOutput.Type) { string directory = row[0].ToString(); if (WindowsInstallerStandard.IsStandardDirectory(directory)) { // if the directory table contains references to standard windows folders // mergemod.dll will add customactions to set the MSM directory to // the same directory as the standard windows folder and will add references to // custom action to all the standard sequence tables. A problem will occur // if the MSI does not have these tables as mergemod.dll does not add these // tables to the MSI if absent. This code adds the tables in case mergemod.dll // needs them. this.activeOutput.EnsureTable(this.tableDefinitions["CustomAction"]); this.activeOutput.EnsureTable(this.tableDefinitions["AdminExecuteSequence"]); this.activeOutput.EnsureTable(this.tableDefinitions["AdminUISequence"]); this.activeOutput.EnsureTable(this.tableDefinitions["AdvtExecuteSequence"]); this.activeOutput.EnsureTable(this.tableDefinitions["InstallExecuteSequence"]); this.activeOutput.EnsureTable(this.tableDefinitions["InstallUISequence"]); } else { foreach (string standardDirectory in WindowsInstallerStandard.GetStandardDirectories()) { if (directory.StartsWith(standardDirectory, StringComparison.Ordinal)) { this.OnMessage(WixWarnings.StandardDirectoryConflictInMergeModule(row.SourceLineNumbers, directory, standardDirectory)); } } } } } break; case "Extension": if (OutputType.Product == output.Type) { this.ResolveFeatures(table.Rows, 1, 4, componentsToFeatures, multipleFeatureComponents); } break; case "ModuleSubstitution": containsModuleSubstitution = true; break; case "ModuleConfiguration": containsModuleConfiguration = true; break; case "MsiAssembly": if (OutputType.Product == output.Type) { this.ResolveFeatures(table.Rows, 0, 1, componentsToFeatures, multipleFeatureComponents); } break; case "ProgId": // the Extension table is required with a ProgId table this.activeOutput.EnsureTable(this.tableDefinitions["Extension"]); break; case "Property": // Remove property rows with no value. These are properties associated with // AppSearch but without a default value. for (int i = 0; i < table.Rows.Count; i++) { if (null == table.Rows[i][1]) { table.Rows.RemoveAt(i); i--; } } break; case "PublishComponent": if (OutputType.Product == output.Type) { this.ResolveFeatures(table.Rows, 2, 4, componentsToFeatures, multipleFeatureComponents); } break; case "Shortcut": if (OutputType.Product == output.Type) { this.ResolveFeatures(table.Rows, 3, 4, componentsToFeatures, multipleFeatureComponents); } break; case "TypeLib": if (OutputType.Product == output.Type) { this.ResolveFeatures(table.Rows, 2, 6, componentsToFeatures, multipleFeatureComponents); } break; case "WixAction": if (this.sectionIdOnRows) { foreach (Row row in table.Rows) { row.SectionId = sectionId; } } actionRows.AddRange(table.Rows); break; case "WixCustomTable": this.LinkCustomTable(table, customTableDefinitions); copyRows = false; // we've created table definitions from these rows, no need to process them any longer break; case "WixCustomRow": foreach (Row row in table.Rows) { row.SectionId = (this.sectionIdOnRows ? sectionId : null); customRows.Add(row); } copyRows = false; break; case "WixEnsureTable": ensureTableRows.AddRange(table.Rows); break; case "WixFile": foreach (Row row in table.Rows) { // DiskId is not valid when creating a module, so set it to // 0 for all files to ensure proper sorting in the binder if (OutputType.Module == this.activeOutput.Type) { row[5] = 0; } // if the short file name was generated, check for collisions if (0x1 == (int)row[9]) { generatedShortFileNameIdentifiers.Add((string)row[0]); } } break; case "WixMerge": if (OutputType.Product == output.Type) { this.ResolveFeatures(table.Rows, 0, 7, modulesToFeatures, null); } break; case "WixSuppressAction": suppressActionRows.AddRange(table.Rows); break; case "WixVariable": // check for colliding values and collect the wix variable rows foreach (WixVariableRow row in table.Rows) { WixVariableRow collidingRow = (WixVariableRow)wixVariables[row.Id]; if (null == collidingRow || (collidingRow.Overridable && !row.Overridable)) { wixVariables[row.Id] = row; } else if (!row.Overridable || (collidingRow.Overridable && row.Overridable)) { this.OnMessage(WixErrors.WixVariableCollision(row.SourceLineNumbers, row.Id)); } } copyRows = false; break; } if (copyRows) { Table outputTable = this.activeOutput.EnsureTable(this.tableDefinitions[table.Name]); this.CopyTableRowsToOutputTable(table, outputTable, sectionId); } } } // copy the module to feature connections into the output if (0 < modulesToFeatures.Count) { Table wixFeatureModulesTable = this.activeOutput.EnsureTable(this.tableDefinitions["WixFeatureModules"]); foreach (ConnectToFeature connectToFeature in modulesToFeatures) { foreach (string feature in connectToFeature.ConnectFeatures) { Row row = wixFeatureModulesTable.CreateRow(null); row[0] = feature; row[1] = connectToFeature.ChildId; } } } // ensure the creation of tables that need to exist if (0 < ensureTableRows.Count) { foreach (Row row in ensureTableRows) { string tableId = (string)row[0]; TableDefinition tableDef = null; try { tableDef = this.tableDefinitions[tableId]; } catch (WixMissingTableDefinitionException) { tableDef = customTableDefinitions[tableId]; } this.activeOutput.EnsureTable(tableDef); } } // copy all the suppress action rows to the output to suppress actions from merge modules if (0 < suppressActionRows.Count) { Table suppressActionTable = this.activeOutput.EnsureTable(this.tableDefinitions["WixSuppressAction"]); suppressActionRows.ForEach(r => suppressActionTable.Rows.Add(r)); } // sequence all the actions this.SequenceActions(actionRows, suppressActionRows); // check for missing table and add them or display an error as appropriate switch (this.activeOutput.Type) { case OutputType.Module: this.activeOutput.EnsureTable(this.tableDefinitions["Component"]); this.activeOutput.EnsureTable(this.tableDefinitions["Directory"]); this.activeOutput.EnsureTable(this.tableDefinitions["FeatureComponents"]); this.activeOutput.EnsureTable(this.tableDefinitions["File"]); this.activeOutput.EnsureTable(this.tableDefinitions["ModuleComponents"]); this.activeOutput.EnsureTable(this.tableDefinitions["ModuleSignature"]); break; case OutputType.PatchCreation: Table imageFamiliesTable = this.activeOutput.Tables["ImageFamilies"]; Table targetImagesTable = this.activeOutput.Tables["TargetImages"]; Table upgradedImagesTable = this.activeOutput.Tables["UpgradedImages"]; if (null == imageFamiliesTable || 1 > imageFamiliesTable.Rows.Count) { this.OnMessage(WixErrors.ExpectedRowInPatchCreationPackage("ImageFamilies")); } if (null == targetImagesTable || 1 > targetImagesTable.Rows.Count) { this.OnMessage(WixErrors.ExpectedRowInPatchCreationPackage("TargetImages")); } if (null == upgradedImagesTable || 1 > upgradedImagesTable.Rows.Count) { this.OnMessage(WixErrors.ExpectedRowInPatchCreationPackage("UpgradedImages")); } this.activeOutput.EnsureTable(this.tableDefinitions["Properties"]); break; case OutputType.Product: this.activeOutput.EnsureTable(this.tableDefinitions["File"]); this.activeOutput.EnsureTable(this.tableDefinitions["Media"]); break; } this.CheckForIllegalTables(this.activeOutput); // add the custom row data foreach (Row row in customRows) { TableDefinition customTableDefinition = (TableDefinition)customTableDefinitions[row[0].ToString()]; Table customTable = this.activeOutput.EnsureTable(customTableDefinition); Row customRow = customTable.CreateRow(row.SourceLineNumbers); customRow.SectionId = row.SectionId; string[] data = row[1].ToString().Split(Common.CustomRowFieldSeparator); for (int i = 0; i < data.Length; ++i) { bool foundColumn = false; string[] item = data[i].Split(colonCharacter, 2); for (int j = 0; j < customRow.Fields.Length; ++j) { if (customRow.Fields[j].Column.Name == item[0]) { if (0 < item[1].Length) { if (ColumnType.Number == customRow.Fields[j].Column.Type) { try { customRow.Fields[j].Data = Convert.ToInt32(item[1], CultureInfo.InvariantCulture); } catch (FormatException) { this.OnMessage(WixErrors.IllegalIntegerValue(row.SourceLineNumbers, customTableDefinition.Columns[i].Name, customTableDefinition.Name, item[1])); } catch (OverflowException) { this.OnMessage(WixErrors.IllegalIntegerValue(row.SourceLineNumbers, customTableDefinition.Columns[i].Name, customTableDefinition.Name, item[1])); } } else if (ColumnCategory.Identifier == customRow.Fields[j].Column.Category) { if (Common.IsIdentifier(item[1]) || Common.IsValidBinderVariable(item[1]) || ColumnCategory.Formatted == customRow.Fields[j].Column.Category) { customRow.Fields[j].Data = item[1]; } else { this.OnMessage(WixErrors.IllegalIdentifier(row.SourceLineNumbers, "Data", item[1])); } } else { customRow.Fields[j].Data = item[1]; } } foundColumn = true; break; } } if (!foundColumn) { this.OnMessage(WixErrors.UnexpectedCustomTableColumn(row.SourceLineNumbers, item[0])); } } for (int i = 0; i < customTableDefinition.Columns.Count; ++i) { if (!customTableDefinition.Columns[i].Nullable && (null == customRow.Fields[i].Data || 0 == customRow.Fields[i].Data.ToString().Length)) { this.OnMessage(WixErrors.NoDataForColumn(row.SourceLineNumbers, customTableDefinition.Columns[i].Name, customTableDefinition.Name)); } } } //correct the section Id in FeatureComponents table if (this.sectionIdOnRows) { Hashtable componentSectionIds = new Hashtable(); Table componentTable = output.Tables["Component"]; if (null != componentTable) { foreach (Row componentRow in componentTable.Rows) { componentSectionIds.Add(componentRow.Fields[0].Data.ToString(), componentRow.SectionId); } } Table featureComponentsTable = output.Tables["FeatureComponents"]; if (null != featureComponentsTable) { foreach (Row featureComponentsRow in featureComponentsTable.Rows) { if (componentSectionIds.Contains(featureComponentsRow.Fields[1].Data.ToString())) { featureComponentsRow.SectionId = (string)componentSectionIds[featureComponentsRow.Fields[1].Data.ToString()]; } } } } // add the ModuleSubstitution table to the ModuleIgnoreTable if (containsModuleSubstitution) { Table moduleIgnoreTableTable = this.activeOutput.EnsureTable(this.tableDefinitions["ModuleIgnoreTable"]); Row moduleIgnoreTableRow = moduleIgnoreTableTable.CreateRow(null); moduleIgnoreTableRow[0] = "ModuleSubstitution"; } // add the ModuleConfiguration table to the ModuleIgnoreTable if (containsModuleConfiguration) { Table moduleIgnoreTableTable = this.activeOutput.EnsureTable(this.tableDefinitions["ModuleIgnoreTable"]); Row moduleIgnoreTableRow = moduleIgnoreTableTable.CreateRow(null); moduleIgnoreTableRow[0] = "ModuleConfiguration"; } // index all the file rows Table fileTable = this.activeOutput.Tables["File"]; RowDictionary indexedFileRows = (null == fileTable) ? new RowDictionary() : new RowDictionary(fileTable); // flag all the generated short file name collisions foreach (string fileId in generatedShortFileNameIdentifiers) { FileRow fileRow = indexedFileRows[fileId]; string[] names = fileRow.FileName.Split('|'); string shortFileName = names[0]; // create lists of conflicting generated short file names if (!generatedShortFileNames.Contains(shortFileName)) { generatedShortFileNames.Add(shortFileName, new ArrayList()); } ((ArrayList)generatedShortFileNames[shortFileName]).Add(fileRow); } // check for generated short file name collisions foreach (DictionaryEntry entry in generatedShortFileNames) { string shortFileName = (string)entry.Key; ArrayList fileRows = (ArrayList)entry.Value; if (1 < fileRows.Count) { // sort the rows by DiskId fileRows.Sort(); this.OnMessage(WixWarnings.GeneratedShortFileNameConflict(((FileRow)fileRows[0]).SourceLineNumbers, shortFileName)); for (int i = 1; i < fileRows.Count; i++) { FileRow fileRow = (FileRow)fileRows[i]; if (null != fileRow.SourceLineNumbers) { this.OnMessage(WixWarnings.GeneratedShortFileNameConflict2(fileRow.SourceLineNumbers)); } } } } // copy the wix variable rows to the output after all overriding has been accounted for. if (0 < wixVariables.Count) { Table wixVariableTable = output.EnsureTable(this.tableDefinitions["WixVariable"]); foreach (WixVariableRow row in wixVariables.Values) { wixVariableTable.Rows.Add(row); } } // Bundles have groups of data that must be flattened in a way different from other types. this.FlattenBundleTables(output); if (Messaging.Instance.EncounteredError) { return null; } this.CheckOutputConsistency(output); // inspect the output InspectorCore inspectorCore = new InspectorCore(); foreach (InspectorExtension inspectorExtension in this.inspectorExtensions) { inspectorExtension.Core = inspectorCore; inspectorExtension.InspectOutput(output); // reset inspectorExtension.Core = null; } } finally { this.activeOutput = null; } return Messaging.Instance.EncounteredError ? null : output; } /// /// Links the definition of a custom table. /// /// The table to link. /// Receives the linked definition of the custom table. private void LinkCustomTable(Table table, TableDefinitionCollection customTableDefinitions) { foreach (Row row in table.Rows) { bool bootstrapperApplicationData = (null != row[13] && 1 == (int)row[13]); if (null == row[4]) { this.OnMessage(WixErrors.ExpectedAttribute(row.SourceLineNumbers, "CustomTable/Column", "PrimaryKey")); } string[] columnNames = row[2].ToString().Split('\t'); string[] columnTypes = row[3].ToString().Split('\t'); string[] primaryKeys = row[4].ToString().Split('\t'); string[] minValues = row[5] == null ? null : row[5].ToString().Split('\t'); string[] maxValues = row[6] == null ? null : row[6].ToString().Split('\t'); string[] keyTables = row[7] == null ? null : row[7].ToString().Split('\t'); string[] keyColumns = row[8] == null ? null : row[8].ToString().Split('\t'); string[] categories = row[9] == null ? null : row[9].ToString().Split('\t'); string[] sets = row[10] == null ? null : row[10].ToString().Split('\t'); string[] descriptions = row[11] == null ? null : row[11].ToString().Split('\t'); string[] modularizations = row[12] == null ? null : row[12].ToString().Split('\t'); int currentPrimaryKey = 0; List columns = new List(columnNames.Length); for (int i = 0; i < columnNames.Length; ++i) { string name = columnNames[i]; ColumnType type = ColumnType.Unknown; if (columnTypes[i].StartsWith("s", StringComparison.OrdinalIgnoreCase)) { type = ColumnType.String; } else if (columnTypes[i].StartsWith("l", StringComparison.OrdinalIgnoreCase)) { type = ColumnType.Localized; } else if (columnTypes[i].StartsWith("i", StringComparison.OrdinalIgnoreCase)) { type = ColumnType.Number; } else if (columnTypes[i].StartsWith("v", StringComparison.OrdinalIgnoreCase)) { type = ColumnType.Object; } else { throw new WixException(WixErrors.UnknownCustomTableColumnType(row.SourceLineNumbers, columnTypes[i])); } bool nullable = columnTypes[i].Substring(0, 1) == columnTypes[i].Substring(0, 1).ToUpper(CultureInfo.InvariantCulture); int length = Convert.ToInt32(columnTypes[i].Substring(1), CultureInfo.InvariantCulture); bool primaryKey = false; if (currentPrimaryKey < primaryKeys.Length && primaryKeys[currentPrimaryKey] == columnNames[i]) { primaryKey = true; currentPrimaryKey++; } bool minValSet = null != minValues && null != minValues[i] && 0 < minValues[i].Length; int minValue = 0; if (minValSet) { minValue = Convert.ToInt32(minValues[i], CultureInfo.InvariantCulture); } bool maxValSet = null != maxValues && null != maxValues[i] && 0 < maxValues[i].Length; int maxValue = 0; if (maxValSet) { maxValue = Convert.ToInt32(maxValues[i], CultureInfo.InvariantCulture); } bool keyColumnSet = null != keyColumns && null != keyColumns[i] && 0 < keyColumns[i].Length; int keyColumn = 0; if (keyColumnSet) { keyColumn = Convert.ToInt32(keyColumns[i], CultureInfo.InvariantCulture); } ColumnCategory category = ColumnCategory.Unknown; if (null != categories && null != categories[i] && 0 < categories[i].Length) { switch (categories[i]) { case "Text": category = ColumnCategory.Text; break; case "UpperCase": category = ColumnCategory.UpperCase; break; case "LowerCase": category = ColumnCategory.LowerCase; break; case "Integer": category = ColumnCategory.Integer; break; case "DoubleInteger": category = ColumnCategory.DoubleInteger; break; case "TimeDate": category = ColumnCategory.TimeDate; break; case "Identifier": category = ColumnCategory.Identifier; break; case "Property": category = ColumnCategory.Property; break; case "Filename": category = ColumnCategory.Filename; break; case "WildCardFilename": category = ColumnCategory.WildCardFilename; break; case "Path": category = ColumnCategory.Path; break; case "Paths": category = ColumnCategory.Paths; break; case "AnyPath": category = ColumnCategory.AnyPath; break; case "DefaultDir": category = ColumnCategory.DefaultDir; break; case "RegPath": category = ColumnCategory.RegPath; break; case "Formatted": category = ColumnCategory.Formatted; break; case "FormattedSddl": category = ColumnCategory.FormattedSDDLText; break; case "Template": category = ColumnCategory.Template; break; case "Condition": category = ColumnCategory.Condition; break; case "Guid": category = ColumnCategory.Guid; break; case "Version": category = ColumnCategory.Version; break; case "Language": category = ColumnCategory.Language; break; case "Binary": category = ColumnCategory.Binary; break; case "CustomSource": category = ColumnCategory.CustomSource; break; case "Cabinet": category = ColumnCategory.Cabinet; break; case "Shortcut": category = ColumnCategory.Shortcut; break; default: break; } } string keyTable = keyTables != null ? keyTables[i] : null; string setValue = sets != null ? sets[i] : null; string description = descriptions != null ? descriptions[i] : null; string modString = modularizations != null ? modularizations[i] : null; ColumnModularizeType modularization = ColumnModularizeType.None; if (modString != null) { switch (modString) { case "None": modularization = ColumnModularizeType.None; break; case "Column": modularization = ColumnModularizeType.Column; break; case "Property": modularization = ColumnModularizeType.Property; break; case "Condition": modularization = ColumnModularizeType.Condition; break; case "CompanionFile": modularization = ColumnModularizeType.CompanionFile; break; case "SemicolonDelimited": modularization = ColumnModularizeType.SemicolonDelimited; break; } } ColumnDefinition columnDefinition = new ColumnDefinition(name, type, length, primaryKey, nullable, modularization, ColumnType.Localized == type, minValSet, minValue, maxValSet, maxValue, keyTable, keyColumnSet, keyColumn, category, setValue, description, true, true); columns.Add(columnDefinition); } TableDefinition customTable = new TableDefinition((string)row[0], columns, false, bootstrapperApplicationData, bootstrapperApplicationData); customTableDefinitions.Add(customTable); } } /// /// Checks for any tables in the output which are not allowed in the output type. /// /// The output to check. private void CheckForIllegalTables(Output output) { foreach (Table table in output.Tables) { switch (output.Type) { case OutputType.Module: if ("BBControl" == table.Name || "Billboard" == table.Name || "CCPSearch" == table.Name || "Feature" == table.Name || "LaunchCondition" == table.Name || "Media" == table.Name || "Patch" == table.Name || "Upgrade" == table.Name || "WixMerge" == table.Name) { foreach (Row row in table.Rows) { this.OnMessage(WixErrors.UnexpectedTableInMergeModule(row.SourceLineNumbers, table.Name)); } } else if ("Error" == table.Name) { foreach (Row row in table.Rows) { this.OnMessage(WixWarnings.DangerousTableInMergeModule(row.SourceLineNumbers, table.Name)); } } break; case OutputType.PatchCreation: if (!table.Definition.Unreal && "_SummaryInformation" != table.Name && "ExternalFiles" != table.Name && "FamilyFileRanges" != table.Name && "ImageFamilies" != table.Name && "PatchMetadata" != table.Name && "PatchSequence" != table.Name && "Properties" != table.Name && "TargetFiles_OptionalData" != table.Name && "TargetImages" != table.Name && "UpgradedFiles_OptionalData" != table.Name && "UpgradedFilesToIgnore" != table.Name && "UpgradedImages" != table.Name) { foreach (Row row in table.Rows) { this.OnMessage(WixErrors.UnexpectedTableInPatchCreationPackage(row.SourceLineNumbers, table.Name)); } } break; case OutputType.Patch: if (!table.Definition.Unreal && "_SummaryInformation" != table.Name && "Media" != table.Name && "MsiPatchMetadata" != table.Name && "MsiPatchSequence" != table.Name) { foreach (Row row in table.Rows) { this.OnMessage(WixErrors.UnexpectedTableInPatch(row.SourceLineNumbers, table.Name)); } } break; case OutputType.Product: if ("ModuleAdminExecuteSequence" == table.Name || "ModuleAdminUISequence" == table.Name || "ModuleAdvtExecuteSequence" == table.Name || "ModuleAdvtUISequence" == table.Name || "ModuleComponents" == table.Name || "ModuleConfiguration" == table.Name || "ModuleDependency" == table.Name || "ModuleExclusion" == table.Name || "ModuleIgnoreTable" == table.Name || "ModuleInstallExecuteSequence" == table.Name || "ModuleInstallUISequence" == table.Name || "ModuleSignature" == table.Name || "ModuleSubstitution" == table.Name) { foreach (Row row in table.Rows) { this.OnMessage(WixWarnings.UnexpectedTableInProduct(row.SourceLineNumbers, table.Name)); } } break; } } } /// /// Performs various consistency checks on the output. /// /// Output containing instance transform definitions. private void CheckOutputConsistency(Output output) { // Get the output's minimum installer version int outputInstallerVersion = int.MinValue; Table summaryInformationTable = output.Tables["_SummaryInformation"]; if (null != summaryInformationTable) { foreach (Row row in summaryInformationTable.Rows) { if (14 == (int)row[0]) { outputInstallerVersion = Convert.ToInt32(row[1], CultureInfo.InvariantCulture); break; } } } // ensure the Error table exists if output is marked for MSI 1.0 or below (see ICE40) if (100 >= outputInstallerVersion && OutputType.Product == output.Type) { output.EnsureTable(this.tableDefinitions["Error"]); } // check for the presence of tables/rows/columns that require MSI 1.1 or later if (110 > outputInstallerVersion) { Table isolatedComponentTable = output.Tables["IsolatedComponent"]; if (null != isolatedComponentTable) { foreach (Row row in isolatedComponentTable.Rows) { this.OnMessage(WixWarnings.TableIncompatibleWithInstallerVersion(row.SourceLineNumbers, "IsolatedComponent", outputInstallerVersion)); } } } // check for the presence of tables/rows/columns that require MSI 4.0 or later if (400 > outputInstallerVersion) { Table shortcutTable = output.Tables["Shortcut"]; if (null != shortcutTable) { foreach (Row row in shortcutTable.Rows) { if (null != row[12] || null != row[13] || null != row[14] || null != row[15]) { this.OnMessage(WixWarnings.ColumnsIncompatibleWithInstallerVersion(row.SourceLineNumbers, "Shortcut", outputInstallerVersion)); } } } } } /// /// Sends a message to the message delegate if there is one. /// /// Message event arguments. public void OnMessage(MessageEventArgs e) { Messaging.Instance.OnMessage(e); } /// /// Load the standard action symbols. /// /// Collection of symbols. private void LoadStandardActionSymbols(IDictionary allSymbols) { foreach (WixActionRow actionRow in this.standardActions) { Symbol actionSymbol = new Symbol(actionRow); // If the action's symbol has not already been defined (i.e. overriden by the user), add it now. if (!allSymbols.ContainsKey(actionSymbol.Name)) { allSymbols.Add(actionSymbol.Name, actionSymbol); } } } /// /// Process the complex references. /// /// Active output to add sections to. /// Sections that are referenced during the link process. /// Collection of all components referenced by complex reference. /// Component to feature complex references. /// Feature to feature complex references. /// Module to feature complex references. private void ProcessComplexReferences(Output output, IEnumerable
sections, ISet referencedComponents, ConnectToFeatureCollection componentsToFeatures, ConnectToFeatureCollection featuresToFeatures, ConnectToFeatureCollection modulesToFeatures) { Hashtable componentsToModules = new Hashtable(); foreach (Section section in sections) { Table wixComplexReferenceTable = section.Tables["WixComplexReference"]; if (null != wixComplexReferenceTable) { foreach (WixComplexReferenceRow wixComplexReferenceRow in wixComplexReferenceTable.Rows) { ConnectToFeature connection; switch (wixComplexReferenceRow.ParentType) { case ComplexReferenceParentType.Feature: switch (wixComplexReferenceRow.ChildType) { case ComplexReferenceChildType.Component: connection = componentsToFeatures[wixComplexReferenceRow.ChildId]; if (null == connection) { componentsToFeatures.Add(new ConnectToFeature(section, wixComplexReferenceRow.ChildId, wixComplexReferenceRow.ParentId, wixComplexReferenceRow.IsPrimary)); } else if (wixComplexReferenceRow.IsPrimary) { if (connection.IsExplicitPrimaryFeature) { this.OnMessage(WixErrors.MultiplePrimaryReferences(section.SourceLineNumbers, wixComplexReferenceRow.ChildType.ToString(), wixComplexReferenceRow.ChildId, wixComplexReferenceRow.ParentType.ToString(), wixComplexReferenceRow.ParentId, (null != connection.PrimaryFeature ? "Feature" : "Product"), (null != connection.PrimaryFeature ? connection.PrimaryFeature : this.activeOutput.EntrySection.Id))); continue; } else { connection.ConnectFeatures.Add(connection.PrimaryFeature); // move the guessed primary feature to the list of connects connection.PrimaryFeature = wixComplexReferenceRow.ParentId; // set the new primary feature connection.IsExplicitPrimaryFeature = true; // and make sure we remember that we set it so we can fail if we try to set it again } } else { connection.ConnectFeatures.Add(wixComplexReferenceRow.ParentId); } // add a row to the FeatureComponents table Table featureComponentsTable = output.EnsureTable(this.tableDefinitions["FeatureComponents"]); Row row = featureComponentsTable.CreateRow(null); if (this.sectionIdOnRows) { row.SectionId = section.Id; } row[0] = wixComplexReferenceRow.ParentId; row[1] = wixComplexReferenceRow.ChildId; // index the component for finding orphaned records string symbolName = String.Concat("Component:", wixComplexReferenceRow.ChildId); referencedComponents.Add(symbolName); break; case ComplexReferenceChildType.Feature: connection = featuresToFeatures[wixComplexReferenceRow.ChildId]; if (null != connection) { this.OnMessage(WixErrors.MultiplePrimaryReferences(section.SourceLineNumbers, wixComplexReferenceRow.ChildType.ToString(), wixComplexReferenceRow.ChildId, wixComplexReferenceRow.ParentType.ToString(), wixComplexReferenceRow.ParentId, (null != connection.PrimaryFeature ? "Feature" : "Product"), (null != connection.PrimaryFeature ? connection.PrimaryFeature : this.activeOutput.EntrySection.Id))); continue; } featuresToFeatures.Add(new ConnectToFeature(section, wixComplexReferenceRow.ChildId, wixComplexReferenceRow.ParentId, wixComplexReferenceRow.IsPrimary)); break; case ComplexReferenceChildType.Module: connection = modulesToFeatures[wixComplexReferenceRow.ChildId]; if (null == connection) { modulesToFeatures.Add(new ConnectToFeature(section, wixComplexReferenceRow.ChildId, wixComplexReferenceRow.ParentId, wixComplexReferenceRow.IsPrimary)); } else if (wixComplexReferenceRow.IsPrimary) { if (connection.IsExplicitPrimaryFeature) { this.OnMessage(WixErrors.MultiplePrimaryReferences(section.SourceLineNumbers, wixComplexReferenceRow.ChildType.ToString(), wixComplexReferenceRow.ChildId, wixComplexReferenceRow.ParentType.ToString(), wixComplexReferenceRow.ParentId, (null != connection.PrimaryFeature ? "Feature" : "Product"), (null != connection.PrimaryFeature ? connection.PrimaryFeature : this.activeOutput.EntrySection.Id))); continue; } else { connection.ConnectFeatures.Add(connection.PrimaryFeature); // move the guessed primary feature to the list of connects connection.PrimaryFeature = wixComplexReferenceRow.ParentId; // set the new primary feature connection.IsExplicitPrimaryFeature = true; // and make sure we remember that we set it so we can fail if we try to set it again } } else { connection.ConnectFeatures.Add(wixComplexReferenceRow.ParentId); } break; default: throw new InvalidOperationException(String.Format(CultureInfo.CurrentUICulture, WixStrings.EXP_UnexpectedComplexReferenceChildType, Enum.GetName(typeof(ComplexReferenceChildType), wixComplexReferenceRow.ChildType))); } break; case ComplexReferenceParentType.Module: switch (wixComplexReferenceRow.ChildType) { case ComplexReferenceChildType.Component: if (componentsToModules.ContainsKey(wixComplexReferenceRow.ChildId)) { this.OnMessage(WixErrors.ComponentReferencedTwice(section.SourceLineNumbers, wixComplexReferenceRow.ChildId)); continue; } else { componentsToModules.Add(wixComplexReferenceRow.ChildId, wixComplexReferenceRow); // should always be new // add a row to the ModuleComponents table Table moduleComponentsTable = output.EnsureTable(this.tableDefinitions["ModuleComponents"]); Row row = moduleComponentsTable.CreateRow(null); if (this.sectionIdOnRows) { row.SectionId = section.Id; } row[0] = wixComplexReferenceRow.ChildId; row[1] = wixComplexReferenceRow.ParentId; row[2] = wixComplexReferenceRow.ParentLanguage; } // index the component for finding orphaned records string componentSymbolName = String.Concat("Component:", wixComplexReferenceRow.ChildId); referencedComponents.Add(componentSymbolName); break; default: throw new InvalidOperationException(String.Format(CultureInfo.CurrentUICulture, WixStrings.EXP_UnexpectedComplexReferenceChildType, Enum.GetName(typeof(ComplexReferenceChildType), wixComplexReferenceRow.ChildType))); } break; case ComplexReferenceParentType.Patch: switch (wixComplexReferenceRow.ChildType) { case ComplexReferenceChildType.PatchFamily: case ComplexReferenceChildType.PatchFamilyGroup: break; default: throw new InvalidOperationException(String.Format(CultureInfo.CurrentUICulture, WixStrings.EXP_UnexpectedComplexReferenceChildType, Enum.GetName(typeof(ComplexReferenceChildType), wixComplexReferenceRow.ChildType))); } break; case ComplexReferenceParentType.Product: switch (wixComplexReferenceRow.ChildType) { case ComplexReferenceChildType.Feature: connection = featuresToFeatures[wixComplexReferenceRow.ChildId]; if (null != connection) { this.OnMessage(WixErrors.MultiplePrimaryReferences(section.SourceLineNumbers, wixComplexReferenceRow.ChildType.ToString(), wixComplexReferenceRow.ChildId, wixComplexReferenceRow.ParentType.ToString(), wixComplexReferenceRow.ParentId, (null != connection.PrimaryFeature ? "Feature" : "Product"), (null != connection.PrimaryFeature ? connection.PrimaryFeature : this.activeOutput.EntrySection.Id))); continue; } featuresToFeatures.Add(new ConnectToFeature(section, wixComplexReferenceRow.ChildId, null, wixComplexReferenceRow.IsPrimary)); break; default: throw new InvalidOperationException(String.Format(CultureInfo.CurrentUICulture, WixStrings.EXP_UnexpectedComplexReferenceChildType, Enum.GetName(typeof(ComplexReferenceChildType), wixComplexReferenceRow.ChildType))); } break; default: // Note: Groups have been processed before getting here so they are not handled by any case above. throw new InvalidOperationException(String.Format(CultureInfo.CurrentUICulture, WixStrings.EXP_UnexpectedComplexReferenceChildType, Enum.GetName(typeof(ComplexReferenceParentType), wixComplexReferenceRow.ParentType))); } } } } } /// /// Flattens all complex references in all sections in the collection. /// /// Sections that are referenced during the link process. private void FlattenSectionsComplexReferences(IEnumerable
sections) { Hashtable parentGroups = new Hashtable(); Hashtable parentGroupsSections = new Hashtable(); Hashtable parentGroupsNeedingProcessing = new Hashtable(); // DisplaySectionComplexReferences("--- section's complex references before flattening ---", sections); // Step 1: Gather all of the complex references that are going participate // in the flatting process. This means complex references that have "grouping // parents" of Features, Modules, and, of course, Groups. These references // that participate in a "grouping parent" will be removed from their section // now and after processing added back in Step 3 below. foreach (Section section in sections) { Table wixComplexReferenceTable = section.Tables["WixComplexReference"]; if (null != wixComplexReferenceTable) { // Count down because we'll sometimes remove items from the list. for (int i = wixComplexReferenceTable.Rows.Count - 1; i >= 0; --i) { WixComplexReferenceRow wixComplexReferenceRow = (WixComplexReferenceRow)wixComplexReferenceTable.Rows[i]; // Only process the "grouping parents" such as FeatureGroup, ComponentGroup, Feature, // and Module. Non-grouping complex references are simple and // resolved during normal complex reference resolutions. if (ComplexReferenceParentType.FeatureGroup == wixComplexReferenceRow.ParentType || ComplexReferenceParentType.ComponentGroup == wixComplexReferenceRow.ParentType || ComplexReferenceParentType.Feature == wixComplexReferenceRow.ParentType || ComplexReferenceParentType.Module == wixComplexReferenceRow.ParentType || ComplexReferenceParentType.PatchFamilyGroup == wixComplexReferenceRow.ParentType || ComplexReferenceParentType.Product == wixComplexReferenceRow.ParentType) { string parentTypeAndId = CombineTypeAndId(wixComplexReferenceRow.ParentType, wixComplexReferenceRow.ParentId); // Group all complex references with a common parent // together so we can find them quickly while processing in // Step 2. ArrayList childrenComplexRefs = parentGroups[parentTypeAndId] as ArrayList; if (null == childrenComplexRefs) { childrenComplexRefs = new ArrayList(); parentGroups.Add(parentTypeAndId, childrenComplexRefs); } childrenComplexRefs.Add(wixComplexReferenceRow); wixComplexReferenceTable.Rows.RemoveAt(i); // Remember the mapping from set of complex references with a common // parent to their section. We'll need this to add them back to the // correct section in Step 3. Section parentSection = parentGroupsSections[parentTypeAndId] as Section; if (null == parentSection) { parentGroupsSections.Add(parentTypeAndId, section); } // Debug.Assert(section == (Section)parentGroupsSections[parentTypeAndId]); // If the child of the complex reference is another group, then in Step 2 // we're going to have to process this complex reference again to copy // the child group's references into the parent group. if ((ComplexReferenceChildType.ComponentGroup == wixComplexReferenceRow.ChildType) || (ComplexReferenceChildType.FeatureGroup == wixComplexReferenceRow.ChildType) || (ComplexReferenceChildType.PatchFamilyGroup == wixComplexReferenceRow.ChildType)) { if (!parentGroupsNeedingProcessing.ContainsKey(parentTypeAndId)) { parentGroupsNeedingProcessing.Add(parentTypeAndId, section); } // Debug.Assert(section == (Section)parentGroupsNeedingProcessing[parentTypeAndId]); } } } } } Debug.Assert(parentGroups.Count == parentGroupsSections.Count); Debug.Assert(parentGroupsNeedingProcessing.Count <= parentGroups.Count); // DisplaySectionComplexReferences("\r\n\r\n--- section's complex references middle of flattening ---", sections); // Step 2: Loop through the parent groups that have nested groups removing // them from the hash table as they are processed. At the end of this the // complex references should all be flattened. string[] keys = new string[parentGroupsNeedingProcessing.Keys.Count]; parentGroupsNeedingProcessing.Keys.CopyTo(keys, 0); foreach (string key in keys) { if (parentGroupsNeedingProcessing.Contains(key)) { Stack loopDetector = new Stack(); this.FlattenGroup(key, loopDetector, parentGroups, parentGroupsNeedingProcessing); } else { // the group must have allready been procesed and removed from the hash table } } Debug.Assert(0 == parentGroupsNeedingProcessing.Count); // Step 3: Finally, ensure that all of the groups that were removed // in Step 1 and flattened in Step 2 are added to their appropriate // section. This is where we will toss out the final no-longer-needed // groups. foreach (string parentGroup in parentGroups.Keys) { Section section = (Section)parentGroupsSections[parentGroup]; Table wixComplexReferenceTable = section.Tables["WixComplexReference"]; foreach (WixComplexReferenceRow wixComplexReferenceRow in (ArrayList)parentGroups[parentGroup]) { if ((ComplexReferenceParentType.FeatureGroup != wixComplexReferenceRow.ParentType) && (ComplexReferenceParentType.ComponentGroup != wixComplexReferenceRow.ParentType) && (ComplexReferenceParentType.PatchFamilyGroup != wixComplexReferenceRow.ParentType)) { wixComplexReferenceTable.Rows.Add(wixComplexReferenceRow); } } } // DisplaySectionComplexReferences("\r\n\r\n--- section's complex references after flattening ---", sections); } private string CombineTypeAndId(ComplexReferenceParentType type, string id) { return String.Concat(type.ToString(), ":", id); } private string CombineTypeAndId(ComplexReferenceChildType type, string id) { return String.Concat(type.ToString(), ":", id); } /// /// Recursively processes the group. /// /// String combination type and id of group to process next. /// Stack of groups processed thus far. Used to detect loops. /// Hash table of complex references grouped by parent id. /// Hash table of parent groups that still have nested groups that need to be flattened. private void FlattenGroup(string parentTypeAndId, Stack loopDetector, Hashtable parentGroups, Hashtable parentGroupsNeedingProcessing) { Debug.Assert(parentGroupsNeedingProcessing.Contains(parentTypeAndId)); loopDetector.Push(parentTypeAndId); // push this complex reference parent identfier into the stack for loop verifying ArrayList allNewChildComplexReferences = new ArrayList(); ArrayList referencesToParent = (ArrayList)parentGroups[parentTypeAndId]; foreach (WixComplexReferenceRow wixComplexReferenceRow in referencesToParent) { Debug.Assert(ComplexReferenceParentType.ComponentGroup == wixComplexReferenceRow.ParentType || ComplexReferenceParentType.FeatureGroup == wixComplexReferenceRow.ParentType || ComplexReferenceParentType.Feature == wixComplexReferenceRow.ParentType || ComplexReferenceParentType.Module == wixComplexReferenceRow.ParentType || ComplexReferenceParentType.Product == wixComplexReferenceRow.ParentType || ComplexReferenceParentType.PatchFamilyGroup == wixComplexReferenceRow.ParentType || ComplexReferenceParentType.Patch == wixComplexReferenceRow.ParentType); Debug.Assert(parentTypeAndId == CombineTypeAndId(wixComplexReferenceRow.ParentType, wixComplexReferenceRow.ParentId)); // We are only interested processing when the child is a group. if ((ComplexReferenceChildType.ComponentGroup == wixComplexReferenceRow.ChildType) || (ComplexReferenceChildType.FeatureGroup == wixComplexReferenceRow.ChildType) || (ComplexReferenceChildType.PatchFamilyGroup == wixComplexReferenceRow.ChildType)) { string childTypeAndId = CombineTypeAndId(wixComplexReferenceRow.ChildType, wixComplexReferenceRow.ChildId); if (loopDetector.Contains(childTypeAndId)) { // Create a comma delimited list of the references that participate in the // loop for the error message. Start at the bottom of the stack and work the // way up to present the loop as a directed graph. object[] stack = loopDetector.ToArray(); StringBuilder loop = new StringBuilder(); for (int i = stack.Length - 1; i >= 0; --i) { loop.Append((string)stack[i]); if (0 < i) { loop.Append(" -> "); } } this.OnMessage(WixErrors.ReferenceLoopDetected(wixComplexReferenceRow.Table.Section == null ? null : wixComplexReferenceRow.Table.Section.SourceLineNumbers, loop.ToString())); // Cleanup the parentGroupsNeedingProcessing and the loopDetector just like the // exit of this method does at the end because we are exiting early. loopDetector.Pop(); parentGroupsNeedingProcessing.Remove(parentTypeAndId); return; // bail } // Check to see if the child group still needs to be processed. If so, // go do that so that we'll get all of that children's (and children's // children) complex references correctly merged into our parent group. if (parentGroupsNeedingProcessing.ContainsKey(childTypeAndId)) { this.FlattenGroup(childTypeAndId, loopDetector, parentGroups, parentGroupsNeedingProcessing); } // If the child is a parent to anything (i.e. the parent has grandchildren) // clone each of the children's complex references, repoint them to the parent // complex reference (because we're moving references up the tree), and finally // add the cloned child's complex reference to the list of complex references // that we'll eventually add to the parent group. ArrayList referencesToChild = (ArrayList)parentGroups[childTypeAndId]; if (null != referencesToChild) { foreach (WixComplexReferenceRow crefChild in referencesToChild) { // Only merge up the non-group items since groups are purged // after this part of the processing anyway (cloning them would // be a complete waste of time). if ((ComplexReferenceChildType.FeatureGroup != crefChild.ChildType) || (ComplexReferenceChildType.ComponentGroup != crefChild.ChildType) || (ComplexReferenceChildType.PatchFamilyGroup != crefChild.ChildType)) { WixComplexReferenceRow crefChildClone = crefChild.Clone(); Debug.Assert(crefChildClone.ParentId == wixComplexReferenceRow.ChildId); crefChildClone.Reparent(wixComplexReferenceRow); allNewChildComplexReferences.Add(crefChildClone); } } } } } // Add the children group's complex references to the parent // group. Clean out any left over groups and quietly remove any // duplicate complex references that occurred during the merge. referencesToParent.AddRange(allNewChildComplexReferences); referencesToParent.Sort(); for (int i = referencesToParent.Count - 1; i >= 0; --i) { WixComplexReferenceRow wixComplexReferenceRow = (WixComplexReferenceRow)referencesToParent[i]; if ((ComplexReferenceChildType.FeatureGroup == wixComplexReferenceRow.ChildType) || (ComplexReferenceChildType.ComponentGroup == wixComplexReferenceRow.ChildType) || (ComplexReferenceChildType.PatchFamilyGroup == wixComplexReferenceRow.ChildType)) { referencesToParent.RemoveAt(i); } else if (i > 0) { // Since the list is already sorted, we can find duplicates by simply // looking at the next sibling in the list and tossing out one if they // match. WixComplexReferenceRow crefCompare = (WixComplexReferenceRow)referencesToParent[i - 1]; if (0 == wixComplexReferenceRow.CompareToWithoutConsideringPrimary(crefCompare)) { referencesToParent.RemoveAt(i); } } } loopDetector.Pop(); // pop this complex reference off the stack since we're done verify the loop here parentGroupsNeedingProcessing.Remove(parentTypeAndId); // remove the newly processed complex reference } /* /// /// Debugging method for displaying the section complex references. /// /// The header. /// The sections to display. private void DisplaySectionComplexReferences(string header, SectionCollection sections) { Console.WriteLine(header); foreach (Section section in sections) { Table wixComplexReferenceTable = section.Tables["WixComplexReference"]; foreach (WixComplexReferenceRow cref in wixComplexReferenceTable.Rows) { Console.WriteLine("Section: {0} Parent: {1} Type: {2} Child: {3} Primary: {4}", section.Id, cref.ParentId, cref.ParentType, cref.ChildId, cref.IsPrimary); } } } */ /// /// Flattens the tables used in a Bundle. /// /// Output containing the tables to process. private void FlattenBundleTables(Output output) { if (OutputType.Bundle != output.Type) { return; } // We need to flatten the nested PayloadGroups and PackageGroups under // UX, Chain, and any Containers. When we're done, the WixGroups table // will hold Payloads under UX, ChainPackages (references?) under Chain, // and ChainPackages/Payloads under the attached and any detatched // Containers. WixGroupingOrdering groups = new WixGroupingOrdering(output, this); // Create UX payloads and Package payloads groups.UseTypes(new string[] { "Container", "Layout", "PackageGroup", "PayloadGroup", "Package" }, new string[] { "PackageGroup", "Package", "PayloadGroup", "Payload" }); groups.FlattenAndRewriteGroups("Package", false); groups.FlattenAndRewriteGroups("Container", false); groups.FlattenAndRewriteGroups("Layout", false); // Create Chain packages... groups.UseTypes(new string[] { "PackageGroup" }, new string[] { "Package", "PackageGroup" }); groups.FlattenAndRewriteRows("PackageGroup", "WixChain", false); groups.RemoveUsedGroupRows(); } /// /// Resolves the features connected to other features in the active output. /// /// Feature to feature complex references. /// All symbols loaded from the sections. private void ResolveFeatureToFeatureConnects(ConnectToFeatureCollection featuresToFeatures, IDictionary allSymbols) { foreach (ConnectToFeature connection in featuresToFeatures) { WixSimpleReferenceRow wixSimpleReferenceRow = new WixSimpleReferenceRow(null, this.tableDefinitions["WixSimpleReference"]); wixSimpleReferenceRow.TableName = "Feature"; wixSimpleReferenceRow.PrimaryKeys = connection.ChildId; Symbol symbol; if (!allSymbols.TryGetValue(wixSimpleReferenceRow.SymbolicName, out symbol)) { continue; } Row row = symbol.Row; row[1] = connection.PrimaryFeature; } } /// /// Copies a table's rows to an output table. /// /// Source table to copy rows from. /// Destination table in output to copy rows into. /// Id of the section that the table lives in. private void CopyTableRowsToOutputTable(Table table, Table outputTable, string sectionId) { int[] localizedColumns = new int[table.Definition.Columns.Count]; int localizedColumnCount = 0; // if there are localization strings, figure out which columns can be localized in this table if (null != this.Localizer) { for (int i = 0; i < table.Definition.Columns.Count; i++) { if (table.Definition.Columns[i].IsLocalizable) { localizedColumns[localizedColumnCount++] = i; } } } // process each row in the table doing the string resource substitutions // then add the row to the output foreach (Row row in table.Rows) { for (int j = 0; j < localizedColumnCount; j++) { Field field = row.Fields[localizedColumns[j]]; if (null != field.Data) { field.Data = this.WixVariableResolver.ResolveVariables(row.SourceLineNumbers, (string)field.Data, true); } } row.SectionId = (this.sectionIdOnRows ? sectionId : null); outputTable.Rows.Add(row); } } /// /// Set sequence numbers for all the actions and create rows in the output object. /// /// Collection of actions to schedule. /// Collection of actions to suppress. private void SequenceActions(List actionRows, List suppressActionRows) { WixActionRowCollection overridableActionRows = new WixActionRowCollection(); WixActionRowCollection requiredActionRows = new WixActionRowCollection(); ArrayList scheduledActionRows = new ArrayList(); // gather the required actions for the output type if (OutputType.Product == this.activeOutput.Type) { // AdminExecuteSequence table overridableActionRows.Add(this.standardActions[SequenceTable.AdminExecuteSequence, "CostFinalize"]); overridableActionRows.Add(this.standardActions[SequenceTable.AdminExecuteSequence, "CostInitialize"]); overridableActionRows.Add(this.standardActions[SequenceTable.AdminExecuteSequence, "FileCost"]); overridableActionRows.Add(this.standardActions[SequenceTable.AdminExecuteSequence, "InstallAdminPackage"]); overridableActionRows.Add(this.standardActions[SequenceTable.AdminExecuteSequence, "InstallFiles"]); overridableActionRows.Add(this.standardActions[SequenceTable.AdminExecuteSequence, "InstallFinalize"]); overridableActionRows.Add(this.standardActions[SequenceTable.AdminExecuteSequence, "InstallInitialize"]); overridableActionRows.Add(this.standardActions[SequenceTable.AdminExecuteSequence, "InstallValidate"]); // AdminUISequence table overridableActionRows.Add(this.standardActions[SequenceTable.AdminUISequence, "CostFinalize"]); overridableActionRows.Add(this.standardActions[SequenceTable.AdminUISequence, "CostInitialize"]); overridableActionRows.Add(this.standardActions[SequenceTable.AdminUISequence, "ExecuteAction"]); overridableActionRows.Add(this.standardActions[SequenceTable.AdminUISequence, "FileCost"]); // AdvtExecuteSequence table overridableActionRows.Add(this.standardActions[SequenceTable.AdvtExecuteSequence, "CostFinalize"]); overridableActionRows.Add(this.standardActions[SequenceTable.AdvtExecuteSequence, "CostInitialize"]); overridableActionRows.Add(this.standardActions[SequenceTable.AdvtExecuteSequence, "InstallFinalize"]); overridableActionRows.Add(this.standardActions[SequenceTable.AdvtExecuteSequence, "InstallInitialize"]); overridableActionRows.Add(this.standardActions[SequenceTable.AdvtExecuteSequence, "InstallValidate"]); overridableActionRows.Add(this.standardActions[SequenceTable.AdvtExecuteSequence, "PublishFeatures"]); overridableActionRows.Add(this.standardActions[SequenceTable.AdvtExecuteSequence, "PublishProduct"]); // InstallExecuteSequence table overridableActionRows.Add(this.standardActions[SequenceTable.InstallExecuteSequence, "CostFinalize"]); overridableActionRows.Add(this.standardActions[SequenceTable.InstallExecuteSequence, "CostInitialize"]); overridableActionRows.Add(this.standardActions[SequenceTable.InstallExecuteSequence, "FileCost"]); overridableActionRows.Add(this.standardActions[SequenceTable.InstallExecuteSequence, "InstallFinalize"]); overridableActionRows.Add(this.standardActions[SequenceTable.InstallExecuteSequence, "InstallInitialize"]); overridableActionRows.Add(this.standardActions[SequenceTable.InstallExecuteSequence, "InstallValidate"]); overridableActionRows.Add(this.standardActions[SequenceTable.InstallExecuteSequence, "ProcessComponents"]); overridableActionRows.Add(this.standardActions[SequenceTable.InstallExecuteSequence, "PublishFeatures"]); overridableActionRows.Add(this.standardActions[SequenceTable.InstallExecuteSequence, "PublishProduct"]); overridableActionRows.Add(this.standardActions[SequenceTable.InstallExecuteSequence, "RegisterProduct"]); overridableActionRows.Add(this.standardActions[SequenceTable.InstallExecuteSequence, "RegisterUser"]); overridableActionRows.Add(this.standardActions[SequenceTable.InstallExecuteSequence, "UnpublishFeatures"]); overridableActionRows.Add(this.standardActions[SequenceTable.InstallExecuteSequence, "ValidateProductID"]); // InstallUISequence table overridableActionRows.Add(this.standardActions[SequenceTable.InstallUISequence, "CostFinalize"]); overridableActionRows.Add(this.standardActions[SequenceTable.InstallUISequence, "CostInitialize"]); overridableActionRows.Add(this.standardActions[SequenceTable.InstallUISequence, "ExecuteAction"]); overridableActionRows.Add(this.standardActions[SequenceTable.InstallUISequence, "FileCost"]); overridableActionRows.Add(this.standardActions[SequenceTable.InstallUISequence, "ValidateProductID"]); } // gather the required actions for each table foreach (Table table in this.activeOutput.Tables) { switch (table.Name) { case "AppSearch": overridableActionRows.Add(this.standardActions[SequenceTable.InstallExecuteSequence, "AppSearch"], true); overridableActionRows.Add(this.standardActions[SequenceTable.InstallUISequence, "AppSearch"], true); break; case "BindImage": overridableActionRows.Add(this.standardActions[SequenceTable.InstallExecuteSequence, "BindImage"], true); break; case "CCPSearch": overridableActionRows.Add(this.standardActions[SequenceTable.InstallExecuteSequence, "AppSearch"], true); overridableActionRows.Add(this.standardActions[SequenceTable.InstallExecuteSequence, "CCPSearch"], true); overridableActionRows.Add(this.standardActions[SequenceTable.InstallExecuteSequence, "RMCCPSearch"], true); overridableActionRows.Add(this.standardActions[SequenceTable.InstallUISequence, "AppSearch"], true); overridableActionRows.Add(this.standardActions[SequenceTable.InstallUISequence, "CCPSearch"], true); overridableActionRows.Add(this.standardActions[SequenceTable.InstallUISequence, "RMCCPSearch"], true); break; case "Class": overridableActionRows.Add(this.standardActions[SequenceTable.AdvtExecuteSequence, "RegisterClassInfo"], true); overridableActionRows.Add(this.standardActions[SequenceTable.InstallExecuteSequence, "RegisterClassInfo"], true); overridableActionRows.Add(this.standardActions[SequenceTable.InstallExecuteSequence, "UnregisterClassInfo"], true); break; case "Complus": overridableActionRows.Add(this.standardActions[SequenceTable.InstallExecuteSequence, "RegisterComPlus"], true); overridableActionRows.Add(this.standardActions[SequenceTable.InstallExecuteSequence, "UnregisterComPlus"], true); break; case "CreateFolder": overridableActionRows.Add(this.standardActions[SequenceTable.InstallExecuteSequence, "CreateFolders"], true); overridableActionRows.Add(this.standardActions[SequenceTable.InstallExecuteSequence, "RemoveFolders"], true); break; case "DuplicateFile": overridableActionRows.Add(this.standardActions[SequenceTable.InstallExecuteSequence, "DuplicateFiles"], true); overridableActionRows.Add(this.standardActions[SequenceTable.InstallExecuteSequence, "RemoveDuplicateFiles"], true); break; case "Environment": overridableActionRows.Add(this.standardActions[SequenceTable.InstallExecuteSequence, "WriteEnvironmentStrings"], true); overridableActionRows.Add(this.standardActions[SequenceTable.InstallExecuteSequence, "RemoveEnvironmentStrings"], true); break; case "Extension": overridableActionRows.Add(this.standardActions[SequenceTable.AdvtExecuteSequence, "RegisterExtensionInfo"], true); overridableActionRows.Add(this.standardActions[SequenceTable.InstallExecuteSequence, "RegisterExtensionInfo"], true); overridableActionRows.Add(this.standardActions[SequenceTable.InstallExecuteSequence, "UnregisterExtensionInfo"], true); break; case "File": overridableActionRows.Add(this.standardActions[SequenceTable.InstallExecuteSequence, "InstallFiles"], true); overridableActionRows.Add(this.standardActions[SequenceTable.InstallExecuteSequence, "RemoveFiles"], true); break; case "Font": overridableActionRows.Add(this.standardActions[SequenceTable.InstallExecuteSequence, "RegisterFonts"], true); overridableActionRows.Add(this.standardActions[SequenceTable.InstallExecuteSequence, "UnregisterFonts"], true); break; case "IniFile": case "RemoveIniFile": overridableActionRows.Add(this.standardActions[SequenceTable.InstallExecuteSequence, "WriteIniValues"], true); overridableActionRows.Add(this.standardActions[SequenceTable.InstallExecuteSequence, "RemoveIniValues"], true); break; case "IsolatedComponent": overridableActionRows.Add(this.standardActions[SequenceTable.InstallExecuteSequence, "IsolateComponents"], true); break; case "LaunchCondition": overridableActionRows.Add(this.standardActions[SequenceTable.InstallExecuteSequence, "LaunchConditions"], true); overridableActionRows.Add(this.standardActions[SequenceTable.InstallUISequence, "LaunchConditions"], true); break; case "MIME": overridableActionRows.Add(this.standardActions[SequenceTable.AdvtExecuteSequence, "RegisterMIMEInfo"], true); overridableActionRows.Add(this.standardActions[SequenceTable.InstallExecuteSequence, "RegisterMIMEInfo"], true); overridableActionRows.Add(this.standardActions[SequenceTable.InstallExecuteSequence, "UnregisterMIMEInfo"], true); break; case "MoveFile": overridableActionRows.Add(this.standardActions[SequenceTable.InstallExecuteSequence, "MoveFiles"], true); break; case "MsiAssembly": overridableActionRows.Add(this.standardActions[SequenceTable.AdvtExecuteSequence, "MsiPublishAssemblies"], true); overridableActionRows.Add(this.standardActions[SequenceTable.InstallExecuteSequence, "MsiPublishAssemblies"], true); overridableActionRows.Add(this.standardActions[SequenceTable.InstallExecuteSequence, "MsiUnpublishAssemblies"], true); break; case "MsiServiceConfig": case "MsiServiceConfigFailureActions": overridableActionRows.Add(this.standardActions[SequenceTable.InstallExecuteSequence, "MsiConfigureServices"], true); break; case "ODBCDataSource": case "ODBCTranslator": case "ODBCDriver": overridableActionRows.Add(this.standardActions[SequenceTable.InstallExecuteSequence, "SetODBCFolders"], true); overridableActionRows.Add(this.standardActions[SequenceTable.InstallExecuteSequence, "InstallODBC"], true); overridableActionRows.Add(this.standardActions[SequenceTable.InstallExecuteSequence, "RemoveODBC"], true); break; case "ProgId": overridableActionRows.Add(this.standardActions[SequenceTable.AdvtExecuteSequence, "RegisterProgIdInfo"], true); overridableActionRows.Add(this.standardActions[SequenceTable.InstallExecuteSequence, "RegisterProgIdInfo"], true); overridableActionRows.Add(this.standardActions[SequenceTable.InstallExecuteSequence, "UnregisterProgIdInfo"], true); break; case "PublishComponent": overridableActionRows.Add(this.standardActions[SequenceTable.AdvtExecuteSequence, "PublishComponents"], true); overridableActionRows.Add(this.standardActions[SequenceTable.InstallExecuteSequence, "PublishComponents"], true); overridableActionRows.Add(this.standardActions[SequenceTable.InstallExecuteSequence, "UnpublishComponents"], true); break; case "Registry": case "RemoveRegistry": overridableActionRows.Add(this.standardActions[SequenceTable.InstallExecuteSequence, "WriteRegistryValues"], true); overridableActionRows.Add(this.standardActions[SequenceTable.InstallExecuteSequence, "RemoveRegistryValues"], true); break; case "RemoveFile": overridableActionRows.Add(this.standardActions[SequenceTable.InstallExecuteSequence, "RemoveFiles"], true); break; case "SelfReg": overridableActionRows.Add(this.standardActions[SequenceTable.InstallExecuteSequence, "SelfRegModules"], true); overridableActionRows.Add(this.standardActions[SequenceTable.InstallExecuteSequence, "SelfUnregModules"], true); break; case "ServiceControl": overridableActionRows.Add(this.standardActions[SequenceTable.InstallExecuteSequence, "StartServices"], true); overridableActionRows.Add(this.standardActions[SequenceTable.InstallExecuteSequence, "StopServices"], true); overridableActionRows.Add(this.standardActions[SequenceTable.InstallExecuteSequence, "DeleteServices"], true); break; case "ServiceInstall": overridableActionRows.Add(this.standardActions[SequenceTable.InstallExecuteSequence, "InstallServices"], true); break; case "Shortcut": overridableActionRows.Add(this.standardActions[SequenceTable.AdvtExecuteSequence, "CreateShortcuts"], true); overridableActionRows.Add(this.standardActions[SequenceTable.InstallExecuteSequence, "CreateShortcuts"], true); overridableActionRows.Add(this.standardActions[SequenceTable.InstallExecuteSequence, "RemoveShortcuts"], true); break; case "TypeLib": overridableActionRows.Add(this.standardActions[SequenceTable.InstallExecuteSequence, "RegisterTypeLibraries"], true); overridableActionRows.Add(this.standardActions[SequenceTable.InstallExecuteSequence, "UnregisterTypeLibraries"], true); break; case "Upgrade": overridableActionRows.Add(this.standardActions[SequenceTable.InstallExecuteSequence, "FindRelatedProducts"], true); overridableActionRows.Add(this.standardActions[SequenceTable.InstallUISequence, "FindRelatedProducts"], true); // Only add the MigrateFeatureStates action if MigrateFeature attribute is set to yes on at least one UpgradeVersion element. foreach (Row row in table.Rows) { int options = (int)row[4]; if (MsiInterop.MsidbUpgradeAttributesMigrateFeatures == (options & MsiInterop.MsidbUpgradeAttributesMigrateFeatures)) { overridableActionRows.Add(this.standardActions[SequenceTable.InstallExecuteSequence, "MigrateFeatureStates"], true); overridableActionRows.Add(this.standardActions[SequenceTable.InstallUISequence, "MigrateFeatureStates"], true); break; } } break; } } // index all the action rows (look for collisions) foreach (WixActionRow actionRow in actionRows) { if (actionRow.Overridable) // overridable action { WixActionRow collidingActionRow = overridableActionRows[actionRow.SequenceTable, actionRow.Action]; if (null != collidingActionRow) { this.OnMessage(WixErrors.OverridableActionCollision(actionRow.SourceLineNumbers, actionRow.SequenceTable.ToString(), actionRow.Action)); if (null != collidingActionRow.SourceLineNumbers) { this.OnMessage(WixErrors.OverridableActionCollision2(collidingActionRow.SourceLineNumbers)); } } else { overridableActionRows.Add(actionRow); } } else // unscheduled/scheduled action { // unscheduled action (allowed for certain standard actions) if (null == actionRow.Before && null == actionRow.After && 0 == actionRow.Sequence) { WixActionRow standardAction = this.standardActions[actionRow.SequenceTable, actionRow.Action]; if (null != standardAction) { // populate the sequence from the standard action actionRow.Sequence = standardAction.Sequence; } else // not a supported unscheduled action { throw new InvalidOperationException(WixStrings.EXP_FoundActionRowWithNoSequenceBeforeOrAfterColumnSet); } } WixActionRow collidingActionRow = requiredActionRows[actionRow.SequenceTable, actionRow.Action]; if (null != collidingActionRow) { this.OnMessage(WixErrors.ActionCollision(actionRow.SourceLineNumbers, actionRow.SequenceTable.ToString(), actionRow.Action)); if (null != collidingActionRow.SourceLineNumbers) { this.OnMessage(WixErrors.ActionCollision2(collidingActionRow.SourceLineNumbers)); } } else { requiredActionRows.Add(actionRow.Clone()); } } } // add the overridable action rows that are not overridden to the required action rows foreach (WixActionRow actionRow in overridableActionRows) { if (null == requiredActionRows[actionRow.SequenceTable, actionRow.Action]) { requiredActionRows.Add(actionRow.Clone()); } } // suppress the required actions that are overridable foreach (Row suppressActionRow in suppressActionRows) { SequenceTable sequenceTable = (SequenceTable)Enum.Parse(typeof(SequenceTable), (string)suppressActionRow[0]); string action = (string)suppressActionRow[1]; // get the action being suppressed (if it exists) WixActionRow requiredActionRow = requiredActionRows[sequenceTable, action]; // if there is an overridable row to suppress; suppress it // there is no warning if there is no action to suppress because the action may be suppressed from a merge module in the binder if (null != requiredActionRow) { if (requiredActionRow.Overridable) { this.OnMessage(WixWarnings.SuppressAction(suppressActionRow.SourceLineNumbers, action, sequenceTable.ToString())); if (null != requiredActionRow.SourceLineNumbers) { this.OnMessage(WixWarnings.SuppressAction2(requiredActionRow.SourceLineNumbers)); } requiredActionRows.Remove(sequenceTable, action); } else // suppressing a non-overridable action row { this.OnMessage(WixErrors.SuppressNonoverridableAction(suppressActionRow.SourceLineNumbers, sequenceTable.ToString(), action)); if (null != requiredActionRow.SourceLineNumbers) { this.OnMessage(WixErrors.SuppressNonoverridableAction2(requiredActionRow.SourceLineNumbers)); } } } } // create a copy of the required action rows so that new rows can be added while enumerating WixActionRow[] copyOfRequiredActionRows = new WixActionRow[requiredActionRows.Count]; requiredActionRows.CopyTo(copyOfRequiredActionRows, 0); // build up dependency trees of the relatively scheduled actions foreach (WixActionRow actionRow in copyOfRequiredActionRows) { if (0 == actionRow.Sequence) { // check for standard actions that don't have a sequence number in a merge module if (OutputType.Module == this.activeOutput.Type && WindowsInstallerStandard.IsStandardAction(actionRow.Action)) { this.OnMessage(WixErrors.StandardActionRelativelyScheduledInModule(actionRow.SourceLineNumbers, actionRow.SequenceTable.ToString(), actionRow.Action)); } this.SequenceActionRow(actionRow, requiredActionRows); } else if (OutputType.Module == this.activeOutput.Type && 0 < actionRow.Sequence && !WindowsInstallerStandard.IsStandardAction(actionRow.Action)) // check for custom actions and dialogs that have a sequence number { this.OnMessage(WixErrors.CustomActionSequencedInModule(actionRow.SourceLineNumbers, actionRow.SequenceTable.ToString(), actionRow.Action)); } } // look for standard actions with sequence restrictions that aren't necessarily scheduled based on the presence of a particular table if (requiredActionRows.Contains(SequenceTable.InstallExecuteSequence, "DuplicateFiles") && !requiredActionRows.Contains(SequenceTable.InstallExecuteSequence, "InstallFiles")) { requiredActionRows.Add(this.standardActions[SequenceTable.InstallExecuteSequence, "InstallFiles"], true); } // schedule actions if (OutputType.Module == this.activeOutput.Type) { // add the action row to the list of scheduled action rows scheduledActionRows.AddRange(requiredActionRows); } else { // process each sequence table individually foreach (SequenceTable sequenceTable in Enum.GetValues(typeof(SequenceTable))) { // create a collection of just the action rows in this sequence WixActionRowCollection sequenceActionRows = new WixActionRowCollection(); foreach (WixActionRow actionRow in requiredActionRows) { if (sequenceTable == actionRow.SequenceTable) { sequenceActionRows.Add(actionRow); } } // schedule the absolutely scheduled actions (by sorting them by their sequence numbers) ArrayList absoluteActionRows = new ArrayList(); foreach (WixActionRow actionRow in sequenceActionRows) { if (0 != actionRow.Sequence) { // look for sequence number collisions foreach (WixActionRow sequenceScheduledActionRow in absoluteActionRows) { if (sequenceScheduledActionRow.Sequence == actionRow.Sequence) { this.OnMessage(WixWarnings.ActionSequenceCollision(actionRow.SourceLineNumbers, actionRow.SequenceTable.ToString(), actionRow.Action, sequenceScheduledActionRow.Action, actionRow.Sequence)); if (null != sequenceScheduledActionRow.SourceLineNumbers) { this.OnMessage(WixWarnings.ActionSequenceCollision2(sequenceScheduledActionRow.SourceLineNumbers)); } } } absoluteActionRows.Add(actionRow); } } absoluteActionRows.Sort(); // schedule the relatively scheduled actions (by resolving the dependency trees) int previousUsedSequence = 0; ArrayList relativeActionRows = new ArrayList(); for (int j = 0; j < absoluteActionRows.Count; j++) { WixActionRow absoluteActionRow = (WixActionRow)absoluteActionRows[j]; int unusedSequence; // get all the relatively scheduled action rows occuring before this absolutely scheduled action row RowIndexedList allPreviousActionRows = new RowIndexedList(); absoluteActionRow.GetAllPreviousActionRows(sequenceTable, allPreviousActionRows); // get all the relatively scheduled action rows occuring after this absolutely scheduled action row RowIndexedList allNextActionRows = new RowIndexedList(); absoluteActionRow.GetAllNextActionRows(sequenceTable, allNextActionRows); // check for relatively scheduled actions occuring before/after a special action (these have a negative sequence number) if (0 > absoluteActionRow.Sequence && (0 < allPreviousActionRows.Count || 0 < allNextActionRows.Count)) { // create errors for all the before actions foreach (WixActionRow actionRow in allPreviousActionRows) { this.OnMessage(WixErrors.ActionScheduledRelativeToTerminationAction(actionRow.SourceLineNumbers, actionRow.SequenceTable.ToString(), actionRow.Action, absoluteActionRow.Action)); } // create errors for all the after actions foreach (WixActionRow actionRow in allNextActionRows) { this.OnMessage(WixErrors.ActionScheduledRelativeToTerminationAction(actionRow.SourceLineNumbers, actionRow.SequenceTable.ToString(), actionRow.Action, absoluteActionRow.Action)); } // if there is source line information for the absolutely scheduled action display it if (null != absoluteActionRow.SourceLineNumbers) { this.OnMessage(WixErrors.ActionScheduledRelativeToTerminationAction2(absoluteActionRow.SourceLineNumbers)); } continue; } // schedule the action rows before this one unusedSequence = absoluteActionRow.Sequence - 1; for (int i = allPreviousActionRows.Count - 1; i >= 0; i--) { WixActionRow relativeActionRow = (WixActionRow)allPreviousActionRows[i]; // look for collisions if (unusedSequence == previousUsedSequence) { this.OnMessage(WixErrors.NoUniqueActionSequenceNumber(relativeActionRow.SourceLineNumbers, relativeActionRow.SequenceTable.ToString(), relativeActionRow.Action, absoluteActionRow.Action)); if (null != absoluteActionRow.SourceLineNumbers) { this.OnMessage(WixErrors.NoUniqueActionSequenceNumber2(absoluteActionRow.SourceLineNumbers)); } unusedSequence++; } relativeActionRow.Sequence = unusedSequence; relativeActionRows.Add(relativeActionRow); unusedSequence--; } // determine the next used action sequence number int nextUsedSequence; if (absoluteActionRows.Count > j + 1) { nextUsedSequence = ((WixActionRow)absoluteActionRows[j + 1]).Sequence; } else { nextUsedSequence = short.MaxValue + 1; } // schedule the action rows after this one unusedSequence = absoluteActionRow.Sequence + 1; for (int i = 0; i < allNextActionRows.Count; i++) { WixActionRow relativeActionRow = (WixActionRow)allNextActionRows[i]; if (unusedSequence == nextUsedSequence) { this.OnMessage(WixErrors.NoUniqueActionSequenceNumber(relativeActionRow.SourceLineNumbers, relativeActionRow.SequenceTable.ToString(), relativeActionRow.Action, absoluteActionRow.Action)); if (null != absoluteActionRow.SourceLineNumbers) { this.OnMessage(WixErrors.NoUniqueActionSequenceNumber2(absoluteActionRow.SourceLineNumbers)); } unusedSequence--; } relativeActionRow.Sequence = unusedSequence; relativeActionRows.Add(relativeActionRow); unusedSequence++; } // keep track of this sequence number as the previous used sequence number for the next iteration previousUsedSequence = absoluteActionRow.Sequence; } // add the absolutely and relatively scheduled actions to the list of scheduled actions scheduledActionRows.AddRange(absoluteActionRows); scheduledActionRows.AddRange(relativeActionRows); } } // create the action rows for sequences that are not suppressed foreach (WixActionRow actionRow in scheduledActionRows) { // get the table definition for the action (and ensure the proper table exists for a module) TableDefinition sequenceTableDefinition = null; switch (actionRow.SequenceTable) { case SequenceTable.AdminExecuteSequence: if (OutputType.Module == this.activeOutput.Type) { this.activeOutput.EnsureTable(this.tableDefinitions["AdminExecuteSequence"]); sequenceTableDefinition = this.tableDefinitions["ModuleAdminExecuteSequence"]; } else { sequenceTableDefinition = this.tableDefinitions["AdminExecuteSequence"]; } break; case SequenceTable.AdminUISequence: if (OutputType.Module == this.activeOutput.Type) { this.activeOutput.EnsureTable(this.tableDefinitions["AdminUISequence"]); sequenceTableDefinition = this.tableDefinitions["ModuleAdminUISequence"]; } else { sequenceTableDefinition = this.tableDefinitions["AdminUISequence"]; } break; case SequenceTable.AdvtExecuteSequence: if (OutputType.Module == this.activeOutput.Type) { this.activeOutput.EnsureTable(this.tableDefinitions["AdvtExecuteSequence"]); sequenceTableDefinition = this.tableDefinitions["ModuleAdvtExecuteSequence"]; } else { sequenceTableDefinition = this.tableDefinitions["AdvtExecuteSequence"]; } break; case SequenceTable.InstallExecuteSequence: if (OutputType.Module == this.activeOutput.Type) { this.activeOutput.EnsureTable(this.tableDefinitions["InstallExecuteSequence"]); sequenceTableDefinition = this.tableDefinitions["ModuleInstallExecuteSequence"]; } else { sequenceTableDefinition = this.tableDefinitions["InstallExecuteSequence"]; } break; case SequenceTable.InstallUISequence: if (OutputType.Module == this.activeOutput.Type) { this.activeOutput.EnsureTable(this.tableDefinitions["InstallUISequence"]); sequenceTableDefinition = this.tableDefinitions["ModuleInstallUISequence"]; } else { sequenceTableDefinition = this.tableDefinitions["InstallUISequence"]; } break; } // create the action sequence row in the output Table sequenceTable = this.activeOutput.EnsureTable(sequenceTableDefinition); Row row = sequenceTable.CreateRow(actionRow.SourceLineNumbers); if (this.sectionIdOnRows) { row.SectionId = actionRow.SectionId; } if (OutputType.Module == this.activeOutput.Type) { row[0] = actionRow.Action; if (0 != actionRow.Sequence) { row[1] = actionRow.Sequence; } else { bool after = (null == actionRow.Before); row[2] = after ? actionRow.After : actionRow.Before; row[3] = after ? 1 : 0; } row[4] = actionRow.Condition; } else { row[0] = actionRow.Action; row[1] = actionRow.Condition; row[2] = actionRow.Sequence; } } } /// /// Sequence an action before or after a standard action. /// /// The action row to be sequenced. /// Collection of actions which must be included. [SuppressMessage("Microsoft.Globalization", "CA1303:DoNotPassLiteralsAsLocalizedParameters", MessageId = "System.InvalidOperationException.#ctor(System.String)")] private void SequenceActionRow(WixActionRow actionRow, WixActionRowCollection requiredActionRows) { bool after = false; if (actionRow.After != null) { after = true; } else if (actionRow.Before == null) { throw new InvalidOperationException(WixStrings.EXP_FoundActionRowWithNoSequenceBeforeOrAfterColumnSet); } string parentActionName = (after ? actionRow.After : actionRow.Before); WixActionRow parentActionRow = requiredActionRows[actionRow.SequenceTable, parentActionName]; if (null == parentActionRow) { parentActionRow = this.standardActions[actionRow.SequenceTable, parentActionName]; // if the missing parent action is a standard action (with a suggested sequence number), add it if (null != parentActionRow) { // Create a clone to avoid modifying the static copy of the object. parentActionRow = parentActionRow.Clone(); requiredActionRows.Add(parentActionRow); } else { throw new InvalidOperationException(String.Format(CultureInfo.CurrentUICulture, WixStrings.EXP_FoundActionRowWinNonExistentAction, (after ? "After" : "Before"), parentActionName)); } } else if (actionRow == parentActionRow || actionRow.ContainsChildActionRow(parentActionRow)) // cycle detected { throw new WixException(WixErrors.ActionCircularDependency(actionRow.SourceLineNumbers, actionRow.SequenceTable.ToString(), actionRow.Action, parentActionRow.Action)); } // Add this action to the appropriate list of dependent action rows. WixActionRowCollection relatedRows = (after ? parentActionRow.NextActionRows : parentActionRow.PreviousActionRows); relatedRows.Add(actionRow); } /// /// Resolve features for columns that have null guid placeholders. /// /// Rows to resolve. /// Number of the column containing the connection identifier. /// Number of the column containing the feature. /// Connect to feature complex references. /// Hashtable of known components under multiple features. private void ResolveFeatures(IEnumerable rows, int connectionColumn, int featureColumn, ConnectToFeatureCollection connectToFeatures, Hashtable multipleFeatureComponents) { foreach (Row row in rows) { string connectionId = (string)row[connectionColumn]; string featureId = (string)row[featureColumn]; if (emptyGuid == featureId) { ConnectToFeature connection = connectToFeatures[connectionId]; if (null == connection) { // display an error for the component or merge module as approrpriate if (null != multipleFeatureComponents) { this.OnMessage(WixErrors.ComponentExpectedFeature(row.SourceLineNumbers, connectionId, row.Table.Name, row.GetPrimaryKey('/'))); } else { this.OnMessage(WixErrors.MergeModuleExpectedFeature(row.SourceLineNumbers, connectionId)); } } else { // check for unique, implicit, primary feature parents with multiple possible parent features if (this.ShowPedanticMessages && !connection.IsExplicitPrimaryFeature && 0 < connection.ConnectFeatures.Count) { // display a warning for the component or merge module as approrpriate if (null != multipleFeatureComponents) { if (!multipleFeatureComponents.Contains(connectionId)) { this.OnMessage(WixWarnings.ImplicitComponentPrimaryFeature(connectionId)); // remember this component so only one warning is generated for it multipleFeatureComponents[connectionId] = null; } } else { this.OnMessage(WixWarnings.ImplicitMergeModulePrimaryFeature(connectionId)); } } // set the feature row[featureColumn] = connection.PrimaryFeature; } } } } } }