// 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.Globalization; using System.Linq; using WixToolset.Data; using WixToolset.Data.Symbols; using WixToolset.Data.WindowsInstaller; using WixToolset.Extensibility.Services; /// /// Set sequence numbers for all the actions and create symbols in the output object. /// internal class SequenceActionsCommand { public SequenceActionsCommand(IMessaging messaging, IntermediateSection section) { this.Messaging = messaging; this.Section = section; this.RelativeActionsForActions = new Dictionary(); } private IMessaging Messaging { get; } private IntermediateSection Section { get; } private Dictionary RelativeActionsForActions { get; } public void Execute() { var requiredActionSymbols = new Dictionary(); // Index all the action symbols and look for collisions. foreach (var actionSymbol in this.Section.Symbols.OfType()) { if (actionSymbol.Overridable) // overridable action { if (requiredActionSymbols.TryGetValue(actionSymbol.Id.Id, out var collidingActionSymbol)) { if (collidingActionSymbol.Overridable) { this.Messaging.Write(ErrorMessages.OverridableActionCollision(actionSymbol.SourceLineNumbers, actionSymbol.SequenceTable.ToString(), actionSymbol.Action)); if (null != collidingActionSymbol.SourceLineNumbers) { this.Messaging.Write(ErrorMessages.OverridableActionCollision2(collidingActionSymbol.SourceLineNumbers)); } } } else { requiredActionSymbols.Add(actionSymbol.Id.Id, actionSymbol); } } else // unsequenced or sequenced action. { // Unsequenced action (allowed for certain standard actions). if (null == actionSymbol.Before && null == actionSymbol.After && !actionSymbol.Sequence.HasValue) { if (WindowsInstallerStandard.TryGetStandardAction(actionSymbol.Id.Id, out var standardAction)) { // Populate the sequence from the standard action actionSymbol.Sequence = standardAction.Sequence; } else // not a supported unscheduled action. { throw new WixException($"Found action '{actionSymbol.Id.Id}' at {actionSymbol.SourceLineNumbers}' with no Sequence, Before, or After column set. The compiler should have prevented this."); } } if (requiredActionSymbols.TryGetValue(actionSymbol.Id.Id, out var collidingActionSymbol) && !collidingActionSymbol.Overridable) { this.Messaging.Write(ErrorMessages.ActionCollision(actionSymbol.SourceLineNumbers, actionSymbol.SequenceTable.ToString(), actionSymbol.Action)); if (null != collidingActionSymbol.SourceLineNumbers) { this.Messaging.Write(ErrorMessages.ActionCollision2(collidingActionSymbol.SourceLineNumbers)); } } else { requiredActionSymbols[actionSymbol.Id.Id] = actionSymbol; } } } // Get the standard actions required based on symbols in the section. var requiredStandardActions = this.GetRequiredStandardActions(); // Add the overridable action symbols that are not overridden to the required action symbols. foreach (var actionSymbol in requiredStandardActions.Values) { if (!requiredActionSymbols.ContainsKey(actionSymbol.Id.Id)) { requiredActionSymbols.Add(actionSymbol.Id.Id, actionSymbol); } } // Suppress the required actions that are overridable. foreach (var suppressActionSymbol in this.Section.Symbols.OfType()) { var key = suppressActionSymbol.Id.Id; // If there is an overridable symbol 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 (requiredActionSymbols.TryGetValue(key, out var requiredActionSymbol)) { if (requiredActionSymbol.Overridable) { this.Messaging.Write(WarningMessages.SuppressAction(suppressActionSymbol.SourceLineNumbers, suppressActionSymbol.Action, suppressActionSymbol.SequenceTable.ToString())); if (null != requiredActionSymbol.SourceLineNumbers) { this.Messaging.Write(WarningMessages.SuppressAction2(requiredActionSymbol.SourceLineNumbers)); } requiredActionSymbols.Remove(key); } else // suppressing a non-overridable action symbol { this.Messaging.Write(ErrorMessages.SuppressNonoverridableAction(suppressActionSymbol.SourceLineNumbers, suppressActionSymbol.SequenceTable.ToString(), suppressActionSymbol.Action)); if (null != requiredActionSymbol.SourceLineNumbers) { this.Messaging.Write(ErrorMessages.SuppressNonoverridableAction2(requiredActionSymbol.SourceLineNumbers)); } } } } // A dictionary used for detecting cyclic references among action symbols. var firstReference = new Dictionary(); // Build up dependency trees of the relatively scheduled actions. // Use ToList() to create a copy of the required action symbols so that new symbols can // be added while enumerating. foreach (var actionSymbol in requiredActionSymbols.Values.ToList()) { if (!actionSymbol.Sequence.HasValue) { // check for standard actions that don't have a sequence number in a merge module if (SectionType.Module == this.Section.Type && WindowsInstallerStandard.IsStandardAction(actionSymbol.Action)) { this.Messaging.Write(ErrorMessages.StandardActionRelativelyScheduledInModule(actionSymbol.SourceLineNumbers, actionSymbol.SequenceTable.ToString(), actionSymbol.Action)); } this.SequenceActionSymbol(actionSymbol, requiredActionSymbols, firstReference); } else if (SectionType.Module == this.Section.Type && 0 < actionSymbol.Sequence && !WindowsInstallerStandard.IsStandardAction(actionSymbol.Action)) // check for custom actions and dialogs that have a sequence number { this.Messaging.Write(ErrorMessages.CustomActionSequencedInModule(actionSymbol.SourceLineNumbers, actionSymbol.SequenceTable.ToString(), actionSymbol.Action)); } } // Look for standard actions with sequence restrictions that aren't necessarily scheduled based // on the presence of a particular table. if (requiredActionSymbols.ContainsKey("InstallExecuteSequence/DuplicateFiles") && !requiredActionSymbols.ContainsKey("InstallExecuteSequence/InstallFiles")) { WindowsInstallerStandard.TryGetStandardAction("InstallExecuteSequence/InstallFiles", out var standardAction); requiredActionSymbols.Add(standardAction.Id.Id, standardAction); } // Schedule actions. List scheduledActionSymbols; if (SectionType.Module == this.Section.Type) { scheduledActionSymbols = requiredActionSymbols.Values.ToList(); } else { scheduledActionSymbols = this.ScheduleActions(requiredActionSymbols); } // Remove all existing WixActionSymbols from the section then add the // scheduled actions back to the section. var removeActionSymbols = this.Section.Symbols.Where(s => s.Definition.Type == SymbolDefinitionType.WixAction).ToList(); foreach (var removeSymbol in removeActionSymbols) { this.Section.RemoveSymbol(removeSymbol); } foreach (var action in scheduledActionSymbols) { this.Section.AddSymbol(action); } } private Dictionary GetRequiredStandardActions() { var overridableActionSymbols = new Dictionary(); var requiredActionIds = this.GetRequiredActionIds(); foreach (var actionId in requiredActionIds) { WindowsInstallerStandard.TryGetStandardAction(actionId, out var standardAction); overridableActionSymbols.Add(standardAction.Id.Id, standardAction); } return overridableActionSymbols; } private List ScheduleActions(Dictionary requiredActionSymbols) { var scheduledActionSymbols = new List(); // Process each sequence table individually. foreach (SequenceTable sequenceTable in Enum.GetValues(typeof(SequenceTable))) { // Create a collection of just the action symbols in this sequence var sequenceActionSymbols = requiredActionSymbols.Values.Where(a => a.SequenceTable == sequenceTable).ToList(); // Schedule the absolutely scheduled actions (by sorting them by their sequence numbers). var absoluteActionSymbols = new List(); foreach (var actionSymbol in sequenceActionSymbols) { if (actionSymbol.Sequence.HasValue) { // Look for sequence number collisions foreach (var sequenceScheduledActionSymbol in absoluteActionSymbols) { if (sequenceScheduledActionSymbol.Sequence == actionSymbol.Sequence) { this.Messaging.Write(WarningMessages.ActionSequenceCollision(actionSymbol.SourceLineNumbers, actionSymbol.SequenceTable.ToString(), actionSymbol.Action, sequenceScheduledActionSymbol.Action, actionSymbol.Sequence ?? 0)); if (null != sequenceScheduledActionSymbol.SourceLineNumbers) { this.Messaging.Write(WarningMessages.ActionSequenceCollision2(sequenceScheduledActionSymbol.SourceLineNumbers)); } } } absoluteActionSymbols.Add(actionSymbol); } } absoluteActionSymbols.Sort((x, y) => (x.Sequence ?? 0).CompareTo(y.Sequence ?? 0)); // Schedule the relatively scheduled actions (by resolving the dependency trees). var previousUsedSequence = 0; var relativeActionSymbols = new List(); for (int j = 0; j < absoluteActionSymbols.Count; j++) { var absoluteActionSymbol = absoluteActionSymbols[j]; // Get all the relatively scheduled action symbols occuring before and after this absolutely scheduled action symbol. var relativeActions = this.GetAllRelativeActionsForSequenceType(sequenceTable, absoluteActionSymbol); // Check for relatively scheduled actions occuring before/after a special action // (those actions with a negative sequence number). if (absoluteActionSymbol.Sequence < 0 && (relativeActions.PreviousActions.Any() || relativeActions.NextActions.Any())) { // Create errors for all the before actions. foreach (var actionSymbol in relativeActions.PreviousActions) { this.Messaging.Write(ErrorMessages.ActionScheduledRelativeToTerminationAction(actionSymbol.SourceLineNumbers, actionSymbol.SequenceTable.ToString(), actionSymbol.Action, absoluteActionSymbol.Action)); } // Create errors for all the after actions. foreach (var actionSymbol in relativeActions.NextActions) { this.Messaging.Write(ErrorMessages.ActionScheduledRelativeToTerminationAction(actionSymbol.SourceLineNumbers, actionSymbol.SequenceTable.ToString(), actionSymbol.Action, absoluteActionSymbol.Action)); } // If there is source line information for the absolutely scheduled action display it if (absoluteActionSymbol.SourceLineNumbers != null) { this.Messaging.Write(ErrorMessages.ActionScheduledRelativeToTerminationAction2(absoluteActionSymbol.SourceLineNumbers)); } continue; } // Schedule the action symbols before this one. var unusedSequence = absoluteActionSymbol.Sequence - 1; for (var i = relativeActions.PreviousActions.Count - 1; i >= 0; i--) { var relativeActionSymbol = relativeActions.PreviousActions[i]; // look for collisions if (unusedSequence == previousUsedSequence) { this.Messaging.Write(ErrorMessages.NoUniqueActionSequenceNumber(relativeActionSymbol.SourceLineNumbers, relativeActionSymbol.SequenceTable.ToString(), relativeActionSymbol.Action, absoluteActionSymbol.Action)); if (absoluteActionSymbol.SourceLineNumbers != null) { this.Messaging.Write(ErrorMessages.NoUniqueActionSequenceNumber2(absoluteActionSymbol.SourceLineNumbers)); } unusedSequence++; } relativeActionSymbol.Sequence = unusedSequence; relativeActionSymbols.Add(relativeActionSymbol); unusedSequence--; } // Determine the next used action sequence number. var nextUsedSequence = Int16.MaxValue + 1; if (absoluteActionSymbols.Count > j + 1) { nextUsedSequence = absoluteActionSymbols[j + 1].Sequence ?? 0; } // Schedule the action symbols after this one. unusedSequence = absoluteActionSymbol.Sequence + 1; for (var i = 0; i < relativeActions.NextActions.Count; i++) { var relativeActionSymbol = relativeActions.NextActions[i]; if (unusedSequence == nextUsedSequence) { this.Messaging.Write(ErrorMessages.NoUniqueActionSequenceNumber(relativeActionSymbol.SourceLineNumbers, relativeActionSymbol.SequenceTable.ToString(), relativeActionSymbol.Action, absoluteActionSymbol.Action)); if (absoluteActionSymbol.SourceLineNumbers != null) { this.Messaging.Write(ErrorMessages.NoUniqueActionSequenceNumber2(absoluteActionSymbol.SourceLineNumbers)); } unusedSequence--; } relativeActionSymbol.Sequence = unusedSequence; relativeActionSymbols.Add(relativeActionSymbol); unusedSequence++; } // keep track of this sequence number as the previous used sequence number for the next iteration previousUsedSequence = absoluteActionSymbol.Sequence ?? 0; } // add the absolutely and relatively scheduled actions to the list of scheduled actions scheduledActionSymbols.AddRange(absoluteActionSymbols); scheduledActionSymbols.AddRange(relativeActionSymbols); } return scheduledActionSymbols; } private IEnumerable GetRequiredActionIds() { var set = new HashSet(); // gather the required actions for the output type if (SectionType.Product == this.Section.Type) { // AdminExecuteSequence table set.Add("AdminExecuteSequence/CostFinalize"); set.Add("AdminExecuteSequence/CostInitialize"); set.Add("AdminExecuteSequence/FileCost"); set.Add("AdminExecuteSequence/InstallAdminPackage"); set.Add("AdminExecuteSequence/InstallFiles"); set.Add("AdminExecuteSequence/InstallFinalize"); set.Add("AdminExecuteSequence/InstallInitialize"); set.Add("AdminExecuteSequence/InstallValidate"); // AdminUISequence table set.Add("AdminUISequence/CostFinalize"); set.Add("AdminUISequence/CostInitialize"); set.Add("AdminUISequence/ExecuteAction"); set.Add("AdminUISequence/FileCost"); // AdvtExecuteSequence table set.Add("AdvertiseExecuteSequence/CostFinalize"); set.Add("AdvertiseExecuteSequence/CostInitialize"); set.Add("AdvertiseExecuteSequence/InstallInitialize"); set.Add("AdvertiseExecuteSequence/InstallFinalize"); set.Add("AdvertiseExecuteSequence/InstallValidate"); set.Add("AdvertiseExecuteSequence/PublishFeatures"); set.Add("AdvertiseExecuteSequence/PublishProduct"); // InstallExecuteSequence table set.Add("InstallExecuteSequence/CostFinalize"); set.Add("InstallExecuteSequence/CostInitialize"); set.Add("InstallExecuteSequence/FileCost"); set.Add("InstallExecuteSequence/InstallFinalize"); set.Add("InstallExecuteSequence/InstallInitialize"); set.Add("InstallExecuteSequence/InstallValidate"); set.Add("InstallExecuteSequence/ProcessComponents"); set.Add("InstallExecuteSequence/PublishFeatures"); set.Add("InstallExecuteSequence/PublishProduct"); set.Add("InstallExecuteSequence/RegisterProduct"); set.Add("InstallExecuteSequence/RegisterUser"); set.Add("InstallExecuteSequence/UnpublishFeatures"); set.Add("InstallExecuteSequence/ValidateProductID"); // InstallUISequence table set.Add("InstallUISequence/CostFinalize"); set.Add("InstallUISequence/CostInitialize"); set.Add("InstallUISequence/ExecuteAction"); set.Add("InstallUISequence/FileCost"); set.Add("InstallUISequence/ValidateProductID"); } // Gather the required actions for each symbol type. foreach (var symbolType in this.Section.Symbols.Select(t => t.Definition.Type).Distinct()) { switch (symbolType) { case SymbolDefinitionType.AppSearch: set.Add("InstallExecuteSequence/AppSearch"); set.Add("InstallUISequence/AppSearch"); break; case SymbolDefinitionType.CCPSearch: set.Add("InstallExecuteSequence/AppSearch"); set.Add("InstallExecuteSequence/CCPSearch"); set.Add("InstallExecuteSequence/RMCCPSearch"); set.Add("InstallUISequence/AppSearch"); set.Add("InstallUISequence/CCPSearch"); set.Add("InstallUISequence/RMCCPSearch"); break; case SymbolDefinitionType.Class: set.Add("AdvertiseExecuteSequence/RegisterClassInfo"); set.Add("InstallExecuteSequence/RegisterClassInfo"); set.Add("InstallExecuteSequence/UnregisterClassInfo"); break; case SymbolDefinitionType.Complus: set.Add("InstallExecuteSequence/RegisterComPlus"); set.Add("InstallExecuteSequence/UnregisterComPlus"); break; case SymbolDefinitionType.Component: case SymbolDefinitionType.CreateFolder: set.Add("InstallExecuteSequence/CreateFolders"); set.Add("InstallExecuteSequence/RemoveFolders"); break; case SymbolDefinitionType.DuplicateFile: set.Add("InstallExecuteSequence/DuplicateFiles"); set.Add("InstallExecuteSequence/RemoveDuplicateFiles"); break; case SymbolDefinitionType.Environment: set.Add("InstallExecuteSequence/WriteEnvironmentStrings"); set.Add("InstallExecuteSequence/RemoveEnvironmentStrings"); break; case SymbolDefinitionType.Extension: set.Add("AdvertiseExecuteSequence/RegisterExtensionInfo"); set.Add("InstallExecuteSequence/RegisterExtensionInfo"); set.Add("InstallExecuteSequence/UnregisterExtensionInfo"); break; case SymbolDefinitionType.File: set.Add("InstallExecuteSequence/InstallFiles"); set.Add("InstallExecuteSequence/RemoveFiles"); var foundFont = false; var foundSelfReg = false; var foundBindPath = false; foreach (var file in this.Section.Symbols.OfType()) { if (!foundFont && !String.IsNullOrEmpty(file.FontTitle)) { set.Add("InstallExecuteSequence/RegisterFonts"); set.Add("InstallExecuteSequence/UnregisterFonts"); foundFont = true; } if (!foundSelfReg && file.SelfRegCost.HasValue) { set.Add("InstallExecuteSequence/SelfRegModules"); set.Add("InstallExecuteSequence/SelfUnregModules"); foundSelfReg = true; } if (!foundBindPath && !String.IsNullOrEmpty(file.BindPath)) { set.Add("InstallExecuteSequence/BindImage"); foundBindPath = true; } } break; case SymbolDefinitionType.IniFile: set.Add("InstallExecuteSequence/WriteIniValues"); set.Add("InstallExecuteSequence/RemoveIniValues"); break; case SymbolDefinitionType.IsolatedComponent: set.Add("InstallExecuteSequence/IsolateComponents"); break; case SymbolDefinitionType.LaunchCondition: set.Add("InstallExecuteSequence/LaunchConditions"); set.Add("InstallUISequence/LaunchConditions"); break; case SymbolDefinitionType.MIME: set.Add("AdvertiseExecuteSequence/RegisterMIMEInfo"); set.Add("InstallExecuteSequence/RegisterMIMEInfo"); set.Add("InstallExecuteSequence/UnregisterMIMEInfo"); break; case SymbolDefinitionType.MoveFile: set.Add("InstallExecuteSequence/MoveFiles"); break; case SymbolDefinitionType.Assembly: set.Add("AdvertiseExecuteSequence/MsiPublishAssemblies"); set.Add("InstallExecuteSequence/MsiPublishAssemblies"); set.Add("InstallExecuteSequence/MsiUnpublishAssemblies"); break; case SymbolDefinitionType.MsiServiceConfig: case SymbolDefinitionType.MsiServiceConfigFailureActions: set.Add("InstallExecuteSequence/MsiConfigureServices"); break; case SymbolDefinitionType.ODBCDataSource: case SymbolDefinitionType.ODBCTranslator: case SymbolDefinitionType.ODBCDriver: set.Add("InstallExecuteSequence/SetODBCFolders"); set.Add("InstallExecuteSequence/InstallODBC"); set.Add("InstallExecuteSequence/RemoveODBC"); break; case SymbolDefinitionType.ProgId: set.Add("AdvertiseExecuteSequence/RegisterProgIdInfo"); set.Add("InstallExecuteSequence/RegisterProgIdInfo"); set.Add("InstallExecuteSequence/UnregisterProgIdInfo"); break; case SymbolDefinitionType.PublishComponent: set.Add("AdvertiseExecuteSequence/PublishComponents"); set.Add("InstallExecuteSequence/PublishComponents"); set.Add("InstallExecuteSequence/UnpublishComponents"); break; case SymbolDefinitionType.Registry: case SymbolDefinitionType.RemoveRegistry: set.Add("InstallExecuteSequence/WriteRegistryValues"); set.Add("InstallExecuteSequence/RemoveRegistryValues"); break; case SymbolDefinitionType.RemoveFile: set.Add("InstallExecuteSequence/RemoveFiles"); break; case SymbolDefinitionType.ServiceControl: set.Add("InstallExecuteSequence/StartServices"); set.Add("InstallExecuteSequence/StopServices"); set.Add("InstallExecuteSequence/DeleteServices"); break; case SymbolDefinitionType.ServiceInstall: set.Add("InstallExecuteSequence/InstallServices"); break; case SymbolDefinitionType.Shortcut: set.Add("AdvertiseExecuteSequence/CreateShortcuts"); set.Add("InstallExecuteSequence/CreateShortcuts"); set.Add("InstallExecuteSequence/RemoveShortcuts"); break; case SymbolDefinitionType.TypeLib: set.Add("InstallExecuteSequence/RegisterTypeLibraries"); set.Add("InstallExecuteSequence/UnregisterTypeLibraries"); break; case SymbolDefinitionType.Upgrade: set.Add("InstallExecuteSequence/FindRelatedProducts"); set.Add("InstallUISequence/FindRelatedProducts"); // Only add the MigrateFeatureStates action if MigrateFeature attribute is set on // at least one UpgradeVersion element. if (this.Section.Symbols.OfType().Any(t => t.MigrateFeatures)) { set.Add("InstallExecuteSequence/MigrateFeatureStates"); set.Add("InstallUISequence/MigrateFeatureStates"); } break; } } return set; } /// /// Sequence an action before or after a standard action. /// /// The action symbol to be sequenced. /// Collection of actions which must be included. /// A dictionary used for detecting cyclic references among action symbols. private void SequenceActionSymbol(WixActionSymbol actionSymbol, Dictionary requiredActionSymbols, Dictionary firstReference) { var after = false; if (actionSymbol.After != null) { after = true; } else if (actionSymbol.Before == null) { throw new WixException($"Found action '{actionSymbol.Id.Id}' at {actionSymbol.SourceLineNumbers}' with no Sequence, Before, or After column set. The compiler should have prevented this."); } var parentActionName = (after ? actionSymbol.After : actionSymbol.Before); var parentActionKey = actionSymbol.SequenceTable.ToString() + "/" + parentActionName; if (!requiredActionSymbols.TryGetValue(parentActionKey, out var parentActionSymbol)) { // If the missing parent action is a standard action (with a suggested sequence number), add it. if (WindowsInstallerStandard.TryGetStandardAction(parentActionKey, out parentActionSymbol)) { // Create a clone to avoid modifying the static copy of the object. // TODO: consider this: parentActionSymbol = parentActionSymbol.Clone(); requiredActionSymbols.Add(parentActionSymbol.Id.Id, parentActionSymbol); } else { throw new WixException($"Found action {actionSymbol.Id.Id} with a non-existent {(after ? "After" : "Before")} action '{parentActionName}'. The linker should have prevented this."); } } this.CheckForCircularActionReference(actionSymbol, requiredActionSymbols, firstReference); // Add this action to the appropriate list of dependent action symbols. var relativeActions = this.GetRelativeActions(parentActionSymbol); var relatedSymbols = (after ? relativeActions.NextActions : relativeActions.PreviousActions); relatedSymbols.Add(actionSymbol); } /// /// Check the specified action symbol to see if it leads to a cycle. /// /// Use the provided dictionary to note the initial action symbol that first led to each action /// symbol. Any action symbol encountered that has already been encountered starting from a different /// initial action symbol inherits the loop characteristics of that initial action symbol, and thus is /// also not part of a cycle. However, any action symbol encountered that has already been encountered /// starting from the same initial action symbol is an indication that the current action symbol is /// part of a cycle. /// /// The action symbol to be checked. /// Collection of actions which must be included. /// The first encountered action symbol that led to each action symbol. private void CheckForCircularActionReference(WixActionSymbol actionSymbol, Dictionary requiredActionSymbols, Dictionary firstReference) { WixActionSymbol currentActionSymbol = null; var parentActionSymbol = actionSymbol; do { var previousActionSymbol = currentActionSymbol ?? parentActionSymbol; currentActionSymbol = parentActionSymbol; if (!firstReference.TryGetValue(currentActionSymbol, out var existingInitialActionSymbol)) { firstReference[currentActionSymbol] = actionSymbol; } else if (existingInitialActionSymbol == actionSymbol) { this.Messaging.Write(ErrorMessages.ActionCircularDependency(currentActionSymbol.SourceLineNumbers, currentActionSymbol.SequenceTable.ToString(), currentActionSymbol.Action, previousActionSymbol.Action)); } parentActionSymbol = this.GetParentActionSymbol(currentActionSymbol, requiredActionSymbols); } while (null != parentActionSymbol && !this.Messaging.EncounteredError); } /// /// Get the action symbol that is the parent of the given action symbol. /// /// The given action symbol. /// Collection of actions which must be included. /// Null if there is no parent. Used for loop termination. private WixActionSymbol GetParentActionSymbol(WixActionSymbol actionSymbol, Dictionary requiredActionSymbols) { if (null == actionSymbol.Before && null == actionSymbol.After) { return null; } var parentActionKey = actionSymbol.SequenceTable.ToString() + "/" + (actionSymbol.After ?? actionSymbol.Before); if (!requiredActionSymbols.TryGetValue(parentActionKey, out var parentActionSymbol)) { WindowsInstallerStandard.TryGetStandardAction(parentActionKey, out parentActionSymbol); } return parentActionSymbol; } private RelativeActions GetRelativeActions(WixActionSymbol action) { if (!this.RelativeActionsForActions.TryGetValue(action.Id.Id, out var relativeActions)) { relativeActions = new RelativeActions(); this.RelativeActionsForActions.Add(action.Id.Id, relativeActions); } return relativeActions; } private RelativeActions GetAllRelativeActionsForSequenceType(SequenceTable sequenceType, WixActionSymbol action) { var relativeActions = new RelativeActions(); if (this.RelativeActionsForActions.TryGetValue(action.Id.Id, out var actionRelatives)) { this.RecurseRelativeActionsForSequenceType(sequenceType, actionRelatives.PreviousActions, relativeActions.PreviousActions); this.RecurseRelativeActionsForSequenceType(sequenceType, actionRelatives.NextActions, relativeActions.NextActions); } return relativeActions; } private void RecurseRelativeActionsForSequenceType(SequenceTable sequenceType, List actions, List visitedActions) { foreach (var action in actions.Where(a => a.SequenceTable == sequenceType)) { if (this.RelativeActionsForActions.TryGetValue(action.Id.Id, out var actionRelatives)) { this.RecurseRelativeActionsForSequenceType(sequenceType, actionRelatives.PreviousActions, visitedActions); } visitedActions.Add(action); if (actionRelatives != null) { this.RecurseRelativeActionsForSequenceType(sequenceType, actionRelatives.NextActions, visitedActions); } } } private class RelativeActions { public List PreviousActions { get; } = new List(); public List NextActions { get; } = new List(); } } }