/
githubmirror
/
PowerShell
Обзор
Документация
Войти
/
githubmirror
/
PowerShell
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
src/System.Management.Automation/engine/remoting/client/remoterunspace.cs
3 331 строка
125 KB
Dongbo Wang
Some more code cleanup related to the use of `PSVersionInfo` (#18231)
11 окт 2022, 20:24
Не верифицирован
11 окт 2022, 20:24
80d2fb7
Код
Авторство
О чём код?
// Copyright (c) Microsoft Corporation. // Licensed under the MIT License. using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Management.Automation.Host; using System.Management.Automation.Internal; using System.Management.Automation.Remoting; using System.Management.Automation.Runspaces; using System.Management.Automation.Runspaces.Internal; using System.Management.Automation.Tracing; #if !UNIX using System.Security.Principal; #endif using System.Threading; using Microsoft.PowerShell.Commands; using Dbg = System.Management.Automation.Diagnostics; #pragma warning disable 1634, 1691 // Stops compiler from warning about unknown warnings namespace System.Management.Automation { /// <summary> /// Remote runspace which will be created on the client side. This /// runspace is wrapped on a RunspacePool(1). /// </summary> internal class RemoteRunspace : Runspace, IDisposable { #region Private Members private readonly List<RemotePipeline> _runningPipelines = new List<RemotePipeline>(); private readonly object _syncRoot = new object(); private RunspaceStateInfo _runspaceStateInfo = new RunspaceStateInfo(RunspaceState.BeforeOpen); private readonly bool _bSessionStateProxyCallInProgress = false; private readonly RunspaceConnectionInfo _connectionInfo; private RemoteDebugger _remoteDebugger; private PSPrimitiveDictionary _applicationPrivateData; private bool _disposed = false; // the following two variables have been added for supporting // the Invoke-Command | Invoke-Command scenario private InvokeCommandCommand _currentInvokeCommand = null; private long _currentLocalPipelineId = 0; /// <summary> /// This is queue of all the state change event which have occurred for /// this runspace. RaiseRunspaceStateEvents raises event for each /// item in this queue. We don't raise events from with SetRunspaceState /// because SetRunspaceState is often called from with in the a lock. /// Raising event with in a lock introduces chances of deadlock in GUI /// applications. /// </summary> private Queue<RunspaceEventQueueItem> _runspaceEventQueue = new Queue<RunspaceEventQueueItem>(); protected class RunspaceEventQueueItem { public RunspaceEventQueueItem(RunspaceStateInfo runspaceStateInfo, RunspaceAvailability currentAvailability, RunspaceAvailability newAvailability) { this.RunspaceStateInfo = runspaceStateInfo; this.CurrentRunspaceAvailability = currentAvailability; this.NewRunspaceAvailability = newAvailability; } public RunspaceStateInfo RunspaceStateInfo; public RunspaceAvailability CurrentRunspaceAvailability; public RunspaceAvailability NewRunspaceAvailability; } /// <summary> /// In RemoteRunspace, it is required to invoke pipeline /// as part of open call (i.e. while state is Opening). /// If this property is true, runspace state check is /// not performed in AddToRunningPipelineList call. /// </summary> private bool _bypassRunspaceStateCheck; /// <summary> /// In RemoteRunspace, it is required to invoke pipeline /// as part of open call (i.e. while state is Opening). /// If this property is true, runspace state check is /// not performed in AddToRunningPipelineList call. /// </summary> protected bool ByPassRunspaceStateCheck { get { return _bypassRunspaceStateCheck; } set { _bypassRunspaceStateCheck = value; } } /// <summary> /// Temporary place to remember whether to close this runspace on pop or not. /// Used by Start-PSSession. /// </summary> internal bool ShouldCloseOnPop { get; set; } = false; #endregion Private Members #region Constructors /// <summary> /// Construct a remote runspace based on the connection information /// and the specified host. /// </summary> /// <param name="typeTable"> /// The TypeTable to use while deserializing/serializing remote objects. /// TypeTable has the following information used by serializer: /// 1. SerializationMethod /// 2. SerializationDepth /// 3. SpecificSerializationProperties /// TypeTable has the following information used by deserializer: /// 1. TargetTypeForDeserialization /// 2. TypeConverter /// </param> /// <param name="connectionInfo">connection information which identifies /// the remote computer</param> /// <param name="host">Host on the client.</param> /// <param name="applicationArguments"> /// <param name="name">Friendly name for remote runspace session.</param> /// <param name="id">Id for remote runspace.</param> /// Application arguments the server can see in <see cref="System.Management.Automation.Remoting.PSSenderInfo.ApplicationArguments"/> /// </param> internal RemoteRunspace(TypeTable typeTable, RunspaceConnectionInfo connectionInfo, PSHost host, PSPrimitiveDictionary applicationArguments, string name = null, int id = -1) { PSEtwLog.SetActivityIdForCurrentThread(this.InstanceId); PSEtwLog.LogOperationalVerbose(PSEventId.RunspaceConstructor, PSOpcode.Constructor, PSTask.CreateRunspace, PSKeyword.UseAlwaysOperational, InstanceId.ToString()); _connectionInfo = connectionInfo.Clone(); OriginalConnectionInfo = connectionInfo.Clone(); RunspacePool = new RunspacePool(1, 1, typeTable, host, applicationArguments, connectionInfo, name); this.PSSessionId = id; SetEventHandlers(); } /// <summary> /// Constructs a RemoteRunspace object based on the passed in RunspacePool object, /// with a starting state of Disconnected. /// </summary> /// <param name="runspacePool"></param> internal RemoteRunspace(RunspacePool runspacePool) { // The RemoteRunspace object can only be constructed this way with a RunspacePool that // is in the disconnected state. if (runspacePool.RunspacePoolStateInfo.State != RunspacePoolState.Disconnected || runspacePool.ConnectionInfo is not WSManConnectionInfo) { throw PSTraceSource.NewInvalidOperationException(RunspaceStrings.InvalidRunspacePool); } RunspacePool = runspacePool; // The remote runspace pool object can only have the value one set for min/max pools. // This sets the runspace pool object min/max pool values to one. The PSRP/WSMan stack // will fail during connection if the min/max pool values do not match. RunspacePool.RemoteRunspacePoolInternal.SetMinRunspaces(1); RunspacePool.RemoteRunspacePoolInternal.SetMaxRunspaces(1); _connectionInfo = runspacePool.ConnectionInfo.Clone(); // Update runspace DisconnectedOn and ExpiresOn property from WSManConnectionInfo UpdateDisconnectExpiresOn(); // Initial state must be Disconnected. SetRunspaceState(RunspaceState.Disconnected, null); // Normal Availability for a disconnected runspace is "None", which means it can be connected. // However, we can also have disconnected runspace objects that are *not* available for // connection and in this case the Availability is set to "Busy". _runspaceAvailability = RunspacePool.RemoteRunspacePoolInternal.AvailableForConnection ? Runspaces.RunspaceAvailability.None : Runspaces.RunspaceAvailability.Busy; SetEventHandlers(); PSEtwLog.SetActivityIdForCurrentThread(this.InstanceId); PSEtwLog.LogOperationalVerbose(PSEventId.RunspaceConstructor, PSOpcode.Constructor, PSTask.CreateRunspace, PSKeyword.UseAlwaysOperational, this.InstanceId.ToString()); } /// <summary> /// Helper function to set event handlers. /// </summary> private void SetEventHandlers() { // RemoteRunspace must have the same instanceID as its contained RunspacePool instance because // the PSRP/WinRS layer tracks remote runspace Ids. this.InstanceId = RunspacePool.InstanceId; _eventManager = new PSRemoteEventManager(_connectionInfo.ComputerName, this.InstanceId); RunspacePool.StateChanged += HandleRunspacePoolStateChanged; RunspacePool.RemoteRunspacePoolInternal.HostCallReceived += HandleHostCallReceived; RunspacePool.RemoteRunspacePoolInternal.URIRedirectionReported += HandleURIDirectionReported; RunspacePool.ForwardEvent += HandleRunspacePoolForwardEvent; RunspacePool.RemoteRunspacePoolInternal.SessionCreateCompleted += HandleSessionCreateCompleted; } #endregion Constructors #region Properties /// <summary> /// Initialsessionstate information for this runspace. /// </summary> public override InitialSessionState InitialSessionState { get { throw PSTraceSource.NewNotImplementedException(); } } /// <summary> /// Manager for JobSourceAdapters registered in this runspace. /// </summary> public override JobManager JobManager { get { throw PSTraceSource.NewNotImplementedException(); } } /// <summary> /// Return version of this runspace. /// </summary> public override Version Version { get; } = PSVersionInfo.PSVersion; /// <summary> /// PS Version running on server. /// </summary> internal Version ServerVersion { get; private set; } /// <summary> /// Retrieve information about current state of the runspace. /// </summary> public override RunspaceStateInfo RunspaceStateInfo { get { lock (_syncRoot) { // Do not return internal state. return _runspaceStateInfo.Clone(); } } } /// <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 override PSThreadOptions ThreadOptions { get { return _createThreadOptions; } set { lock (_syncRoot) { if (value != _createThreadOptions) { if (this.RunspaceStateInfo.State != RunspaceState.BeforeOpen) { throw new InvalidRunspaceStateException(StringUtil.Format(RunspaceStrings.ChangePropertyAfterOpen)); } _createThreadOptions = value; } } } } private PSThreadOptions _createThreadOptions = PSThreadOptions.Default; /// <summary> /// Gets the current availability of the Runspace. /// </summary> public override RunspaceAvailability RunspaceAvailability { get { return _runspaceAvailability; } protected set { _runspaceAvailability = value; } } private RunspaceAvailability _runspaceAvailability = RunspaceAvailability.None; /// <summary> /// Event raised when RunspaceState changes. /// </summary> public override event EventHandler<RunspaceStateEventArgs> StateChanged; /// <summary> /// Event raised when the availability of the Runspace changes. /// </summary> public override event EventHandler<RunspaceAvailabilityEventArgs> AvailabilityChanged; /// <summary> /// Returns true if there are any subscribers to the AvailabilityChanged event. /// </summary> internal override bool HasAvailabilityChangedSubscribers { get { return this.AvailabilityChanged != null; } } /// <summary> /// Raises the AvailabilityChanged event. /// </summary> protected override void OnAvailabilityChanged(RunspaceAvailabilityEventArgs e) { EventHandler<RunspaceAvailabilityEventArgs> eh = this.AvailabilityChanged; if (eh != null) { try { eh(this, e); } catch (Exception) { } } } /// <summary> /// Connection information to this runspace. /// </summary> public override RunspaceConnectionInfo ConnectionInfo { get { return _connectionInfo; } } /// <summary> /// ConnectionInfo originally supplied by the user. /// </summary> public override RunspaceConnectionInfo OriginalConnectionInfo { get; } /// <summary> /// Gets the event manager. /// </summary> public override PSEventManager Events { get { return _eventManager; } } private PSRemoteEventManager _eventManager; #pragma warning disable 56503 /// <summary> /// Gets the execution context for this runspace. /// </summary> internal override ExecutionContext GetExecutionContext { get { throw PSTraceSource.NewNotImplementedException(); } } /// <summary> /// Returns true if the internal host is in a nested prompt. /// </summary> internal override bool InNestedPrompt { get { return false; // nested prompts are not supported on remote runspaces } } #pragma warning restore 56503 /// <summary> /// Gets the client remote session associated with this /// runspace. /// </summary> /// <remarks>This member is actually not required /// for the product code. However, there are /// existing transport manager tests which depend on /// the same. Once transport manager is modified, /// this needs to be removed</remarks> internal ClientRemoteSession ClientRemoteSession { get { try { return RunspacePool.RemoteRunspacePoolInternal.DataStructureHandler.RemoteSession; } catch (InvalidRunspacePoolStateException e) { throw e.ToInvalidRunspaceStateException(); } } } /// <summary> /// Gets command information on a currently running remote command. /// If no command is running then null is returned. /// </summary> internal ConnectCommandInfo RemoteCommand { get { if (RunspacePool.RemoteRunspacePoolInternal.ConnectCommands == null) { return null; } Dbg.Assert(RunspacePool.RemoteRunspacePoolInternal.ConnectCommands.Length < 2, "RemoteRunspace should have no more than one remote running command."); if (RunspacePool.RemoteRunspacePoolInternal.ConnectCommands.Length > 0) { return RunspacePool.RemoteRunspacePoolInternal.ConnectCommands[0]; } else { return null; } } } /// <summary> /// Gets friendly name for the remote PSSession. /// </summary> internal string PSSessionName { get { return RunspacePool.RemoteRunspacePoolInternal.Name; } set { RunspacePool.RemoteRunspacePoolInternal.Name = value; } } /// <summary> /// Gets the Id value for the remote PSSession. /// </summary> internal int PSSessionId { get; set; } = -1; /// <summary> /// Returns true if Runspace supports disconnect. /// </summary> internal bool CanDisconnect { get { return RunspacePool.RemoteRunspacePoolInternal.CanDisconnect; } } /// <summary> /// Returns true if Runspace can be connected. /// </summary> internal bool CanConnect { get { return RunspacePool.RemoteRunspacePoolInternal.AvailableForConnection; } } /// <summary> /// This is used to indicate a special loopback remote session used for JEA restrictions. /// </summary> internal bool IsConfiguredLoopBack { get; set; } /// <summary> /// Debugger. /// </summary> public override Debugger Debugger { get { return _remoteDebugger; } } #endregion Properties #region Open /// <summary> /// Open the runspace Asynchronously. /// </summary> /// <exception cref="InvalidRunspaceStateException"> /// RunspaceState is not BeforeOpen /// </exception> public override void OpenAsync() { AssertIfStateIsBeforeOpen(); try { RunspacePool.BeginOpen(null, null); } catch (InvalidRunspacePoolStateException e) { throw e.ToInvalidRunspaceStateException(); } } /// <summary> /// Open the runspace synchronously. /// </summary> /// <exception cref="InvalidRunspaceStateException"> /// RunspaceState is not BeforeOpen /// </exception> public override void Open() { AssertIfStateIsBeforeOpen(); try { RunspacePool.ThreadOptions = this.ThreadOptions; RunspacePool.ApartmentState = this.ApartmentState; RunspacePool.Open(); } catch (InvalidRunspacePoolStateException e) { throw e.ToInvalidRunspaceStateException(); } } #endregion Open #region Close /// <summary> /// Close the runspace Asynchronously. /// </summary> public override void CloseAsync() { try { RunspacePool.BeginClose(null, null); } catch (InvalidRunspacePoolStateException e) { throw e.ToInvalidRunspaceStateException(); } } /// <summary> /// Close the runspace synchronously. /// </summary> /// <remarks> /// Attempts to execute pipelines after a call to close will fail. /// </remarks> public override void Close() { try { IAsyncResult result = RunspacePool.BeginClose(null, null); WaitForFinishofPipelines(); // It is possible for the result ASyncResult object to be null if the runspace // pool is already being closed from a server initiated close event. if (result != null) { RunspacePool.EndClose(result); } } catch (InvalidRunspacePoolStateException e) { throw e.ToInvalidRunspaceStateException(); } } /// <summary> /// Dispose this runspace. /// </summary> /// <param name="disposing">True if called from Dispose.</param> protected override void Dispose(bool disposing) { try { if (_disposed) { return; } lock (_syncRoot) { if (_disposed) { return; } _disposed = true; } if (disposing) { try { Close(); } catch (PSRemotingTransportException) { // // If the WinRM listener has been removed before the runspace is closed, then calling // Close() will cause a PSRemotingTransportException. We don't want this exception // surfaced. Most developers don't expect an exception from calling Dispose. // See [Windows 8 Bugs] 968184. // } // Release RunspacePool event forwarding handlers. _remoteDebugger?.Dispose(); try { RunspacePool.StateChanged -= HandleRunspacePoolStateChanged; RunspacePool.RemoteRunspacePoolInternal.HostCallReceived -= HandleHostCallReceived; RunspacePool.RemoteRunspacePoolInternal.URIRedirectionReported -= HandleURIDirectionReported; RunspacePool.ForwardEvent -= HandleRunspacePoolForwardEvent; RunspacePool.RemoteRunspacePoolInternal.SessionCreateCompleted -= HandleSessionCreateCompleted; _eventManager = null; RunspacePool.Dispose(); // _runspacePool = null; } catch (InvalidRunspacePoolStateException e) { throw e.ToInvalidRunspaceStateException(); } } } finally { base.Dispose(disposing); } } #endregion Close #region Reset Runspace State /// <summary> /// Resets the runspace state to allow for fast reuse. Not all of the runspace /// elements are reset. The goal is to minimize the chance of the user taking /// accidental dependencies on prior runspace state. /// </summary> /// <exception cref="PSInvalidOperationException"> /// Thrown when runspace is not in proper state or availability or if the /// reset operation fails in the remote session. /// </exception> public override void ResetRunspaceState() { PSInvalidOperationException invalidOperation = null; if (this.RunspaceStateInfo.State != Runspaces.RunspaceState.Opened) { invalidOperation = PSTraceSource.NewInvalidOperationException( RunspaceStrings.RunspaceNotInOpenedState, this.RunspaceStateInfo.State); } else if (this.RunspaceAvailability != Runspaces.RunspaceAvailability.Available) { invalidOperation = PSTraceSource.NewInvalidOperationException( RunspaceStrings.ConcurrentInvokeNotAllowed); } else { bool success = RunspacePool.RemoteRunspacePoolInternal.ResetRunspaceState(); if (!success) { invalidOperation = PSTraceSource.NewInvalidOperationException(); } } if (invalidOperation != null) { invalidOperation.Source = "ResetRunspaceState"; throw invalidOperation; } } #endregion #region Disconnect-Connect /// <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> internal static Runspace[] GetRemoteRunspaces(RunspaceConnectionInfo connectionInfo, PSHost host, TypeTable typeTable) { List<Runspace> runspaces = new List<Runspace>(); RunspacePool[] runspacePools = RemoteRunspacePoolInternal.GetRemoteRunspacePools(connectionInfo, host, typeTable); // We don't yet know how many runspaces there are in these runspace pool objects. This information isn't updated // until a Connect() is performed. But we can use the ConnectCommands list to prune runspace pool objects that // clearly have more than one command/runspace. foreach (RunspacePool runspacePool in runspacePools) { if (runspacePool.RemoteRunspacePoolInternal.ConnectCommands.Length < 2) { runspaces.Add(new RemoteRunspace(runspacePool)); } } return runspaces.ToArray(); } /// <summary> /// Creates a single disconnected remote Runspace object based on connection information and /// session / command identifiers. /// </summary> /// <param name="connectionInfo">Connection object for target machine.</param> /// <param name="sessionId">Session Id to connect to.</param> /// <param name="commandId">Optional command Id to connect to.</param> /// <param name="host">Optional PSHost.</param> /// <param name="typeTable">Optional TypeTable.</param> /// <returns>Disconnect remote Runspace object.</returns> internal static Runspace GetRemoteRunspace(RunspaceConnectionInfo connectionInfo, Guid sessionId, Guid? commandId, PSHost host, TypeTable typeTable) { RunspacePool runspacePool = RemoteRunspacePoolInternal.GetRemoteRunspacePool( connectionInfo, sessionId, commandId, host, typeTable); return new RemoteRunspace(runspacePool); } /// <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 override void Disconnect() { if (!CanDisconnect) { throw PSTraceSource.NewInvalidOperationException(RunspaceStrings.DisconnectNotSupportedOnServer); } UpdatePoolDisconnectOptions(); try { RunspacePool.Disconnect(); } catch (InvalidRunspacePoolStateException e) { throw e.ToInvalidRunspaceStateException(); } } /// <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 override void DisconnectAsync() { if (!CanDisconnect) { throw PSTraceSource.NewInvalidOperationException(RunspaceStrings.DisconnectNotSupportedOnServer); } UpdatePoolDisconnectOptions(); try { RunspacePool.BeginDisconnect(null, null); } catch (InvalidRunspacePoolStateException e) { throw e.ToInvalidRunspaceStateException(); } } /// <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 override void Connect() { if (!CanConnect) { throw PSTraceSource.NewInvalidOperationException(RunspaceStrings.CannotConnect); } UpdatePoolDisconnectOptions(); try { RunspacePool.Connect(); } catch (InvalidRunspacePoolStateException e) { throw e.ToInvalidRunspaceStateException(); } } /// <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 override void ConnectAsync() { if (!CanConnect) { throw PSTraceSource.NewInvalidOperationException(RunspaceStrings.CannotConnect); } UpdatePoolDisconnectOptions(); try { RunspacePool.BeginConnect(null, null); } catch (InvalidRunspacePoolStateException e) { throw e.ToInvalidRunspaceStateException(); } } /// <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 override Pipeline CreateDisconnectedPipeline() { if (RemoteCommand == null) { throw PSTraceSource.NewInvalidOperationException(RunspaceStrings.NoDisconnectedCommand); } return new RemotePipeline(this); } /// <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 override PowerShell CreateDisconnectedPowerShell() { if (RemoteCommand == null) { throw PSTraceSource.NewInvalidOperationException(RunspaceStrings.NoDisconnectedCommand); } return new PowerShell(RemoteCommand, this); } /// <summary> /// Returns Runspace capabilities. /// </summary> /// <returns>RunspaceCapability.</returns> public override RunspaceCapability GetCapabilities() { RunspaceCapability returnCaps = RunspaceCapability.Default; if (CanDisconnect) { returnCaps |= RunspaceCapability.SupportsDisconnect; } if (_connectionInfo is WSManConnectionInfo) { return returnCaps; } if (_connectionInfo is NamedPipeConnectionInfo) { returnCaps |= RunspaceCapability.NamedPipeTransport; } else if (_connectionInfo is VMConnectionInfo) { returnCaps |= RunspaceCapability.VMSocketTransport; } else if (_connectionInfo is SSHConnectionInfo) { returnCaps |= RunspaceCapability.SSHTransport; } else if (_connectionInfo is ContainerConnectionInfo containerConnectionInfo) { if ((containerConnectionInfo != null) && (containerConnectionInfo.ContainerProc.RuntimeId == Guid.Empty)) { returnCaps |= RunspaceCapability.NamedPipeTransport; } } else { // Unknown connection info type means a custom connection/transport, which at // minimum supports remote runspace capability starting from PowerShell v7.x. returnCaps |= RunspaceCapability.CustomTransport; } return returnCaps; } /// <summary> /// Update the pool disconnect options so that any changes will be /// passed to the server during the disconnect/connect operations. /// </summary> private void UpdatePoolDisconnectOptions() { WSManConnectionInfo runspaceWSManConnectionInfo = RunspacePool.ConnectionInfo as WSManConnectionInfo; WSManConnectionInfo wsManConnectionInfo = ConnectionInfo as WSManConnectionInfo; Dbg.Assert(runspaceWSManConnectionInfo != null, "Disconnect-Connect feature is currently only supported for WSMan transport"); Dbg.Assert(wsManConnectionInfo != null, "Disconnect-Connect feature is currently only supported for WSMan transport"); runspaceWSManConnectionInfo.IdleTimeout = wsManConnectionInfo.IdleTimeout; runspaceWSManConnectionInfo.OutputBufferingMode = wsManConnectionInfo.OutputBufferingMode; } #endregion #region Remote Debugging /// <summary> /// Remote DebuggerStop event. /// </summary> internal event EventHandler<PSEventArgs> RemoteDebuggerStop; /// <summary> /// Remote BreakpointUpdated event. /// </summary> internal event EventHandler<PSEventArgs> RemoteDebuggerBreakpointUpdated; #endregion #region CreatePipeline /// <summary> /// Create an empty pipeline. /// </summary> /// <returns>An empty pipeline.</returns> public override Pipeline CreatePipeline() { return CoreCreatePipeline(null, false, false); } /// <summary> /// Create a pipeline from a command string. /// </summary> /// <param name="command">A valid command string.</param> /// <returns> /// A pipeline pre-filled with Commands specified in commandString. /// </returns> /// <exception cref="ArgumentNullException"> /// command is null /// </exception> public override Pipeline CreatePipeline(string command) { if (command == null) { throw PSTraceSource.NewArgumentNullException(nameof(command)); } return CoreCreatePipeline(command, false, false); } /// <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 Commands specified in commandString. /// </returns> /// <exception cref="ArgumentNullException"> /// command is null /// </exception> public override Pipeline CreatePipeline(string command, bool addToHistory) { if (command == null) { throw PSTraceSource.NewArgumentNullException(nameof(command)); } return CoreCreatePipeline(command, addToHistory, false); } /// <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> /// <exception cref="PSNotSupportedException">Not supported in remoting /// scenarios</exception> public override Pipeline CreateNestedPipeline() { return CoreCreatePipeline(null, false, true); } /// <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 Commands specified in commandString. /// </returns> /// <exception cref="PSNotSupportedException">Not supported in remoting /// scenarios</exception> public override Pipeline CreateNestedPipeline(string command, bool addToHistory) { if (command == null) { throw PSTraceSource.NewArgumentNullException(nameof(command)); } return CoreCreatePipeline(command, addToHistory, true); } #endregion CreatePipeline #region Running Pipeline Management /// <summary> /// Add the pipeline to list of pipelines in execution. /// </summary> /// <param name="pipeline">Pipeline to add to the /// list of pipelines in execution</param> /// <exception cref="InvalidRunspaceStateException"> /// Thrown if the runspace is not in the Opened state. /// <see cref="RunspaceState"/>. /// </exception> /// <exception cref="ArgumentNullException">Thrown if /// <paramref name="pipeline"/> is null. /// </exception> internal void AddToRunningPipelineList(RemotePipeline pipeline) { Dbg.Assert(pipeline != null, "caller should validate the parameter"); lock (_syncRoot) { if (!_bypassRunspaceStateCheck && _runspaceStateInfo.State != RunspaceState.Opened && _runspaceStateInfo.State != RunspaceState.Disconnected) // Disconnected runspaces can have running pipelines. { InvalidRunspaceStateException e = new InvalidRunspaceStateException ( StringUtil.Format(RunspaceStrings.RunspaceNotOpenForPipeline, _runspaceStateInfo.State.ToString() ), _runspaceStateInfo.State, RunspaceState.Opened ); if (this.ConnectionInfo != null) { e.Source = this.ConnectionInfo.ComputerName; } throw e; } // Add the pipeline to list of Executing pipeline. // Note:_runningPipelines is always accessed with the lock so // there is no need to create a synchronized version of list _runningPipelines.Add(pipeline); } } /// <summary> /// Remove the pipeline from list of pipelines in execution. /// </summary> /// <param name="pipeline">Pipeline to remove from the /// list of pipelines in execution</param> /// <exception cref="ArgumentNullException"> /// Thrown if <paramref name="pipeline"/> is null. /// </exception> internal void RemoveFromRunningPipelineList(RemotePipeline pipeline) { Dbg.Assert(pipeline != null, "caller should validate the parameter"); lock (_syncRoot) { Dbg.Assert(_runspaceStateInfo.State != RunspaceState.BeforeOpen, "Runspace should not be before open when pipeline is running"); // Remove the pipeline to list of Executing pipeline. // Note:_runningPipelines is always accessed with the lock so // there is no need to create a synchronized version of list _runningPipelines.Remove(pipeline); pipeline.PipelineFinishedEvent.Set(); } } /// <summary> /// Check to see, if there is any other pipeline running in this /// runspace. If not, then add this to the list of pipelines. /// </summary> /// <param name="pipeline">Pipeline to check and add.</param> /// <param name="syncCall">whether this is being called from /// a synchronous method call</param> internal void DoConcurrentCheckAndAddToRunningPipelines(RemotePipeline pipeline, bool syncCall) { // Concurrency check should be done under runspace lock lock (_syncRoot) { if (_bSessionStateProxyCallInProgress) { throw PSTraceSource.NewInvalidOperationException(RunspaceStrings.NoPipelineWhenSessionStateProxyInProgress); } // Delegate to pipeline to do check if it is fine to invoke if another // pipeline is running. pipeline.DoConcurrentCheck(syncCall); // Finally add to the list of running pipelines. AddToRunningPipelineList(pipeline); } } #endregion Running Pipeline Management #region SessionState Proxy /// <summary> /// Returns SessionState proxy object. /// </summary> /// <returns></returns> internal override SessionStateProxy GetSessionStateProxy() { return _sessionStateProxy ??= new RemoteSessionStateProxy(this); } private RemoteSessionStateProxy _sessionStateProxy = null; #endregion SessionState Proxy #region Private Methods private void HandleRunspacePoolStateChanged(object sender, RunspacePoolStateChangedEventArgs e) { RunspaceState newState = (RunspaceState)e.RunspacePoolStateInfo.State; RunspaceState prevState = SetRunspaceState(newState, e.RunspacePoolStateInfo.Reason); switch (newState) { case RunspaceState.Opened: switch (prevState) { case RunspaceState.Opening: // For newly opened remote runspaces, set the debug mode based on the // associated host. This involves running a remote command and is Ok // since this event is called on a worker thread and not a WinRM callback. SetDebugModeOnOpen(); break; case RunspaceState.Connecting: UpdateDisconnectExpiresOn(); // Application private data containing server debug state is updated on // a *reconstruct* connect operation when _applicationPrivateData is null. // Pass new information to the debugger. if (_applicationPrivateData == null) { _applicationPrivateData = GetApplicationPrivateData(); SetDebugInfo(_applicationPrivateData); } break; } break; case RunspaceState.Disconnected: UpdateDisconnectExpiresOn(); break; } RaiseRunspaceStateEvents(); } /// <summary> /// Set debug mode on remote session based on the interactive host /// setting, if available. /// </summary> private void SetDebugModeOnOpen() { // Update client remote debugger based on server capabilities. _applicationPrivateData = GetApplicationPrivateData(); bool serverSupportsDebugging = SetDebugInfo(_applicationPrivateData); if (!serverSupportsDebugging) { return; } // Set server side initial debug mode based on interactive host. DebugModes hostDebugMode = DebugModes.Default; try { IHostSupportsInteractiveSession interactiveHost = RunspacePool.RemoteRunspacePoolInternal.Host as IHostSupportsInteractiveSession; if (interactiveHost != null && interactiveHost.Runspace != null && interactiveHost.Runspace.Debugger != null) { hostDebugMode = interactiveHost.Runspace.Debugger.DebugMode; } } catch (PSNotImplementedException) { } if ((hostDebugMode & DebugModes.RemoteScript) == DebugModes.RemoteScript) { try { _remoteDebugger.SetDebugMode(hostDebugMode); } catch (Exception) { } } } private bool SetDebugInfo(PSPrimitiveDictionary psApplicationPrivateData) { DebugModes? debugMode = null; bool inDebugger = false; int breakpointCount = 0; bool breakAll = false; UnhandledBreakpointProcessingMode unhandledBreakpointMode = UnhandledBreakpointProcessingMode.Ignore; if (psApplicationPrivateData != null) { if (psApplicationPrivateData.ContainsKey(RemoteDebugger.DebugModeSetting)) { debugMode = (DebugModes)(int)psApplicationPrivateData[RemoteDebugger.DebugModeSetting]; } if (psApplicationPrivateData.ContainsKey(RemoteDebugger.DebugStopState)) { inDebugger = (bool)psApplicationPrivateData[RemoteDebugger.DebugStopState]; } if (psApplicationPrivateData.ContainsKey(RemoteDebugger.DebugBreakpointCount)) { breakpointCount = (int)psApplicationPrivateData[RemoteDebugger.DebugBreakpointCount]; } if (psApplicationPrivateData.ContainsKey(RemoteDebugger.BreakAllSetting)) { breakAll = (bool)psApplicationPrivateData[RemoteDebugger.BreakAllSetting]; } if (psApplicationPrivateData.ContainsKey(RemoteDebugger.UnhandledBreakpointModeSetting)) { unhandledBreakpointMode = (UnhandledBreakpointProcessingMode)(int)psApplicationPrivateData[RemoteDebugger.UnhandledBreakpointModeSetting]; } if (psApplicationPrivateData.ContainsKey(PSVersionInfo.PSVersionTableName)) { var psVersionTable = psApplicationPrivateData[PSVersionInfo.PSVersionTableName] as PSPrimitiveDictionary; if (psVersionTable.ContainsKey(PSVersionInfo.PSVersionName)) { ServerVersion = PSObject.Base(psVersionTable[PSVersionInfo.PSVersionName]) as Version; } } } if (debugMode != null) { // Server supports remote debugging. Create Debugger object for // this remote runspace. Dbg.Assert(_remoteDebugger == null, "Remote runspace should not have a debugger yet."); _remoteDebugger = new RemoteDebugger(this); // Set initial debugger state. _remoteDebugger.SetClientDebugInfo(debugMode, inDebugger, breakpointCount, breakAll, unhandledBreakpointMode, ServerVersion); return true; } return false; } /// <summary> /// Asserts if the current state of the runspace is BeforeOpen. /// </summary> private void AssertIfStateIsBeforeOpen() { lock (_syncRoot) { // Call fails if RunspaceState is not BeforeOpen. if (_runspaceStateInfo.State != RunspaceState.BeforeOpen) { InvalidRunspaceStateException e = new InvalidRunspaceStateException ( StringUtil.Format(RunspaceStrings.CannotOpenAgain, new object[] { _runspaceStateInfo.State.ToString() } ), _runspaceStateInfo.State, RunspaceState.BeforeOpen ); throw e; } } } /// <summary> /// Set the new runspace state. /// </summary> /// <param name="state">The new state.</param> /// <param name="reason">An exception indicating the state change is the /// result of an error, otherwise; null. /// </param> /// <returns>Previous runspace state.</returns> /// <remarks> /// Sets the internal runspace state information member variable. It also /// adds RunspaceStateInfo to a queue. /// RaiseRunspaceStateEvents raises event for each item in this queue. /// </remarks> private RunspaceState SetRunspaceState(RunspaceState state, Exception reason) { RunspaceState prevState; lock (_syncRoot) { prevState = _runspaceStateInfo.State; if (state != prevState) { _runspaceStateInfo = new RunspaceStateInfo(state, reason); // Add _runspaceStateInfo to _runspaceEventQueue. // RaiseRunspaceStateEvents will raise event for each item // in this queue. // Note:We are doing clone here instead of passing the member // _runspaceStateInfo because we donot want outside // to change our runspace state. RunspaceAvailability previousAvailability = _runspaceAvailability; this.UpdateRunspaceAvailability(_runspaceStateInfo.State, false); _runspaceEventQueue.Enqueue( new RunspaceEventQueueItem( _runspaceStateInfo.Clone(), previousAvailability, _runspaceAvailability)); PSEtwLog.LogOperationalVerbose(PSEventId.RunspaceStateChange, PSOpcode.Open, PSTask.CreateRunspace, PSKeyword.UseAlwaysOperational, state.ToString()); } } return prevState; } /// <summary> /// Raises events for changes in runspace state. /// </summary> private void RaiseRunspaceStateEvents() { Queue<RunspaceEventQueueItem> tempEventQueue = null; EventHandler<RunspaceStateEventArgs> stateChanged = null; bool hasAvailabilityChangedSubscribers = false; lock (_syncRoot) { stateChanged = this.StateChanged; hasAvailabilityChangedSubscribers = this.HasAvailabilityChangedSubscribers; if (stateChanged != null || hasAvailabilityChangedSubscribers) { tempEventQueue = _runspaceEventQueue; _runspaceEventQueue = new Queue<RunspaceEventQueueItem>(); } else { // Clear the events if there are no EventHandlers. This // ensures that events do not get called for state // changes prior to their registration. _runspaceEventQueue.Clear(); } } if (tempEventQueue != null) { while (tempEventQueue.Count > 0) { RunspaceEventQueueItem queueItem = tempEventQueue.Dequeue(); if (hasAvailabilityChangedSubscribers && queueItem.NewRunspaceAvailability != queueItem.CurrentRunspaceAvailability) { this.OnAvailabilityChanged(new RunspaceAvailabilityEventArgs(queueItem.NewRunspaceAvailability)); } // Exception raised by events are not error condition for runspace // object. if (stateChanged != null) { try { stateChanged(this, new RunspaceStateEventArgs(queueItem.RunspaceStateInfo)); } catch (Exception) { } } } } } /// <summary> /// Creates a pipeline. /// </summary> /// <param name="command"></param> /// <param name="addToHistory"></param> /// <param name="isNested"></param> /// <returns></returns> private Pipeline CoreCreatePipeline(string command, bool addToHistory, bool isNested) { return new RemotePipeline(this, command, addToHistory, isNested); } /// <summary> /// Waits till all the pipelines running in the runspace have /// finished execution. /// </summary> [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Finishof")] private bool WaitForFinishofPipelines() { // Take a snapshot of list of active pipelines. // Note:Before we enter to this CloseHelper routine // CoreClose has already set the state of Runspace // to closing. So no new pipelines can be executed on this // runspace and so no new pipelines will be added to // _runningPipelines. However we still need to lock because // running pipelines can be removed from this. RemotePipeline[] runningPipelines; lock (_syncRoot) { runningPipelines = _runningPipelines.ToArray(); } if (runningPipelines.Length > 0) { WaitHandle[] waitHandles = new WaitHandle[runningPipelines.Length]; for (int i = 0; i < runningPipelines.Length; i++) { waitHandles[i] = runningPipelines[i].PipelineFinishedEvent; } return WaitHandle.WaitAll(waitHandles); } else { return true; } } /// <summary> /// Gets the currently executing pipeline. /// </summary> /// <remarks>Internal because it is needed by invoke-history</remarks> internal override Pipeline GetCurrentlyRunningPipeline() { lock (_syncRoot) { if (_runningPipelines.Count != 0) { return (Pipeline)_runningPipelines[_runningPipelines.Count - 1]; } else { return null; } } } /// <summary> /// Handles any host calls received from the server. /// </summary> /// <param name="sender">Sender of this information, unused.</param> /// <param name="eventArgs">arguments describing this event, contains /// a RemoteHostCall object</param> private void HandleHostCallReceived(object sender, RemoteDataEventArgs<RemoteHostCall> eventArgs) { ClientMethodExecutor.Dispatch( RunspacePool.RemoteRunspacePoolInternal.DataStructureHandler.TransportManager, RunspacePool.RemoteRunspacePoolInternal.Host, null, /* error stream */ null, /* method executor stream */ false, /* is method stream enabled */ RunspacePool.RemoteRunspacePoolInternal, Guid.Empty, /* powershell id */ eventArgs.Data); } /// <summary> /// When the client remote session reports a URI redirection, this method will report the /// message to the user as a Warning using Host method calls. /// </summary> /// <param name="sender"></param> /// <param name="eventArgs"></param> private void HandleURIDirectionReported(object sender, RemoteDataEventArgs<Uri> eventArgs) { WSManConnectionInfo wsmanConnectionInfo = _connectionInfo as WSManConnectionInfo; if (wsmanConnectionInfo != null) { // change the runspace's uri to the new URI. wsmanConnectionInfo.ConnectionUri = eventArgs.Data; URIRedirectionReported.SafeInvoke(this, eventArgs); } } /// <summary> /// Forward the events from the runspace pool to the current instance. /// </summary> private void HandleRunspacePoolForwardEvent(object sender, PSEventArgs e) { if (e.SourceIdentifier.Equals(RemoteDebugger.RemoteDebuggerStopEvent)) { // Special processing for forwarded remote DebuggerStop event. RemoteDebuggerStop.SafeInvoke(this, e); } else if (e.SourceIdentifier.Equals(RemoteDebugger.RemoteDebuggerBreakpointUpdatedEvent)) { // Special processing for forwarded remote DebuggerBreakpointUpdated event. RemoteDebuggerBreakpointUpdated.SafeInvoke(this, e); } else { _eventManager.AddForwardedEvent(e); } } /// <summary> /// The session has been successfully created. /// </summary> /// <param name="sender"></param> /// <param name="eventArgs"></param> private void HandleSessionCreateCompleted(object sender, CreateCompleteEventArgs eventArgs) { // Update connectionInfo with updated information from the transport. if (eventArgs != null) { _connectionInfo.IdleTimeout = eventArgs.ConnectionInfo.IdleTimeout; _connectionInfo.MaxIdleTimeout = eventArgs.ConnectionInfo.MaxIdleTimeout; WSManConnectionInfo wsmanConnectionInfo = _connectionInfo as WSManConnectionInfo; if (wsmanConnectionInfo != null) { wsmanConnectionInfo.OutputBufferingMode = ((WSManConnectionInfo)eventArgs.ConnectionInfo).OutputBufferingMode; } } } /// <summary> /// Updates runspace DisconnectedOn/ExpiresOn based on RS Pool connectionInfo. /// </summary> private void UpdateDisconnectExpiresOn() { WSManConnectionInfo wsmanConnectionInfo = RunspacePool.RemoteRunspacePoolInternal.ConnectionInfo as WSManConnectionInfo; if (wsmanConnectionInfo != null) { this.DisconnectedOn = wsmanConnectionInfo.DisconnectedOn; this.ExpiresOn = wsmanConnectionInfo.ExpiresOn; } } #endregion Private Methods #region Internal Methods /// <summary> /// Determines if another Invoke-Command is executing /// in this runspace in the currently running local pipeline /// ahead on the specified invoke-command. /// </summary> /// <param name="invokeCommand">current invoke-command /// instance</param> /// <param name="localPipelineId">Local pipeline id.</param> /// <returns>True, if another invoke-command is running /// before, false otherwise.</returns> internal bool IsAnotherInvokeCommandExecuting(InvokeCommandCommand invokeCommand, long localPipelineId) { // the invoke-command's pipeline should be the currently // running pipeline. This will ensure that, we do not // return true, when one invoke-command is running as a // job and another invoke-command is entered at the // console prompt if (_currentLocalPipelineId != localPipelineId && _currentLocalPipelineId != 0) { return false; } else { // the local pipeline ids are the same // this invoke command is running may be // running in the same pipeline as another // invoke command if (_currentInvokeCommand == null) { // this is the first invoke-command, just // set the reference SetCurrentInvokeCommand(invokeCommand, localPipelineId); return false; } else if (_currentInvokeCommand.Equals(invokeCommand)) { // the currently active invoke command is the one // specified return false; } else { // the local pipeline id is the same and there // is another invoke command that is active already return true; } } } /// <summary> /// Keeps track of the current invoke command executing /// within the current local pipeline. /// </summary> /// <param name="invokeCommand">reference to invoke command /// which is currently being processed</param> /// <param name="localPipelineId">The local pipeline id.</param> internal void SetCurrentInvokeCommand(InvokeCommandCommand invokeCommand, long localPipelineId) { Dbg.Assert(invokeCommand != null, "InvokeCommand instance cannot be null, use ClearInvokeCommand() method to reset current command"); Dbg.Assert(localPipelineId != 0, "Local pipeline id needs to be supplied - cannot be 0"); _currentInvokeCommand = invokeCommand; _currentLocalPipelineId = localPipelineId; } /// <summary> /// Clears the current invoke-command reference stored within /// this remote runspace. /// </summary> internal void ClearInvokeCommand() { _currentLocalPipelineId = 0; _currentInvokeCommand = null; } /// <summary> /// Aborts any current Opening process. If runspace is not opening then this has no effect. /// This is currently *only* for named pipe connections where a connection /// to a process is limited to a single client. /// </summary> internal void AbortOpen() { System.Management.Automation.Remoting.Client.NamedPipeClientSessionTransportManager transportManager = RunspacePool.RemoteRunspacePoolInternal.DataStructureHandler.TransportManager as System.Management.Automation.Remoting.Client.NamedPipeClientSessionTransportManager; transportManager?.AbortConnect(); } #endregion Internal Methods #region Misc Properties / Events /// <summary> /// The runspace pool that this remote runspace wraps. /// </summary> internal RunspacePool RunspacePool { get; } /// <summary> /// EventHandler used to report connection URI redirections to the application. /// </summary> internal event EventHandler<RemoteDataEventArgs<Uri>> URIRedirectionReported; #endregion Misc Properties #region Application private data /// <summary> /// Private data to be used by applications built on top of PowerShell. /// /// Remote runspace gets its application private data from the server (when creating the remote runspace pool) /// - calling this method on a remote runspace will block until the data is received from the server. /// /// Runspaces that are part of a <see cref="RunspacePool"/> inherit application private data from the pool. /// </summary> public override PSPrimitiveDictionary GetApplicationPrivateData() { try { return RunspacePool.GetApplicationPrivateData(); } catch (InvalidRunspacePoolStateException e) { throw e.ToInvalidRunspaceStateException(); } } internal override void SetApplicationPrivateData(PSPrimitiveDictionary applicationPrivateData) { Dbg.Assert(false, "RemoteRunspace.SetApplicationPrivateData shouldn't be called - this runspace does not belong to a runspace pool [although it does use a remote runspace pool internally]"); } #endregion } #region Remote Debugger /// <summary> /// RemoteDebugger. /// </summary> internal sealed class RemoteDebugger : Debugger, IDisposable { #region Members private readonly RemoteRunspace _runspace; private PowerShell _psDebuggerCommand; private bool _remoteDebugSupported; private bool _isActive; private int _breakpointCount; private RemoteDebuggingCapability _remoteDebuggingCapability; private bool? _remoteBreakpointManagementIsSupported; private volatile bool _handleDebuggerStop; private bool _isDebuggerSteppingEnabled; private UnhandledBreakpointProcessingMode _unhandledBreakpointMode; private bool _detachCommand; #if !UNIX // Windows impersonation flow private WindowsIdentity _identityToPersonate; private bool _identityPersonationChecked; #endif /// <summary> /// RemoteDebuggerStopEvent. /// </summary> public const string RemoteDebuggerStopEvent = "PSInternalRemoteDebuggerStopEvent"; /// <summary> /// RemoteDebuggerBreakpointUpdatedEvent. /// </summary> public const string RemoteDebuggerBreakpointUpdatedEvent = "PSInternalRemoteDebuggerBreakpointUpdatedEvent"; // Remote debugger settings public const string DebugModeSetting = "DebugMode"; public const string DebugStopState = "DebugStop"; public const string DebugBreakpointCount = "DebugBreakpointCount"; public const string BreakAllSetting = "BreakAll"; public const string UnhandledBreakpointModeSetting = "UnhandledBreakpointMode"; #endregion #region Constructor private RemoteDebugger() { } /// <summary> /// Constructor. /// </summary> /// <param name="runspace">Associated remote runspace.</param> public RemoteDebugger(RemoteRunspace runspace) { if (runspace == null) { throw new PSArgumentNullException(nameof(runspace)); } _runspace = runspace; _unhandledBreakpointMode = UnhandledBreakpointProcessingMode.Ignore; // Hook up remote debugger forwarded event handlers. _runspace.RemoteDebuggerStop += HandleForwardedDebuggerStopEvent; _runspace.RemoteDebuggerBreakpointUpdated += HandleForwardedDebuggerBreakpointUpdatedEvent; } #endregion #region Class overrides /// <summary> /// Process debugger command. /// </summary> /// <param name="command">Debugger PSCommand.</param> /// <param name="output">Output.</param> /// <returns>DebuggerCommandResults.</returns> public override DebuggerCommandResults ProcessCommand(PSCommand command, PSDataCollection<PSObject> output) { CheckForValidateState(); _detachCommand = false; if (command == null) { throw new PSArgumentNullException(nameof(command)); } if (output == null) { throw new PSArgumentNullException(nameof(output)); } if (!DebuggerStopped) { throw new PSInvalidOperationException( DebuggerStrings.CannotProcessDebuggerCommandNotStopped, null, Debugger.CannotProcessCommandNotStopped, ErrorCategory.InvalidOperation, null); } DebuggerCommandResults results = null; // Execute command on server. bool executionError = false; using (_psDebuggerCommand = GetNestedPowerShell()) { foreach (var cmd in command.Commands) { cmd.MergeMyResults(PipelineResultTypes.All, PipelineResultTypes.Output); _psDebuggerCommand.AddCommand(cmd); } PSDataCollection<PSObject> internalOutput = new PSDataCollection<PSObject>(); internalOutput.DataAdded += (sender, args) => { foreach (var item in internalOutput.ReadAll()) { if (item == null) { return; } DebuggerCommand dbgCmd = item.BaseObject as DebuggerCommand; if (dbgCmd != null) { bool executedByDebugger = (dbgCmd.ResumeAction != null || dbgCmd.ExecutedByDebugger); results = new DebuggerCommandResults(dbgCmd.ResumeAction, executedByDebugger); } else if (item.BaseObject is DebuggerCommandResults) { results = item.BaseObject as DebuggerCommandResults; } else { output.Add(item); } } }; try { _psDebuggerCommand.Invoke(null, internalOutput, null); } catch (Exception e) { executionError = true; RemoteException re = e as RemoteException; if ((re != null) && (re.ErrorRecord != null)) { // Allow the IncompleteParseException to throw so that the console // can handle here strings and continued parsing. if (re.ErrorRecord.CategoryInfo.Reason == nameof(IncompleteParseException)) { throw new IncompleteParseException( re.ErrorRecord.Exception?.Message, re.ErrorRecord.FullyQualifiedErrorId); } // Allow the RemoteException and InvalidRunspacePoolStateException to propagate so that the host can // clean up the debug session. if ((re.ErrorRecord.CategoryInfo.Reason == nameof(InvalidRunspacePoolStateException)) || (re.ErrorRecord.CategoryInfo.Reason == nameof(RemoteException))) { throw new PSRemotingTransportException( (re.ErrorRecord.Exception != null) ? re.ErrorRecord.Exception.Message : string.Empty); } } // Allow all PSRemotingTransportException and RemoteException errors to propagate as this // indicates a broken debug session. if ((e is PSRemotingTransportException) || (e is RemoteException)) { throw; } output.Add( new PSObject( new ErrorRecord( e, "DebuggerError", ErrorCategory.InvalidOperation, null))); } } executionError = executionError || _psDebuggerCommand.HadErrors; _psDebuggerCommand = null; // Special processing when the detach command is run. _detachCommand = (!executionError) && (command.Commands.Count > 0) && (command.Commands[0].CommandText.Equals("Detach", StringComparison.OrdinalIgnoreCase)); return results ?? new DebuggerCommandResults(null, false); } /// <summary> /// StopProcessCommand. /// </summary> public override void StopProcessCommand() { CheckForValidateState(); PowerShell ps = _psDebuggerCommand; if ((ps != null) && (ps.InvocationStateInfo.State == PSInvocationState.Running)) { ps.BeginStop(null, null); } } /// <summary> /// Adds the provided set of breakpoints to the debugger. /// </summary> /// <param name="breakpoints">Breakpoints to set.</param> /// <param name="runspaceId">The runspace id of the runspace you want to interact with. A null value will use the current runspace.</param> public override void SetBreakpoints(IEnumerable<Breakpoint> breakpoints, int? runspaceId) { // This is supported only for PowerShell versions >= 7.0 CheckRemoteBreakpointManagementSupport(RemoteDebuggingCommands.SetBreakpoint); var functionParameters = new Dictionary<string, object> { { "BreakpointList", breakpoints }, }; if (runspaceId.HasValue) { functionParameters.Add("RunspaceId", runspaceId.Value); } InvokeRemoteBreakpointFunction<CommandBreakpoint>(RemoteDebuggingCommands.SetBreakpoint, functionParameters); } /// <summary> /// Get a breakpoint by id, primarily for Enable/Disable/Remove-PSBreakpoint cmdlets. /// </summary> /// <param name="id">Id of the breakpoint you want.</param> /// <param name="runspaceId">The runspace id of the runspace you want to interact with. A null value will use the current runspace.</param> /// <returns>The breakpoint with the specified id.</returns> public override Breakpoint GetBreakpoint(int id, int? runspaceId) { // This is supported only for PowerShell versions >= 7.0 CheckRemoteBreakpointManagementSupport(RemoteDebuggingCommands.GetBreakpoint); var functionParameters = new Dictionary<string, object> { { "Id", id }, }; if (runspaceId.HasValue) { functionParameters.Add("RunspaceId", runspaceId.Value); } return InvokeRemoteBreakpointFunction<Breakpoint>(RemoteDebuggingCommands.GetBreakpoint, functionParameters); } /// <summary> /// Returns breakpoints primarily for the Get-PSBreakpoint cmdlet. /// </summary> /// <param name="runspaceId">The runspace id of the runspace you want to interact with. A null value will use the current runspace.</param> /// <returns>A list of breakpoints in a runspace.</returns> public override List<Breakpoint> GetBreakpoints(int? runspaceId) { // This is supported only for PowerShell versions >= 7.0 CheckRemoteBreakpointManagementSupport(RemoteDebuggingCommands.GetBreakpoint); CheckForValidateState(); var breakpoints = new List<Breakpoint>(); using (PowerShell ps = GetNestedPowerShell()) { ps.AddCommand(RemoteDebuggingCommands.GetBreakpoint); if (runspaceId.HasValue) { ps.AddParameter("RunspaceId", runspaceId.Value); } Collection<PSObject> output = ps.Invoke<PSObject>(); foreach (var item in output) { if (item?.BaseObject is Breakpoint bp) { breakpoints.Add(bp); } else if (TryGetRemoteDebuggerException(item, out Exception ex)) { throw ex; } } } return breakpoints; } /// <summary> /// Sets a command breakpoint in the debugger. /// </summary> /// <param name="command">The name of the command that will trigger the breakpoint. This value may not be null.</param> /// <param name="action">The action to take when the breakpoint is hit. If null, PowerShell will break into the debugger when the breakpoint is hit.</param> /// <param name="path">The path to the script file where the breakpoint may be hit. If null, the breakpoint may be hit anywhere the command is invoked.</param> /// <param name="runspaceId">The runspace id of the runspace you want to interact with. A null value will use the current runspace.</param> /// <returns>The command breakpoint that was set.</returns> public override CommandBreakpoint SetCommandBreakpoint(string command, ScriptBlock action, string path, int? runspaceId) { // This is supported only for PowerShell versions >= 7.0 CheckRemoteBreakpointManagementSupport(RemoteDebuggingCommands.SetBreakpoint); Breakpoint breakpoint = new CommandBreakpoint(path, null, command, action); var functionParameters = new Dictionary<string, object> { { "Breakpoint", breakpoint }, }; if (runspaceId.HasValue) { functionParameters.Add("RunspaceId", runspaceId.Value); } return InvokeRemoteBreakpointFunction<CommandBreakpoint>(RemoteDebuggingCommands.SetBreakpoint, functionParameters); } /// <summary> /// Sets a line breakpoint in the debugger. /// </summary> /// <param name="path">The path to the script file where the breakpoint may be hit. This value may not be null.</param> /// <param name="line">The line in the script file where the breakpoint may be hit. This value must be greater than or equal to 1.</param> /// <param name="column">The column in the script file where the breakpoint may be hit. If 0, the breakpoint will trigger on any statement on the line.</param> /// <param name="action">The action to take when the breakpoint is hit. If null, PowerShell will break into the debugger when the breakpoint is hit.</param> /// <param name="runspaceId">The runspace id of the runspace you want to interact with. A null value will use the current runspace.</param> /// <returns>The line breakpoint that was set.</returns> public override LineBreakpoint SetLineBreakpoint(string path, int line, int column, ScriptBlock action, int? runspaceId) { // This is supported only for PowerShell versions >= 7.0 CheckRemoteBreakpointManagementSupport(RemoteDebuggingCommands.SetBreakpoint); Breakpoint breakpoint = new LineBreakpoint(path, line, column, action); var functionParameters = new Dictionary<string, object> { { "Breakpoint", breakpoint }, }; if (runspaceId.HasValue) { functionParameters.Add("RunspaceId", runspaceId.Value); } return InvokeRemoteBreakpointFunction<LineBreakpoint>(RemoteDebuggingCommands.SetBreakpoint, functionParameters); } /// <summary> /// Sets a variable breakpoint in the debugger. /// </summary> /// <param name="variableName">The name of the variable that will trigger the breakpoint. This value may not be null.</param> /// <param name="accessMode">The variable access mode that will trigger the breakpoint.</param> /// <param name="action">The action to take when the breakpoint is hit. If null, PowerShell will break into the debugger when the breakpoint is hit.</param> /// <param name="path">The path to the script file where the breakpoint may be hit. If null, the breakpoint may be hit anywhere the variable is accessed using the specified access mode.</param> /// <param name="runspaceId">The runspace id of the runspace you want to interact with. A null value will use the current runspace.</param> /// <returns>The variable breakpoint that was set.</returns> public override VariableBreakpoint SetVariableBreakpoint(string variableName, VariableAccessMode accessMode, ScriptBlock action, string path, int? runspaceId) { // This is supported only for PowerShell versions >= 7.0 CheckRemoteBreakpointManagementSupport(RemoteDebuggingCommands.SetBreakpoint); Breakpoint breakpoint = new VariableBreakpoint(path, variableName, accessMode, action); var functionParameters = new Dictionary<string, object> { { "Breakpoint", breakpoint }, }; if (runspaceId.HasValue) { functionParameters.Add("RunspaceId", runspaceId.Value); } return InvokeRemoteBreakpointFunction<VariableBreakpoint>(RemoteDebuggingCommands.SetBreakpoint, functionParameters); } /// <summary> /// Removes a breakpoint from the debugger. /// </summary> /// <param name="breakpoint">The breakpoint to remove from the debugger. This value may not be null.</param> /// <param name="runspaceId">The runspace id of the runspace you want to interact with. A null value will use the current runspace.</param> /// <returns>True if the breakpoint was removed from the debugger; false otherwise.</returns> public override bool RemoveBreakpoint(Breakpoint breakpoint, int? runspaceId) { // This is supported only for PowerShell versions >= 7.0 CheckRemoteBreakpointManagementSupport(RemoteDebuggingCommands.RemoveBreakpoint); if (breakpoint == null) { return false; } var functionParameters = new Dictionary<string, object> { { "Id", breakpoint.Id }, }; if (runspaceId.HasValue) { functionParameters.Add("RunspaceId", runspaceId.Value); } return InvokeRemoteBreakpointFunction<bool>(RemoteDebuggingCommands.RemoveBreakpoint, functionParameters); } /// <summary> /// Enables a breakpoint in the debugger. /// </summary> /// <param name="breakpoint">The breakpoint to enable in the debugger. This value may not be null.</param> /// <param name="runspaceId">The runspace id of the runspace you want to interact with. A null value will use the current runspace.</param> /// <returns>The updated breakpoint if it was found; null if the breakpoint was not found in the debugger.</returns> public override Breakpoint EnableBreakpoint(Breakpoint breakpoint, int? runspaceId) { // This is supported only for PowerShell versions >= 7.0 CheckRemoteBreakpointManagementSupport(RemoteDebuggingCommands.EnableBreakpoint); if (breakpoint == null) { return null; } var functionParameters = new Dictionary<string, object> { { "Id", breakpoint.Id }, }; if (runspaceId.HasValue) { functionParameters.Add("RunspaceId", runspaceId.Value); } return InvokeRemoteBreakpointFunction<Breakpoint>(RemoteDebuggingCommands.EnableBreakpoint, functionParameters); } /// <summary> /// Disables a breakpoint in the debugger. /// </summary> /// <param name="breakpoint">The breakpoint to enable in the debugger. This value may not be null.</param> /// <param name="runspaceId">The runspace id of the runspace you want to interact with. A null value will use the current runspace.</param> /// <returns>The updated breakpoint if it was found; null if the breakpoint was not found in the debugger.</returns> public override Breakpoint DisableBreakpoint(Breakpoint breakpoint, int? runspaceId) { // This is supported only for PowerShell versions >= 7.0 CheckRemoteBreakpointManagementSupport(RemoteDebuggingCommands.DisableBreakpoint); if (breakpoint == null) { return null; } var functionParameters = new Dictionary<string, object> { { "Id", breakpoint.Id }, }; if (runspaceId.HasValue) { functionParameters.Add("RunspaceId", runspaceId.Value); } return InvokeRemoteBreakpointFunction<Breakpoint>(RemoteDebuggingCommands.DisableBreakpoint, functionParameters); } /// <summary> /// SetDebuggerAction. /// </summary> /// <param name="resumeAction">DebuggerResumeAction.</param> public override void SetDebuggerAction(DebuggerResumeAction resumeAction) { CheckForValidateState(); SetRemoteDebug(false, RunspaceAvailability.Busy); using (PowerShell ps = GetNestedPowerShell()) { ps.AddCommand(RemoteDebuggingCommands.SetDebuggerAction).AddParameter("ResumeAction", resumeAction); ps.Invoke(); // If an error exception is returned then throw it here. if (ps.ErrorBuffer.Count > 0) { Exception e = ps.ErrorBuffer[0].Exception; if (e != null) { throw e; } } } } /// <summary> /// GetDebuggerStopped. /// </summary> /// <returns>DebuggerStopEventArgs.</returns> public override DebuggerStopEventArgs GetDebuggerStopArgs() { CheckForValidateState(); DebuggerStopEventArgs rtnArgs = null; try { using (PowerShell ps = GetNestedPowerShell()) { ps.AddCommand(RemoteDebuggingCommands.GetDebuggerStopArgs); Collection<PSObject> output = ps.Invoke<PSObject>(); foreach (var item in output) { if (item == null) { continue; } rtnArgs = item.BaseObject as DebuggerStopEventArgs; if (rtnArgs != null) { break; } } } } catch (Exception) { } return rtnArgs; } /// <summary> /// SetDebugMode. /// </summary> /// <param name="mode"></param> public override void SetDebugMode(DebugModes mode) { CheckForValidateState(); // Only set debug mode on server if no commands are currently // running on remote runspace. if ((_runspace.GetCurrentlyRunningPipeline() != null) || (_runspace.RemoteCommand != null)) { return; } using (PowerShell ps = GetNestedPowerShell()) { ps.SetIsNested(false); ps.AddCommand(RemoteDebuggingCommands.SetDebugMode).AddParameter("Mode", mode); ps.Invoke(); } base.SetDebugMode(mode); SetIsActive(_breakpointCount); } /// <summary> /// Sets debugger stepping mode. /// </summary> /// <param name="enabled">True if stepping is to be enabled.</param> public override void SetDebuggerStepMode(bool enabled) { CheckForValidateState(); // This is supported only for PowerShell versions >= 5.0 if (!_remoteDebuggingCapability.IsCommandSupported(RemoteDebuggingCommands.SetDebuggerStepMode)) { return; } try { // Ensure debugger is in correct mode. base.SetDebugMode(DebugModes.LocalScript | DebugModes.RemoteScript); // Send Enable-DebuggerStepping virtual command. using (PowerShell ps = GetNestedPowerShell()) { ps.AddCommand(RemoteDebuggingCommands.SetDebuggerStepMode).AddParameter("Enabled", enabled); ps.Invoke(); _isDebuggerSteppingEnabled = enabled; } } catch (Exception) { // Don't propagate exceptions. } } /// <summary> /// True when debugger is active with breakpoints. /// </summary> public override bool IsActive { get { return _isActive; } } /// <summary> /// True when debugger is stopped at a breakpoint. /// </summary> public override bool InBreakpoint { get { return _handleDebuggerStop || (_runspace.RunspaceAvailability == RunspaceAvailability.RemoteDebug); } } /// <summary> /// InternalProcessCommand. /// </summary> /// <param name="command"></param> /// <param name="output"></param> /// <returns></returns> internal override DebuggerCommand InternalProcessCommand(string command, IList<PSObject> output) { throw new PSNotImplementedException(); } /// <summary> /// IsRemote. /// </summary> internal override bool IsRemote { get { return true; } } /// <summary> /// Sets how the debugger deals with breakpoint events that are not handled. /// Ignore - This is the default behavior and ignores any breakpoint event /// if there is no handler. Releases any preserved event. /// Wait - This mode preserves a breakpoint event until a handler is /// subscribed. /// </summary> internal override UnhandledBreakpointProcessingMode UnhandledBreakpointMode { get { return _unhandledBreakpointMode; } set { CheckForValidateState(); // This is supported only for PowerShell versions >= 5.0 if (!_remoteDebuggingCapability.IsCommandSupported(RemoteDebuggingCommands.SetUnhandledBreakpointMode)) { return; } SetRemoteDebug(false, (RunspaceAvailability?)null); // Send Set-PSUnhandledBreakpointMode virtual command. using (PowerShell ps = GetNestedPowerShell()) { ps.AddCommand(RemoteDebuggingCommands.SetUnhandledBreakpointMode).AddParameter("UnhandledBreakpointMode", value); ps.Invoke(); } _unhandledBreakpointMode = value; } } #endregion #region IDisposable /// <summary> /// Dispose. /// </summary> public void Dispose() { _runspace.RemoteDebuggerStop -= HandleForwardedDebuggerStopEvent; _runspace.RemoteDebuggerBreakpointUpdated -= HandleForwardedDebuggerBreakpointUpdatedEvent; #if !UNIX if (_identityToPersonate != null) { _identityToPersonate.Dispose(); _identityToPersonate = null; } #endif } #endregion #region Internal Methods /// <summary> /// Internal method that checks the debug state of /// the remote session and raises the DebuggerStop event /// if debugger is in stopped state. /// This is used internally to help clients get back to /// debug state when reconnecting to remote session in debug state. /// </summary> internal void CheckStateAndRaiseStopEvent() { DebuggerStopEventArgs stopArgs = GetDebuggerStopArgs(); if (stopArgs != null) { ProcessDebuggerStopEvent(stopArgs); } } /// <summary> /// IsRemoteDebug. /// </summary> internal bool IsRemoteDebug { get; private set; } /// <summary> /// Sets client debug info state based on server info. /// </summary> /// <param name="debugMode">Debug mode.</param> /// <param name="inBreakpoint">Currently in breakpoint.</param> /// <param name="breakpointCount">Breakpoint count.</param> /// <param name="breakAll">Break All setting.</param> /// <param name="unhandledBreakpointMode">UnhandledBreakpointMode.</param> /// <param name="serverPSVersion">Server PowerShell version.</param> internal void SetClientDebugInfo( DebugModes? debugMode, bool inBreakpoint, int breakpointCount, bool breakAll, UnhandledBreakpointProcessingMode unhandledBreakpointMode, Version serverPSVersion) { if (debugMode != null) { _remoteDebugSupported = true; DebugMode = debugMode.Value; } else { _remoteDebugSupported = false; } if (inBreakpoint) { SetRemoteDebug(true, RunspaceAvailability.RemoteDebug); } _remoteDebuggingCapability = RemoteDebuggingCapability.CreateDebuggingCapability(serverPSVersion); _breakpointCount = breakpointCount; _isDebuggerSteppingEnabled = breakAll; _unhandledBreakpointMode = unhandledBreakpointMode; SetIsActive(breakpointCount); } /// <summary> /// If a command is stopped while in debug stopped state and it /// is the only command running then server is no longer debug stopped. /// </summary> internal void OnCommandStopped() { if (IsRemoteDebug) { IsRemoteDebug = false; } } /// <summary> /// Gets breakpoint information from the target machine and passes that information /// on through the BreakpointUpdated event. /// </summary> internal void SendBreakpointUpdatedEvents() { if (!IsDebuggerBreakpointUpdatedEventSubscribed() || (_breakpointCount == 0)) { return; } PSDataCollection<PSObject> breakpoints = new PSDataCollection<PSObject>(); // Get breakpoint information by running "Get-PSBreakpoint" PowerShell command. using (PowerShell ps = GetNestedPowerShell()) { if (!this.InBreakpoint) { // Can't use nested PowerShell if we are not stopped in a breakpoint. ps.SetIsNested(false); } ps.AddCommand("Get-PSBreakpoint"); ps.Invoke(null, breakpoints); } // Raise BreakpointUpdated event to client for each breakpoint. foreach (PSObject obj in breakpoints) { Breakpoint breakpoint = obj.BaseObject as Breakpoint; if (breakpoint != null) { RaiseBreakpointUpdatedEvent( new BreakpointUpdatedEventArgs(breakpoint, BreakpointUpdateType.Set, _breakpointCount)); } } } /// <summary> /// IsDebuggerSteppingEnabled. /// </summary> internal override bool IsDebuggerSteppingEnabled { get { return _isDebuggerSteppingEnabled; } } #endregion #region Private Methods private static bool TryGetRemoteDebuggerException( PSObject item, out Exception exception) { exception = null; if (item == null) { return false; } bool haveExceptionType = false; foreach (var typeName in item.TypeNames) { if (typeName.Equals("Deserialized.System.Exception")) { haveExceptionType = true; break; } } if (haveExceptionType) { var errorMessage = item.Properties["Message"]?.Value ?? string.Empty; exception = new RemoteException( StringUtil.Format( RemotingErrorIdStrings.RemoteDebuggerError, item.TypeNames[0], errorMessage)); return true; } return false; } // // Event handlers // private void HandleForwardedDebuggerStopEvent(object sender, PSEventArgs e) { Dbg.Assert(e.SourceArgs.Length == 1, "Forwarded debugger stop event args must always contain one SourceArgs item."); DebuggerStopEventArgs args; if (e.SourceArgs[0] is PSObject) { args = ((PSObject)e.SourceArgs[0]).BaseObject as DebuggerStopEventArgs; } else { args = e.SourceArgs[0] as DebuggerStopEventArgs; } ProcessDebuggerStopEvent(args); } private void ProcessDebuggerStopEvent(DebuggerStopEventArgs args) { // It is possible to get a stop event raise request while // debugger is already in stop mode (after remote runspace debugger // reconnect). In this case ignore the request. if (_handleDebuggerStop) { return; } // Attempt to process debugger stop event on original thread if it // is available (i.e., if it is blocked by EndInvoke). PowerShell powershell = _runspace.RunspacePool.RemoteRunspacePoolInternal.GetCurrentRunningPowerShell(); AsyncResult invokeAsyncResult = powershell?.EndInvokeAsyncResult; bool invokedOnBlockedThread = false; if ((invokeAsyncResult != null) && (!invokeAsyncResult.IsCompleted)) { invokedOnBlockedThread = invokeAsyncResult.InvokeCallbackOnThread( ProcessDebuggerStopEventProc, args); } if (!invokedOnBlockedThread) { // Otherwise run on worker thread. #if !UNIX Utils.QueueWorkItemWithImpersonation( _identityToPersonate, ProcessDebuggerStopEventProc, args); #else Threading.ThreadPool.QueueUserWorkItem( ProcessDebuggerStopEventProc, args); #endif } } private void ProcessDebuggerStopEventProc(object state) { RunspaceAvailability prevAvailability = _runspace.RunspaceAvailability; bool restoreAvailability = true; try { _handleDebuggerStop = true; // Update runspace availability SetRemoteDebug(true, RunspaceAvailability.RemoteDebug); // Raise event and wait for response. DebuggerStopEventArgs args = state as DebuggerStopEventArgs; if (args != null) { if (IsDebuggerStopEventSubscribed()) { try { // Blocking call. base.RaiseDebuggerStopEvent(args); } finally { _handleDebuggerStop = false; if (!_detachCommand && !args.SuspendRemote) { SetDebuggerAction(args.ResumeAction); } } } else { // If no debugger is subscribed to the DebuggerStop event then we // allow the server side script execution to remain blocked in debug // stop mode. The runspace Availability reflects this and the client // must take action (attach debugger or release remote debugger stop // via SetDebuggerAction()). restoreAvailability = false; _handleDebuggerStop = false; } } else { // Null arguments may indicate that remote runspace was created without // default type table and so arguments cannot be serialized. In this case // we don't want to block the remote side script execution. _handleDebuggerStop = false; SetDebuggerAction(DebuggerResumeAction.Continue); } } catch (Exception) { _handleDebuggerStop = false; } finally { // Restore runspace availability. if (restoreAvailability && (_runspace.RunspaceAvailability == RunspaceAvailability.RemoteDebug)) { SetRemoteDebug(false, prevAvailability); } if (_detachCommand) { _detachCommand = false; } } } private void HandleForwardedDebuggerBreakpointUpdatedEvent(object sender, PSEventArgs e) { Dbg.Assert(e.SourceArgs.Length == 1, "Forwarded debugger breakpoint event args must always contain one SourceArgs item."); BreakpointUpdatedEventArgs bpArgs = e.SourceArgs[0] as BreakpointUpdatedEventArgs; if (bpArgs != null) { UpdateBreakpointCount(bpArgs.BreakpointCount); base.RaiseBreakpointUpdatedEvent(bpArgs); } } private PowerShell GetNestedPowerShell() { PowerShell ps = PowerShell.Create(); ps.Runspace = _runspace; ps.SetIsNested(true); return ps; } private void CheckForValidateState() { if (!_remoteDebugSupported) { throw new PSInvalidOperationException( // The remote session to which you are connected does not support remote debugging. // You must connect to a remote computer that is running PowerShell 4.0 or greater. RemotingErrorIdStrings.RemoteDebuggingEndpointVersionError, null, "RemoteDebugger:RemoteDebuggingNotSupported", ErrorCategory.NotImplemented, null); } if (_runspace.RunspaceStateInfo.State != RunspaceState.Opened) { throw new InvalidRunspaceStateException(); } #if !UNIX if (!_identityPersonationChecked) { _identityPersonationChecked = true; // Save identity to impersonate. Utils.TryGetWindowsImpersonatedIdentity(out _identityToPersonate); } #endif } private void SetRemoteDebug(bool remoteDebug, RunspaceAvailability? availability) { if (_runspace.RunspaceStateInfo.State != RunspaceState.Opened) { return; } if (IsRemoteDebug != remoteDebug) { IsRemoteDebug = remoteDebug; _runspace.RunspacePool.RemoteRunspacePoolInternal.IsRemoteDebugStop = remoteDebug; } if (availability != null) { RunspaceAvailability newAvailability = availability.Value; if ((_runspace.RunspaceAvailability != newAvailability) && (remoteDebug || (newAvailability != RunspaceAvailability.RemoteDebug))) { try { _runspace.UpdateRunspaceAvailability(newAvailability, true); } catch (Exception) { } } } } private void UpdateBreakpointCount(int bpCount) { _breakpointCount = bpCount; SetIsActive(bpCount); } private void SetIsActive(int breakpointCount) { if ((DebugMode & DebugModes.RemoteScript) == 0) { // Debugger is always inactive if RemoteScript is not selected. if (_isActive) { _isActive = false; } return; } if (breakpointCount > 0) { if (!_isActive) { _isActive = true; } } else { if (_isActive) { _isActive = false; } } } private T InvokeRemoteBreakpointFunction<T>(string functionName, Dictionary<string, object> parameters) { CheckForValidateState(); using (PowerShell ps = GetNestedPowerShell()) { ps.AddCommand(functionName); foreach (var parameterName in parameters.Keys) { ps.AddParameter(parameterName, parameters[parameterName]); } Collection<PSObject> output = ps.Invoke<PSObject>(); // If an error exception is returned then throw it here. if (ps.ErrorBuffer.Count > 0) { Exception e = ps.ErrorBuffer[0].Exception; if (e != null) { throw e; } } // This helper is only used to return a single output item of type T. foreach (var item in output) { if (item?.BaseObject is T) { return (T)item.BaseObject; } if (TryGetRemoteDebuggerException(item, out Exception ex)) { throw ex; } } return default(T); } } private void CheckRemoteBreakpointManagementSupport(string breakpointCommandNameToCheck) { _remoteBreakpointManagementIsSupported ??= _remoteDebuggingCapability.IsCommandSupported(breakpointCommandNameToCheck); if (!_remoteBreakpointManagementIsSupported.Value) { throw new PSNotSupportedException( StringUtil.Format( DebuggerStrings.CommandNotSupportedForRemoteUseInServerDebugger, RemoteDebuggingCommands.CleanCommandName(breakpointCommandNameToCheck))); } } #endregion } #endregion #region RemoteSessionStateProxy internal class RemoteSessionStateProxy : SessionStateProxy { private readonly RemoteRunspace _runspace; internal RemoteSessionStateProxy(RemoteRunspace runspace) { Dbg.Assert(runspace != null, "Caller should validate the parameter"); _runspace = runspace; } private Exception _isInNoLanguageModeException = null; private Exception _getVariableCommandNotFoundException = null; private Exception _setVariableCommandNotFoundException = null; /// <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 override void SetVariable(string name, object value) { if (name == null) { throw PSTraceSource.NewArgumentNullException(nameof(name)); } // Verify the runspace has the Set-Variable command. For performance, throw if we got an error // before. if (_setVariableCommandNotFoundException != null) throw _setVariableCommandNotFoundException; // Since these are implemented as pipelines, we don't need to do our own // locking of sessionStateCallInProgress like we do with local runspaces. Pipeline remotePipeline = _runspace.CreatePipeline(); Command command = new Command("Microsoft.PowerShell.Utility\\Set-Variable"); command.Parameters.Add("Name", name); command.Parameters.Add("Value", value); remotePipeline.Commands.Add(command); try { remotePipeline.Invoke(); } catch (RemoteException e) { if (string.Equals("CommandNotFoundException", e.ErrorRecord.FullyQualifiedErrorId, StringComparison.OrdinalIgnoreCase)) { _setVariableCommandNotFoundException = new PSNotSupportedException(RunspaceStrings.NotSupportedOnRestrictedRunspace, e); throw _setVariableCommandNotFoundException; } else throw; } if (remotePipeline.Error.Count > 0) { // Don't cache these errors, as they are related to the actual variable being set. ErrorRecord error = (ErrorRecord)remotePipeline.Error.Read(); throw new PSNotSupportedException(RunspaceStrings.NotSupportedOnRestrictedRunspace, error.Exception); } } /// <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 override object GetVariable(string name) { if (name == null) { throw PSTraceSource.NewArgumentNullException(nameof(name)); } // Verify the runspace has the Get-Variable command. For performance, throw if we got an error // before. if (_getVariableCommandNotFoundException != null) throw _getVariableCommandNotFoundException; // Since these are implemented as pipelines, we don't need to do our own // locking of sessionStateCallInProgress like we do with local runspaces. Pipeline remotePipeline = _runspace.CreatePipeline(); Command command = new Command("Microsoft.PowerShell.Utility\\Get-Variable"); command.Parameters.Add("Name", name); remotePipeline.Commands.Add(command); System.Collections.ObjectModel.Collection<PSObject> result = null; try { result = remotePipeline.Invoke(); } catch (RemoteException e) { if (string.Equals("CommandNotFoundException", e.ErrorRecord.FullyQualifiedErrorId, StringComparison.OrdinalIgnoreCase)) { _getVariableCommandNotFoundException = new PSNotSupportedException(RunspaceStrings.NotSupportedOnRestrictedRunspace, e); throw _getVariableCommandNotFoundException; } else throw; } if (remotePipeline.Error.Count > 0) { // Don't cache these errors, as they are related to the actual variable being set. ErrorRecord error = (ErrorRecord)remotePipeline.Error.Read(); if (string.Equals("CommandNotFoundException", error.FullyQualifiedErrorId, StringComparison.OrdinalIgnoreCase)) { throw new PSNotSupportedException(RunspaceStrings.NotSupportedOnRestrictedRunspace, error.Exception); } else { throw new PSInvalidOperationException(error.Exception.Message, error.Exception); } } if (result.Count != 1) return null; else return result[0].Properties["Value"].Value; } /// <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 override List<string> Applications { get { // Verify the runspace has is not in NoLanguage mode. For performance, throw if we got an error // before. if (_isInNoLanguageModeException != null) throw _isInNoLanguageModeException; // Since these are implemented as pipelines, we don't need to do our own // locking of sessionStateCallInProgress like we do with local runspaces. Pipeline remotePipeline = _runspace.CreatePipeline(); remotePipeline.Commands.AddScript("$executionContext.SessionState.Applications"); List<string> result = new List<string>(); try { foreach (PSObject application in remotePipeline.Invoke()) { result.Add(application.BaseObject as string); } } catch (RemoteException e) { if (e.ErrorRecord.CategoryInfo.Category == ErrorCategory.ParserError) { _isInNoLanguageModeException = new PSNotSupportedException(RunspaceStrings.NotSupportedOnRestrictedRunspace, e); throw _isInNoLanguageModeException; } else throw; } return result; } } /// <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 override List<string> Scripts { get { // Verify the runspace has is not in NoLanguage mode. For performance, throw if we got an error // before. if (_isInNoLanguageModeException != null) throw _isInNoLanguageModeException; // Since these are implemented as pipelines, we don't need to do our own // locking of sessionStateCallInProgress like we do with local runspaces. Pipeline remotePipeline = _runspace.CreatePipeline(); remotePipeline.Commands.AddScript("$executionContext.SessionState.Scripts"); List<string> result = new List<string>(); try { foreach (PSObject application in remotePipeline.Invoke()) { result.Add(application.BaseObject as string); } } catch (RemoteException e) { if (e.ErrorRecord.CategoryInfo.Category == ErrorCategory.ParserError) { _isInNoLanguageModeException = new PSNotSupportedException(RunspaceStrings.NotSupportedOnRestrictedRunspace, e); throw _isInNoLanguageModeException; } else throw; } return result; } } /// <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 override DriveManagementIntrinsics Drive { get { throw new PSNotSupportedException(); } } /// <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 override PSLanguageMode LanguageMode { get { // Verify the runspace has is not in NoLanguage mode. For performance, return our // cached value if we got an error before. if (_isInNoLanguageModeException != null) return PSLanguageMode.NoLanguage; // Since these are implemented as pipelines, we don't need to do our own // locking of sessionStateCallInProgress like we do with local runspaces. Pipeline remotePipeline = _runspace.CreatePipeline(); remotePipeline.Commands.AddScript("$executionContext.SessionState.LanguageMode"); System.Collections.ObjectModel.Collection<PSObject> result = null; try { result = remotePipeline.Invoke(); } catch (RemoteException e) { if (e.ErrorRecord.CategoryInfo.Category == ErrorCategory.ParserError) { _isInNoLanguageModeException = new PSNotSupportedException(RunspaceStrings.NotSupportedOnRestrictedRunspace, e); return PSLanguageMode.NoLanguage; } else throw; } return (PSLanguageMode)LanguagePrimitives.ConvertTo(result[0], typeof(PSLanguageMode), CultureInfo.InvariantCulture); } set { throw new PSNotSupportedException(); } } /// <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> public override PSModuleInfo Module { get { throw new PSNotSupportedException(); } } /// <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 override PathIntrinsics Path { get { throw new PSNotSupportedException(); } } /// <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 override CmdletProviderManagementIntrinsics Provider { get { throw new PSNotSupportedException(); } } /// <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 override PSVariableIntrinsics PSVariable { get { throw new PSNotSupportedException(); } } /// <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 override CommandInvocationIntrinsics InvokeCommand { get { throw new PSNotSupportedException(); } } /// <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 override ProviderIntrinsics InvokeProvider { get { throw new PSNotSupportedException(); } } } #endregion }