Files
PowerShell/src/System.Management.Automation/engine/remoting/server/ServerRunspacePoolDriver.cs
T
2019-04-30 10:25:11 +05:00

2666 lines
99 KiB
C#

// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Management.Automation.Host;
using System.Management.Automation.Internal;
using System.Management.Automation.Remoting;
using System.Management.Automation.Remoting.Server;
using System.Management.Automation.Runspaces;
using System.Management.Automation.Security;
#if !UNIX
using System.Security.Principal;
#endif
using System.Threading;
using Dbg = System.Management.Automation.Diagnostics;
namespace System.Management.Automation
{
/// <summary>
/// Interface exposing driver single thread invoke enter/exit
/// nested pipeline.
/// </summary>
internal interface IRSPDriverInvoke
{
void EnterNestedPipeline();
void ExitNestedPipeline();
bool HandleStopSignal();
}
/// <summary>
/// This class wraps a RunspacePoolInternal object. It is used to function
/// as a server side runspacepool.
/// </summary>
internal class ServerRunspacePoolDriver : IRSPDriverInvoke
{
#region Private Members
// local runspace pool at the server
// Script to run after a RunspacePool/Runspace is created in this session.
private ConfigurationDataFromXML _configData;
// application private data to send back to the client in when we get into "opened" state
private PSPrimitiveDictionary _applicationPrivateData;
// the client runspacepool's guid that is
// associated with this runspace pool driver
// data structure handler object to handle all communications
// with the client
// powershell's associated with this runspace pool
private Dictionary<Guid, ServerPowerShellDriver> _associatedShells
= new Dictionary<Guid, ServerPowerShellDriver>();
// remote host associated with this runspacepool
private ServerDriverRemoteHost _remoteHost;
private bool _isClosed;
// server capability reported to the client during negotiation (not the actual capability)
private RemoteSessionCapability _serverCapability;
private Runspace _rsToUseForSteppablePipeline;
// steppable pipeline event subscribers exist per-session
private ServerSteppablePipelineSubscriber _eventSubscriber = new ServerSteppablePipelineSubscriber();
private PSDataCollection<object> _inputCollection; // PowerShell driver input collection
// Object to invoke nested PowerShell drivers on single pipeline worker thread.
private PowerShellDriverInvoker _driverNestedInvoker;
// Remote wrapper for script debugger.
private ServerRemoteDebugger _serverRemoteDebugger;
// Version of PowerShell client.
private Version _clientPSVersion;
// Optional endpoint configuration name.
// Used in OutOfProc scenarios that do not support PSSession endpoint configuration.
// Results in a configured remote runspace pushed onto driver host.
private string _configurationName;
/// <summary>
/// Event that get raised when the RunspacePool is closed.
/// </summary>
internal EventHandler<EventArgs> Closed;
#endregion Private Members
#region Constructors
#if CORECLR // No ApartmentState In CoreCLR
/// <summary>
/// Creates the runspace pool driver.
/// </summary>
/// <param name="clientRunspacePoolId">Client runspace pool id to associate.</param>
/// <param name="transportManager">transport manager associated with this
/// runspace pool driver</param>
/// <param name="maxRunspaces">Maximum runspaces to open.</param>
/// <param name="minRunspaces">Minimum runspaces to open.</param>
/// <param name="threadOptions">Threading options for the runspaces in the pool.</param>
/// <param name="hostInfo">Host information about client side host.</param>
/// <param name="configData">
/// Contains:
/// 1. Script to run after a RunspacePool/Runspace is created in this session.
/// For RunspacePool case, every newly created Runspace (in the pool) will run
/// this script.
/// 2. ThreadOptions for RunspacePool/Runspace
/// 3. ThreadApartment for RunspacePool/Runspace
/// </param>
/// <param name="initialSessionState">Configuration of the runspace.</param>
/// <param name="applicationPrivateData">Application private data.</param>
/// <param name="isAdministrator">True if the driver is being created by an administrator.</param>
/// <param name="serverCapability">Server capability reported to the client during negotiation (not the actual capability).</param>
/// <param name="psClientVersion">Client PowerShell version.</param>
/// <param name="configurationName">Optional endpoint configuration name to create a pushed configured runspace.</param>
internal ServerRunspacePoolDriver(
Guid clientRunspacePoolId,
int minRunspaces,
int maxRunspaces,
PSThreadOptions threadOptions,
HostInfo hostInfo,
InitialSessionState initialSessionState,
PSPrimitiveDictionary applicationPrivateData,
ConfigurationDataFromXML configData,
AbstractServerSessionTransportManager transportManager,
bool isAdministrator,
RemoteSessionCapability serverCapability,
Version psClientVersion,
string configurationName)
#else
/// <summary>
/// Creates the runspace pool driver.
/// </summary>
/// <param name="clientRunspacePoolId">Client runspace pool id to associate.</param>
/// <param name="transportManager">transport manager associated with this
/// runspace pool driver</param>
/// <param name="maxRunspaces">Maximum runspaces to open.</param>
/// <param name="minRunspaces">Minimum runspaces to open.</param>
/// <param name="threadOptions">Threading options for the runspaces in the pool.</param>
/// <param name="apartmentState">Apartment state for the runspaces in the pool.</param>
/// <param name="hostInfo">Host information about client side host.</param>
/// <param name="configData">
/// Contains:
/// 1. Script to run after a RunspacePool/Runspace is created in this session.
/// For RunspacePool case, every newly created Runspace (in the pool) will run
/// this script.
/// 2. ThreadOptions for RunspacePool/Runspace
/// 3. ThreadApartment for RunspacePool/Runspace
/// </param>
/// <param name="initialSessionState">Configuration of the runspace.</param>
/// <param name="applicationPrivateData">Application private data.</param>
/// <param name="isAdministrator">True if the driver is being created by an administrator.</param>
/// <param name="serverCapability">Server capability reported to the client during negotiation (not the actual capability).</param>
/// <param name="psClientVersion">Client PowerShell version.</param>
/// <param name="configurationName">Optional endpoint configuration name to create a pushed configured runspace.</param>
internal ServerRunspacePoolDriver(
Guid clientRunspacePoolId,
int minRunspaces,
int maxRunspaces,
PSThreadOptions threadOptions,
ApartmentState apartmentState,
HostInfo hostInfo,
InitialSessionState initialSessionState,
PSPrimitiveDictionary applicationPrivateData,
ConfigurationDataFromXML configData,
AbstractServerSessionTransportManager transportManager,
bool isAdministrator,
RemoteSessionCapability serverCapability,
Version psClientVersion,
string configurationName)
#endif
{
Dbg.Assert(configData != null, "ConfigurationData cannot be null");
_serverCapability = serverCapability;
_clientPSVersion = psClientVersion;
_configurationName = configurationName;
// Create a new server host and associate for host call
// integration
_remoteHost = new ServerDriverRemoteHost(clientRunspacePoolId,
Guid.Empty, hostInfo, transportManager, null);
_configData = configData;
_applicationPrivateData = applicationPrivateData;
RunspacePool = RunspaceFactory.CreateRunspacePool(
minRunspaces, maxRunspaces, initialSessionState, _remoteHost);
// Set ThreadOptions for this RunspacePool
// The default server settings is to make new commands execute in the calling thread...this saves
// thread switching time and thread pool pressure on the service.
// Users can override the server settings only if they are administrators
PSThreadOptions serverThreadOptions = configData.ShellThreadOptions.HasValue ? configData.ShellThreadOptions.Value : PSThreadOptions.UseCurrentThread;
if (threadOptions == PSThreadOptions.Default || threadOptions == serverThreadOptions)
{
RunspacePool.ThreadOptions = serverThreadOptions;
}
else
{
if (!isAdministrator)
{
throw new InvalidOperationException(PSRemotingErrorInvariants.FormatResourceString(RemotingErrorIdStrings.MustBeAdminToOverrideThreadOptions));
}
RunspacePool.ThreadOptions = threadOptions;
}
#if !CORECLR // No ApartmentState In CoreCLR
// Set Thread ApartmentState for this RunspacePool
ApartmentState serverApartmentState = configData.ShellThreadApartmentState.HasValue ? configData.ShellThreadApartmentState.Value : Runspace.DefaultApartmentState;
if (apartmentState == ApartmentState.Unknown || apartmentState == serverApartmentState)
{
RunspacePool.ApartmentState = serverApartmentState;
}
else
{
RunspacePool.ApartmentState = apartmentState;
}
#endif
// If we have a runspace pool with a single runspace then we can run nested pipelines on
// on it in a single pipeline invoke thread.
if (maxRunspaces == 1 &&
(RunspacePool.ThreadOptions == PSThreadOptions.Default ||
RunspacePool.ThreadOptions == PSThreadOptions.UseCurrentThread))
{
_driverNestedInvoker = new PowerShellDriverInvoker();
}
InstanceId = clientRunspacePoolId;
DataStructureHandler = new ServerRunspacePoolDataStructureHandler(this, transportManager);
// handle the StateChanged event of the runspace pool
RunspacePool.StateChanged +=
new EventHandler<RunspacePoolStateChangedEventArgs>(HandleRunspacePoolStateChanged);
// listen for events on the runspace pool
RunspacePool.ForwardEvent +=
new EventHandler<PSEventArgs>(HandleRunspacePoolForwardEvent);
RunspacePool.RunspaceCreated += HandleRunspaceCreated;
// register for all the events from the data structure handler
DataStructureHandler.CreateAndInvokePowerShell +=
new EventHandler<RemoteDataEventArgs<RemoteDataObject<PSObject>>>(HandleCreateAndInvokePowerShell);
DataStructureHandler.GetCommandMetadata +=
new EventHandler<RemoteDataEventArgs<RemoteDataObject<PSObject>>>(HandleGetCommandMetadata);
DataStructureHandler.HostResponseReceived +=
new EventHandler<RemoteDataEventArgs<RemoteHostResponse>>(HandleHostResponseReceived);
DataStructureHandler.SetMaxRunspacesReceived +=
new EventHandler<RemoteDataEventArgs<PSObject>>(HandleSetMaxRunspacesReceived);
DataStructureHandler.SetMinRunspacesReceived +=
new EventHandler<RemoteDataEventArgs<PSObject>>(HandleSetMinRunspacesReceived);
DataStructureHandler.GetAvailableRunspacesReceived +=
new EventHandler<RemoteDataEventArgs<PSObject>>(HandleGetAvailableRunspacesReceived);
DataStructureHandler.ResetRunspaceState +=
new EventHandler<RemoteDataEventArgs<PSObject>>(HandleResetRunspaceState);
}
#endregion Constructors
#region Internal Methods
/// <summary>
/// Data structure handler for communicating with client.
/// </summary>
internal ServerRunspacePoolDataStructureHandler DataStructureHandler { get; }
/// <summary>
/// The server host associated with the runspace pool.
/// </summary>
internal ServerRemoteHost ServerRemoteHost
{
get { return _remoteHost; }
}
/// <summary>
/// The client runspacepool id.
/// </summary>
internal Guid InstanceId { get; }
/// <summary>
/// The local runspace pool associated with
/// this driver.
/// </summary>
internal RunspacePool RunspacePool { get; private set; }
/// <summary>
/// Start the RunspacePoolDriver. This will open the
/// underlying RunspacePool.
/// </summary>
internal void Start()
{
// open the runspace pool
RunspacePool.Open();
}
/// <summary>
/// Send application private data to client
/// will be called during runspace creation
/// and each time a new client connects to the server session.
/// </summary>
internal void SendApplicationPrivateDataToClient()
{
// Include Debug mode information.
if (_applicationPrivateData == null)
{
_applicationPrivateData = new PSPrimitiveDictionary();
}
if (_serverRemoteDebugger != null)
{
// Current debug mode.
DebugModes debugMode = _serverRemoteDebugger.DebugMode;
if (_applicationPrivateData.ContainsKey(RemoteDebugger.DebugModeSetting))
{
_applicationPrivateData[RemoteDebugger.DebugModeSetting] = (int)debugMode;
}
else
{
_applicationPrivateData.Add(RemoteDebugger.DebugModeSetting, (int)debugMode);
}
// Current debug state.
bool inBreakpoint = _serverRemoteDebugger.InBreakpoint;
if (_applicationPrivateData.ContainsKey(RemoteDebugger.DebugStopState))
{
_applicationPrivateData[RemoteDebugger.DebugStopState] = inBreakpoint;
}
else
{
_applicationPrivateData.Add(RemoteDebugger.DebugStopState, inBreakpoint);
}
// Current debug breakpoint count.
int breakpointCount = _serverRemoteDebugger.GetBreakpointCount();
if (_applicationPrivateData.ContainsKey(RemoteDebugger.DebugBreakpointCount))
{
_applicationPrivateData[RemoteDebugger.DebugBreakpointCount] = breakpointCount;
}
else
{
_applicationPrivateData.Add(RemoteDebugger.DebugBreakpointCount, breakpointCount);
}
// Current debugger BreakAll option setting.
bool breakAll = _serverRemoteDebugger.IsDebuggerSteppingEnabled;
if (_applicationPrivateData.ContainsKey(RemoteDebugger.BreakAllSetting))
{
_applicationPrivateData[RemoteDebugger.BreakAllSetting] = breakAll;
}
else
{
_applicationPrivateData.Add(RemoteDebugger.BreakAllSetting, breakAll);
}
// Current debugger PreserveUnhandledBreakpoints setting.
UnhandledBreakpointProcessingMode bpMode = _serverRemoteDebugger.UnhandledBreakpointMode;
if (_applicationPrivateData.ContainsKey(RemoteDebugger.UnhandledBreakpointModeSetting))
{
_applicationPrivateData[RemoteDebugger.UnhandledBreakpointModeSetting] = (int)bpMode;
}
else
{
_applicationPrivateData.Add(RemoteDebugger.UnhandledBreakpointModeSetting, (int)bpMode);
}
}
DataStructureHandler.SendApplicationPrivateDataToClient(_applicationPrivateData, _serverCapability);
}
/// <summary>
/// Dispose the runspace pool driver and release all its resources.
/// </summary>
internal void Close()
{
if (!_isClosed)
{
_isClosed = true;
if ((_remoteHost != null) && (_remoteHost.IsRunspacePushed))
{
Runspace runspaceToDispose = _remoteHost.PushedRunspace;
_remoteHost.PopRunspace();
if (runspaceToDispose != null)
{
runspaceToDispose.Dispose();
}
}
DisposeRemoteDebugger();
RunspacePool.Close();
RunspacePool.StateChanged -=
new EventHandler<RunspacePoolStateChangedEventArgs>(HandleRunspacePoolStateChanged);
RunspacePool.ForwardEvent -=
new EventHandler<PSEventArgs>(HandleRunspacePoolForwardEvent);
RunspacePool.Dispose();
RunspacePool = null;
if (_rsToUseForSteppablePipeline != null)
{
_rsToUseForSteppablePipeline.Close();
_rsToUseForSteppablePipeline.Dispose();
_rsToUseForSteppablePipeline = null;
}
Closed.SafeInvoke(this, EventArgs.Empty);
}
}
#endregion Internal Methods
#region IRSPDriverInvoke interface methods
/// <summary>
/// This method blocks the current thread execution and starts a
/// new Invoker pump that will handle invoking client side nested commands.
/// This method returns after ExitNestedPipeline is called.
/// </summary>
public void EnterNestedPipeline()
{
if (_driverNestedInvoker == null)
{
throw new PSNotSupportedException(RemotingErrorIdStrings.NestedPipelineNotSupported);
}
_driverNestedInvoker.PushInvoker();
}
/// <summary>
/// Removes current nested command Invoker pump and allows parent command
/// to continue running.
/// </summary>
public void ExitNestedPipeline()
{
if (_driverNestedInvoker == null)
{
throw new PSNotSupportedException(RemotingErrorIdStrings.NestedPipelineNotSupported);
}
_driverNestedInvoker.PopInvoker();
}
/// <summary>
/// If script execution is currently in debugger stopped mode, this will
/// release the debugger and terminate script execution, or if processing
/// a debug command will stop the debug command.
/// This is used to implement the remote stop signal and ensures a command
/// will stop even when in debug stop mode.
/// </summary>
public bool HandleStopSignal()
{
if (_serverRemoteDebugger != null)
{
return _serverRemoteDebugger.HandleStopSignal();
}
return false;
}
#endregion
#region Private Methods
/// <summary>
/// RunspaceCreated eventhandler. This is used to set TypeTable for TransportManager.
/// TransportManager needs TypeTable for Serializing/Deserializing objects.
/// </summary>
/// <param name="sender"></param>
/// <param name="args"></param>
private void HandleRunspaceCreatedForTypeTable(object sender, RunspaceCreatedEventArgs args)
{
DataStructureHandler.TypeTable = args.Runspace.ExecutionContext.TypeTable;
_rsToUseForSteppablePipeline = args.Runspace;
SetupRemoteDebugger(_rsToUseForSteppablePipeline);
if (!string.IsNullOrEmpty(_configurationName))
{
// Client is requesting a configured session.
// Create a configured remote runspace and push onto host stack.
if ((_remoteHost != null) && !(_remoteHost.IsRunspacePushed))
{
// Let exceptions propagate.
RemoteRunspace remoteRunspace = HostUtilities.CreateConfiguredRunspace(_configurationName, _remoteHost);
_remoteHost.AllowPushRunspace = true;
_remoteHost.PropagatePop = true;
_remoteHost.PushRunspace(remoteRunspace);
}
}
}
private void SetupRemoteDebugger(Runspace runspace)
{
CmdletInfo cmdletInfo = runspace.ExecutionContext.SessionState.InvokeCommand.GetCmdlet(ServerRemoteDebugger.SetPSBreakCommandText);
if (cmdletInfo == null)
{
if ((runspace.ExecutionContext.LanguageMode != PSLanguageMode.FullLanguage) &&
(!runspace.ExecutionContext.UseFullLanguageModeInDebugger))
{
return;
}
}
else
{
if (cmdletInfo.Visibility != SessionStateEntryVisibility.Public)
{
return;
}
}
// Remote debugger is created only when client version is PSVersion (4.0)
// or greater, and remote session supports debugging.
if ((_driverNestedInvoker != null) &&
(_clientPSVersion != null && _clientPSVersion >= PSVersionInfo.PSV4Version) &&
(runspace != null && runspace.Debugger != null))
{
_serverRemoteDebugger = new ServerRemoteDebugger(this, runspace, runspace.Debugger);
_remoteHost.ServerDebugger = _serverRemoteDebugger;
}
}
private void DisposeRemoteDebugger()
{
if (_serverRemoteDebugger != null)
{
_serverRemoteDebugger.Dispose();
}
}
/// <summary>
/// Invokes a script.
/// </summary>
/// <param name="cmdToRun"></param>
/// <param name="args"></param>
/// <returns></returns>
private PSDataCollection<PSObject> InvokeScript(Command cmdToRun, RunspaceCreatedEventArgs args)
{
Debug.Assert(cmdToRun != null, "cmdToRun shouldn't be null");
// Don't invoke initialization script as trusted (CommandOrigin == Internal) if the system is in lock down mode.
cmdToRun.CommandOrigin = (SystemPolicy.GetSystemLockdownPolicy() == SystemEnforcementMode.Enforce) ? CommandOrigin.Runspace : CommandOrigin.Internal;
cmdToRun.MergeMyResults(PipelineResultTypes.Error, PipelineResultTypes.Output);
PowerShell powershell = PowerShell.Create();
powershell.AddCommand(cmdToRun).AddCommand("out-default");
return InvokePowerShell(powershell, args);
}
/// <summary>
/// Invokes a PowerShell instance.
/// </summary>
/// <param name="powershell"></param>
/// <param name="args"></param>
/// <returns></returns>
private PSDataCollection<PSObject> InvokePowerShell(PowerShell powershell, RunspaceCreatedEventArgs args)
{
Debug.Assert(powershell != null, "powershell shouldn't be null");
// run the startup script on the runspace's host
HostInfo hostInfo = _remoteHost.HostInfo;
ServerPowerShellDriver driver = new ServerPowerShellDriver(
powershell,
null,
true,
Guid.Empty,
this.InstanceId,
this,
#if !CORECLR // No ApartmentState In CoreCLR
args.Runspace.ApartmentState,
#endif
hostInfo,
RemoteStreamOptions.AddInvocationInfo,
false,
args.Runspace);
IAsyncResult asyncResult = driver.Start();
// if there was an exception running the script..this may throw..this will
// result in the runspace getting closed/broken.
PSDataCollection<PSObject> results = powershell.EndInvoke(asyncResult);
// find out if there are any error records reported. If there is one, report the error..
// this will result in the runspace getting closed/broken.
ArrayList errorList = (ArrayList)powershell.Runspace.GetExecutionContext.DollarErrorVariable;
if (errorList.Count > 0)
{
string exceptionThrown;
ErrorRecord lastErrorRecord = errorList[0] as ErrorRecord;
if (lastErrorRecord != null)
{
exceptionThrown = lastErrorRecord.ToString();
}
else
{
Exception lastException = errorList[0] as Exception;
if (lastException != null)
{
exceptionThrown = (lastException.Message != null) ? lastException.Message : string.Empty;
}
else
{
exceptionThrown = string.Empty;
}
}
throw PSTraceSource.NewInvalidOperationException(RemotingErrorIdStrings.StartupScriptThrewTerminatingError, exceptionThrown);
}
return results;
}
/// <summary>
/// Raised by RunspacePool whenever a new runspace is created. This is used
/// by the driver to run startup script as well as set personal folder
/// as the current working directory.
/// </summary>
/// <param name="sender"></param>
/// <param name="args">
/// Runspace that was created by the RunspacePool.
/// </param>
private void HandleRunspaceCreated(object sender, RunspaceCreatedEventArgs args)
{
this.ServerRemoteHost.Runspace = args.Runspace;
// If the system lockdown policy says "Enforce", do so (unless it's in the
// more restrictive NoLanguage mode)
Utils.EnforceSystemLockDownLanguageMode(args.Runspace.ExecutionContext);
// Set the current location to MyDocuments folder for this runspace.
// This used to be set to the Personal folder but was changed to MyDocuments folder for
// compatibility with PowerShell on Nano Server for PowerShell V5.
// This is needed because in the remoting scenario, Environment.CurrentDirectory
// always points to System Folder (%windir%\system32) irrespective of the
// user as %HOMEDRIVE% and %HOMEPATH% are not available for the logon process.
// Doing this here than AutomationEngine as I dont want to introduce a dependency
// on Remoting in PowerShell engine
try
{
string personalfolder = Platform.GetFolderPath(Environment.SpecialFolder.MyDocuments);
args.Runspace.ExecutionContext.EngineSessionState.SetLocation(personalfolder);
}
catch (Exception)
{
// SetLocation API can call 3rd party code and so there is no telling what exception may be thrown.
// Setting location is not critical and is expected not to work with some account types, so we want
// to ignore all but critical errors.
}
// Run startup scripts
InvokeStartupScripts(args);
// Now that the server side runspace is set up allow the secondary handler to run.
HandleRunspaceCreatedForTypeTable(sender, args);
}
private void InvokeStartupScripts(RunspaceCreatedEventArgs args)
{
Command cmdToRun = null;
if (!string.IsNullOrEmpty(_configData.StartupScript))
{
// build the startup script..merge output / error.
cmdToRun = new Command(_configData.StartupScript, false, false);
}
else if (!string.IsNullOrEmpty(_configData.InitializationScriptForOutOfProcessRunspace))
{
cmdToRun = new Command(_configData.InitializationScriptForOutOfProcessRunspace, true, false);
}
if (cmdToRun != null)
{
InvokeScript(cmdToRun, args);
// if startup script set $PSApplicationPrivateData, then use that value as ApplicationPrivateData
// instead of using results from PSSessionConfiguration.GetApplicationPrivateData()
if (RunspacePool.RunspacePoolStateInfo.State == RunspacePoolState.Opening)
{
object privateDataVariable = args.Runspace.SessionStateProxy.PSVariable.GetValue("global:PSApplicationPrivateData");
if (privateDataVariable != null)
{
_applicationPrivateData = (PSPrimitiveDictionary)LanguagePrimitives.ConvertTo(
privateDataVariable,
typeof(PSPrimitiveDictionary),
true,
CultureInfo.InvariantCulture,
null);
}
}
}
}
/// <summary>
/// Handler to the runspace pool state changed events.
/// </summary>
/// <param name="sender">Sender of this events.</param>
/// <param name="eventArgs">arguments which describe the
/// RunspacePool's StateChanged event</param>
private void HandleRunspacePoolStateChanged(object sender,
RunspacePoolStateChangedEventArgs eventArgs)
{
RunspacePoolState state = eventArgs.RunspacePoolStateInfo.State;
Exception reason = eventArgs.RunspacePoolStateInfo.Reason;
switch (state)
{
case RunspacePoolState.Broken:
case RunspacePoolState.Closing:
case RunspacePoolState.Closed:
{
DataStructureHandler.SendStateInfoToClient(new RunspacePoolStateInfo(state, reason));
}
break;
case RunspacePoolState.Opened:
{
SendApplicationPrivateDataToClient();
DataStructureHandler.SendStateInfoToClient(new RunspacePoolStateInfo(state, reason));
}
break;
}
}
/// <summary>
/// Handler to the runspace pool psevents.
/// </summary>
private void HandleRunspacePoolForwardEvent(object sender, PSEventArgs e)
{
if (e.ForwardEvent)
{
DataStructureHandler.SendPSEventArgsToClient(e);
}
}
/// <summary>
/// Handle the invocation of powershell.
/// </summary>
/// <param name="sender">Sender of this event, unused.</param>
/// <param name="eventArgs">Arguments describing this event.</param>
private void HandleCreateAndInvokePowerShell(object sender, RemoteDataEventArgs<RemoteDataObject<PSObject>> eventArgs)
{
RemoteDataObject<PSObject> data = eventArgs.Data;
// it is sufficient to just construct the powershell
// driver, the local powershell on server side is
// invoked from within the driver
HostInfo hostInfo = RemotingDecoder.GetHostInfo(data.Data);
#if !CORECLR // No ApartmentState In CoreCLR
ApartmentState apartmentState = RemotingDecoder.GetApartmentState(data.Data);
#endif
RemoteStreamOptions streamOptions = RemotingDecoder.GetRemoteStreamOptions(data.Data);
PowerShell powershell = RemotingDecoder.GetPowerShell(data.Data);
bool noInput = RemotingDecoder.GetNoInput(data.Data);
bool addToHistory = RemotingDecoder.GetAddToHistory(data.Data);
bool isNested = false;
// The server would've dropped the protocol version of an older client was connecting
if (_serverCapability.ProtocolVersion >= RemotingConstants.ProtocolVersionWin8RTM)
{
isNested = RemotingDecoder.GetIsNested(data.Data);
}
// Perform pre-processing of command for over the wire debugging commands.
if (_serverRemoteDebugger != null)
{
DebuggerCommandArgument commandArgument;
bool terminateImmediate = false;
var result = PreProcessDebuggerCommand(powershell.Commands, _serverRemoteDebugger.IsActive, _serverRemoteDebugger.IsRemote, out commandArgument);
switch (result)
{
case PreProcessCommandResult.SetDebuggerAction:
// Run this directly on the debugger and terminate the remote command.
_serverRemoteDebugger.SetDebuggerAction(commandArgument.ResumeAction.Value);
terminateImmediate = true;
break;
case PreProcessCommandResult.SetDebugMode:
// Set debug mode directly and terminate remote command.
_serverRemoteDebugger.SetDebugMode(commandArgument.Mode.Value);
terminateImmediate = true;
break;
case PreProcessCommandResult.SetDebuggerStepMode:
// Enable debugger and set to step action, then terminate remote command.
_serverRemoteDebugger.SetDebuggerStepMode(commandArgument.DebuggerStepEnabled.Value);
terminateImmediate = true;
break;
case PreProcessCommandResult.SetPreserveUnhandledBreakpointMode:
_serverRemoteDebugger.UnhandledBreakpointMode = commandArgument.UnhandledBreakpointMode.Value;
terminateImmediate = true;
break;
case PreProcessCommandResult.ValidNotProcessed:
terminateImmediate = true;
break;
}
// If we don't want to run or queue a command to run in the server session then
// terminate the command here by making it a No Op.
if (terminateImmediate)
{
ServerPowerShellDriver noOpDriver = new ServerPowerShellDriver(
powershell,
null,
noInput,
data.PowerShellId,
data.RunspacePoolId,
this,
#if !CORECLR // No ApartmentState In CoreCLR
apartmentState,
#endif
hostInfo,
streamOptions,
addToHistory,
null);
noOpDriver.RunNoOpCommand();
return;
}
}
if (_remoteHost.IsRunspacePushed)
{
// If we have a pushed runspace then execute there.
// Ensure debugger is enabled to the original mode it was set to.
if (_serverRemoteDebugger != null)
{
_serverRemoteDebugger.CheckDebuggerState();
}
StartPowerShellCommandOnPushedRunspace(
powershell,
null,
data.PowerShellId,
data.RunspacePoolId,
hostInfo,
streamOptions,
noInput,
addToHistory);
return;
}
else if (isNested)
{
if (RunspacePool.GetMaxRunspaces() == 1)
{
if (_driverNestedInvoker != null && _driverNestedInvoker.IsActive)
{
if (_driverNestedInvoker.IsAvailable == false)
{
// A nested command is already running.
throw new PSInvalidOperationException(
StringUtil.Format(RemotingErrorIdStrings.CannotInvokeNestedCommandNestedCommandRunning));
}
// Handle as nested pipeline invocation.
powershell.SetIsNested(true);
// Always invoke PowerShell commands on pipeline worker thread
// for single runspace case, to support nested invocation requests (debugging scenario).
ServerPowerShellDriver srdriver = new ServerPowerShellDriver(
powershell,
null,
noInput,
data.PowerShellId,
data.RunspacePoolId,
this,
#if !CORECLR // No ApartmentState In CoreCLR
apartmentState,
#endif
hostInfo,
streamOptions,
addToHistory,
_rsToUseForSteppablePipeline);
_inputCollection = srdriver.InputCollection;
_driverNestedInvoker.InvokeDriverAsync(srdriver);
return;
}
else if (_serverRemoteDebugger != null &&
_serverRemoteDebugger.InBreakpoint &&
_serverRemoteDebugger.IsPushed)
{
_serverRemoteDebugger.StartPowerShellCommand(
powershell,
data.PowerShellId,
data.RunspacePoolId,
this,
#if !CORECLR // No ApartmentState In CoreCLR
apartmentState,
#endif
_remoteHost,
hostInfo,
streamOptions,
addToHistory);
return;
}
else if (powershell.Commands.Commands.Count == 1 &&
!powershell.Commands.Commands[0].IsScript &&
((powershell.Commands.Commands[0].CommandText.IndexOf("Get-PSDebuggerStopArgs", StringComparison.OrdinalIgnoreCase) != -1) ||
(powershell.Commands.Commands[0].CommandText.IndexOf("Set-PSDebuggerAction", StringComparison.OrdinalIgnoreCase) != -1)))
{
// We do not want to invoke debugger commands in the steppable pipeline.
// Consider adding IsSteppable message to PSRP to handle this.
// This will be caught on the client.
throw new PSInvalidOperationException();
}
ServerPowerShellDataStructureHandler psHandler = DataStructureHandler.GetPowerShellDataStructureHandler();
if (psHandler != null)
{
// Have steppable invocation request.
powershell.SetIsNested(false);
// Execute command concurrently
ServerSteppablePipelineDriver spDriver = new ServerSteppablePipelineDriver(
powershell,
noInput,
data.PowerShellId,
data.RunspacePoolId,
this,
#if !CORECLR // No ApartmentState In CoreCLR
apartmentState,
#endif
hostInfo,
streamOptions,
addToHistory,
_rsToUseForSteppablePipeline,
_eventSubscriber,
_inputCollection);
spDriver.Start();
return;
}
}
// Allow command to run as non-nested and non-stepping.
powershell.SetIsNested(false);
}
// Invoke command normally. Ensure debugger is enabled to the
// original mode it was set to.
if (_serverRemoteDebugger != null)
{
_serverRemoteDebugger.CheckDebuggerState();
}
// Invoke PowerShell on driver runspace pool.
ServerPowerShellDriver driver = new ServerPowerShellDriver(
powershell,
null,
noInput,
data.PowerShellId,
data.RunspacePoolId,
this,
#if !CORECLR // No ApartmentState In CoreCLR
apartmentState,
#endif
hostInfo,
streamOptions,
addToHistory,
null);
_inputCollection = driver.InputCollection;
driver.Start();
}
private bool? _initialSessionStateIncludesGetCommandWithListImportedSwitch;
private object _initialSessionStateIncludesGetCommandWithListImportedSwitchLock = new object();
private bool DoesInitialSessionStateIncludeGetCommandWithListImportedSwitch()
{
if (!_initialSessionStateIncludesGetCommandWithListImportedSwitch.HasValue)
{
lock (_initialSessionStateIncludesGetCommandWithListImportedSwitchLock)
{
if (!_initialSessionStateIncludesGetCommandWithListImportedSwitch.HasValue)
{
bool newValue = false;
InitialSessionState iss = this.RunspacePool.InitialSessionState;
if (iss != null)
{
IEnumerable<SessionStateCommandEntry> publicGetCommandEntries = iss
.Commands["Get-Command"]
.Where(entry => entry.Visibility == SessionStateEntryVisibility.Public);
SessionStateFunctionEntry getCommandProxy = publicGetCommandEntries.OfType<SessionStateFunctionEntry>().FirstOrDefault();
if (getCommandProxy != null)
{
if (getCommandProxy.ScriptBlock.ParameterMetadata.BindableParameters.ContainsKey("ListImported"))
{
newValue = true;
}
}
else
{
SessionStateCmdletEntry getCommandCmdlet = publicGetCommandEntries.OfType<SessionStateCmdletEntry>().FirstOrDefault();
if ((getCommandCmdlet != null) && (getCommandCmdlet.ImplementingType.Equals(typeof(Microsoft.PowerShell.Commands.GetCommandCommand))))
{
newValue = true;
}
}
}
_initialSessionStateIncludesGetCommandWithListImportedSwitch = newValue;
}
}
}
return _initialSessionStateIncludesGetCommandWithListImportedSwitch.Value;
}
/// <summary>
/// Handle the invocation of command discovery pipeline.
/// </summary>
/// <param name="sender">Sender of this event, unused.</param>
/// <param name="eventArgs">Arguments describing this event.</param>
private void HandleGetCommandMetadata(object sender, RemoteDataEventArgs<RemoteDataObject<PSObject>> eventArgs)
{
RemoteDataObject<PSObject> data = eventArgs.Data;
PowerShell countingPipeline = RemotingDecoder.GetCommandDiscoveryPipeline(data.Data);
if (this.DoesInitialSessionStateIncludeGetCommandWithListImportedSwitch())
{
countingPipeline.AddParameter("ListImported", true);
}
countingPipeline
.AddParameter("ErrorAction", "SilentlyContinue")
.AddCommand("Measure-Object")
.AddCommand("Select-Object")
.AddParameter("Property", "Count");
PowerShell mainPipeline = RemotingDecoder.GetCommandDiscoveryPipeline(data.Data);
if (this.DoesInitialSessionStateIncludeGetCommandWithListImportedSwitch())
{
mainPipeline.AddParameter("ListImported", true);
}
mainPipeline
.AddCommand("Select-Object")
.AddParameter("Property", new string[] {
"Name", "Namespace", "HelpUri", "CommandType", "ResolvedCommandName", "OutputType", "Parameters" });
HostInfo useRunspaceHost = new HostInfo(null);
useRunspaceHost.UseRunspaceHost = true;
if (_remoteHost.IsRunspacePushed)
{
// If we have a pushed runspace then execute there.
StartPowerShellCommandOnPushedRunspace(
countingPipeline,
mainPipeline,
data.PowerShellId,
data.RunspacePoolId,
useRunspaceHost,
0,
true,
false);
}
else
{
// Run on usual driver.
ServerPowerShellDriver driver = new ServerPowerShellDriver(
countingPipeline,
mainPipeline,
true /* no input */,
data.PowerShellId,
data.RunspacePoolId,
this,
#if !CORECLR // No ApartmentState In CoreCLR
ApartmentState.Unknown,
#endif
useRunspaceHost,
0 /* stream options */,
false /* addToHistory */,
null /* use default rsPool runspace */);
driver.Start();
}
}
/// <summary>
/// Handles host responses.
/// </summary>
/// <param name="sender">Sender of this event, unused.</param>
/// <param name="eventArgs">Arguments describing this event.</param>
private void HandleHostResponseReceived(object sender,
RemoteDataEventArgs<RemoteHostResponse> eventArgs)
{
_remoteHost.ServerMethodExecutor.HandleRemoteHostResponseFromClient((eventArgs.Data));
}
/// <summary>
/// Sets the maximum runspace of the runspace pool and sends a response back.
/// </summary>
/// <param name="sender">Sender of this event, unused.</param>
/// <param name="eventArgs">contains information about the new maxRunspaces
/// and the callId at the client</param>
private void HandleSetMaxRunspacesReceived(object sender, RemoteDataEventArgs<PSObject> eventArgs)
{
PSObject data = eventArgs.Data;
int maxRunspaces = (int)((PSNoteProperty)data.Properties[RemoteDataNameStrings.MaxRunspaces]).Value;
long callId = (long)((PSNoteProperty)data.Properties[RemoteDataNameStrings.CallId]).Value;
bool response = RunspacePool.SetMaxRunspaces(maxRunspaces);
DataStructureHandler.SendResponseToClient(callId, response);
}
/// <summary>
/// Sets the minimum runspace of the runspace pool and sends a response back.
/// </summary>
/// <param name="sender">Sender of this event, unused.</param>
/// <param name="eventArgs">contains information about the new minRunspaces
/// and the callId at the client</param>
private void HandleSetMinRunspacesReceived(object sender, RemoteDataEventArgs<PSObject> eventArgs)
{
PSObject data = eventArgs.Data;
int minRunspaces = (int)((PSNoteProperty)data.Properties[RemoteDataNameStrings.MinRunspaces]).Value;
long callId = (long)((PSNoteProperty)data.Properties[RemoteDataNameStrings.CallId]).Value;
bool response = RunspacePool.SetMinRunspaces(minRunspaces);
DataStructureHandler.SendResponseToClient(callId, response);
}
/// <summary>
/// Gets the available runspaces from the server and sends it across
/// to the client.
/// </summary>
/// <param name="sender">Sender of this event, unused.</param>
/// <param name="eventArgs">Contains information on the callid.</param>
private void HandleGetAvailableRunspacesReceived(object sender, RemoteDataEventArgs<PSObject> eventArgs)
{
PSObject data = eventArgs.Data;
long callId = (long)((PSNoteProperty)data.Properties[RemoteDataNameStrings.CallId]).Value;
int availableRunspaces = RunspacePool.GetAvailableRunspaces();
DataStructureHandler.SendResponseToClient(callId, availableRunspaces);
}
/// <summary>
/// Forces a state reset on a single runspace runspace pool.
/// </summary>
/// <param name="sender"></param>
/// <param name="eventArgs"></param>
private void HandleResetRunspaceState(object sender, RemoteDataEventArgs<PSObject> eventArgs)
{
long callId = (long)((PSNoteProperty)(eventArgs.Data).Properties[RemoteDataNameStrings.CallId]).Value;
bool response = ResetRunspaceState();
DataStructureHandler.SendResponseToClient(callId, response);
}
/// <summary>
/// Resets the single runspace in the runspace pool.
/// </summary>
/// <returns></returns>
private bool ResetRunspaceState()
{
LocalRunspace runspaceToReset = _rsToUseForSteppablePipeline as LocalRunspace;
if ((runspaceToReset == null) || (RunspacePool.GetMaxRunspaces() > 1))
{
return false;
}
try
{
// Local runspace state reset.
runspaceToReset.ResetRunspaceState();
}
catch (Exception)
{
return false;
}
return true;
}
/// <summary>
/// Starts the PowerShell command on the currently pushed Runspace.
/// </summary>
/// <param name="powershell">PowerShell command or script.</param>
/// <param name="extraPowerShell">PowerShell command to run after first completes.</param>
/// <param name="powershellId">PowerShell Id.</param>
/// <param name="runspacePoolId">RunspacePool Id.</param>
/// <param name="hostInfo">Host Info.</param>
/// <param name="streamOptions">Remote stream options.</param>
/// <param name="noInput">False when input is provided.</param>
/// <param name="addToHistory">Add to history.</param>
private void StartPowerShellCommandOnPushedRunspace(
PowerShell powershell,
PowerShell extraPowerShell,
Guid powershellId,
Guid runspacePoolId,
HostInfo hostInfo,
RemoteStreamOptions streamOptions,
bool noInput,
bool addToHistory)
{
Runspace runspace = _remoteHost.PushedRunspace;
ServerPowerShellDriver driver = new ServerPowerShellDriver(
powershell,
extraPowerShell,
noInput,
powershellId,
runspacePoolId,
this,
#if !CORECLR // No ApartmentState In CoreCLR
ApartmentState.MTA,
#endif
hostInfo,
streamOptions,
addToHistory,
runspace);
try
{
driver.Start();
}
catch (Exception)
{
// Pop runspace on error.
_remoteHost.PopRunspace();
throw;
}
}
#endregion Private Methods
#region Remote Debugger Command Helpers
/// <summary>
/// Debugger command pre processing result type.
/// </summary>
private enum PreProcessCommandResult
{
/// <summary>
/// No debugger pre-processing.
/// </summary>
None = 0,
/// <summary>
/// This is a valid debugger command but was not processed because
/// the debugger state was not correct.
/// </summary>
ValidNotProcessed,
/// <summary>
/// GetDebuggerStopArgs.
/// </summary>
GetDebuggerStopArgs,
/// <summary>
/// SetDebuggerAction.
/// </summary>
SetDebuggerAction,
/// <summary>
/// SetDebugMode.
/// </summary>
SetDebugMode,
/// <summary>
/// SetDebuggerStepMode.
/// </summary>
SetDebuggerStepMode,
/// <summary>
/// SetPreserveUnhandledBreakpointMode.
/// </summary>
SetPreserveUnhandledBreakpointMode
};
private class DebuggerCommandArgument
{
public DebugModes? Mode { get; set; }
public DebuggerResumeAction? ResumeAction { get; set; }
public bool? DebuggerStepEnabled { get; set; }
public UnhandledBreakpointProcessingMode? UnhandledBreakpointMode { get; set; }
}
/// <summary>
/// Pre-processor for debugger commands.
/// Parses special debugger commands and converts to equivalent script for remote execution as needed.
/// </summary>
/// <param name="commands">PSCommand.</param>
/// <param name="isDebuggerActive">True if debugger is active.</param>
/// <param name="isDebuggerRemote">True if active debugger is pushed and is a remote debugger.</param>
/// <param name="commandArgument">Command argument.</param>
/// <returns>PreProcessCommandResult type if preprocessing occurred.</returns>
private static PreProcessCommandResult PreProcessDebuggerCommand(
PSCommand commands,
bool isDebuggerActive,
bool isDebuggerRemote,
out DebuggerCommandArgument commandArgument)
{
commandArgument = new DebuggerCommandArgument();
PreProcessCommandResult result = PreProcessCommandResult.None;
if ((commands.Commands.Count == 0) || (commands.Commands[0].IsScript))
{
return result;
}
var command = commands.Commands[0];
string commandText = command.CommandText;
if (commandText.Equals(DebuggerUtils.GetDebuggerStopArgsFunctionName, StringComparison.OrdinalIgnoreCase))
{
//
// __Get-PSDebuggerStopArgs private virtual command.
// No input parameters.
// Returns DebuggerStopEventArgs object.
//
// Evaluate this command only if the debugger is activated.
if (!isDebuggerActive) { return PreProcessCommandResult.ValidNotProcessed; }
// Translate into debugger method call.
ScriptBlock scriptBlock = ScriptBlock.Create("$host.Runspace.Debugger.GetDebuggerStopArgs()");
scriptBlock.LanguageMode = PSLanguageMode.FullLanguage;
commands.Clear();
commands.AddCommand("Invoke-Command").AddParameter("ScriptBlock", scriptBlock).AddParameter("NoNewScope", true);
result = PreProcessCommandResult.GetDebuggerStopArgs;
}
else if (commandText.Equals(DebuggerUtils.SetDebuggerActionFunctionName, StringComparison.OrdinalIgnoreCase))
{
//
// __Set-PSDebuggerAction private virtual command.
// DebuggerResumeAction enum input parameter.
// Returns void.
//
// Evaluate this command only if the debugger is activated.
if (!isDebuggerActive) { return PreProcessCommandResult.ValidNotProcessed; }
if ((command.Parameters == null) || (command.Parameters.Count == 0) ||
(!command.Parameters[0].Name.Equals("ResumeAction", StringComparison.OrdinalIgnoreCase)))
{
throw new PSArgumentException("ResumeAction");
}
DebuggerResumeAction? resumeAction = null;
PSObject resumeObject = command.Parameters[0].Value as PSObject;
if (resumeObject != null)
{
try
{
resumeAction = (DebuggerResumeAction)resumeObject.BaseObject;
}
catch (InvalidCastException) { }
}
if (resumeAction == null)
{
throw new PSArgumentException("ResumeAction");
}
commandArgument.ResumeAction = resumeAction;
result = PreProcessCommandResult.SetDebuggerAction;
}
else if (commandText.Equals(DebuggerUtils.SetDebugModeFunctionName, StringComparison.OrdinalIgnoreCase))
{
//
// __Set-PSDebugMode private virtual command.
// DebugModes enum input parameter.
// Returns void.
//
if ((command.Parameters == null) || (command.Parameters.Count == 0) ||
(!command.Parameters[0].Name.Equals("Mode", StringComparison.OrdinalIgnoreCase)))
{
throw new PSArgumentException("Mode");
}
DebugModes? mode = null;
PSObject modeObject = command.Parameters[0].Value as PSObject;
if (modeObject != null)
{
try
{
mode = (DebugModes)modeObject.BaseObject;
}
catch (InvalidCastException) { }
}
if (mode == null)
{
throw new PSArgumentException("Mode");
}
commandArgument.Mode = mode;
result = PreProcessCommandResult.SetDebugMode;
}
else if (commandText.Equals(DebuggerUtils.SetDebuggerStepMode, StringComparison.OrdinalIgnoreCase))
{
//
// __Set-PSDebuggerStepMode private virtual command.
// Boolean Enabled input parameter.
// Returns void.
//
if ((command.Parameters == null) || (command.Parameters.Count == 0) ||
(!command.Parameters[0].Name.Equals("Enabled", StringComparison.OrdinalIgnoreCase)))
{
throw new PSArgumentException("Enabled");
}
bool enabled = (bool)command.Parameters[0].Value;
commandArgument.DebuggerStepEnabled = enabled;
result = PreProcessCommandResult.SetDebuggerStepMode;
}
else if (commandText.Equals(DebuggerUtils.SetPSUnhandledBreakpointMode, StringComparison.OrdinalIgnoreCase))
{
//
// __Set-PSUnhandledBreakpointMode private virtual command.
// UnhandledBreakpointMode input parameter.
// Returns void.
//
if ((command.Parameters == null) || (command.Parameters.Count == 0) ||
(!command.Parameters[0].Name.Equals("UnhandledBreakpointMode", StringComparison.OrdinalIgnoreCase)))
{
throw new PSArgumentException("UnhandledBreakpointMode");
}
UnhandledBreakpointProcessingMode? mode = null;
PSObject modeObject = command.Parameters[0].Value as PSObject;
if (modeObject != null)
{
try
{
mode = (UnhandledBreakpointProcessingMode)modeObject.BaseObject;
}
catch (InvalidCastException) { }
}
if (mode == null)
{
throw new PSArgumentException("Mode");
}
commandArgument.UnhandledBreakpointMode = mode;
result = PreProcessCommandResult.SetPreserveUnhandledBreakpointMode;
}
return result;
}
#endregion
#region Private Classes
/// <summary>
/// Helper class to run ServerPowerShellDriver objects on a single thread. This is
/// needed to support nested pipeline execution and remote debugging.
/// </summary>
private sealed class PowerShellDriverInvoker
{
#region Private Members
private ConcurrentStack<InvokePump> _invokePumpStack;
#endregion
#region Constructor
/// <summary>
/// Constructor.
/// </summary>
public PowerShellDriverInvoker()
{
_invokePumpStack = new ConcurrentStack<InvokePump>();
}
#endregion
#region Properties
/// <summary>
/// IsActive.
/// </summary>
public bool IsActive
{
get { return !_invokePumpStack.IsEmpty; }
}
/// <summary>
/// True if thread is ready to invoke a PowerShell driver.
/// </summary>
public bool IsAvailable
{
get
{
InvokePump pump;
if (!_invokePumpStack.TryPeek(out pump))
{
pump = null;
}
return (pump != null) ? !(pump.IsBusy) : false;
}
}
#endregion
#region Public Methods
/// <summary>
/// Submit a driver object to be invoked.
/// </summary>
/// <param name="driver">ServerPowerShellDriver.</param>
public void InvokeDriverAsync(ServerPowerShellDriver driver)
{
InvokePump currentPump;
if (!_invokePumpStack.TryPeek(out currentPump))
{
throw new PSInvalidOperationException(RemotingErrorIdStrings.PowerShellInvokerInvalidState);
}
currentPump.Dispatch(driver);
}
/// <summary>
/// Blocking call that creates a new pump object and pumps
/// driver invokes until stopped via a PopInvoker call.
/// </summary>
public void PushInvoker()
{
InvokePump newPump = new InvokePump();
_invokePumpStack.Push(newPump);
// Blocking call while new driver invocations are handled on
// new pump.
newPump.Start();
}
/// <summary>
/// Stops the current driver invoker and restores the previous
/// invoker object on the stack, if any, to handle driver invocations.
/// </summary>
public void PopInvoker()
{
InvokePump oldPump;
if (_invokePumpStack.TryPop(out oldPump))
{
oldPump.Stop();
}
else
{
throw new PSInvalidOperationException(RemotingErrorIdStrings.CannotExitNestedPipeline);
}
}
#endregion
#region Private classes
/// <summary>
/// Class that queues and invokes ServerPowerShellDriver objects
/// in sequence.
/// </summary>
private sealed class InvokePump
{
private Queue<ServerPowerShellDriver> _driverInvokeQueue;
private ManualResetEvent _processDrivers;
private object _syncObject;
private bool _stopPump;
private bool _isDisposed;
public InvokePump()
{
_driverInvokeQueue = new Queue<ServerPowerShellDriver>();
_processDrivers = new ManualResetEvent(false);
_syncObject = new object();
}
public void Start()
{
try
{
while (true)
{
_processDrivers.WaitOne();
// Synchronously invoke one ServerPowerShellDriver at a time.
ServerPowerShellDriver driver = null;
lock (_syncObject)
{
if (_stopPump)
{
break;
}
if (_driverInvokeQueue.Count > 0)
{
driver = _driverInvokeQueue.Dequeue();
}
if (_driverInvokeQueue.Count == 0)
{
_processDrivers.Reset();
}
}
if (driver != null)
{
try
{
IsBusy = true;
driver.InvokeMain();
}
catch (Exception)
{
}
finally
{
IsBusy = false;
}
}
}
}
finally
{
_isDisposed = true;
_processDrivers.Dispose();
}
}
public void Dispatch(ServerPowerShellDriver driver)
{
CheckDisposed();
lock (_syncObject)
{
_driverInvokeQueue.Enqueue(driver);
_processDrivers.Set();
}
}
public void Stop()
{
CheckDisposed();
lock (_syncObject)
{
_stopPump = true;
_processDrivers.Set();
}
}
public bool IsBusy { get; private set; }
private void CheckDisposed()
{
if (_isDisposed)
{
throw new ObjectDisposedException("InvokePump");
}
}
}
#endregion
}
#endregion
}
/// <summary>
/// This class wraps the script debugger for a ServerRunspacePoolDriver runspace.
/// </summary>
internal sealed class ServerRemoteDebugger : Debugger, IDisposable
{
#region Private Members
private IRSPDriverInvoke _driverInvoker;
private Runspace _runspace;
private ObjectRef<Debugger> _wrappedDebugger;
private bool _inDebugMode;
private DebuggerStopEventArgs _debuggerStopEventArgs;
private ManualResetEventSlim _nestedDebugStopCompleteEvent;
private bool _nestedDebugging;
private ManualResetEventSlim _processCommandCompleteEvent;
private ThreadCommandProcessing _threadCommandProcessing;
private bool _raiseStopEventLocally;
internal const string SetPSBreakCommandText = "Set-PSBreakpoint";
#endregion
#region Constructor
private ServerRemoteDebugger() { }
/// <summary>
/// Constructor.
/// </summary>
/// <param name="driverInvoker"></param>
/// <param name="runspace"></param>
/// <param name="debugger"></param>
internal ServerRemoteDebugger(
IRSPDriverInvoke driverInvoker,
Runspace runspace,
Debugger debugger)
{
if (driverInvoker == null)
{
throw new PSArgumentNullException("driverInvoker");
}
if (runspace == null)
{
throw new PSArgumentNullException("runspace");
}
if (debugger == null)
{
throw new PSArgumentNullException("debugger");
}
_driverInvoker = driverInvoker;
_runspace = runspace;
_wrappedDebugger = new ObjectRef<Debugger>(debugger);
SetDebuggerCallbacks();
_runspace.Name = "RemoteHost";
_runspace.InternalDebugger = this;
}
#endregion
#region Debugger overrides
/// <summary>
/// True when debugger is stopped at a breakpoint.
/// </summary>
public override bool InBreakpoint
{
get { return _inDebugMode; }
}
/// <summary>
/// Adds the provided set of breakpoints to the debugger.
/// </summary>
/// <param name="breakpoints">Breakpoints.</param>
public override void SetBreakpoints(IEnumerable<Breakpoint> breakpoints)
{
_wrappedDebugger.Value.SetBreakpoints(breakpoints);
}
/// <summary>
/// Get a breakpoint by id, primarily for Enable/Disable/Remove-PSBreakpoint cmdlets.
/// </summary>
/// <param name="id">Id of the breakpoint you want.</param>
public override Breakpoint GetBreakpoint(int id) =>
_wrappedDebugger.Value.GetBreakpoint(id);
/// <summary>
/// Returns breakpoints primarily for the Get-PSBreakpoint cmdlet.
/// </summary>
public override List<Breakpoint> GetBreakpoints() =>
_wrappedDebugger.Value.GetBreakpoints();
/// <summary>
/// Exits debugger mode with the provided resume action.
/// </summary>
/// <param name="resumeAction">DebuggerResumeAction.</param>
public override void SetDebuggerAction(DebuggerResumeAction resumeAction)
{
if (!_inDebugMode)
{
throw new PSInvalidOperationException(
StringUtil.Format(DebuggerStrings.CannotSetRemoteDebuggerAction));
}
ExitDebugMode(resumeAction);
}
/// <summary>
/// Returns debugger stop event args if in debugger stop state.
/// </summary>
/// <returns>DebuggerStopEventArgs.</returns>
public override DebuggerStopEventArgs GetDebuggerStopArgs()
{
return _wrappedDebugger.Value.GetDebuggerStopArgs();
}
/// <summary>
/// ProcessCommand.
/// </summary>
/// <param name="command">Command.</param>
/// <param name="output">Output.</param>
/// <returns></returns>
public override DebuggerCommandResults ProcessCommand(PSCommand command, PSDataCollection<PSObject> output)
{
if (LocalDebugMode)
{
return _wrappedDebugger.Value.ProcessCommand(command, output);
}
if (!InBreakpoint || (_threadCommandProcessing != null))
{
throw new PSInvalidOperationException(
StringUtil.Format(DebuggerStrings.CannotProcessDebuggerCommandNotStopped));
}
if (_processCommandCompleteEvent == null)
{
_processCommandCompleteEvent = new ManualResetEventSlim(false);
}
_threadCommandProcessing = new ThreadCommandProcessing(command, output, _wrappedDebugger.Value, _processCommandCompleteEvent);
try
{
return _threadCommandProcessing.Invoke(_nestedDebugStopCompleteEvent);
}
finally
{
_threadCommandProcessing = null;
}
}
/// <summary>
/// StopProcessCommand.
/// </summary>
public override void StopProcessCommand()
{
if (LocalDebugMode)
{
_wrappedDebugger.Value.StopProcessCommand();
}
ThreadCommandProcessing threadCommandProcessing = _threadCommandProcessing;
if (threadCommandProcessing != null)
{
threadCommandProcessing.Stop();
}
}
/// <summary>
/// SetDebugMode.
/// </summary>
/// <param name="mode"></param>
public override void SetDebugMode(DebugModes mode)
{
_wrappedDebugger.Value.SetDebugMode(mode);
base.SetDebugMode(mode);
}
/// <summary>
/// True when debugger is active with breakpoints.
/// </summary>
public override bool IsActive
{
get
{
return (InBreakpoint || _wrappedDebugger.Value.IsActive || _wrappedDebugger.Value.InBreakpoint);
}
}
/// <summary>
/// Sets debugger stepping mode.
/// </summary>
/// <param name="enabled">True if stepping is to be enabled.</param>
public override void SetDebuggerStepMode(bool enabled)
{
// Enable both the wrapper and wrapped debuggers for debugging before setting step mode.
DebugModes mode = DebugModes.LocalScript | DebugModes.RemoteScript;
base.SetDebugMode(mode);
_wrappedDebugger.Value.SetDebugMode(mode);
_wrappedDebugger.Value.SetDebuggerStepMode(enabled);
}
/// <summary>
/// InternalProcessCommand.
/// </summary>
/// <param name="command"></param>
/// <param name="output"></param>
/// <returns></returns>
internal override DebuggerCommand InternalProcessCommand(string command, IList<PSObject> output)
{
return _wrappedDebugger.Value.InternalProcessCommand(command, output);
}
/// <summary>
/// Sets up debugger to debug provided job or its child jobs.
/// </summary>
/// <param name="job">
/// Job object that is either a debuggable job or a container
/// of debuggable child jobs.
/// </param>
internal override void DebugJob(Job job)
{
_wrappedDebugger.Value.DebugJob(job);
}
/// <summary>
/// Removes job from debugger job list and pops its
/// debugger from the active debugger stack.
/// </summary>
/// <param name="job">Job.</param>
internal override void StopDebugJob(Job job)
{
_wrappedDebugger.Value.StopDebugJob(job);
}
/// <summary>
/// Sets up debugger to debug provided Runspace in a nested debug session.
/// </summary>
/// <param name="runspace">Runspace to debug.</param>
internal override void DebugRunspace(Runspace runspace)
{
_wrappedDebugger.Value.DebugRunspace(runspace);
}
/// <summary>
/// Sets up debugger to debug provided Runspace in a nested debug session.
/// </summary>
/// <param name="runspace">Runspace to debug.</param>
/// <param name="disableBreakAll"></param>
internal override void DebugRunspace(Runspace runspace, bool disableBreakAll)
{
_wrappedDebugger.Value.DebugRunspace(runspace, disableBreakAll);
}
/// <summary>
/// Removes the provided Runspace from the nested "active" debugger state.
/// </summary>
/// <param name="runspace">Runspace.</param>
internal override void StopDebugRunspace(Runspace runspace)
{
_wrappedDebugger.Value.StopDebugRunspace(runspace);
}
/// <summary>
/// IsPushed.
/// </summary>
internal override bool IsPushed
{
get
{
return _wrappedDebugger.Value.IsPushed;
}
}
/// <summary>
/// IsRemote.
/// </summary>
internal override bool IsRemote
{
get
{
return _wrappedDebugger.Value.IsRemote;
}
}
/// <summary>
/// IsDebuggerSteppingEnabled.
/// </summary>
internal override bool IsDebuggerSteppingEnabled
{
get
{
return _wrappedDebugger.Value.IsDebuggerSteppingEnabled;
}
}
/// <summary>
/// UnhandledBreakpointMode.
/// </summary>
internal override UnhandledBreakpointProcessingMode UnhandledBreakpointMode
{
get
{
return _wrappedDebugger.Value.UnhandledBreakpointMode;
}
set
{
_wrappedDebugger.Value.UnhandledBreakpointMode = value;
if (value == UnhandledBreakpointProcessingMode.Ignore &&
_inDebugMode)
{
// Release debugger stop hold.
ExitDebugMode(DebuggerResumeAction.Continue);
}
}
}
/// <summary>
/// IsPendingDebugStopEvent.
/// </summary>
internal override bool IsPendingDebugStopEvent
{
get { return _wrappedDebugger.Value.IsPendingDebugStopEvent; }
}
/// <summary>
/// ReleaseSavedDebugStop.
/// </summary>
internal override void ReleaseSavedDebugStop()
{
_wrappedDebugger.Value.ReleaseSavedDebugStop();
}
/// <summary>
/// Returns IEnumerable of CallStackFrame objects.
/// </summary>
/// <returns></returns>
public override IEnumerable<CallStackFrame> GetCallStack()
{
return _wrappedDebugger.Value.GetCallStack();
}
#endregion
#region IDisposable
/// <summary>
/// Dispose.
/// </summary>
public void Dispose()
{
RemoveDebuggerCallbacks();
if (_inDebugMode)
{
ExitDebugMode(DebuggerResumeAction.Stop);
}
if (_nestedDebugStopCompleteEvent != null)
{
_nestedDebugStopCompleteEvent.Dispose();
}
if (_processCommandCompleteEvent != null)
{
_processCommandCompleteEvent.Dispose();
}
}
#endregion
#region Private Classes
private sealed class ThreadCommandProcessing
{
// Members
private ManualResetEventSlim _commandCompleteEvent;
private Debugger _wrappedDebugger;
private PSCommand _command;
private PSDataCollection<PSObject> _output;
private DebuggerCommandResults _results;
private Exception _exception;
#if !UNIX
private WindowsIdentity _identityToImpersonate;
#endif
// Constructors
private ThreadCommandProcessing() { }
public ThreadCommandProcessing(
PSCommand command,
PSDataCollection<PSObject> output,
Debugger debugger,
ManualResetEventSlim processCommandCompleteEvent)
{
_command = command;
_output = output;
_wrappedDebugger = debugger;
_commandCompleteEvent = processCommandCompleteEvent;
}
// Methods
public DebuggerCommandResults Invoke(ManualResetEventSlim startInvokeEvent)
{
#if !UNIX
// Get impersonation information to flow if any.
Utils.TryGetWindowsImpersonatedIdentity(out _identityToImpersonate);
#endif
// Signal thread to process command.
Dbg.Assert(!_commandCompleteEvent.IsSet, "Command complete event shoulds always be non-signaled here.");
Dbg.Assert(!startInvokeEvent.IsSet, "The event should always be in non-signaled state here.");
startInvokeEvent.Set();
// Wait for completion.
_commandCompleteEvent.Wait();
_commandCompleteEvent.Reset();
#if !UNIX
if (_identityToImpersonate != null)
{
_identityToImpersonate.Dispose();
_identityToImpersonate = null;
}
#endif
// Propagate exception.
if (_exception != null)
{
throw _exception;
}
// Return command processing results.
return _results;
}
public void Stop()
{
Debugger debugger = _wrappedDebugger;
if (debugger != null)
{
debugger.StopProcessCommand();
}
}
internal void DoInvoke()
{
try
{
#if !UNIX
if (_identityToImpersonate != null)
{
_results = WindowsIdentity.RunImpersonated(
_identityToImpersonate.AccessToken,
() => _wrappedDebugger.ProcessCommand(_command, _output));
return;
}
#endif
_results = _wrappedDebugger.ProcessCommand(_command, _output);
}
catch (Exception e)
{
_exception = e;
}
finally
{
_commandCompleteEvent.Set();
}
}
}
#endregion
#region Private methods
/// <summary>
/// Add Debugger suspend execution callback.
/// </summary>
private void SetDebuggerCallbacks()
{
if (_runspace != null &&
_runspace.ExecutionContext != null &&
_wrappedDebugger.Value != null)
{
SubscribeWrappedDebugger(_wrappedDebugger.Value);
// Register debugger events for remote forwarding.
var eventManager = _runspace.ExecutionContext.Events;
if (!eventManager.GetEventSubscribers(RemoteDebugger.RemoteDebuggerStopEvent).GetEnumerator().MoveNext())
{
eventManager.SubscribeEvent(
source: null,
eventName: null,
sourceIdentifier: RemoteDebugger.RemoteDebuggerStopEvent,
data: null,
action: null,
supportEvent: true,
forwardEvent: true);
}
if (!eventManager.GetEventSubscribers(RemoteDebugger.RemoteDebuggerBreakpointUpdatedEvent).GetEnumerator().MoveNext())
{
eventManager.SubscribeEvent(
source: null,
eventName: null,
sourceIdentifier: RemoteDebugger.RemoteDebuggerBreakpointUpdatedEvent,
data: null,
action: null,
supportEvent: true,
forwardEvent: true);
}
}
}
/// <summary>
/// Remove the suspend execution callback.
/// </summary>
private void RemoveDebuggerCallbacks()
{
if (_runspace != null &&
_runspace.ExecutionContext != null &&
_wrappedDebugger.Value != null)
{
UnsubscribeWrappedDebugger(_wrappedDebugger.Value);
// Unregister debugger events for remote forwarding.
var eventManager = _runspace.ExecutionContext.Events;
foreach (var subscriber in eventManager.GetEventSubscribers(RemoteDebugger.RemoteDebuggerStopEvent))
{
eventManager.UnsubscribeEvent(subscriber);
}
foreach (var subscriber in eventManager.GetEventSubscribers(RemoteDebugger.RemoteDebuggerBreakpointUpdatedEvent))
{
eventManager.UnsubscribeEvent(subscriber);
}
}
}
/// <summary>
/// Handler for debugger events.
/// </summary>
private void HandleDebuggerStop(object sender, DebuggerStopEventArgs e)
{
// Ignore if we are in restricted mode.
if (!IsDebuggingSupported()) { return; }
if (LocalDebugMode)
{
// Forward event locally.
RaiseDebuggerStopEvent(e);
return;
}
if ((DebugMode & DebugModes.RemoteScript) != DebugModes.RemoteScript)
{
return;
}
_debuggerStopEventArgs = e;
PSHost contextHost = null;
try
{
// Save current context remote host.
contextHost = _runspace.ExecutionContext.InternalHost.ExternalHost;
// Forward event to remote client.
Dbg.Assert(_runspace != null, "Runspace cannot be null.");
_runspace.ExecutionContext.Events.GenerateEvent(
sourceIdentifier: RemoteDebugger.RemoteDebuggerStopEvent,
sender: null,
args: new object[] { e },
extraData: null);
//
// Start the debug mode. This is a blocking call and will return only
// after ExitDebugMode() is called.
//
EnterDebugMode(_wrappedDebugger.Value.IsPushed);
// Restore original context remote host.
_runspace.ExecutionContext.InternalHost.SetHostRef(contextHost);
}
catch (Exception)
{
}
finally
{
_debuggerStopEventArgs = null;
}
}
/// <summary>
/// HandleBreakpointUpdated.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void HandleBreakpointUpdated(object sender, BreakpointUpdatedEventArgs e)
{
// Ignore if we are in restricted mode.
if (!IsDebuggingSupported()) { return; }
if (LocalDebugMode)
{
// Forward event locally.
RaiseBreakpointUpdatedEvent(e);
return;
}
try
{
// Forward event to remote client.
Dbg.Assert(_runspace != null, "Runspace cannot be null.");
_runspace.ExecutionContext.Events.GenerateEvent(
sourceIdentifier: RemoteDebugger.RemoteDebuggerBreakpointUpdatedEvent,
sender: null,
args: new object[] { e },
extraData: null);
}
catch (Exception)
{
}
}
private void HandleNestedDebuggingCancelEvent(object sender, EventArgs e)
{
// Forward cancel event from wrapped debugger.
RaiseNestedDebuggingCancelEvent();
// Release debugger.
if (_inDebugMode)
{
ExitDebugMode(DebuggerResumeAction.Continue);
}
}
/// <summary>
/// Sends a DebuggerStop event to the client and enters a nested pipeline.
/// </summary>
private void EnterDebugMode(bool isNestedStop)
{
_inDebugMode = true;
try
{
_runspace.ExecutionContext.SetVariable(SpecialVariables.NestedPromptCounterVarPath, 1);
if (isNestedStop)
{
// Blocking call for nested debugger execution (Debug-Runspace) stop events.
// The root debugger never makes two EnterDebugMode calls without an ExitDebugMode.
if (_nestedDebugStopCompleteEvent == null)
{
_nestedDebugStopCompleteEvent = new ManualResetEventSlim(false);
}
_nestedDebugging = true;
OnEnterDebugMode(_nestedDebugStopCompleteEvent);
}
else
{
// Blocking call.
// Process all client commands as nested until nested pipeline is exited at
// which point this call returns.
_driverInvoker.EnterNestedPipeline();
}
}
catch (Exception)
{
}
finally
{
_inDebugMode = false;
_nestedDebugging = false;
}
// Check to see if we should re-raise the stop event locally.
if (_raiseStopEventLocally)
{
_raiseStopEventLocally = false;
LocalDebugMode = true;
HandleDebuggerStop(this, _debuggerStopEventArgs);
}
}
/// <summary>
/// Blocks DebuggerStop event thread until exit debug mode is
/// received from the client.
/// </summary>
private void OnEnterDebugMode(ManualResetEventSlim debugModeCompletedEvent)
{
Dbg.Assert(!debugModeCompletedEvent.IsSet, "Event should always be non-signaled here.");
while (true)
{
debugModeCompletedEvent.Wait();
debugModeCompletedEvent.Reset();
if (_threadCommandProcessing != null)
{
// Process command.
_threadCommandProcessing.DoInvoke();
_threadCommandProcessing = null;
}
else
{
// No command to process. Exit debug mode.
break;
}
}
}
/// <summary>
/// Exits the server side nested pipeline.
/// </summary>
private void ExitDebugMode(DebuggerResumeAction resumeAction)
{
_debuggerStopEventArgs.ResumeAction = resumeAction;
try
{
if (_nestedDebugging)
{
// Release nested debugger.
_nestedDebugStopCompleteEvent.Set();
}
else
{
// Release EnterDebugMode blocking call.
_driverInvoker.ExitNestedPipeline();
}
_runspace.ExecutionContext.SetVariable(SpecialVariables.NestedPromptCounterVarPath, 0);
}
catch (Exception)
{
}
}
private void SubscribeWrappedDebugger(Debugger wrappedDebugger)
{
wrappedDebugger.DebuggerStop += HandleDebuggerStop; ;
wrappedDebugger.BreakpointUpdated += HandleBreakpointUpdated; ;
wrappedDebugger.NestedDebuggingCancelledEvent += HandleNestedDebuggingCancelEvent;
}
private void UnsubscribeWrappedDebugger(Debugger wrappedDebugger)
{
wrappedDebugger.DebuggerStop -= HandleDebuggerStop; ;
wrappedDebugger.BreakpointUpdated -= HandleBreakpointUpdated; ;
wrappedDebugger.NestedDebuggingCancelledEvent -= HandleNestedDebuggingCancelEvent;
}
private bool IsDebuggingSupported()
{
// Restriction only occurs on a (non-pushed) local runspace.
LocalRunspace localRunspace = _runspace as LocalRunspace;
if (localRunspace != null)
{
CmdletInfo cmdletInfo = localRunspace.ExecutionContext.EngineSessionState.GetCmdlet(SetPSBreakCommandText);
if ((cmdletInfo != null) && (cmdletInfo.Visibility != SessionStateEntryVisibility.Public))
{
return false;
}
}
return true;
}
#endregion
#region Internal Methods
/// <summary>
/// HandleStopSignal.
/// </summary>
/// <returns>True if stop signal is handled.</returns>
internal bool HandleStopSignal()
{
// If in pushed mode then stop any running command.
if (IsPushed && (_threadCommandProcessing != null))
{
StopProcessCommand();
return true;
}
// Set debug mode to "None" so that current command can stop and not
// potentially not respond in a debugger stop. Use RestoreDebugger() to
// restore debugger to original mode.
_wrappedDebugger.Value.SetDebugMode(DebugModes.None);
if (InBreakpoint)
{
try
{
SetDebuggerAction(DebuggerResumeAction.Continue);
}
catch (PSInvalidOperationException) { }
}
return false;
}
// Sets the wrapped debugger to the same mode as the wrapper
// server remote debugger, enabling it if remote debugging is enabled.
internal void CheckDebuggerState()
{
if ((_wrappedDebugger.Value.DebugMode == DebugModes.None &&
(DebugMode & DebugModes.RemoteScript) == DebugModes.RemoteScript))
{
_wrappedDebugger.Value.SetDebugMode(DebugMode);
}
}
internal void StartPowerShellCommand(
PowerShell powershell,
Guid powershellId,
Guid runspacePoolId,
ServerRunspacePoolDriver runspacePoolDriver,
#if !CORECLR // No ApartmentState In CoreCLR
ApartmentState apartmentState,
#endif
ServerRemoteHost remoteHost,
HostInfo hostInfo,
RemoteStreamOptions streamOptions,
bool addToHistory)
{
// For nested debugger command processing, invoke command on new local runspace since
// the root script debugger runspace is unavailable (it is running a PS script).
Runspace runspace = (remoteHost != null) ?
RunspaceFactory.CreateRunspace(remoteHost) : RunspaceFactory.CreateRunspace();
runspace.Open();
try
{
powershell.InvocationStateChanged += HandlePowerShellInvocationStateChanged;
powershell.SetIsNested(false);
string script = @"
param ($Debugger, $Commands, $output)
trap { throw $_ }
$Debugger.ProcessCommand($Commands, $output)
";
PSDataCollection<PSObject> output = new PSDataCollection<PSObject>();
PSCommand Commands = new PSCommand(powershell.Commands);
powershell.Commands.Clear();
powershell.AddScript(script).AddParameter("Debugger", this).AddParameter("Commands", Commands).AddParameter("output", output);
ServerPowerShellDriver driver = new ServerPowerShellDriver(
powershell,
null,
true,
powershellId,
runspacePoolId,
runspacePoolDriver,
#if !CORECLR // No ApartmentState In CoreCLR
apartmentState,
#endif
hostInfo,
streamOptions,
addToHistory,
runspace,
output);
driver.Start();
}
catch (Exception)
{
runspace.Close();
runspace.Dispose();
}
}
private void HandlePowerShellInvocationStateChanged(object sender, PSInvocationStateChangedEventArgs e)
{
if (e.InvocationStateInfo.State == PSInvocationState.Completed ||
e.InvocationStateInfo.State == PSInvocationState.Stopped ||
e.InvocationStateInfo.State == PSInvocationState.Failed)
{
PowerShell powershell = sender as PowerShell;
powershell.InvocationStateChanged -= HandlePowerShellInvocationStateChanged;
Runspace runspace = powershell.GetRunspaceConnection() as Runspace;
runspace.Close();
runspace.Dispose();
}
}
internal int GetBreakpointCount()
{
ScriptDebugger scriptDebugger = _wrappedDebugger.Value as ScriptDebugger;
if (scriptDebugger != null)
{
return scriptDebugger.GetBreakpoints().Count;
}
else
{
return 0;
}
}
internal void PushDebugger(Debugger debugger)
{
if (debugger == null)
{
return;
}
if (debugger.Equals(this))
{
throw new PSInvalidOperationException(DebuggerStrings.RemoteServerDebuggerCannotPushSelf);
}
if (_wrappedDebugger.IsOverridden)
{
throw new PSInvalidOperationException(DebuggerStrings.RemoteServerDebuggerAlreadyPushed);
}
// Swap wrapped debugger.
UnsubscribeWrappedDebugger(_wrappedDebugger.Value);
_wrappedDebugger.Override(debugger);
SubscribeWrappedDebugger(_wrappedDebugger.Value);
}
internal void PopDebugger()
{
if (!_wrappedDebugger.IsOverridden) { return; }
// Swap wrapped debugger.
UnsubscribeWrappedDebugger(_wrappedDebugger.Value);
_wrappedDebugger.Revert();
SubscribeWrappedDebugger(_wrappedDebugger.Value);
}
internal void ReleaseAndRaiseDebugStopLocal()
{
if (_inDebugMode)
{
// Release debugger stop and signal to re-raise locally.
_raiseStopEventLocally = true;
ExitDebugMode(DebuggerResumeAction.Continue);
}
}
#endregion
#region Internal Properties
/// <summary>
/// When true, this debugger is being used for local debugging (not remote debugging)
/// via the Debug-Runspace cmdlet.
/// </summary>
internal bool LocalDebugMode
{
get;
set;
}
#endregion
}
}