mirror of
https://github.com/aphrody-labs/PowerShell.git
synced 2026-10-17 04:36:39 +00:00
3078 lines
113 KiB
C#
3078 lines
113 KiB
C#
/********************************************************************++
|
|
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
|
|
|
|
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
|
|
private InvokeCommandCommand currentInvokeCommand = null;
|
|
private long currentLocalPipelineId = 0;
|
|
|
|
/// <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;
|
|
}
|
|
}
|
|
|
|
bool _shouldCloseOnPop = false;
|
|
|
|
/// <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.
|
|
if ( (runspacePool.RunspacePoolStateInfo.State != RunspacePoolState.Disconnected) ||
|
|
!(runspacePool.ConnectionInfo is 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.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".
|
|
this._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 +=
|
|
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);
|
|
}
|
|
|
|
#endregion Constructors
|
|
|
|
#region Properties
|
|
|
|
/// <summary>
|
|
/// runspaceConfiguration information for this runspace
|
|
/// </summary>
|
|
public override RunspaceConfiguration RunspaceConfiguration
|
|
{
|
|
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
|
|
{
|
|
return this.createThreadOptions;
|
|
}
|
|
|
|
set
|
|
{
|
|
lock (_syncRoot)
|
|
{
|
|
if (value != this.createThreadOptions)
|
|
{
|
|
if (this.RunspaceStateInfo.State != RunspaceState.BeforeOpen)
|
|
{
|
|
throw new InvalidRunspaceStateException(StringUtil.Format(RunspaceStrings.ChangePropertyAfterOpen));
|
|
}
|
|
|
|
this.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 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
|
|
{
|
|
get { return this._runspacePool.RemoteRunspacePoolInternal.Name; }
|
|
set { this._runspacePool.RemoteRunspacePoolInternal.Name = value; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the Id value for the remote PSSession.
|
|
/// </summary>
|
|
internal int PSSessionId
|
|
{
|
|
get { return this.id; }
|
|
set { this.id = value; }
|
|
}
|
|
private int id = -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>
|
|
/// 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;
|
|
#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
|
|
{
|
|
_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.
|
|
//
|
|
}
|
|
|
|
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;
|
|
}
|
|
else if (_connectionInfo is VMConnectionInfo)
|
|
{
|
|
returnCaps |= RunspaceCapability.VMSocketTransport;
|
|
}
|
|
else
|
|
{
|
|
ContainerConnectionInfo containerConnectionInfo = _connectionInfo as ContainerConnectionInfo;
|
|
|
|
if ((containerConnectionInfo != null) &&
|
|
(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)
|
|
{
|
|
if (_bypassRunspaceStateCheck == false &&
|
|
_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()
|
|
{
|
|
if(sessionStateProxy == null)
|
|
sessionStateProxy = new RemoteSessionStateProxy(this);
|
|
|
|
return sessionStateProxy;
|
|
}
|
|
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 _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,
|
|
_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 (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)
|
|
{
|
|
this._connectionInfo.IdleTimeout = eventArgs.ConnectionInfo.IdleTimeout;
|
|
this._connectionInfo.MaxIdleTimeout = eventArgs.ConnectionInfo.MaxIdleTimeout;
|
|
WSManConnectionInfo wsmanConnectionInfo = this._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 = this._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 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");
|
|
|
|
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;
|
|
|
|
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
|
|
WindowsIdentity _identityToPersonate;
|
|
bool _identityPersonationChecked;
|
|
|
|
/// <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
|
|
{
|
|
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 ((_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;
|
|
|
|
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(
|
|
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);
|
|
}
|
|
|
|
_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);
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
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 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();
|
|
}
|
|
|
|
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)
|
|
{
|
|
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 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
|
|
{
|
|
RemoteRunspace _runspace;
|
|
internal RemoteSessionStateProxy(RemoteRunspace runspace)
|
|
{
|
|
Dbg.Assert(runspace != null, "Caller should validate the parameter");
|
|
_runspace = runspace;
|
|
}
|
|
|
|
private Exception isInNoLangugeModeException = 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("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("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 (isInNoLangugeModeException != null)
|
|
throw isInNoLangugeModeException;
|
|
|
|
// 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)
|
|
{
|
|
isInNoLangugeModeException = new PSNotSupportedException(RunspaceStrings.NotSupportedOnRestrictedRunspace, e);
|
|
throw isInNoLangugeModeException;
|
|
}
|
|
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 (isInNoLangugeModeException != null)
|
|
throw isInNoLangugeModeException;
|
|
|
|
// 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)
|
|
{
|
|
isInNoLangugeModeException = new PSNotSupportedException(RunspaceStrings.NotSupportedOnRestrictedRunspace, e);
|
|
throw isInNoLangugeModeException;
|
|
}
|
|
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 (isInNoLangugeModeException != 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)
|
|
{
|
|
isInNoLangugeModeException = 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
|
|
}
|