/
githubmirror
/
PowerShell
Обзор
Документация
Войти
/
githubmirror
/
PowerShell
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
src/System.Management.Automation/engine/ExecutionContext.cs
1 711 строк
64 KB
Anam Navied
Add CodeQL suppressions for PowerShell intended behavior (#25359)
15 апр 2025, 04:52
Не верифицирован
15 апр 2025, 04:52
4e3875c
Код
Авторство
О чём код?
// 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> /// <param name="forCompletion"></param> /// <returns>The command processor object.</returns> internal CommandProcessorBase CreateCommand(string command, bool dotSource, bool forCompletion = false) { CommandOrigin commandOrigin = this.EngineSessionState.CurrentScope.ScopeOrigin; CommandProcessorBase commandProcessor = CommandDiscovery.LookupCommandProcessor(command, commandOrigin, !dotSource, forCompletion); // 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 } }