2016-03-30 14:20:52 -07:00
/********************************************************************++
Copyright (c) Microsoft Corporation. All rights reserved.
--********************************************************************/
using System.Management.Automation.Runspaces ;
using System.Management.Automation.Host ;
using System.Management.Automation.Internal ;
using System.Management.Automation.Tracing ;
using Dbg = System . Management . Automation . Diagnostics ;
using System.Threading ;
using System.Collections ;
using System.Collections.Generic ;
using System.Collections.ObjectModel ;
using System.Management.Automation.Remoting ;
using System.Diagnostics.CodeAnalysis ;
using System.Globalization ;
using Microsoft.PowerShell.Commands ;
using System.Security.Principal ;
using System.Management.Automation.Runspaces.Internal ;
#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
2016-07-29 13:02:49 -07:00
2016-03-30 14:20:52 -07:00
private RunspacePool _runspacePool ;
private ArrayList _runningPipelines = new ArrayList ();
private object _syncRoot = new object ();
private RunspaceStateInfo _runspaceStateInfo = new RunspaceStateInfo ( RunspaceState . BeforeOpen );
private Version _version = PSVersionInfo . PSVersion ;
private Version _serverVersion ;
private bool _bSessionStateProxyCallInProgress = false ;
private RunspaceConnectionInfo _connectionInfo ;
private RunspaceConnectionInfo _originalConnectionInfo ;
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
2016-07-29 13:02:49 -07:00
private InvokeCommandCommand _currentInvokeCommand = null ;
private long _currentLocalPipelineId = 0 ;
2016-03-30 14:20:52 -07:00
/// <summary>
/// This is queue of all the state change event which have occured 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 ;
}
}
2016-07-29 13:02:49 -07:00
private bool _shouldCloseOnPop = false ;
2016-03-30 14:20:52 -07:00
/// <summary>
/// Temporary place to remember whether to close this runspace on pop or not.
/// Used by Start-PSSession.
/// </summary>
internal bool ShouldCloseOnPop
{
get { return _shouldCloseOnPop ; }
set { _shouldCloseOnPop = value ; }
}
#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. SerailizationDepth
/// 3. SpecificSerializationProperties
/// TypeTable has the following inforamtion used by deserializer:
/// 1. TargetTypeForDeserializaiton
/// 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 . InternalCopy ();
_originalConnectionInfo = connectionInfo . InternalCopy ();
_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.
2016-07-29 13:02:49 -07:00
if (( runspacePool . RunspacePoolStateInfo . State != RunspacePoolState . Disconnected ) ||
!( runspacePool . ConnectionInfo is WSManConnectionInfo ))
2016-03-30 14:20:52 -07:00
{
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 . InternalCopy ();
// 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* avaialable for
// connection and in this case the Availability is set to "Busy".
2016-07-29 13:02:49 -07:00
_runspaceAvailability = _runspacePool . RemoteRunspacePoolInternal . AvailableForConnection ?
2016-03-30 14:20:52 -07:00
Runspaces . RunspaceAvailability . None : Runspaces . RunspaceAvailability . Busy ;
SetEventHandlers ();
PSEtwLog . SetActivityIdForCurrentThread ( this . InstanceId );
PSEtwLog . LogOperationalVerbose ( PSEventId . RunspaceConstructor , PSOpcode . Constructor ,
2016-07-29 13:02:49 -07:00
PSTask . CreateRunspace , PSKeyword . UseAlwaysOperational ,
2016-03-30 14:20:52 -07:00
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 +=
new EventHandler < RunspacePoolStateChangedEventArgs >( HandleRunspacePoolStateChanged );
_runspacePool . RemoteRunspacePoolInternal . HostCallReceived +=
new EventHandler < RemoteDataEventArgs < RemoteHostCall >>( HandleHostCallReceived );
_runspacePool . RemoteRunspacePoolInternal . URIRedirectionReported +=
new EventHandler < RemoteDataEventArgs < Uri >>( HandleURIDirectionReported );
_runspacePool . ForwardEvent +=
new EventHandler < PSEventArgs >( HandleRunspacePoolForwardEvent );
2016-07-29 13:02:49 -07:00
_runspacePool . RemoteRunspacePoolInternal . SessionCreateCompleted +=
2016-03-30 14:20:52 -07:00
new EventHandler < CreateCompleteEventArgs >( HandleSessionCreateCompleted );
}
2016-07-29 13:02:49 -07:00
2016-03-30 14:20:52 -07:00
#endregion Constructors
#region Properties
/// <summary>
/// runspaceConfiguration information for this runspace
/// </summary>
2016-04-28 13:41:25 -07:00
#if CORECLR
internal
#else
public
#endif
override RunspaceConfiguration RunspaceConfiguration
2016-03-30 14:20:52 -07:00
{
get
{
#pragma warning disable 56503
throw PSTraceSource . NewNotImplementedException ();
#pragma warning restore 56503
}
}
/// <summary>
/// initialsessionstate information for this runspace
/// </summary>
public override InitialSessionState InitialSessionState
{
get
{
#pragma warning disable 56503
throw PSTraceSource . NewNotImplementedException ();
#pragma warning restore 56503
}
}
/// <summary>
/// Manager for JobSourceAdapters registered in this runspace.
/// </summary>
public override JobManager JobManager
{
get
{
#pragma warning disable 56503
throw PSTraceSource . NewNotImplementedException ();
#pragma warning restore 56503
}
}
/// <summary>
/// Return version of this runspace
/// </summary>
public override Version Version
{
get
{
return _version ;
}
}
/// <summary>
/// PS Version running on server.
/// </summary>
internal Version ServerVersion
{
get { return _serverVersion ; }
}
/// <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
{
2016-07-29 13:02:49 -07:00
return _createThreadOptions ;
2016-03-30 14:20:52 -07:00
}
set
{
lock ( _syncRoot )
{
2016-07-29 13:02:49 -07:00
if ( value != _createThreadOptions )
2016-03-30 14:20:52 -07:00
{
if ( this . RunspaceStateInfo . State != RunspaceState . BeforeOpen )
{
throw new InvalidRunspaceStateException ( StringUtil . Format ( RunspaceStrings . ChangePropertyAfterOpen ));
}
2016-07-29 13:02:49 -07:00
_createThreadOptions = value ;
2016-03-30 14:20:52 -07:00
}
}
}
}
2016-07-29 13:02:49 -07:00
private PSThreadOptions _createThreadOptions = PSThreadOptions . Default ;
2016-03-30 14:20:52 -07:00
/// <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 exception ) // ignore non-severe exceptions
{
CommandProcessorBase . CheckForSevereException ( 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 { return _originalConnectionInfo ; }
}
/// <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
{
2016-07-29 13:02:49 -07:00
get { return _runspacePool . RemoteRunspacePoolInternal . Name ; }
set { _runspacePool . RemoteRunspacePoolInternal . Name = value ; }
2016-03-30 14:20:52 -07:00
}
/// <summary>
/// Gets the Id value for the remote PSSession.
/// </summary>
internal int PSSessionId
{
2016-07-29 13:02:49 -07:00
get { return _id ; }
set { _id = value ; }
2016-03-30 14:20:52 -07:00
}
2016-07-29 13:02:49 -07:00
private int _id = - 1 ;
2016-03-30 14:20:52 -07:00
/// <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>
/// Debugger
/// </summary>
public override Debugger Debugger
{
get
{
return _remoteDebugger ;
}
}
2016-07-29 13:02:49 -07:00
2016-03-30 14:20:52 -07:00
#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 ;
#if ! CORECLR // No ApartmentState In CoreCLR
_runspacePool . ApartmentState = this . ApartmentState ;
#endif
_runspacePool . Open ();
}
catch ( InvalidRunspacePoolStateException e )
{
throw e . ToInvalidRunspaceStateException ();
}
}
#endregion Open
#region Close
/// <summary>
/// Close the runspace Asynchronously.
/// </summary>
public override void CloseAsync ()
{
try
{
2016-07-29 13:02:49 -07:00
_runspacePool . BeginClose ( null , null );
2016-03-30 14:20:52 -07:00
}
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
{
2016-07-29 13:02:49 -07:00
IAsyncResult result = _runspacePool . BeginClose ( null , null );
2016-03-30 14:20:52 -07:00
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.
//
}
if ( _remoteDebugger != null )
{
// Release RunspacePool event forwarding handlers.
_remoteDebugger . Dispose ();
}
try
{
_runspacePool . StateChanged -=
new EventHandler < RunspacePoolStateChangedEventArgs >( HandleRunspacePoolStateChanged );
_runspacePool . RemoteRunspacePoolInternal . HostCallReceived -=
new EventHandler < RemoteDataEventArgs < RemoteHostCall >>( HandleHostCallReceived );
_runspacePool . RemoteRunspacePoolInternal . URIRedirectionReported -=
new EventHandler < RemoteDataEventArgs < Uri >>( HandleURIDirectionReported );
_runspacePool . ForwardEvent -=
new EventHandler < PSEventArgs >( HandleRunspacePoolForwardEvent );
_runspacePool . RemoteRunspacePoolInternal . SessionCreateCompleted -=
new EventHandler < CreateCompleteEventArgs >( 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 avaialablity 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 NamedPipeConnectionInfo )
{
returnCaps |= RunspaceCapability . NamedPipeTransport ;
}
2016-04-12 13:06:17 -07:00
else if ( _connectionInfo is VMConnectionInfo )
{
returnCaps |= RunspaceCapability . VMSocketTransport ;
}
2016-03-30 14:20:52 -07:00
else
{
ContainerConnectionInfo containerConnectionInfo = _connectionInfo as ContainerConnectionInfo ;
2016-07-29 13:02:49 -07:00
if (( containerConnectionInfo != null ) &&
2016-03-30 14:20:52 -07:00
( containerConnectionInfo . ContainerProc . RuntimeId == Guid . Empty ))
{
returnCaps |= RunspaceCapability . NamedPipeTransport ;
}
}
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>
/// Createa a pipeline froma command string
/// </summary>
/// <param name="command">A valid command string</param>
/// <returns>
/// A pipline 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 ( "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 pipline 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 ( "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 pipline 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 ( "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 )
{
2016-07-29 13:02:49 -07:00
if ( _bypassRunspaceStateCheck == false &&
2016-03-30 14:20:52 -07:00
_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 Excuting 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 Excuting 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 == true )
{
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 ()
{
2016-07-29 13:02:49 -07:00
if ( _sessionStateProxy == null )
_sessionStateProxy = new RemoteSessionStateProxy ( this );
2016-03-30 14:20:52 -07:00
2016-07-29 13:02:49 -07:00
return _sessionStateProxy ;
2016-03-30 14:20:52 -07:00
}
2016-07-29 13:02:49 -07:00
private RemoteSessionStateProxy _sessionStateProxy = null ;
2016-03-30 14:20:52 -07:00
#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 _applicatPrivateData 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 e )
{
CommandProcessorBase . CheckForSevereException ( e );
}
}
}
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 = 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 exception ) // ignore non-severe exceptions
{
CommandProcessorBase . CheckForSevereException ( 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 = ( RemotePipeline []) _runningPipelines . ToArray ( typeof ( RemotePipeline ));
}
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 ,
2016-07-29 13:02:49 -07:00
_runspacePool . RemoteRunspacePoolInternal . Host ,
2016-03-30 14:20:52 -07:00
null , /* error stream */
null , /* method executor stream */
false , /* is method stream enabled */
2016-07-29 13:02:49 -07:00
_runspacePool . RemoteRunspacePoolInternal ,
2016-03-30 14:20:52 -07:00
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 ( null != wsmanConnectionInfo )
{
// 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 )
{
2016-07-29 13:02:49 -07:00
_connectionInfo . IdleTimeout = eventArgs . ConnectionInfo . IdleTimeout ;
_connectionInfo . MaxIdleTimeout = eventArgs . ConnectionInfo . MaxIdleTimeout ;
WSManConnectionInfo wsmanConnectionInfo = _connectionInfo as WSManConnectionInfo ;
2016-03-30 14:20:52 -07:00
if ( wsmanConnectionInfo != null )
{
wsmanConnectionInfo . OutputBufferingMode =
(( WSManConnectionInfo ) eventArgs . ConnectionInfo ). OutputBufferingMode ;
}
}
}
/// <summary>
/// Updates runspace DisconnectedOn/ExpiresOn based on RS Pool connectionInfo.
/// </summary>
private void UpdateDisconnectExpiresOn ()
{
2016-07-29 13:02:49 -07:00
WSManConnectionInfo wsmanConnectionInfo = _runspacePool . RemoteRunspacePoolInternal . ConnectionInfo as WSManConnectionInfo ;
2016-03-30 14:20:52 -07:00
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
2016-07-29 13:02:49 -07:00
if ( _currentLocalPipelineId != localPipelineId && _currentLocalPipelineId != 0 )
2016-03-30 14:20:52 -07:00
{
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
2016-07-29 13:02:49 -07:00
if ( _currentInvokeCommand == null )
2016-03-30 14:20:52 -07:00
{
// this is the first invoke-command, just
// set the reference
SetCurrentInvokeCommand ( invokeCommand , localPipelineId );
return false ;
}
2016-07-29 13:02:49 -07:00
else if ( _currentInvokeCommand . Equals ( invokeCommand ))
2016-03-30 14:20:52 -07:00
{
// 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 pipline
/// </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" );
2016-07-29 13:02:49 -07:00
_currentInvokeCommand = invokeCommand ;
_currentLocalPipelineId = localPipelineId ;
2016-03-30 14:20:52 -07:00
}
/// <summary>
/// Clears the current invoke-command reference stored within
/// this remote runspace
/// </summary>
internal void ClearInvokeCommand ()
{
2016-07-29 13:02:49 -07:00
_currentLocalPipelineId = 0 ;
_currentInvokeCommand = null ;
2016-03-30 14:20:52 -07:00
}
/// <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 ;
2016-07-29 13:02:49 -07:00
2016-03-30 14:20:52 -07:00
if ( transportManager != null )
{
transportManager . AbortConnect ();
}
}
#endregion Internal Methods
#region Misc Properties / Events
/// <summary>
/// The runspace pool that this remote runspace wraps
/// </summary>
internal RunspacePool RunspacePool
{
get
{
return _runspacePool ;
}
}
/// <summary>
/// EventHandler used to report connecion 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 RemoteRunspace _runspace ;
private PowerShell _psDebuggerCommand ;
private bool _remoteDebugSupported ;
private bool _isActive ;
private int _breakpointCount ;
private Version _serverPSVersion ;
private volatile bool _handleDebuggerStop ;
private bool _isDebuggerSteppingEnabled ;
private UnhandledBreakpointProcessingMode _unhandledBreakpointMode ;
private bool _detachCommand ;
// Impersonation flow
2016-07-29 13:02:49 -07:00
private WindowsIdentity _identityToPersonate ;
private bool _identityPersonationChecked ;
2016-03-30 14:20:52 -07:00
/// <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 ( "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 ( "command" );
}
if ( output == null )
{
throw new PSArgumentNullException ( "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 )
{
CommandProcessor . CheckForSevereException ( 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 == typeof ( IncompleteParseException ). Name )
{
throw new IncompleteParseException (
( re . ErrorRecord . Exception != null ) ? re . ErrorRecord . Exception . Message : null ,
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 == typeof ( InvalidRunspacePoolStateException ). Name ) ||
( re . ErrorRecord . CategoryInfo . Reason == typeof ( RemoteException ). Name ))
{
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>
/// SetDebuggerAction
/// </summary>
/// <param name="resumeAction">DebuggerResumeAction</param>
public override void SetDebuggerAction ( DebuggerResumeAction resumeAction )
{
CheckForValidateState ();
SetRemoteDebug ( false , RunspaceAvailability . Busy );
using ( PowerShell ps = GetNestedPowerShell ())
{
ps . AddCommand ( DebuggerUtils . SetDebuggerActionFunctionName ). 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 ( DebuggerUtils . GetDebuggerStopArgsFunctionName );
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 e )
{
CommandProcessor . CheckForSevereException ( e );
}
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 ( DebuggerUtils . SetDebugModeFunctionName ). 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 (( _serverPSVersion == null ) ||
( _serverPSVersion . Major < PSVersionInfo . PSV5Version . Major ))
{
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 ( DebuggerUtils . SetDebuggerStepMode ). AddParameter ( "Enabled" , enabled );
ps . Invoke ();
_isDebuggerSteppingEnabled = enabled ;
}
}
catch ( Exception e )
{
// Don't propagate exceptions.
CommandProcessorBase . CheckForSevereException ( e );
}
}
/// <summary>
/// True when debugger is active with breakpoints.
/// </summary>
public override bool IsActive
{
2016-07-29 13:02:49 -07:00
get { return _isActive ; }
2016-03-30 14:20:52 -07:00
}
/// <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 (( _serverPSVersion == null ) ||
( _serverPSVersion < PSVersionInfo . PSV5Version ))
{
return ;
}
SetRemoteDebug ( false , ( RunspaceAvailability ?) null );
// Send Set-PSUnhandledBreakpointMode virtual command.
using ( PowerShell ps = GetNestedPowerShell ())
{
ps . AddCommand ( DebuggerUtils . SetPSUnhandledBreakpointMode ). AddParameter ( "UnhandledBreakpointMode" , value );
ps . Invoke ();
}
_unhandledBreakpointMode = value ;
}
}
#endregion
#region IDisposable
/// <summary>
/// Dispose
/// </summary>
public void Dispose ()
{
_runspace . RemoteDebuggerStop -= HandleForwardedDebuggerStopEvent ;
_runspace . RemoteDebuggerBreakpointUpdated -= HandleForwardedDebuggerBreakpointUpdatedEvent ;
2016-07-29 13:02:49 -07:00
2016-03-30 14:20:52 -07:00
if ( _identityToPersonate != null )
{
_identityToPersonate . Dispose ();
_identityToPersonate = null ;
}
}
#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
{
private set ;
get ;
}
/// <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 (
2016-07-29 13:02:49 -07:00
DebugModes ? debugMode ,
bool inBreakpoint ,
2016-03-30 14:20:52 -07:00
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 );
}
_serverPSVersion = 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
//
// 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 != null ) ? powershell . EndInvokeAsyncResult : null ;
bool invokedOnBlockedThread = false ;
if (( invokeAsyncResult != null ) && (! invokeAsyncResult . IsCompleted ))
{
invokedOnBlockedThread = invokeAsyncResult . InvokeCallbackOnThread (
ProcessDebuggerStopEventProc ,
args );
}
2016-07-29 13:02:49 -07:00
2016-03-30 14:20:52 -07:00
if (! invokedOnBlockedThread )
{
#if CORECLR
Threading . ThreadPool . QueueUserWorkItem (
ProcessDebuggerStopEventProc ,
args );
#else
// Otherwise run on worker thread.
Utils . QueueWorkItemWithImpersonation (
_identityToPersonate ,
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 )
{
SetDebuggerAction ( args . ResumeAction );
}
}
}
2016-07-29 13:02:49 -07:00
else
2016-03-30 14:20:52 -07:00
{
// 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 ex )
{
CommandProcessorBase . CheckForSevereException ( ex );
_handleDebuggerStop = false ;
}
finally
{
// Restore runspace availability.
if ( restoreAvailability )
{
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 Windows PowerShell {0} or greater.
StringUtil . Format ( RemotingErrorIdStrings . RemoteDebuggingEndpointVersionError , PSVersionInfo . PSV4Version ),
null ,
"RemoteDebugger:RemoteDebuggingNotSupported" ,
ErrorCategory . NotImplemented ,
null );
}
if ( _runspace . RunspaceStateInfo . State != RunspaceState . Opened )
{
throw new InvalidRunspaceStateException ();
}
2016-07-29 13:02:49 -07:00
2016-03-30 14:20:52 -07:00
if (! _identityPersonationChecked )
{
_identityPersonationChecked = true ;
// Save identity to impersonate.
WindowsIdentity currentIdentity = null ;
try
{
currentIdentity = WindowsIdentity . GetCurrent ();
}
catch ( System . Security . SecurityException ) { }
_identityToPersonate = (( currentIdentity != null ) && ( currentIdentity . ImpersonationLevel == TokenImpersonationLevel . Impersonation )) ? currentIdentity : null ;
}
}
private void SetRemoteDebug ( bool remoteDebug , RunspaceAvailability ? availability )
{
if ( _runspace . RunspaceStateInfo . State != RunspaceState . Opened )
2016-07-29 13:02:49 -07:00
{
return ;
2016-03-30 14:20:52 -07:00
}
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 e )
{
CommandProcessorBase . CheckForSevereException ( e );
}
}
}
}
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 ; }
}
}
#endregion
}
#endregion
#region RemoteSessionStateProxy
internal class RemoteSessionStateProxy : SessionStateProxy
{
2016-07-29 13:02:49 -07:00
private RemoteRunspace _runspace ;
2016-03-30 14:20:52 -07:00
internal RemoteSessionStateProxy ( RemoteRunspace runspace )
{
Dbg . Assert ( runspace != null , "Caller should validate the parameter" );
_runspace = runspace ;
}
2016-07-29 13:02:49 -07:00
private Exception _isInNoLangugeModeException = null ;
private Exception _getVariableCommandNotFoundException = null ;
private Exception _setVariableCommandNotFoundException = null ;
2016-03-30 14:20:52 -07:00
/// <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 ( "name" );
}
// Verify the runspace has the Set-Variable command. For performance, throw if we got an error
// before.
2016-07-29 13:02:49 -07:00
if ( _setVariableCommandNotFoundException != null )
throw _setVariableCommandNotFoundException ;
2016-03-30 14:20:52 -07:00
// 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 ))
{
2016-07-29 13:02:49 -07:00
_setVariableCommandNotFoundException = new PSNotSupportedException ( RunspaceStrings . NotSupportedOnRestrictedRunspace , e );
throw _setVariableCommandNotFoundException ;
2016-03-30 14:20:52 -07:00
}
else throw ;
}
if ( remotePipeline . Error . Count > 0 )
{
// Don't cache these errors, as they are related to the actual variable being set.
2016-07-29 13:02:49 -07:00
ErrorRecord error = ( ErrorRecord ) remotePipeline . Error . Read ();
2016-03-30 14:20:52 -07:00
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 ( "name" );
}
// Verify the runspace has the Get-Variable command. For performance, throw if we got an error
// before.
2016-07-29 13:02:49 -07:00
if ( _getVariableCommandNotFoundException != null )
throw _getVariableCommandNotFoundException ;
2016-03-30 14:20:52 -07:00
// 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 ))
{
2016-07-29 13:02:49 -07:00
_getVariableCommandNotFoundException = new PSNotSupportedException ( RunspaceStrings . NotSupportedOnRestrictedRunspace , e );
throw _getVariableCommandNotFoundException ;
2016-03-30 14:20:52 -07:00
}
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.
2016-07-29 13:02:49 -07:00
if ( _isInNoLangugeModeException != null )
throw _isInNoLangugeModeException ;
2016-03-30 14:20:52 -07:00
// 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 )
{
2016-07-29 13:02:49 -07:00
_isInNoLangugeModeException = new PSNotSupportedException ( RunspaceStrings . NotSupportedOnRestrictedRunspace , e );
throw _isInNoLangugeModeException ;
2016-03-30 14:20:52 -07:00
}
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.
2016-07-29 13:02:49 -07:00
if ( _isInNoLangugeModeException != null )
throw _isInNoLangugeModeException ;
2016-03-30 14:20:52 -07:00
// 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 )
{
2016-07-29 13:02:49 -07:00
_isInNoLangugeModeException = new PSNotSupportedException ( RunspaceStrings . NotSupportedOnRestrictedRunspace , e );
throw _isInNoLangugeModeException ;
2016-03-30 14:20:52 -07:00
}
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.
2016-07-29 13:02:49 -07:00
if ( _isInNoLangugeModeException != null )
2016-03-30 14:20:52 -07:00
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 )
{
2016-07-29 13:02:49 -07:00
_isInNoLangugeModeException = new PSNotSupportedException ( RunspaceStrings . NotSupportedOnRestrictedRunspace , e );
2016-03-30 14:20:52 -07:00
return PSLanguageMode . NoLanguage ;
}
else throw ;
}
2016-07-29 13:02:49 -07:00
return ( PSLanguageMode ) LanguagePrimitives . ConvertTo ( result [ 0 ], typeof ( PSLanguageMode ), CultureInfo . InvariantCulture );
2016-03-30 14:20:52 -07:00
}
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
}