/
githubmirror
/
PowerShell
Обзор
Документация
Войти
/
githubmirror
/
PowerShell
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
src/System.Management.Automation/engine/hostifaces/Connection.cs
1 901 строка
69 KB
Jordan Borean
Add property and event for debug attach (#25788)
08 окт 2025, 01:34
Не верифицирован
08 окт 2025, 01:34
0ef738b
Код
Авторство
О чём код?
// Copyright (c) Microsoft Corporation. // Licensed under the MIT License. using System.Collections.Generic; using System.Collections.ObjectModel; using System.Diagnostics.CodeAnalysis; using System.Management.Automation.Host; using System.Management.Automation.Internal; using System.Runtime.Serialization; using System.Threading; using Dbg = System.Management.Automation.Diagnostics; namespace System.Management.Automation.Runspaces { #region Exceptions /// <summary> /// Exception thrown when state of the runspace is different from /// expected state of runspace. /// </summary> public class InvalidRunspaceStateException : SystemException { /// <summary> /// Initializes a new instance of InvalidRunspaceStateException. /// </summary> public InvalidRunspaceStateException() : base ( StringUtil.Format(RunspaceStrings.InvalidRunspaceStateGeneral) ) { } /// <summary> /// Initializes a new instance of InvalidRunspaceStateException with a specified error message. /// </summary> /// <param name="message"> /// The message that describes the error. /// </param> public InvalidRunspaceStateException(string message) : base(message) { } /// <summary> /// Initializes a new instance of the InvalidRunspaceStateException class /// with a specified error message and a reference to the inner exception /// that is the cause of this exception. /// </summary> /// <param name="message"> /// The message that describes the error. /// </param> /// <param name="innerException"> /// The exception that is the cause of the current exception. /// </param> public InvalidRunspaceStateException(string message, Exception innerException) : base(message, innerException) { } /// <summary> /// Initializes a new instance of the InvalidRunspaceStateException /// with a specified error message and current and expected state. /// </summary> /// <param name="message">The message that describes the error.</param> /// <param name="currentState">Current state of runspace.</param> /// <param name="expectedState">Expected states of runspace.</param> internal InvalidRunspaceStateException ( string message, RunspaceState currentState, RunspaceState expectedState ) : base(message) { _expectedState = expectedState; _currentState = currentState; } #region ISerializable Members // 2005/04/20-JonN No need to implement GetObjectData // if all fields are static or [NonSerialized] /// <summary> /// Initializes a new instance of the <see cref="InvalidRunspaceStateException"/> /// class with serialized data. /// </summary> /// <param name="info"> /// The <see cref="SerializationInfo"/> that holds the serialized object /// data about the exception being thrown. /// </param> /// <param name="context"> /// The <see cref="StreamingContext"/> that contains contextual information /// about the source or destination. /// </param> [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] protected InvalidRunspaceStateException(SerializationInfo info, StreamingContext context) { throw new NotSupportedException(); } #endregion /// <summary> /// Access CurrentState of the runspace. /// </summary> /// <remarks>This is the state of the runspace when exception was thrown. /// </remarks> public RunspaceState CurrentState { get { return _currentState; } internal set { _currentState = value; } } /// <summary> /// Expected state of runspace by the operation which has thrown this exception. /// </summary> public RunspaceState ExpectedState { get { return _expectedState; } internal set { _expectedState = value; } } /// <summary> /// State of the runspace when exception was thrown. /// </summary> [NonSerialized] private RunspaceState _currentState = 0; /// <summary> /// States of the runspace expected in method which throws this exception. /// </summary> [NonSerialized] private RunspaceState _expectedState = 0; } #endregion Exceptions #region Runspace state /// <summary> /// Defines various states of runspace. /// </summary> public enum RunspaceState { /// <summary> /// Beginning state upon creation. /// </summary> BeforeOpen = 0, /// <summary> /// A runspace is being established. /// </summary> Opening = 1, /// <summary> /// The runspace is established and valid. /// </summary> Opened = 2, /// <summary> /// The runspace is closed or has not been established. /// </summary> Closed = 3, /// <summary> /// The runspace is being closed. /// </summary> Closing = 4, /// <summary> /// The runspace has been disconnected abnormally. /// </summary> Broken = 5, /// <summary> /// The runspace is being disconnected. /// </summary> Disconnecting = 6, /// <summary> /// The runspace is disconnected. /// </summary> Disconnected = 7, /// <summary> /// The runspace is Connecting. /// </summary> Connecting = 8 } /// <summary> /// These options control whether a new thread is created when a command is executed within a runspace. /// </summary> public enum PSThreadOptions { /// <summary> /// Use the default options: UseNewThread for local Runspace, ReuseThread for local RunspacePool, server settings for remote Runspace and RunspacePool. /// </summary> Default = 0, /// <summary> /// Creates a new thread for each invocation. /// </summary> UseNewThread = 1, /// <summary> /// Creates a new thread for the first invocation and then re-uses /// that thread in subsequent invocations. /// </summary> ReuseThread = 2, /// <summary> /// Doesn't create a new thread; the execution occurs on the thread /// that calls Invoke. This option is not valid for asynchronous calls. /// </summary> UseCurrentThread = 3 } /// <summary> /// Defines type which has information about RunspaceState and /// Exception associated with RunspaceState. /// </summary> public sealed class RunspaceStateInfo { #region constructors /// <summary> /// Constructor for state changes not resulting from an error. /// </summary> /// <param name="state">The state of the runspace.</param> internal RunspaceStateInfo(RunspaceState state) : this(state, null) { } /// <summary> /// Constructor for state changes with an optional error. /// </summary> /// <param name="state">The state of runspace.</param> /// <param name="reason">A non-null exception if the state change was /// caused by an error, otherwise; null. /// </param> internal RunspaceStateInfo(RunspaceState state, Exception reason) : base() { State = state; Reason = reason; } /// <summary> /// Copy constructor to support cloning. /// </summary> /// <param name="runspaceStateInfo">The source /// RunspaceStateInfo /// </param> internal RunspaceStateInfo(RunspaceStateInfo runspaceStateInfo) { State = runspaceStateInfo.State; Reason = runspaceStateInfo.Reason; } #endregion constructors #region public_properties /// <summary> /// The state of the runspace. /// </summary> public RunspaceState State { get; } /// <summary> /// The reason for the state change, if caused by an error. /// </summary> /// <remarks> /// The value of this property is non-null if the state /// changed due to an error. Otherwise, the value of this /// property is null. /// </remarks> public Exception Reason { get; } #endregion public_properties /// <summary> /// Override for ToString() /// </summary> /// <returns></returns> public override string ToString() { return State.ToString(); } /// <summary> /// Clones current object. /// </summary> /// <returns>Cloned object.</returns> internal RunspaceStateInfo Clone() { return new RunspaceStateInfo(this); } #region private_fields #endregion private_fields } /// <summary> /// Defines Event arguments passed to RunspaceStateEvent handler /// <see cref="Runspace.StateChanged"/> event. /// </summary> public sealed class RunspaceStateEventArgs : EventArgs { #region constructors /// <summary> /// Constructs RunspaceStateEventArgs using RunspaceStateInfo. /// </summary> /// <param name="runspaceStateInfo">The information about /// current state of the runspace.</param> /// <exception cref="ArgumentNullException">RunspaceStateInfo is null /// </exception> internal RunspaceStateEventArgs(RunspaceStateInfo runspaceStateInfo) { if (runspaceStateInfo == null) { throw PSTraceSource.NewArgumentNullException(nameof(runspaceStateInfo)); } RunspaceStateInfo = runspaceStateInfo; } #endregion constructors #region public_properties /// <summary> /// Information about state of the runspace. /// </summary> /// <remarks> /// This value indicates the state of the runspace after the /// change. /// </remarks> public RunspaceStateInfo RunspaceStateInfo { get; } #endregion public_properties } /// <summary> /// Enum to indicate whether a Runspace is busy or available. /// </summary> public enum RunspaceAvailability { /// <summary> /// The Runspace is not been in the Opened state. /// </summary> None = 0, /// <summary> /// The Runspace is available to execute commands. /// </summary> Available, /// <summary> /// The Runspace is available to execute nested commands. /// </summary> AvailableForNestedCommand, /// <summary> /// The Runspace is busy executing a command. /// </summary> Busy, /// <summary> /// Applies only to remote runspace case. The remote runspace /// is currently in a Debugger Stop mode and requires a debugger /// SetDebuggerAction() call to continue. /// </summary> RemoteDebug } /// <summary> /// Defines the event arguments passed to the AvailabilityChanged <see cref="Runspace.AvailabilityChanged"/> event. /// </summary> public sealed class RunspaceAvailabilityEventArgs : EventArgs { internal RunspaceAvailabilityEventArgs(RunspaceAvailability runspaceAvailability) { RunspaceAvailability = runspaceAvailability; } /// <summary> /// Whether the Runspace is available to execute commands. /// </summary> public RunspaceAvailability RunspaceAvailability { get; } } #endregion Runspace state #region Runspace capabilities /// <summary> /// Defines runspace capabilities. /// </summary> public enum RunspaceCapability { /// <summary> /// Legacy capabilities for WinRM only, from Win7 timeframe. /// </summary> Default = 0x0, /// <summary> /// Runspace and remoting layer supports disconnect/connect feature. /// </summary> SupportsDisconnect = 0x1, /// <summary> /// Runspace is based on a named pipe transport. /// </summary> NamedPipeTransport = 0x2, /// <summary> /// Runspace is based on a VM socket transport. /// </summary> VMSocketTransport = 0x4, /// <summary> /// Runspace is based on SSH transport. /// </summary> SSHTransport = 0x8, /// <summary> /// Runspace is based on open custom connection/transport support. /// </summary> CustomTransport = 0x100 } #endregion /// <summary> /// Public interface to PowerShell Runtime. Provides APIs for creating pipelines, /// access session state etc. /// </summary> public abstract class Runspace : IDisposable { #region Private Data private static int s_globalId; private readonly Stack<PowerShell> _runningPowerShells; private PowerShell _baseRunningPowerShell; private readonly object _syncObject; #endregion #region constructor /// <summary> /// Explicit default constructor. /// </summary> internal Runspace() { // Create the default Runspace Id and friendly name. Id = System.Threading.Interlocked.Increment(ref s_globalId); Name = "Runspace" + Id.ToString(System.Globalization.NumberFormatInfo.InvariantInfo); _runningPowerShells = new Stack<PowerShell>(); _syncObject = new object(); // Keep track of this runspace until it is disposed. lock (s_syncObject) { s_runspaceDictionary.Add(Id, new WeakReference<Runspace>(this)); } } /// <summary> /// Static Constructor. /// </summary> static Runspace() { s_syncObject = new object(); s_runspaceDictionary = new SortedDictionary<int, WeakReference<Runspace>>(); s_globalId = 0; } #endregion constructor #region properties /// <summary> /// Used to store Runspace reference on per thread basis. Used by /// various PowerShell engine features to get access to TypeTable /// etc. /// </summary> [ThreadStatic] private static Runspace t_threadSpecificDefaultRunspace = null; /// <summary> /// Gets and sets the default Runspace used to evaluate scripts. /// </summary> /// <remarks>The Runspace used to set this property should not be shared between different threads.</remarks> public static Runspace DefaultRunspace { get { return t_threadSpecificDefaultRunspace; } set { if (value == null || !value.RunspaceIsRemote) { t_threadSpecificDefaultRunspace = value; } else { throw new InvalidOperationException(RunspaceStrings.RunspaceNotLocal); } } } /// <summary> /// A PrimaryRunspace is a runspace that persists for the entire lifetime of the PowerShell session. It is only /// closed or disposed when the session is ending. So when the PrimaryRunspace is closing it will trigger on-exit /// cleanup that includes closing any other local runspaces left open, and will allow the process to exit. /// </summary> internal static Runspace PrimaryRunspace { get { return s_primaryRunspace; } set { var result = Interlocked.CompareExchange<Runspace>(ref s_primaryRunspace, value, null); if (result != null) { throw new PSInvalidOperationException(RunspaceStrings.PrimaryRunspaceAlreadySet); } } } private static Runspace s_primaryRunspace; /// <summary> /// Returns true if Runspace.DefaultRunspace can be used to /// create an instance of the PowerShell class with /// 'UseCurrentRunspace = true'. /// </summary> public static bool CanUseDefaultRunspace { // can use default runspace in a thread safe manner only if // 1. we have a default runspace // 2. we recognize the type of current runspace and current pipeline // 3. the pipeline executes on the same thread as this method // we don't return "true" for // 1. we have a default runspace // 2. no currently executing pipeline // to avoid a race condition where a pipeline is started // after this property getter did all the checks get { RunspaceBase runspace = Runspace.DefaultRunspace as RunspaceBase; if (runspace != null) { Pipeline currentPipeline = runspace.GetCurrentlyRunningPipeline(); LocalPipeline localPipeline = currentPipeline as LocalPipeline; if ((localPipeline != null) && (localPipeline.NestedPipelineExecutionThread != null)) { return (localPipeline.NestedPipelineExecutionThread.ManagedThreadId == Environment.CurrentManagedThreadId); } } return false; } } internal const ApartmentState DefaultApartmentState = ApartmentState.Unknown; /// <summary> /// ApartmentState of the thread used to execute commands within this Runspace. /// </summary> /// <remarks> /// Any updates to the value of this property must be done before the Runspace is opened /// </remarks> /// <exception cref="InvalidRunspaceStateException"> /// An attempt to change this property was made after opening the Runspace /// </exception> public ApartmentState ApartmentState { get { return this.apartmentState; } set { if (this.RunspaceStateInfo.State != RunspaceState.BeforeOpen) { throw new InvalidRunspaceStateException(StringUtil.Format(RunspaceStrings.ChangePropertyAfterOpen)); } this.apartmentState = value; } } private ApartmentState apartmentState = Runspace.DefaultApartmentState; /// <summary> /// This property determines whether a new thread is create for each invocation. /// </summary> /// <remarks> /// Any updates to the value of this property must be done before the Runspace is opened /// </remarks> /// <exception cref="InvalidRunspaceStateException"> /// An attempt to change this property was made after opening the Runspace /// </exception> /// <exception cref="InvalidOperationException"> /// The thread options cannot be changed to the requested value /// </exception> public abstract PSThreadOptions ThreadOptions { get; set; } /// <summary> /// Return version of this runspace. /// </summary> public abstract Version Version { get; } /// <summary> /// Return whether the Runspace is Remote /// We can determine this by whether the runspace is an implementation of LocalRunspace /// or infer it from whether the ConnectionInfo property is null /// If it happens to be an instance of a LocalRunspace, but has a non-null ConnectionInfo /// we declare it to be remote. /// </summary> public bool RunspaceIsRemote { get { return this is not LocalRunspace && ConnectionInfo != null; } } /// <summary> /// Retrieve information about current state of the runspace. /// </summary> public abstract RunspaceStateInfo RunspaceStateInfo { get; } /// <summary> /// Gets the current availability of the Runspace. /// </summary> public abstract RunspaceAvailability RunspaceAvailability { get; protected set; } /// <summary> /// InitialSessionState information for this runspace. /// </summary> public abstract InitialSessionState InitialSessionState { get; } /// <summary> /// Get unique id for this instance of runspace. It is primarily used /// for logging purposes. /// </summary> public Guid InstanceId { get; // This id is also used to identify proxy and remote runspace objects. // We need to set this when reconstructing a remote runspace to connect // to an existing remote runspace. internal set; } = Guid.NewGuid(); /// <summary> /// Gets execution context. /// </summary> /// <exception cref="InvalidRunspaceStateException">Runspace is not opened. /// </exception> internal ExecutionContext ExecutionContext { get { return GetExecutionContext; } } /// <summary> /// Skip user profile on engine initialization. /// </summary> internal bool SkipUserProfile { get; set; } = false; /// <summary> /// Connection information for remote Runspaces, null for local Runspaces. /// </summary> public abstract RunspaceConnectionInfo ConnectionInfo { get; } /// <summary> /// ConnectionInfo originally supplied by the user. /// </summary> public abstract RunspaceConnectionInfo OriginalConnectionInfo { get; } /// <summary> /// Manager for JobSourceAdapters registered in this runspace. /// </summary> public abstract JobManager JobManager { get; } /// <summary> /// DisconnectedOn property applies to remote runspaces that have /// been disconnected. /// </summary> public DateTime? DisconnectedOn { get; internal set; } /// <summary> /// ExpiresOn property applies to remote runspaces that have been /// disconnected. /// </summary> public DateTime? ExpiresOn { get; internal set; } /// <summary> /// Gets and sets a friendly name for the Runspace. /// </summary> public string Name { get; set; } /// <summary> /// Gets the Runspace Id. /// </summary> public int Id { get; } /// <summary> /// Gets and sets a boolean indicating whether the runspace has a /// debugger attached with <c>Debug-Runspace</c>. /// </summary> public bool IsRemoteDebuggerAttached { get; internal set; } /// <summary> /// Returns protocol version that the remote server uses for PS remoting. /// </summary> internal Version GetRemoteProtocolVersion() { Version remoteProtocolVersionDeclaredByServer; bool isServerDeclarationValid = PSPrimitiveDictionary.TryPathGet( this.GetApplicationPrivateData(), out remoteProtocolVersionDeclaredByServer, PSVersionInfo.PSVersionTableName, PSVersionInfo.PSRemotingProtocolVersionName); if (isServerDeclarationValid) { return remoteProtocolVersionDeclaredByServer; } else { return RemotingConstants.ProtocolVersion; } } /// <summary> /// Engine activity id (for ETW tracing) /// </summary> internal Guid EngineActivityId { get; set; } = Guid.Empty; /// <summary> /// Returns a read only runspace dictionary. /// </summary> internal static ReadOnlyDictionary<int, WeakReference<Runspace>> RunspaceDictionary { get { lock (s_syncObject) { return new ReadOnlyDictionary<int, WeakReference<Runspace>>(new Dictionary<int, WeakReference<Runspace>>(s_runspaceDictionary)); } } } private static readonly SortedDictionary<int, WeakReference<Runspace>> s_runspaceDictionary; private static readonly object s_syncObject; /// <summary> /// Returns a read only list of runspaces. /// </summary> internal static IReadOnlyList<Runspace> RunspaceList { get { List<Runspace> runspaceList = new List<Runspace>(); lock (s_syncObject) { foreach (var item in s_runspaceDictionary.Values) { Runspace runspace; if (item.TryGetTarget(out runspace)) { runspaceList.Add(runspace); } } } return new ReadOnlyCollection<Runspace>(runspaceList); } } #endregion properties #region events /// <summary> /// Event raised when RunspaceState changes. /// </summary> public abstract event EventHandler<RunspaceStateEventArgs> StateChanged; /// <summary> /// Event raised when the availability of the Runspace changes. /// </summary> public abstract event EventHandler<RunspaceAvailabilityEventArgs> AvailabilityChanged; /// <summary> /// Returns true if there are any subscribers to the AvailabilityChanged event. /// </summary> internal abstract bool HasAvailabilityChangedSubscribers { get; } /// <summary> /// Raises the AvailabilityChanged event. /// </summary> protected abstract void OnAvailabilityChanged(RunspaceAvailabilityEventArgs e); /// <summary> /// Used to raise the AvailabilityChanged event when the state of the currently executing pipeline changes. /// </summary> /// <remarks> /// The possible pipeline states are /// NotStarted /// Running /// Disconnected /// Stopping /// Stopped /// Completed /// Failed /// </remarks> internal void UpdateRunspaceAvailability(PipelineState pipelineState, bool raiseEvent, Guid? cmdInstanceId = null) { RunspaceAvailability oldAvailability = this.RunspaceAvailability; switch (oldAvailability) { // Because of disconnect/connect support runspace availability can now transition // in and out of "None" state. case RunspaceAvailability.None: switch (pipelineState) { case PipelineState.Running: this.RunspaceAvailability = RunspaceAvailability.Busy; break; // Otherwise no change. } break; case RunspaceAvailability.Available: switch (pipelineState) { case PipelineState.Running: this.RunspaceAvailability = RunspaceAvailability.Busy; break; case PipelineState.Disconnected: this.RunspaceAvailability = Runspaces.RunspaceAvailability.None; break; } break; case RunspaceAvailability.AvailableForNestedCommand: switch (pipelineState) { case PipelineState.Running: this.RunspaceAvailability = RunspaceAvailability.Busy; break; case PipelineState.Completed: // a nested pipeline caused the host to exit nested prompt this.RunspaceAvailability = (this.InNestedPrompt || (_runningPowerShells.Count > 1)) ? RunspaceAvailability.AvailableForNestedCommand : RunspaceAvailability.Available; break; default: break; // no change in the availability } break; case RunspaceAvailability.Busy: case RunspaceAvailability.RemoteDebug: switch (pipelineState) { case PipelineState.Disconnected: if (oldAvailability == Runspaces.RunspaceAvailability.RemoteDebug) { this.RunspaceAvailability = RunspaceAvailability.RemoteDebug; } else { this.RunspaceAvailability = RunspaceAvailability.None; } break; case PipelineState.Stopping: break; // no change in the availability case PipelineState.Completed: case PipelineState.Stopped: case PipelineState.Failed: if (this.InNestedPrompt || (this is not RemoteRunspace && this.Debugger.InBreakpoint)) { this.RunspaceAvailability = RunspaceAvailability.AvailableForNestedCommand; } else { RemoteRunspace remoteRunspace = this as RemoteRunspace; RemoteDebugger remoteDebugger = (remoteRunspace != null) ? remoteRunspace.Debugger as RemoteDebugger : null; Internal.ConnectCommandInfo remoteCommand = remoteRunspace?.RemoteCommand; if (((pipelineState == PipelineState.Completed) || (pipelineState == PipelineState.Failed) || ((pipelineState == PipelineState.Stopped) && (this.RunspaceStateInfo.State == RunspaceState.Opened))) && (remoteCommand != null) && (cmdInstanceId != null) && (remoteCommand.CommandId == cmdInstanceId)) { // Completed, Failed, and Stopped with Runspace.Opened states are command finish states and we know // that the command is finished on the server. // Setting ConnectCommands to null indicates that the runspace is free to run other // commands. remoteRunspace.RunspacePool.RemoteRunspacePoolInternal.ConnectCommands = null; remoteCommand = null; if ((remoteDebugger != null) && (pipelineState == PipelineState.Stopped)) { // Notify remote debugger of a stop in case the stop occurred while command was in debug stop. remoteDebugger.OnCommandStopped(); } } Pipeline currentPipeline = this.GetCurrentlyRunningPipeline(); RemotePipeline remotePipeline = currentPipeline as RemotePipeline; Guid? pipeLineCmdInstance = (remotePipeline != null && remotePipeline.PowerShell != null) ? remotePipeline.PowerShell.InstanceId : (Guid?)null; if (currentPipeline == null) { // A runspace is available: // - if there is no currently running pipeline // and for remote runspaces: // - if there is no remote command associated with it. // - if the remote runspace pool is marked as available for connection. if (remoteCommand == null) { if (remoteRunspace != null) { if ((remoteDebugger != null) && (pipelineState == PipelineState.Stopped)) { // Notify remote debugger of a stop in case the stop occurred while command was in debug stop. remoteDebugger.OnCommandStopped(); } this.RunspaceAvailability = remoteRunspace.RunspacePool.RemoteRunspacePoolInternal.AvailableForConnection ? RunspaceAvailability.Available : Runspaces.RunspaceAvailability.Busy; } else { this.RunspaceAvailability = RunspaceAvailability.Available; } } } else if ((cmdInstanceId != null) && (pipeLineCmdInstance != null) && (cmdInstanceId == pipeLineCmdInstance)) { if ((remoteDebugger != null) && (pipelineState == PipelineState.Stopped)) { // Notify remote debugger of a stop in case the stop occurred while command was in debug stop. remoteDebugger.OnCommandStopped(); } this.RunspaceAvailability = RunspaceAvailability.Available; } else // a nested pipeline completed, but the parent pipeline is still running { if (oldAvailability == Runspaces.RunspaceAvailability.RemoteDebug) { this.RunspaceAvailability = Runspaces.RunspaceAvailability.RemoteDebug; } else if ((currentPipeline.PipelineStateInfo.State == PipelineState.Running) || (_runningPowerShells.Count > 1)) { // Either the current pipeline is running or there are other nested commands to run in the Runspace. this.RunspaceAvailability = RunspaceAvailability.Busy; } else { this.RunspaceAvailability = RunspaceAvailability.Available; } } } break; case PipelineState.Running: // this can happen if a nested pipeline is created without entering a nested prompt break; // no change in the availability default: break; // no change in the availability } break; default: Diagnostics.Assert(false, "Invalid RunspaceAvailability"); break; } if (raiseEvent && this.RunspaceAvailability != oldAvailability) { OnAvailabilityChanged(new RunspaceAvailabilityEventArgs(this.RunspaceAvailability)); } } /// <summary> /// Used to update the runspace availability when the state of the currently executing PowerShell instance changes. /// </summary> /// <remarks> /// The possible invocation states are /// NotStarted /// Running /// Stopping /// Stopped /// Completed /// Failed /// </remarks> internal void UpdateRunspaceAvailability(PSInvocationState invocationState, bool raiseEvent, Guid cmdInstanceId) { switch (invocationState) { case PSInvocationState.NotStarted: UpdateRunspaceAvailability(PipelineState.NotStarted, raiseEvent, cmdInstanceId); break; case PSInvocationState.Running: UpdateRunspaceAvailability(PipelineState.Running, raiseEvent, cmdInstanceId); break; case PSInvocationState.Completed: UpdateRunspaceAvailability(PipelineState.Completed, raiseEvent, cmdInstanceId); break; case PSInvocationState.Failed: UpdateRunspaceAvailability(PipelineState.Failed, raiseEvent, cmdInstanceId); break; case PSInvocationState.Stopping: UpdateRunspaceAvailability(PipelineState.Stopping, raiseEvent, cmdInstanceId); break; case PSInvocationState.Stopped: UpdateRunspaceAvailability(PipelineState.Stopped, raiseEvent, cmdInstanceId); break; case PSInvocationState.Disconnected: UpdateRunspaceAvailability(PipelineState.Disconnected, raiseEvent, cmdInstanceId); break; default: Diagnostics.Assert(false, "Invalid PSInvocationState"); break; } } /// <summary> /// Used to update the runspace availability event when the state of the runspace changes. /// </summary> /// <remarks> /// The possible runspace states are: /// BeforeOpen /// Opening /// Opened /// Closed /// Closing /// Broken /// </remarks> protected void UpdateRunspaceAvailability(RunspaceState runspaceState, bool raiseEvent) { RunspaceAvailability oldAvailability = this.RunspaceAvailability; RemoteRunspace remoteRunspace = this as RemoteRunspace; Internal.ConnectCommandInfo remoteCommand = null; bool remoteDebug = false; if (remoteRunspace != null) { remoteCommand = remoteRunspace.RemoteCommand; RemoteDebugger remoteDebugger = remoteRunspace.Debugger as RemoteDebugger; remoteDebug = (remoteDebugger != null) && remoteDebugger.IsRemoteDebug; } switch (oldAvailability) { case RunspaceAvailability.None: switch (runspaceState) { case RunspaceState.Opened: if (remoteDebug) { this.RunspaceAvailability = Runspaces.RunspaceAvailability.RemoteDebug; } else { this.RunspaceAvailability = (remoteCommand == null && GetCurrentlyRunningPipeline() == null) ? RunspaceAvailability.Available : RunspaceAvailability.Busy; } break; default: break; // no change in the availability } break; case RunspaceAvailability.Available: case RunspaceAvailability.AvailableForNestedCommand: case RunspaceAvailability.RemoteDebug: case RunspaceAvailability.Busy: switch (runspaceState) { case RunspaceState.Closing: case RunspaceState.Closed: case RunspaceState.Broken: case RunspaceState.Disconnected: this.RunspaceAvailability = RunspaceAvailability.None; break; default: break; // no change in the availability } break; default: Diagnostics.Assert(false, "Invalid RunspaceAvailability"); break; } if (raiseEvent && this.RunspaceAvailability != oldAvailability) { OnAvailabilityChanged(new RunspaceAvailabilityEventArgs(this.RunspaceAvailability)); } } /// <summary> /// Used to update the runspace availability from Enter/ExitNestedPrompt and the debugger. /// </summary> internal void UpdateRunspaceAvailability(RunspaceAvailability availability, bool raiseEvent) { RunspaceAvailability oldAvailability = this.RunspaceAvailability; this.RunspaceAvailability = availability; if (raiseEvent && this.RunspaceAvailability != oldAvailability) { OnAvailabilityChanged(new RunspaceAvailabilityEventArgs(this.RunspaceAvailability)); } } /// <summary> /// Raises the AvailabilityChanged event. /// </summary> internal void RaiseAvailabilityChangedEvent(RunspaceAvailability availability) { OnAvailabilityChanged(new RunspaceAvailabilityEventArgs(availability)); } #endregion events #region Public static methods /// <summary> /// Queries the server for disconnected runspaces and creates an array of runspace /// objects associated with each disconnected runspace on the server. Each /// runspace object in the returned array is in the Disconnected state and can be /// connected to the server by calling the Connect() method on the runspace. /// </summary> /// <param name="connectionInfo">Connection object for the target server.</param> /// <returns>Array of Runspace objects each in the Disconnected state.</returns> [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Runspaces")] public static Runspace[] GetRunspaces(RunspaceConnectionInfo connectionInfo) { return GetRunspaces(connectionInfo, null, null); } /// <summary> /// Queries the server for disconnected runspaces and creates an array of runspace /// objects associated with each disconnected runspace on the server. Each /// runspace object in the returned array is in the Disconnected state and can be /// connected to the server by calling the Connect() method on the runspace. /// </summary> /// <param name="connectionInfo">Connection object for the target server.</param> /// <param name="host">Client host object.</param> /// <returns>Array of Runspace objects each in the Disconnected state.</returns> [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Runspaces")] public static Runspace[] GetRunspaces(RunspaceConnectionInfo connectionInfo, PSHost host) { return GetRunspaces(connectionInfo, host, null); } /// <summary> /// Queries the server for disconnected runspaces and creates an array of runspace /// objects associated with each disconnected runspace on the server. Each /// runspace object in the returned array is in the Disconnected state and can be /// connected to the server by calling the Connect() method on the runspace. /// </summary> /// <param name="connectionInfo">Connection object for the target server.</param> /// <param name="host">Client host object.</param> /// <param name="typeTable">TypeTable object.</param> /// <returns>Array of Runspace objects each in the Disconnected state.</returns> [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Runspaces")] public static Runspace[] GetRunspaces(RunspaceConnectionInfo connectionInfo, PSHost host, TypeTable typeTable) { return RemoteRunspace.GetRemoteRunspaces(connectionInfo, host, typeTable); } /// <summary> /// Returns a single disconnected Runspace object targeted to the remote computer and remote /// session as specified by the connection, session Id, and command Id parameters. /// </summary> /// <param name="connectionInfo">Connection object for the target server.</param> /// <param name="sessionId">Id of a disconnected remote session on the target server.</param> /// <param name="commandId">Optional Id of a disconnected command running in the disconnected remote session on the target server.</param> /// <param name="host">Optional client host object.</param> /// <param name="typeTable">Optional TypeTable object.</param> /// <returns>Disconnected runspace corresponding to the provided session Id.</returns> public static Runspace GetRunspace(RunspaceConnectionInfo connectionInfo, Guid sessionId, Guid? commandId, PSHost host, TypeTable typeTable) { return RemoteRunspace.GetRemoteRunspace(connectionInfo, sessionId, commandId, host, typeTable); } #endregion #region public Disconnect-Connect methods /// <summary> /// Disconnects the runspace synchronously. /// </summary> /// <remarks> /// Disconnects the remote runspace and any running command from the server /// machine. Any data generated by the running command on the server is /// cached on the server machine. This runspace object goes to the disconnected /// state. This object can be reconnected to the server by calling the /// Connect() method. /// If the remote runspace on the server remains disconnected for the IdleTimeout /// value (as defined in the WSManConnectionInfo object) then it is closed and /// torn down on the server. /// </remarks> /// <exception cref="InvalidRunspaceStateException"> /// RunspaceState is not Opened. /// </exception> public abstract void Disconnect(); /// <summary> /// Disconnects the runspace asynchronously. /// </summary> /// <remarks> /// Disconnects the remote runspace and any running command from the server /// machine. Any data generated by the running command on the server is /// cached on the server machine. This runspace object goes to the disconnected /// state. This object can be reconnected to the server by calling the /// Connect() method. /// If the remote runspace on the server remains disconnected for the IdleTimeout /// value (as defined in the WSManConnectionInfo object) then it is closed and /// torn down on the server. /// </remarks> /// <exception cref="InvalidRunspaceStateException"> /// RunspaceState is not Opened. /// </exception> public abstract void DisconnectAsync(); /// <summary> /// Connects the runspace to its remote counterpart synchronously. /// </summary> /// <remarks> /// Connects the runspace object to its corresponding runspace on the target /// server machine. The target server machine is identified by the connection /// object passed in during construction. The remote runspace is identified /// by the internal runspace Guid value. /// </remarks> /// <exception cref="InvalidRunspaceStateException"> /// RunspaceState is not Disconnected. /// </exception> public abstract void Connect(); /// <summary> /// Connects a runspace to its remote counterpart asynchronously. /// </summary> /// <remarks> /// Connects the runspace object to its corresponding runspace on the target /// server machine. The target server machine is identified by the connection /// object passed in during construction. The remote runspace is identified /// by the internal runspace Guid value. /// </remarks> /// <exception cref="InvalidRunspaceStateException"> /// RunspaceState is not Disconnected. /// </exception> public abstract void ConnectAsync(); /// <summary> /// Creates a PipeLine object in the disconnected state for the currently disconnected /// remote running command associated with this runspace. /// </summary> /// <returns>Pipeline object in disconnected state.</returns> public abstract Pipeline CreateDisconnectedPipeline(); /// <summary> /// Creates a PowerShell object in the disconnected state for the currently disconnected /// remote running command associated with this runspace. /// </summary> /// <returns>PowerShell object in disconnected state.</returns> public abstract PowerShell CreateDisconnectedPowerShell(); /// <summary> /// Returns Runspace capabilities. /// </summary> /// <returns>RunspaceCapability.</returns> public abstract RunspaceCapability GetCapabilities(); #endregion #region methods /// <summary> /// Opens the runspace synchronously. Runspace must be opened before it can be used. /// </summary> /// <exception cref="InvalidRunspaceStateException"> /// RunspaceState is not BeforeOpen /// </exception> public abstract void Open(); /// <summary> /// Open the runspace Asynchronously. /// </summary> /// <exception cref="InvalidRunspaceStateException"> /// RunspaceState is not BeforeOpen /// </exception> public abstract void OpenAsync(); /// <summary> /// Close the runspace synchronously. /// </summary> /// <remarks> /// Attempts to execute pipelines after a call to close will fail. /// </remarks> /// <exception cref="InvalidRunspaceStateException"> /// RunspaceState is BeforeOpen or Opening /// </exception> public abstract void Close(); /// <summary> /// Close the runspace Asynchronously. /// </summary> /// <remarks> /// Attempts to execute pipelines after a call to /// close will fail. /// </remarks> /// <exception cref="InvalidRunspaceStateException"> /// RunspaceState is BeforeOpen or Opening /// </exception> public abstract void CloseAsync(); /// <summary> /// Create an empty pipeline. /// </summary> /// <returns>An empty pipeline.</returns> public abstract Pipeline CreatePipeline(); /// <summary> /// Creates a pipeline for specified command string. /// </summary> /// <param name="command">A valid command string.</param> /// <returns> /// A pipeline pre-filled with a <see cref="Command"/> object for specified command parameter. /// </returns> /// <exception cref="ArgumentNullException"> /// command is null /// </exception> public abstract Pipeline CreatePipeline(string command); /// <summary> /// Create a pipeline from a command string. /// </summary> /// <param name="command">A valid command string.</param> /// <param name="addToHistory">If true command is added to history.</param> /// <returns> /// A pipeline pre-filled with a <see cref="Command"/> object for specified command parameter. /// </returns> /// <exception cref="ArgumentNullException"> /// command is null /// </exception> public abstract Pipeline CreatePipeline(string command, bool addToHistory); /// <summary> /// Creates a nested pipeline. /// </summary> /// <remarks> /// Nested pipelines are needed for nested prompt scenario. Nested /// prompt requires that we execute new pipelines( child pipelines) /// while current pipeline (lets call it parent pipeline) is blocked. /// </remarks> public abstract Pipeline CreateNestedPipeline(); /// <summary> /// Creates a nested pipeline. /// </summary> /// <param name="command">A valid command string.</param> /// <param name="addToHistory">If true command is added to history.</param> /// <returns> /// A pipeline pre-filled with Command specified in commandString. /// </returns> /// <exception cref="ArgumentNullException"> /// command is null /// </exception> public abstract Pipeline CreateNestedPipeline(string command, bool addToHistory); /// <summary> /// Returns the currently executing pipeline, or null if no pipeline is executing. /// </summary> internal abstract Pipeline GetCurrentlyRunningPipeline(); /// <summary> /// Private data to be used by applications built on top of PowerShell. /// /// Local runspace is created with application private data set to an empty <see cref="PSPrimitiveDictionary"/>. /// /// Remote runspace gets its application private data from the server (set when creating a remote runspace pool) /// Calling this method on a remote runspace will block until the data is received from the server. /// The server will send application private data before reaching <see cref="RunspacePoolState.Opened"/> state. /// /// Runspaces that are part of a <see cref="RunspacePool"/> inherit application private data from the pool. /// </summary> public abstract PSPrimitiveDictionary GetApplicationPrivateData(); /// <summary> /// A method that runspace pools can use to propagate application private data into runspaces. /// </summary> /// <param name="applicationPrivateData"></param> internal abstract void SetApplicationPrivateData(PSPrimitiveDictionary applicationPrivateData); /// <summary> /// Push a running PowerShell onto the stack. /// </summary> /// <param name="ps">PowerShell.</param> internal void PushRunningPowerShell(PowerShell ps) { Dbg.Assert(ps != null, "Caller should not pass in null reference."); lock (_syncObject) { _runningPowerShells.Push(ps); if (_runningPowerShells.Count == 1) { _baseRunningPowerShell = ps; } } } /// <summary> /// Pop the currently running PowerShell from stack. /// </summary> /// <returns>PowerShell.</returns> internal PowerShell PopRunningPowerShell() { lock (_syncObject) { int count = _runningPowerShells.Count; if (count > 0) { if (count == 1) { _baseRunningPowerShell = null; } return _runningPowerShells.Pop(); } } return null; } internal PowerShell GetCurrentBasePowerShell() { return _baseRunningPowerShell; } #endregion methods #region SessionStateProxy /// <summary> /// Gets session state proxy. /// </summary> public SessionStateProxy SessionStateProxy { get { return GetSessionStateProxy(); } } internal abstract SessionStateProxy GetSessionStateProxy(); #endregion SessionStateProxy #region IDisposable Members /// <summary> /// Disposes this runspace instance. Dispose will close the runspace if not closed already. /// </summary> public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } /// <summary> /// Protected dispose which can be overridden by derived classes. /// </summary> /// <param name="disposing"></param> protected virtual void Dispose(bool disposing) { lock (s_syncObject) { s_runspaceDictionary.Remove(Id); } } #endregion IDisposable Members /// <summary> /// Gets the execution context. /// </summary> internal abstract ExecutionContext GetExecutionContext { get; } /// <summary> /// Returns true if the internal host is in a nested prompt. /// </summary> internal abstract bool InNestedPrompt { get; } /// <summary> /// Gets the debugger. /// </summary> public virtual Debugger Debugger { get { var context = GetExecutionContext; return context?.Debugger; } } /// <summary> /// InternalDebugger. /// </summary> internal Debugger InternalDebugger { get; set; } /// <summary> /// Gets the event manager. /// </summary> public abstract PSEventManager Events { get; } #if !CORECLR // Transaction Not Supported On CSS /// <summary> /// Sets the base transaction for the runspace; any transactions created on this runspace will be nested to this instance. /// </summary> /// <param name="transaction">The base transaction</param> /// <remarks>This overload uses RollbackSeverity.Error; i.e. the transaction will be rolled back automatically on a non-terminating error or worse</remarks> public void SetBaseTransaction(System.Transactions.CommittableTransaction transaction) { this.ExecutionContext.TransactionManager.SetBaseTransaction(transaction, RollbackSeverity.Error); } /// <summary> /// Sets the base transaction for the runspace; any transactions created on this runspace will be nested to this instance. /// </summary> /// <param name="transaction">The base transaction</param> /// <param name="severity">The severity of error that causes PowerShell to automatically rollback the transaction</param> public void SetBaseTransaction(System.Transactions.CommittableTransaction transaction, RollbackSeverity severity) { this.ExecutionContext.TransactionManager.SetBaseTransaction(transaction, severity); } /// <summary> /// Clears the transaction set by SetBaseTransaction() /// </summary> public void ClearBaseTransaction() { this.ExecutionContext.TransactionManager.ClearBaseTransaction(); } #endif /// <summary> /// Resets the variable table for the runspace to the default state. /// </summary> public virtual void ResetRunspaceState() { throw new NotImplementedException("ResetRunspaceState"); } // Used for pipeline id generation. private long _pipelineIdSeed; // Generate pipeline id unique to this runspace internal long GeneratePipelineId() { return System.Threading.Interlocked.Increment(ref _pipelineIdSeed); } } /// <summary> /// This class provides subset of functionality provided by /// session state. /// </summary> public class SessionStateProxy { internal SessionStateProxy() { } private readonly RunspaceBase _runspace; internal SessionStateProxy(RunspaceBase runspace) { Dbg.Assert(runspace != null, "Caller should validate the parameter"); _runspace = runspace; } /// <summary> /// Set a variable in session state. /// </summary> /// <param name="name"> /// The name of the item to set. /// </param> /// <param name="value"> /// The new value of the item being set. /// </param> /// <exception cref="ArgumentNullException"> /// name is null /// </exception> /// <exception cref="InvalidRunspaceStateException"> /// Runspace is not open. /// </exception> /// <exception cref="InvalidOperationException"> /// Another SessionStateProxy call or another pipeline is in progress. /// </exception> public virtual void SetVariable(string name, object value) { if (name == null) { throw PSTraceSource.NewArgumentNullException(nameof(name)); } _runspace.SetVariable(name, value); } /// <summary> /// Get a variable out of session state. /// </summary> /// <param name="name"> /// name of variable to look up /// </param> /// <returns> /// The value of the specified variable. /// </returns> /// <exception cref="ArgumentNullException"> /// name is null /// </exception> /// <exception cref="InvalidRunspaceStateException"> /// Runspace is not open. /// </exception> /// <exception cref="InvalidOperationException"> /// Another SessionStateProxy call or another pipeline is in progress. /// </exception> public virtual object GetVariable(string name) { if (name == null) { throw PSTraceSource.NewArgumentNullException(nameof(name)); } if (name.Equals(string.Empty)) { return null; } return _runspace.GetVariable(name); } /// <summary> /// Get the list of applications out of session state. /// </summary> /// <exception cref="InvalidRunspaceStateException"> /// Runspace is not open. /// </exception> /// <exception cref="InvalidOperationException"> /// Another SessionStateProxy call or another pipeline is in progress. /// </exception> public virtual List<string> Applications { get { return _runspace.Applications; } } /// <summary> /// Get the list of scripts out of session state. /// </summary> /// <exception cref="InvalidRunspaceStateException"> /// Runspace is not open. /// </exception> /// <exception cref="InvalidOperationException"> /// Another SessionStateProxy call or another pipeline is in progress. /// </exception> public virtual List<string> Scripts { get { return _runspace.Scripts; } } /// <summary> /// Get the APIs to access drives out of session state. /// </summary> /// <exception cref="InvalidRunspaceStateException"> /// Runspace is not open. /// </exception> /// <exception cref="InvalidOperationException"> /// Another SessionStateProxy call or another pipeline is in progress. /// </exception> public virtual DriveManagementIntrinsics Drive { get { return _runspace.Drive; } } /// <summary> /// Get/Set the language mode out of session state. /// </summary> /// <exception cref="InvalidRunspaceStateException"> /// Runspace is not open. /// </exception> /// <exception cref="InvalidOperationException"> /// Another SessionStateProxy call or another pipeline is in progress. /// </exception> public virtual PSLanguageMode LanguageMode { get { return _runspace.LanguageMode; } set { _runspace.LanguageMode = value; } } /// <summary> /// Get the module info out of session state. /// </summary> /// <exception cref="InvalidRunspaceStateException"> /// Runspace is not open. /// </exception> /// <exception cref="InvalidOperationException"> /// Another SessionStateProxy call or another pipeline is in progress. /// </exception> [SuppressMessage("Microsoft.Naming", "CA1716:IdentifiersShouldNotMatchKeywords", Justification = "Shipped this way in V2 before becoming virtual.")] public virtual PSModuleInfo Module { get { return _runspace.Module; } } /// <summary> /// Get the APIs to access paths and locations out of session state. /// </summary> /// <exception cref="InvalidRunspaceStateException"> /// Runspace is not open. /// </exception> /// <exception cref="InvalidOperationException"> /// Another SessionStateProxy call or another pipeline is in progress. /// </exception> public virtual PathIntrinsics Path { get { return _runspace.PathIntrinsics; } } /// <summary> /// Get the APIs to access a provider out of session state. /// </summary> /// <exception cref="InvalidRunspaceStateException"> /// Runspace is not open. /// </exception> /// <exception cref="InvalidOperationException"> /// Another SessionStateProxy call or another pipeline is in progress. /// </exception> public virtual CmdletProviderManagementIntrinsics Provider { get { return _runspace.Provider; } } /// <summary> /// Get the APIs to access variables out of session state. /// </summary> /// <exception cref="InvalidRunspaceStateException"> /// Runspace is not open. /// </exception> /// <exception cref="InvalidOperationException"> /// Another SessionStateProxy call or another pipeline is in progress. /// </exception> public virtual PSVariableIntrinsics PSVariable { get { return _runspace.PSVariable; } } /// <summary> /// Get the APIs to build script blocks and execute script out of session state. /// </summary> /// <exception cref="InvalidRunspaceStateException"> /// Runspace is not open. /// </exception> /// <exception cref="InvalidOperationException"> /// Another SessionStateProxy call or another pipeline is in progress. /// </exception> public virtual CommandInvocationIntrinsics InvokeCommand { get { return _runspace.InvokeCommand; } } /// <summary> /// Gets the instance of the provider interface APIs out of session state. /// </summary> /// <exception cref="InvalidRunspaceStateException"> /// Runspace is not open. /// </exception> /// <exception cref="InvalidOperationException"> /// Another SessionStateProxy call or another pipeline is in progress. /// </exception> public virtual ProviderIntrinsics InvokeProvider { get { return _runspace.InvokeProvider; } } } }