// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using System.Collections;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.IO;
using System.Management.Automation.Host;
using System.Management.Automation.Internal;
using System.Management.Automation.Internal.Host;
using System.Management.Automation.Language;
using System.Management.Automation.Runspaces;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Security;

using Microsoft.PowerShell;
using Microsoft.PowerShell.Commands.Internal.Format;

namespace System.Management.Automation
{
    /// <summary>
    /// This class contains the execution context that gets passed
    /// around to commands. This is all of the information that lets you get
    /// at session state and the host interfaces.
    /// </summary>
    internal class ExecutionContext
    {
        #region Properties

        /// <summary>
        /// The events received by this runspace.
        /// </summary>
        internal PSLocalEventManager Events { get; private set; }

        internal HashSet<string> AutoLoadingModuleInProgress { get; } = new HashSet<string>(StringComparer.OrdinalIgnoreCase);

        /// <summary>
        /// The debugger for the interpreter.
        /// </summary>
        internal ScriptDebugger Debugger
        {
            get { return _debugger; }
        }

        internal ScriptDebugger _debugger;

        internal int _debuggingMode;

        /// <summary>
        /// Reset or clear the various context managers so the runspace can be reused without contamination.
        /// </summary>
        internal void ResetManagers()
        {
            _debugger?.ResetDebugger();

            Events?.Dispose();
            Events = new PSLocalEventManager(this);

            this.transactionManager?.Dispose();
            this.transactionManager = new PSTransactionManager();
        }
        /// <summary>
        /// The tracing mode for the interpreter.
        /// </summary>
        /// <value>True if tracing is turned on, false if it's turned off.</value>
        internal int PSDebugTraceLevel
        {
            get
            {
                // Pretend that tracing is off if ignoreScriptDebug is true
                return IgnoreScriptDebug ? 0 : _debugTraceLevel;
            }

            set
            {
                _debugTraceLevel = value;
            }
        }

        private int _debugTraceLevel;

        /// <summary>
        /// The step mode for the interpreter.
        /// </summary>
        /// <value>True of stepping is turned on, false if it's turned off.</value>
        internal bool PSDebugTraceStep
        {
            get
            {
                // Pretend that tracing is off if ignoreScriptDebug is true
                return !IgnoreScriptDebug && _debugTraceStep;
            }

            set
            {
                _debugTraceStep = value;
            }
        }

        private bool _debugTraceStep;

        // Helper for generated code to handle running w/ no execution context
        internal static bool IsStrictVersion(ExecutionContext context, int majorVersion)
        {
            context ??= LocalPipeline.GetExecutionContextFromTLS();

            return (context != null) && context.IsStrictVersion(majorVersion);
        }
        /// <summary>
        /// Check to see a specific version of strict mode is enabled.  The check is always scoped,
        /// even though in version 1 the check was engine wide.
        /// </summary>
        /// <param name="majorVersion">The version for a strict check about to be performed.</param>
        /// <returns></returns>
        internal bool IsStrictVersion(int majorVersion)
        {
            SessionStateScope scope = EngineSessionState.CurrentScope;
            while (scope != null)
            {
                // If StrictModeVersion is null, we haven't seen set-strictmode, so check the parent scope.
                if (scope.StrictModeVersion != null)
                {
                    // If StrictModeVersion is not null, just check the major version.  A version of 0
                    // is the same as off to make this a simple check.
                    return (scope.StrictModeVersion.Major >= majorVersion);
                }
                // We shouldn't check global scope if we were in a module.
                if (scope == EngineSessionState.ModuleScope)
                {
                    break;
                }

                scope = scope.Parent;
            }

            // set-strictmode hasn't been used.
            return false;
        }

        /// <summary>
        /// Is true if the current statement in the interpreter should be traced...
        /// </summary>
        internal bool ShouldTraceStatement
        {
            get
            {
                // Pretend that tracing is off if ignoreScriptDebug is true
                return !IgnoreScriptDebug && (_debugTraceLevel > 0 || _debugTraceStep);
            }
        }

        /// <summary>
        /// If true, then a script command processor should rethrow the exit exception instead of
        /// simply capturing it. This is used by the -file option on the console host.
        /// </summary>
        internal bool ScriptCommandProcessorShouldRethrowExit { get; set; } = false;

        /// <summary>
        /// If this flag is set to true, script trace output
        /// will not be generated regardless of the state of the
        /// trace flag.
        /// </summary>
        /// <value>The current state of the IgnoreScriptDebug flag.</value>
        internal bool IgnoreScriptDebug { get; set; } = true;

        /// <summary>
        /// Gets the automation engine instance.
        /// </summary>
        internal AutomationEngine Engine { get; private set; }

        internal InitialSessionState InitialSessionState { get; }

        /// <summary>
        /// Added for Win8: 336382
        /// Contains the name of the previous module that was processed. This
        /// allows you to skip this module when doing a lookup.
        /// </summary>
        internal string PreviousModuleProcessed { get; set; }

        /// <summary>
        /// Added for 4980967
        /// Contains the name of the latest module that was imported,
        /// Allows "module\function" to call the function from latest imported module instead of randomly choosing the first module in the moduletable.
        /// </summary>
        internal Hashtable previousModuleImported { get; set; } = new Hashtable();

        /// <summary>
        /// Contains the name of the module currently being processed. This
        /// allows you to skip this module when doing a lookup.
        /// </summary>
        internal string ModuleBeingProcessed { get; set; }

        /// <summary>
        /// Authorization manager for this runspace.
        /// </summary>
        internal AuthorizationManager AuthorizationManager { get; private set; }

        /// <summary>
        /// Gets the appropriate provider names for the default
        /// providers based on the type of the shell
        /// (single shell or custom shell).
        /// </summary>
        internal ProviderNames ProviderNames
        {
            get
            {
                _providerNames ??= new SingleShellProviderNames();

                return _providerNames;
            }
        }

        private ProviderNames _providerNames;

        /// <summary>
        /// The module information for this engine...
        /// </summary>
        internal ModuleIntrinsics Modules { get; private set; }

        /// <summary>
        /// Get the shellID for this runspace...
        /// </summary>
        internal string ShellID
        {
            get
            {
                if (_shellId == null)
                {
                    // Use the ShellID from PSAuthorizationManager before everything else because that's what's used
                    // to check execution policy...
                    if (AuthorizationManager is PSAuthorizationManager && !string.IsNullOrEmpty(AuthorizationManager.ShellId))
                    {
                        _shellId = AuthorizationManager.ShellId;
                    }
                    else
                    {
                        // Finally fall back to the default shell id...
                        _shellId = Utils.DefaultPowerShellShellID;
                    }
                }

                return _shellId;
            }
        }

        private string _shellId;

        /// <summary>
        /// Session State with which this instance of engine works.
        /// </summary>
        internal SessionStateInternal EngineSessionState { get; set; }

        /// <summary>
        /// The default or top-level session state instance for the
        /// engine.
        /// </summary>
        internal SessionStateInternal TopLevelSessionState { get; private set; }

        /// <summary>
        /// Get the SessionState facade for the internal session state APIs.
        /// </summary>
        internal SessionState SessionState
        {
            get
            {
                return EngineSessionState.PublicSessionState;
            }
        }

        /// <summary>
        /// Get/set constraints for this execution environment.
        /// </summary>
        internal PSLanguageMode LanguageMode
        {
            get
            {
                return _languageMode;
            }

            set
            {
                // If we're moving to ConstrainedLanguage, invalidate the binding
                // caches. After that, the binding rules encode the language mode.
                if (value == PSLanguageMode.ConstrainedLanguage)
                {
                    HasRunspaceEverUsedConstrainedLanguageMode = true;

                    // If 'ExecutionContext.HasEverUsedConstrainedLanguage' is already set to True, then we have
                    // already invalidated all cached binders, and binders already started to generate code with
                    // consideration of 'LanguageMode'. In such case, we don't need to invalidate cached binders
                    // again.
                    // Note that when executing script blocks marked as 'FullLanguage' in a 'ConstrainedLanguage'
                    // environment, we will set and Restore 'context.LanguageMode' very often. But we should not
                    // invalidate the cached binders every time we restore to 'ConstrainedLanguage'.
                    if (!ExecutionContext.HasEverUsedConstrainedLanguage)
                    {
                        lock (lockObject)
                        {
                            // If another thread has already set 'ExecutionContext.HasEverUsedConstrainedLanguage'
                            // while we are waiting on the lock, then nothing needs to be done.
                            if (!ExecutionContext.HasEverUsedConstrainedLanguage)
                            {
                                PSSetMemberBinder.InvalidateCache();
                                PSInvokeMemberBinder.InvalidateCache();
                                PSConvertBinder.InvalidateCache();
                                PSBinaryOperationBinder.InvalidateCache();
                                PSGetIndexBinder.InvalidateCache();
                                PSSetIndexBinder.InvalidateCache();
                                PSCreateInstanceBinder.InvalidateCache();

                                // Set 'HasEverUsedConstrainedLanguage' at the very end to guarantee other threads to wait until
                                // all invalidation operations are done.
                                UntrustedObjects = new ConditionalWeakTable<object, object>();
                                ExecutionContext.HasEverUsedConstrainedLanguage = true;
                            }
                        }
                    }
                }

                // Conversion caches don't have version info / binding rules, so must be
                // cleared every time.
                LanguagePrimitives.RebuildConversionCache();

                _languageMode = value;
            }
        }

        private PSLanguageMode _languageMode = PSLanguageMode.FullLanguage;

        /// <summary>
        /// True if this runspace has ever used constrained language mode.
        /// </summary>
        internal bool HasRunspaceEverUsedConstrainedLanguageMode { get; private set; }

        /// <summary>
        /// Indicate if a parameter binding is happening that transitions the execution from ConstrainedLanguage
        /// mode to a trusted FullLanguage command.
        /// </summary>
        internal bool LanguageModeTransitionInParameterBinding { get; set; }

        /// <summary>
        /// True if we've ever used ConstrainedLanguage. If this is the case, then the binding restrictions
        /// need to also validate against the language mode.
        /// </summary>
        internal static bool HasEverUsedConstrainedLanguage { get; private set; }

        #region Variable Tracking

        /// <summary>
        /// Initialized when 'ConstrainedLanguage' is applied.
        /// The objects contained in this table are considered to be untrusted.
        /// </summary>
        private static ConditionalWeakTable<object, object> UntrustedObjects { get; set; }

        /// <summary>
        /// Helper for checking if the given value is marked as untrusted.
        /// </summary>
        internal static bool IsMarkedAsUntrusted(object value)
        {
            bool result = false;
            var baseValue = PSObject.Base(value);
            if (baseValue != null && baseValue != NullString.Value)
            {
                result = UntrustedObjects.TryGetValue(baseValue, out _);
            }

            return result;
        }

        /// <summary>
        /// Helper for marking a value as untrusted.
        /// </summary>
        internal static void MarkObjectAsUntrusted(object value)
        {
            // If the value is a PSObject, then we mark its base object untrusted
            var baseValue = PSObject.Base(value);
            if (baseValue != null && baseValue != NullString.Value)
            {
                // It's actually setting a key value pair when the key doesn't exist
                UntrustedObjects.GetValue(baseValue, static key => null);

                try
                {
                    // If it's a PSReference object, we need to also mark the value it's holding on.
                    // This could result in a recursion if psRef.Value points to itself directly or indirectly, so we check if psRef.Value is already
                    // marked before making a recursive call. The additional check adds extra overhead for handling PSReference object, but it should
                    // be rare in practice.
                    var psRef = baseValue as PSReference;
                    if (psRef != null && !IsMarkedAsUntrusted(psRef.Value))
                    {
                        MarkObjectAsUntrusted(psRef.Value);
                    }
                }
                catch { /* psRef.Value may call PSVariable.Value under the hood, which may throw arbitrary exception */ }
            }
        }

        /// <summary>
        /// Helper for setting the untrusted value of an assignment to either a 'Global:' variable, or a 'Script:' variable in a module scope.
        /// </summary>
        /// <remarks>
        /// This method is for tracking assignment to global variables and module script scope varaibles in ConstrainedLanguage mode. Those variables
        /// can go across boundaries between ConstrainedLanguage and FullLanguage, and make it easy for a trusted script to use data from an untrusted
        /// environment. Therefore, in ConstrainedLanguage mode, we need to mark the value objects assigned to those variables as untrusted.
        /// </remarks>
        internal static void MarkObjectAsUntrustedForVariableAssignment(PSVariable variable, SessionStateScope scope, SessionStateInternal sessionState)
        {
            if (scope.Parent == null ||  // If it's the global scope, OR
                (sessionState.Module != null &&  // it's running in a module AND
                 scope.ScriptScope == scope && scope.Parent.Parent == null)) // it's the module's script scope (scope.Parent is global scope and scope.ScriptScope points to itself)
            {
                // We are setting value for either a 'Global:' variable, or a 'Script:' variable within a module in 'ConstrainedLanguage' mode.
                // Global variable may be referenced within trusted script block (scriptBlock.LanguageMode == 'FullLanguage'), and users could
                // also set a 'Script:' variable in a trusted module scope from 'ConstrainedLanguage' environment via '& $mo { $script:<var> }'.
                // So we need to mark the value as untrusted.
                MarkObjectAsUntrusted(variable.Value);
            }
        }

        /// <summary>
        /// The result object is assumed generated by operating on the original object.
        /// So if the original object is from an untrusted input source, we mark the result object as untrusted.
        /// </summary>
        internal static void PropagateInputSource(object originalObject, object resultObject, PSLanguageMode currentLanguageMode)
        {
            // The untrusted flag is populated only in FullLanguage mode and ConstrainedLanguage has been used in the process before.
            if (ExecutionContext.HasEverUsedConstrainedLanguage && currentLanguageMode == PSLanguageMode.FullLanguage && IsMarkedAsUntrusted(originalObject))
            {
                MarkObjectAsUntrusted(resultObject);
            }
        }

        #endregion

        /// <summary>
        /// If true the PowerShell debugger will use FullLanguage mode, otherwise it will use the current language mode.
        /// </summary>
        internal bool UseFullLanguageModeInDebugger
        {
            get
            {
                return InitialSessionState != null && InitialSessionState.UseFullLanguageModeInDebugger;
            }
        }

        internal static readonly List<string> ModulesWithJobSourceAdapters = new List<string>
            {
                Utils.ScheduledJobModuleName,
            };

        /// <summary>
        /// Is true if the PSScheduledJob module is loaded for this runspace.
        /// </summary>
        internal bool IsModuleWithJobSourceAdapterLoaded
        {
            get; set;
        }

        /// <summary>
        /// Gets the location globber for the session state for
        /// this instance of the runspace.
        /// </summary>
        internal LocationGlobber LocationGlobber
        {
            get
            {
                _locationGlobber = new LocationGlobber(this.SessionState);
                return _locationGlobber;
            }
        }

        private LocationGlobber _locationGlobber;

        /// <summary>
        /// The assemblies that have been loaded for this runspace.
        /// </summary>
        internal Dictionary<string, Assembly> AssemblyCache { get; private set; }

        #endregion Properties

        #region Engine State

        /// <summary>
        /// The state for current engine that is running.
        /// </summary>
        /// <value></value>
        internal EngineState EngineState { get; set; } = EngineState.None;

        #endregion

        #region GetSetVariable methods

        /// <summary>
        /// Get a variable out of session state.
        /// </summary>
        internal object GetVariableValue(VariablePath path)
        {
            CmdletProviderContext context;
            SessionStateScope scope;
            return EngineSessionState.GetVariableValue(path, out context, out scope);
        }

        /// <summary>
        /// Get a variable out of session state. This calls GetVariable(name) and returns the
        /// value unless it is null in which case it returns the defaultValue provided by the caller.
        /// </summary>
        internal object GetVariableValue(VariablePath path, object defaultValue)
        {
            return EngineSessionState.GetVariableValue(path, out _, out _) ?? defaultValue;
        }

        /// <summary>
        /// Set a variable in session state.
        /// </summary>
        internal void SetVariable(VariablePath path, object newValue)
        {
            EngineSessionState.SetVariable(path, newValue, true, CommandOrigin.Internal);
        }

        internal T GetEnumPreference<T>(VariablePath preferenceVariablePath, T defaultPref, out bool defaultUsed)
        {
            object val = EngineSessionState.GetVariableValue(preferenceVariablePath, out _, out _);
            if (val is T)
            {
                if (val is ActionPreference actionPreferenceValue)
                {
                    CheckActionPreference(preferenceVariablePath, actionPreferenceValue, defaultPref);
                }

                T convertedResult = (T)val;

                defaultUsed = false;
                return convertedResult;
            }

            defaultUsed = true;
            T result = defaultPref;

            if (val != null)
            {
                try
                {
                    string valString = val as string;
                    if (valString != null)
                    {
                        result = (T)Enum.Parse(typeof(T), valString, true);
                        defaultUsed = false;
                    }
                    else
                    {
                        result = (T)PSObject.Base(val);
                        defaultUsed = false;
                    }

                    if (result is ActionPreference actionPreferenceValue)
                    {
                        CheckActionPreference(preferenceVariablePath, actionPreferenceValue, defaultPref);
                    }
                }
                catch (InvalidCastException)
                {
                    // default value is used
                }
                catch (ArgumentException)
                {
                    // default value is used
                }
            }

            return result;
        }

        private void CheckActionPreference(VariablePath preferenceVariablePath, ActionPreference preference, object defaultValue)
        {
            if (preference == ActionPreference.Suspend)
            {
                // ActionPreference.Suspend is reserved for future use. When it is used, reset
                // the variable to its default.
                string message = StringUtil.Format(ErrorPackage.ReservedActionPreferenceReplacedError, preference, preferenceVariablePath.UserPath, defaultValue);
                EngineSessionState.SetVariable(preferenceVariablePath, defaultValue, true, CommandOrigin.Internal);
                throw new NotSupportedException(message);
            }
        }

        /// <summary>
        /// Same as GetEnumPreference, but for boolean values.
        /// </summary>
        /// <param name="preferenceVariablePath"></param>
        /// <param name="defaultPref"></param>
        /// <param name="defaultUsed"></param>
        /// <returns></returns>
        internal bool GetBooleanPreference(VariablePath preferenceVariablePath, bool defaultPref, out bool defaultUsed)
        {
            object val = EngineSessionState.GetVariableValue(preferenceVariablePath, out _, out _);
            if (val is null)
            {
                defaultUsed = true;
                return defaultPref;
            }

            defaultUsed = !LanguagePrimitives.TryConvertTo(val, out bool converted);
            return defaultUsed ? defaultPref : converted;
        }
        #endregion GetSetVariable methods

        #region HelpSystem

        /// <summary>
        /// Help system for this engine context.
        /// </summary>
        /// <value></value>
        internal HelpSystem HelpSystem
        {
            get { return _helpSystem ??= new HelpSystem(this); }
        }

        private HelpSystem _helpSystem;

        #endregion

        #region FormatAndOutput
        internal object FormatInfo { get; set; }

        #endregion

        internal Dictionary<string, ScriptBlock> CustomArgumentCompleters { get; set; }

        internal Dictionary<string, ScriptBlock> NativeArgumentCompleters { get; set; }

        /// <summary>
        /// Routine to create a command(processor) instance using the factory.
        /// </summary>
        /// <param name="command">The name of the command to lookup.</param>
        /// <param name="dotSource"></param>
        /// <returns>The command processor object.</returns>
        internal CommandProcessorBase CreateCommand(string command, bool dotSource)
        {
            CommandOrigin commandOrigin = this.EngineSessionState.CurrentScope.ScopeOrigin;
            CommandProcessorBase commandProcessor =
                CommandDiscovery.LookupCommandProcessor(command, commandOrigin, !dotSource);
            // Reset the command origin for script commands... // BUGBUG - dotting can get around command origin checks???
            if (commandProcessor != null && commandProcessor is ScriptCommandProcessorBase)
            {
                commandProcessor.Command.CommandOriginInternal = CommandOrigin.Internal;
            }

            return commandProcessor;
        }

        /// <summary>
        /// Hold the current command.
        /// </summary>
        /// <value>Reference to command discovery</value>
        internal CommandProcessorBase CurrentCommandProcessor { get; set; }

        /// <summary>
        /// Redirect to the CommandDiscovery in the engine.
        /// </summary>
        /// <value>Reference to command discovery</value>
        internal CommandDiscovery CommandDiscovery
        {
            get
            {
                return Engine.CommandDiscovery;
            }
        }

        /// <summary>
        /// Interface that should be used for interaction with host.
        /// </summary>
        internal InternalHost EngineHostInterface
        {
            // set not provided: it's not meaningful to change the host post-construction.
            get; private set;
        }

        /// <summary>
        /// Interface to be used for interaction with internal
        /// host. InternalHost wraps the host supplied
        /// during construction. Use this wrapper to access
        /// functionality specific to InternalHost.
        /// </summary>
        internal InternalHost InternalHost
        {
            get { return EngineHostInterface; }
        }

        /// <summary>
        /// Interface to the public API for the engine.
        /// </summary>
        internal EngineIntrinsics EngineIntrinsics
        {
            get { return _engineIntrinsics ??= new EngineIntrinsics(this); }
        }

        private EngineIntrinsics _engineIntrinsics;

        /// <summary>
        /// Log context cache.
        /// </summary>
        internal LogContextCache LogContextCache { get; } = new LogContextCache();

        #region Output pipes
        /// <summary>
        /// The PipelineWriter provided by the connection object for success output.
        /// </summary>
        internal PipelineWriter ExternalSuccessOutput { get; set; }

        /// <summary>
        /// The PipelineWriter provided by the connection object for error output.
        /// </summary>
        internal PipelineWriter ExternalErrorOutput { get; set; }

        /// <summary>
        /// The PipelineWriter provided by the connection object for progress output.
        /// </summary>
        internal PipelineWriter ExternalProgressOutput { get; set; }

        internal class SavedContextData
        {
            private readonly bool _stepScript;
            private readonly bool _ignoreScriptDebug;
            private readonly int _PSDebug;

            private readonly Pipe _shellFunctionErrorOutputPipe;

            public SavedContextData(ExecutionContext context)
            {
                _stepScript = context.PSDebugTraceStep;
                _ignoreScriptDebug = context.IgnoreScriptDebug;
                _PSDebug = context.PSDebugTraceLevel;

                _shellFunctionErrorOutputPipe = context.ShellFunctionErrorOutputPipe;
            }

            public void RestoreContextData(ExecutionContext context)
            {
                context.PSDebugTraceStep = _stepScript;
                context.IgnoreScriptDebug = _ignoreScriptDebug;
                context.PSDebugTraceLevel = _PSDebug;

                context.ShellFunctionErrorOutputPipe = _shellFunctionErrorOutputPipe;
            }
        }

        /// <summary>
        /// Host uses this to saves context data when entering a nested prompt.
        /// </summary>
        /// <returns></returns>
        internal SavedContextData SaveContextData()
        {
            return new SavedContextData(this);
        }

        internal void ResetShellFunctionErrorOutputPipe()
        {
            ShellFunctionErrorOutputPipe = null;
        }

        internal Pipe RedirectErrorPipe(Pipe newPipe)
        {
            Pipe oldPipe = ShellFunctionErrorOutputPipe;
            ShellFunctionErrorOutputPipe = newPipe;
            return oldPipe;
        }

        /// <summary>
        /// Reset all of the redirection book keeping variables. This routine should be called when starting to
        /// execute a script.
        /// </summary>
        internal void ResetRedirection()
        {
            ShellFunctionErrorOutputPipe = null;
        }

        /// <summary>
        /// Function and Script command processors will route their error output to
        /// this pipe if set, unless explicitly routed elsewhere. We also keep track
        /// of the first time this value is set so we can know if it's the default
        /// error output or not.
        /// </summary>
        internal Pipe ShellFunctionErrorOutputPipe { get; set; }

        /// <summary>
        /// Supports expression Warning output redirection.
        /// </summary>
        internal Pipe ExpressionWarningOutputPipe { get; set; }

        /// <summary>
        /// Supports expression Verbose output redirection.
        /// </summary>
        internal Pipe ExpressionVerboseOutputPipe { get; set; }

        /// <summary>
        /// Supports expression Verbose output redirection.
        /// </summary>
        internal Pipe ExpressionDebugOutputPipe { get; set; }

        /// <summary>
        /// Supports expression Information output redirection.
        /// </summary>
        internal Pipe ExpressionInformationOutputPipe { get; set; }

        #endregion Output pipes

        #region Append to $error
        /// <summary>
        /// Appends the object to $global:error if it's an error record or exception.
        /// </summary>
        /// <param name="obj">
        /// ErrorRecord or Exception to be written to $global:error
        /// </param>
        /// <exception cref="ExtendedTypeSystemException">
        /// (get-only) An error occurred accessing $ERROR.
        /// </exception>
        internal void AppendDollarError(object obj)
        {
            ErrorRecord objAsErrorRecord = obj as ErrorRecord;
            if (objAsErrorRecord is null && obj is not Exception)
            {
                Diagnostics.Assert(false, "Object to append was neither an ErrorRecord nor an Exception in ExecutionContext.AppendDollarError");
                return;
            }

            if (DollarErrorVariable is not ArrayList arraylist)
            {
                Diagnostics.Assert(false, "$error should be a global constant ArrayList");
                return;
            }

            // Don't add the same exception twice...
            if (arraylist.Count > 0)
            {
                // There may be exceptions stored directly in which case
                // the direct comparison will catch them...
                if (arraylist[0] == obj)
                    return;
                // otherwise check the exception members of the error records...
                ErrorRecord er1 = arraylist[0] as ErrorRecord;

                if (er1 != null && objAsErrorRecord != null && er1.Exception == objAsErrorRecord.Exception)
                    return;
            }

            const int maxErrorCount = 256;

            int numToErase = arraylist.Count - (maxErrorCount - 1);
            if (numToErase > 0)
            {
                arraylist.RemoveRange(
                    maxErrorCount - 1,
                    numToErase);
            }

            arraylist.Insert(0, obj);
        }
        #endregion

        #region Scope or Commands (in pipeline) Depth Count

        /// <summary>
        /// Check if the stack would overflow soon, if so, throw ScriptCallDepthException.
        /// </summary>
        /// <exception cref="ScriptCallDepthException">
        /// If the stack would overflow soon.
        /// </exception>
        internal static void CheckStackDepth()
        {
            try
            {
                RuntimeHelpers.EnsureSufficientExecutionStack();
            }
            catch (InsufficientExecutionStackException)
            {
                throw new ScriptCallDepthException();
            }
        }

        #endregion

        /// <summary>
        /// The current connection object.
        /// </summary>
        private Runspace _currentRunspace;
        // This should be internal, but it need to be friend of remoting dll.
        /// <summary>
        /// The current connection object.
        /// </summary>
        internal Runspace CurrentRunspace
        {
            get { return _currentRunspace; }

            set { _currentRunspace = value; }
        }

        /// <summary>
        /// Each pipeline has a stack of pipeline processor. This method
        /// pushes pp in to stack for currently executing pipeline.
        /// </summary>
        /// <param name="pp"></param>
        internal void PushPipelineProcessor(PipelineProcessor pp)
        {
            if (_currentRunspace == null)
                return;
            LocalPipeline lpl = (LocalPipeline)((RunspaceBase)_currentRunspace).GetCurrentlyRunningPipeline();
            if (lpl == null)
                return;
            lpl.Stopper.Push(pp);
        }

        /// <summary>
        /// Each pipeline has a stack of pipeline processor. This method pops the
        /// top item from the stack.
        /// </summary>
        internal void PopPipelineProcessor(bool fromSteppablePipeline)
        {
            if (_currentRunspace == null)
                return;
            LocalPipeline lpl = (LocalPipeline)((RunspaceBase)_currentRunspace).GetCurrentlyRunningPipeline();
            if (lpl == null)
                return;
            lpl.Stopper.Pop(fromSteppablePipeline);
        }

        /// <summary>
        /// This flag is checked by parser to stop loops etc.
        /// </summary>
        /// <returns></returns>
        internal bool CurrentPipelineStopping
        {
            get
            {
                if (_currentRunspace == null)
                    return false;
                LocalPipeline lpl = (LocalPipeline)((RunspaceBase)_currentRunspace).GetCurrentlyRunningPipeline();
                if (lpl == null)
                    return false;
                return lpl.IsStopping;
            }
        }

        /// <summary>
        /// True means one of these:
        /// 1) there is a trap statement in a dynamically enclosing statement block that might catch an exception.
        /// 2) execution happens inside a PS class and exceptions should be propagated all the way up, even if there is no enclosing try-catch-finally.
        /// </summary>
        /// <value></value>
        internal bool PropagateExceptionsToEnclosingStatementBlock { get; set; }

        internal RuntimeException CurrentExceptionBeingHandled { get; set; }

        /// <summary>
        /// Shortcut to get at $?
        /// </summary>
        /// <value>The current value of $? </value>
        internal bool QuestionMarkVariableValue { get; set; } = true;

        /// <summary>
        /// Shortcut to get at $error.
        /// </summary>
        /// <value>The current value of $global:error </value>
        internal object DollarErrorVariable
        {
            get
            {
                CmdletProviderContext context = null;
                SessionStateScope scope = null;
                object resultItem = null;

                if (!Events.IsExecutingEventAction)
                {
                    resultItem = EngineSessionState.GetVariableValue(
                        SpecialVariables.ErrorVarPath, out context, out scope);
                }
                else
                {
                    resultItem = EngineSessionState.GetVariableValue(
                        SpecialVariables.EventErrorVarPath, out context, out scope);
                }

                return resultItem;
            }

            set
            {
                EngineSessionState.SetVariable(
                    SpecialVariables.ErrorVarPath, value, true, CommandOrigin.Internal);
            }
        }

        internal ActionPreference DebugPreferenceVariable
        {
            get
            {
                bool defaultUsed = false;
                return this.GetEnumPreference(
                    SpecialVariables.DebugPreferenceVarPath,
                    InitialSessionState.DefaultDebugPreference,
                    out defaultUsed);
            }

            set
            {
                this.EngineSessionState.SetVariable(
                    SpecialVariables.DebugPreferenceVarPath,
                    LanguagePrimitives.ConvertTo(value, typeof(ActionPreference), CultureInfo.InvariantCulture),
                    true,
                    CommandOrigin.Internal);
            }
        }

        internal ActionPreference VerbosePreferenceVariable
        {
            get
            {
                bool defaultUsed = false;
                return this.GetEnumPreference(
                    SpecialVariables.VerbosePreferenceVarPath,
                    InitialSessionState.DefaultVerbosePreference,
                    out defaultUsed);
            }

            set
            {
                this.EngineSessionState.SetVariable(
                    SpecialVariables.VerbosePreferenceVarPath,
                    LanguagePrimitives.ConvertTo(value, typeof(ActionPreference), CultureInfo.InvariantCulture),
                    true,
                    CommandOrigin.Internal);
            }
        }

        internal ActionPreference ErrorActionPreferenceVariable
        {
            get
            {
                bool defaultUsed = false;
                return this.GetEnumPreference(
                    SpecialVariables.ErrorActionPreferenceVarPath,
                    InitialSessionState.DefaultErrorActionPreference,
                    out defaultUsed);
            }

            set
            {
                this.EngineSessionState.SetVariable(
                    SpecialVariables.ErrorActionPreferenceVarPath,
                    LanguagePrimitives.ConvertTo(value, typeof(ActionPreference), CultureInfo.InvariantCulture),
                    true,
                    CommandOrigin.Internal);
            }
        }

        internal ActionPreference WarningActionPreferenceVariable
        {
            get
            {
                bool defaultUsed = false;
                return this.GetEnumPreference(
                    SpecialVariables.WarningPreferenceVarPath,
                    InitialSessionState.DefaultWarningPreference,
                    out defaultUsed);
            }

            set
            {
                this.EngineSessionState.SetVariable(
                    SpecialVariables.WarningPreferenceVarPath,
                    LanguagePrimitives.ConvertTo(value, typeof(ActionPreference), CultureInfo.InvariantCulture),
                    true,
                    CommandOrigin.Internal);
            }
        }

        internal ActionPreference InformationActionPreferenceVariable
        {
            get
            {
                bool defaultUsed = false;
                return this.GetEnumPreference(
                    SpecialVariables.InformationPreferenceVarPath,
                    InitialSessionState.DefaultInformationPreference,
                    out defaultUsed);
            }

            set
            {
                this.EngineSessionState.SetVariable(
                    SpecialVariables.InformationPreferenceVarPath,
                    LanguagePrimitives.ConvertTo(value, typeof(ActionPreference), CultureInfo.InvariantCulture),
                    true,
                    CommandOrigin.Internal);
            }
        }

        internal object WhatIfPreferenceVariable
        {
            get
            {
                CmdletProviderContext context = null;
                SessionStateScope scope = null;

                object resultItem = this.EngineSessionState.GetVariableValue(
                    SpecialVariables.WhatIfPreferenceVarPath,
                    out context,
                    out scope);

                return resultItem;
            }

            set
            {
                this.EngineSessionState.SetVariable(
                    SpecialVariables.WhatIfPreferenceVarPath,
                    value,
                    true,
                    CommandOrigin.Internal);
            }
        }

        internal ConfirmImpact ConfirmPreferenceVariable
        {
            get
            {
                bool defaultUsed = false;
                return this.GetEnumPreference(
                    SpecialVariables.ConfirmPreferenceVarPath,
                    InitialSessionState.DefaultConfirmPreference,
                    out defaultUsed);
            }

            set
            {
                this.EngineSessionState.SetVariable(
                    SpecialVariables.ConfirmPreferenceVarPath,
                    LanguagePrimitives.ConvertTo(value, typeof(ConfirmImpact), CultureInfo.InvariantCulture),
                    true,
                    CommandOrigin.Internal);
            }
        }

        internal void RunspaceClosingNotification()
        {
            EngineSessionState.RunspaceClosingNotification();

            _debugger?.Dispose();

            Events?.Dispose();
            Events = null;

            this.transactionManager?.Dispose();
            this.transactionManager = null;
        }

        /// <summary>
        /// Gets the type table instance for this engine.
        /// </summary>
        internal TypeTable TypeTable
        {
            get
            {
                if (_typeTable == null)
                {
                    _typeTable = new TypeTable();
                    _typeTableWeakReference = new WeakReference<TypeTable>(_typeTable);
                }

                return _typeTable;
            }

            set
            {
                _typeTable = value;
                _typeTableWeakReference = (value != null) ? new WeakReference<TypeTable>(value) : null;
            }
        }

        /// <summary>
        /// Here for PSObject, should probably not be used elsewhere, maybe not even in PSObject.
        /// </summary>
        internal WeakReference<TypeTable> TypeTableWeakReference
        {
            get
            {
                if (_typeTable == null)
                {
                    var unused = TypeTable;
                }

                return _typeTableWeakReference;
            }
        }

        private TypeTable _typeTable;
        private WeakReference<TypeTable> _typeTableWeakReference;

        /// <summary>
        /// Gets the format info database for this engine.
        /// </summary>
        internal TypeInfoDataBaseManager FormatDBManager
        {
            get
            {
                if (_formatDBManager == null)
                {
                    // If no Formatter database has been created, then
                    // create and initialize an empty one.
                    _formatDBManager = new TypeInfoDataBaseManager();
                    _formatDBManager.Update(this.AuthorizationManager, this.EngineHostInterface);
                    if (this.InitialSessionState != null)
                    {
                        // Win8:418011: Set DisableFormatTableUpdates only after performing the initial update. Otherwise, formatDBManager will be
                        // in bad state.
                        _formatDBManager.DisableFormatTableUpdates = this.InitialSessionState.DisableFormatUpdates;
                    }
                }

                return _formatDBManager;
            }

            set
            {
                _formatDBManager = value;
            }
        }

        private TypeInfoDataBaseManager _formatDBManager;

        /// <summary>
        /// Gets the TransactionManager instance that controls transactions in the current
        /// instance.
        /// </summary>
        internal PSTransactionManager TransactionManager
        {
            get
            {
                return transactionManager;
            }
        }

        internal PSTransactionManager transactionManager;

        /// <summary>
        /// This method is used for assembly loading requests stemmed from 'InitialSessionState' binding and module loading.
        /// </summary>
        /// <param name="source">Source of the assembly loading request, should be a module name when specified.</param>
        /// <param name="assemblyName">Name of the assembly to be loaded.</param>
        /// <param name="filePath">Path of the assembly to be loaded.</param>
        /// <param name="error">Exception that is caught when the loading fails.</param>
        internal Assembly AddAssembly(string source, string assemblyName, string filePath, out Exception error)
        {
            // Search the cache by the path, and return the assembly if we find it.
            // It's common to have two loading requests for the same assembly when loading a module -- the first time for
            // resolving a binary module path, and the second time for actually processing that module.
            //
            // That's not a problem when all the module assemblies are loaded into the default ALC. But in a scenario where
            // a module tries to hide its nested/root binary modules in a custom ALC, that will become a problem. This is
            // because:
            // in that scenario, the module will usually setup a handler to load the specific assemblies to the custom ALC,
            // and that will be how the first loading request gets served. However, after the module path is resolved with
            // the first loading, the path will be used for the second loading upon real module processing. Since we prefer
            // loading-by-path over loading-by-name in the 'LoadAssembly' call, we will end up loading the same assembly in
            // the default ALC (because we use 'Assembly.LoadFrom' which always loads an assembly to the default ALC) if we
            // do not search in the cache first. That will break the scenario, because the module means to isolate all its
            // dependencies from the default ALC, and it failed to do so.
            //
            // Therefore, we need to search the cache first. The reason we use path as the key is to make sure the request
            // is for exactly the same assembly. The same assembly file should not be loaded into different ALC's by module
            // loading within the same PowerShell session (Runspace).
            //
            // An example module targeting the abovementioned scenario will likely have the following file structure:
            // IsolatedModule
            //  │   IsolatedModule.psd1 (has 'NestedModules = @('Test.Isolated.Init.dll', 'Test.Isolated.Nested.dll')')
            //  │   Test.Isolated.Init.dll (contains the custom ALC and code to setup 'Resolving' handler)
            //  └───Dependencies (folder under module base)
            //         Newtonsoft.Json.dll (version 10.0.0.0 dependency)
            //         Test.Isolated.Nested.dll (nested binary module referencing the particular dependency)
            //
            // In this example, the following events will happen in sequence:
            //  1. PowerShell is able to find 'Test.Isolated.Init.dll' under module base folder, so it will be loaded into
            //     the default ALC as expected and setup the 'Resolving' handler via the 'OnImport' call.
            //  2. PowerShell cannot find 'Test.Isolated.Nested.dll' under the module base folder, so it will call the method
            //     'FixFileName(.., bool canLoadAssembly)' to resolve the path of this binary module.
            //     This particular overload will attempt to load the assembly by name, which will be served by the 'Resolving'
            //     handler that was setup in the step 1. So, the assembly will be loaded into the custom ALC and insert to the
            //     assembly cache.
            //  3. Path of the nested module 'Test.Isolated.Init.dll' now has been resolved by the step 2 (assembly.Location).
            //     Now it's time to actually load this binary module for processing in the method 'LoadBinaryModule', which
            //     will make a call to this method with the resolved assembly file path.
            // At this poin, we will have to query the cache first, instead of calling 'LoadAssembly' directly, to make sure
            // that the assembly instance loaded in the custom ALC in step 2 gets returned back. Otherwise, the same assembly
            // file will be loaded in the default ALC because 'Assembly.LoadFrom' is used in 'LoadAssembly' and that API will
            // always load an assembly file to the default ALC, and that will break this scenario.
            if (TryGetFromAssemblyCache(source, filePath, out Assembly loadedAssembly))
            {
                error = null;
                return loadedAssembly;
            }

            // Attempt to load the requested assembly, first by path then by name.
            loadedAssembly = LoadAssembly(assemblyName, filePath, out error);
            if (loadedAssembly is not null)
            {
                AddToAssemblyCache(source, loadedAssembly);
            }

            return loadedAssembly;
        }

        /// <summary>
        /// Add a loaded assembly to the 'AssemblyCache'.
        /// The <paramref name="source"/> is used as a prefix for the key to make it easy to remove all associated
        /// assemblies from the cache when a module gets unloaded.
        /// </summary>
        /// <param name="source">The source where the assembly comes from, should be a module name when specified.</param>
        /// <param name="assembly">The assembly we try to cache.</param>
        internal void AddToAssemblyCache(string source, Assembly assembly)
        {
            // Try caching the assembly by its location if possible.
            // When it's a dynamic assembly, we use it's full name. This could happen with 'Import-Module -Assembly'.
            string key = string.IsNullOrEmpty(assembly.Location) ? assembly.FullName : assembly.Location;

            // When the assembly is from a module loading, we prefix the key with the source,
            // so we can remove it from the cache when the module gets unloaded.
            if (!string.IsNullOrEmpty(source))
            {
                // Both 'source' and 'key' are of the string type, so no need to specify 'InvariantCulture'.
                key = $"{source}@{key}";
            }

            AssemblyCache.TryAdd(key, assembly);
        }

        /// <summary>
        /// Remove all cache entries that are associated with the specified source.
        /// </summary>
        internal void RemoveFromAssemblyCache(string source)
        {
            if (string.IsNullOrEmpty(source))
            {
                return;
            }

            var keysToRemove = new List<string>();
            string prefix = $"{source}@";

            foreach (string key in AssemblyCache.Keys)
            {
                if (key.StartsWith(prefix, StringComparison.OrdinalIgnoreCase))
                {
                    keysToRemove.Add(key);
                }
            }

            foreach (string key in keysToRemove)
            {
                AssemblyCache.Remove(key);
            }
        }

        /// <summary>
        /// Try to get an assembly from the cache.
        /// </summary>
        private bool TryGetFromAssemblyCache(string source, string filePath, out Assembly assembly)
        {
            if (string.IsNullOrEmpty(filePath))
            {
                assembly = null;
                return false;
            }

            // Both 'source' and 'filePath' are of the string type, so no need to specify 'InvariantCulture'.
            string key = string.IsNullOrEmpty(source) ? filePath : $"{source}@{filePath}";
            return AssemblyCache.TryGetValue(key, out assembly);
        }

        private static Assembly LoadAssembly(string name, string filePath, out Exception error)
        {
            // First we try to load the assembly based on the filename
            Assembly loadedAssembly = null;
            error = null;
            if (!string.IsNullOrEmpty(filePath))
            {
                try
                {
                    // codeql[cs/dll-injection-remote] - The dll is loaded during the initial state setup, which is expected behavior. This allows users hosting PowerShell to load additional C# types to enable their specific scenarios.
                    loadedAssembly = Assembly.LoadFrom(filePath);
                    return loadedAssembly;
                }
                catch (FileNotFoundException fileNotFound)
                {
                    error = fileNotFound;
                }
                catch (FileLoadException fileLoadException)
                {
                    error = fileLoadException;
                    return null;
                }
                catch (BadImageFormatException badImage)
                {
                    error = badImage;
                    return null;
                }
                catch (SecurityException securityException)
                {
                    error = securityException;
                    return null;
                }
            }

            // Then we try to load the assembly based on the given name
            if (!string.IsNullOrEmpty(name))
            {
                string fixedName = null;
                // Remove the '.dll' if it's there...
                fixedName = name.EndsWith(".dll", StringComparison.OrdinalIgnoreCase)
                                ? Path.GetFileNameWithoutExtension(name)
                                : name;

                var assemblyString = Utils.IsPowerShellAssembly(fixedName)
                                         ? Utils.GetPowerShellAssemblyStrongName(fixedName)
                                         : fixedName;

                try
                {
                    loadedAssembly = Assembly.Load(new AssemblyName(assemblyString));
                }
                catch (FileNotFoundException fileNotFound)
                {
                    error = fileNotFound;
                }
                catch (FileLoadException fileLoadException)
                {
                    error = fileLoadException;
                }
                catch (BadImageFormatException badImage)
                {
                    error = badImage;
                }
                catch (SecurityException securityException)
                {
                    error = securityException;
                }
            }

            // If the assembly is loaded, we ignore error as it may come from the filepath loading.
            if (loadedAssembly != null)
            {
                error = null;
            }

            return loadedAssembly;
        }

        /// <summary>
        /// Report an initialization-time error.
        /// </summary>
        /// <param name="resourceString">Resource string.</param>
        /// <param name="arguments">Arguments.</param>
        internal void ReportEngineStartupError(string resourceString, params object[] arguments)
        {
            try
            {
                Cmdlet currentRunningModuleCommand;
                string errorId;
                if (IsModuleCommandCurrentlyRunning(out currentRunningModuleCommand, out errorId))
                {
                    RuntimeException rte = InterpreterError.NewInterpreterException(null, typeof(RuntimeException), null, errorId, resourceString, arguments);
                    currentRunningModuleCommand.WriteError(new ErrorRecord(rte.ErrorRecord, rte));
                }
                else
                {
                    PSHost host = EngineHostInterface;
                    if (host == null)
                    {
                        return;
                    }

                    PSHostUserInterface ui = host.UI;
                    if (ui == null)
                    {
                        return;
                    }

                    ui.WriteErrorLine(
                        StringUtil.Format(resourceString, arguments));
                }
            }
            catch (Exception) // swallow all exceptions
            {
            }
        }

        /// <summary>
        /// Report an initialization-time error.
        /// </summary>
        /// <param name="error">Error to report.</param>
        internal void ReportEngineStartupError(string error)
        {
            try
            {
                Cmdlet currentRunningModuleCommand;
                string errorId;
                if (IsModuleCommandCurrentlyRunning(out currentRunningModuleCommand, out errorId))
                {
                    RuntimeException rte = InterpreterError.NewInterpreterException(null, typeof(RuntimeException), null, errorId, "{0}", error);
                    currentRunningModuleCommand.WriteError(new ErrorRecord(rte.ErrorRecord, rte));
                }
                else
                {
                    PSHost host = EngineHostInterface;
                    if (host == null)
                    {
                        return;
                    }

                    PSHostUserInterface ui = host.UI;
                    if (ui == null)
                    {
                        return;
                    }

                    ui.WriteErrorLine(error);
                }
            }
            catch (Exception) // swallow all exceptions
            {
            }
        }

        /// <summary>
        /// Report an initialization-time error.
        /// </summary>
        /// <param name="e"></param>
        internal void ReportEngineStartupError(Exception e)
        {
            try
            {
                Cmdlet currentRunningModuleCommand;
                string errorId;
                if (IsModuleCommandCurrentlyRunning(out currentRunningModuleCommand, out errorId))
                {
                    ErrorRecord error = null;
                    var rte = e as RuntimeException;

                    error = rte != null
                        ? new ErrorRecord(rte.ErrorRecord, rte)
                        : new ErrorRecord(e, errorId, ErrorCategory.OperationStopped, null);

                    currentRunningModuleCommand.WriteError(error);
                }
                else
                {
                    PSHost host = EngineHostInterface;
                    if (host == null)
                    {
                        return;
                    }

                    PSHostUserInterface ui = host.UI;
                    if (ui == null)
                    {
                        return;
                    }

                    ui.WriteErrorLine(e.Message);
                }
            }
            catch (Exception) // swallow all exceptions
            {
            }
        }

        /// <summary>
        /// Report an initialization-time error.
        /// </summary>
        /// <param name="errorRecord"></param>
        internal void ReportEngineStartupError(ErrorRecord errorRecord)
        {
            try
            {
                Cmdlet currentRunningModuleCommand;
                if (IsModuleCommandCurrentlyRunning(out currentRunningModuleCommand, out _))
                {
                    currentRunningModuleCommand.WriteError(errorRecord);
                }
                else
                {
                    PSHost host = EngineHostInterface;
                    if (host == null)
                    {
                        return;
                    }

                    PSHostUserInterface ui = host.UI;
                    if (ui == null)
                    {
                        return;
                    }

                    ui.WriteErrorLine(errorRecord.ToString());
                }
            }
            catch (Exception) // swallow all exceptions
            {
            }
        }

        private bool IsModuleCommandCurrentlyRunning(out Cmdlet command, out string errorId)
        {
            command = null;
            errorId = null;
            bool result = false;
            if (this.CurrentCommandProcessor != null)
            {
                CommandInfo cmdletInfo = this.CurrentCommandProcessor.CommandInfo;
                if ((string.Equals(cmdletInfo.Name, "Import-Module", StringComparison.OrdinalIgnoreCase) ||
                     string.Equals(cmdletInfo.Name, "Remove-Module", StringComparison.OrdinalIgnoreCase)) &&
                    cmdletInfo.CommandType.Equals(CommandTypes.Cmdlet) &&
                    InitialSessionState.CoreModule.Equals(cmdletInfo.ModuleName, StringComparison.OrdinalIgnoreCase))
                {
                    result = true;
                    command = (Cmdlet)this.CurrentCommandProcessor.Command;
                    errorId = string.Equals(cmdletInfo.Name, "Import-Module", StringComparison.OrdinalIgnoreCase)
                                  ? "Module_ImportModuleError"
                                  : "Module_RemoveModuleError";
                }
            }

            return result;
        }

        /// <summary>
        /// Constructs an Execution context object for Automation Engine.
        /// </summary>
        /// <param name="engine">
        /// Engine that hosts this execution context
        /// </param>
        /// <param name="hostInterface">
        /// Interface that should be used for interaction with host
        /// </param>
        /// <param name="initialSessionState">
        /// InitialSessionState information
        /// </param>
        internal ExecutionContext(AutomationEngine engine, PSHost hostInterface, InitialSessionState initialSessionState)
        {
            InitialSessionState = initialSessionState;
            AuthorizationManager = initialSessionState.AuthorizationManager;

            InitializeCommon(engine, hostInterface);
        }

        private void InitializeCommon(AutomationEngine engine, PSHost hostInterface)
        {
            Engine = engine;
            Events = new PSLocalEventManager(this);
            transactionManager = new PSTransactionManager();
            _debugger = new ScriptDebugger(this);

            EngineHostInterface = hostInterface as InternalHost ?? new InternalHost(hostInterface, this);

            // Hook up the assembly cache
            AssemblyCache = new Dictionary<string, Assembly>(StringComparer.OrdinalIgnoreCase);

            // Initialize the fixed toplevel session state and the current session state
            TopLevelSessionState = EngineSessionState = new SessionStateInternal(this);

            // if authorizationmanager==null, this means the configuration
            // explicitly asked for dummy authorization manager.
            AuthorizationManager ??= new AuthorizationManager(null);

            // Set up the module intrinsics
            Modules = new ModuleIntrinsics(this);
        }

        private static readonly object lockObject = new object();
    }

    /// <summary>
    /// Enum that defines state of monad engine.
    /// </summary>
    internal enum EngineState
    {
        /// <summary>
        /// Engine state is not defined or initialized.
        /// </summary>
        None = 0,

        /// <summary>
        /// Engine available.
        /// </summary>
        Available = 1,

        /// <summary>
        /// Engine service is degraded.
        /// </summary>
        Degraded = 2,

        /// <summary>
        /// Engine is out of service.
        /// </summary>
        OutOfService = 3,

        /// <summary>
        /// Engine is stopped.
        /// </summary>
        Stopped = 4
    }
}
