Files
PowerShell-PowerShell/src/System.Management.Automation/engine/ExecutionContext.cs
T

1712 lines
64 KiB
C#

// 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
}
}