2837 lines
110 KiB
C#
2837 lines
110 KiB
C#
// Copyright (c) Microsoft Corporation.
|
|
// Licensed under the MIT License.
|
|
|
|
using System.Collections;
|
|
using System.Collections.Concurrent;
|
|
using System.Collections.Generic;
|
|
using System.Collections.ObjectModel;
|
|
using System.Diagnostics;
|
|
using System.Globalization;
|
|
using System.IO;
|
|
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>
|
|
#nullable enable
|
|
internal interface IRSPDriverInvoke
|
|
{
|
|
void EnterNestedPipeline();
|
|
|
|
void ExitNestedPipeline();
|
|
|
|
bool HandleStopSignal();
|
|
}
|
|
#nullable restore
|
|
|
|
/// <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
|
|
|
|
// Optional initial location of the PowerShell session
|
|
private readonly string _initialLocation;
|
|
|
|
// Script to run after a RunspacePool/Runspace is created in this session.
|
|
private readonly 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 readonly Dictionary<Guid, ServerPowerShellDriver> _associatedShells
|
|
= new Dictionary<Guid, ServerPowerShellDriver>();
|
|
|
|
// remote host associated with this runspacepool
|
|
private readonly ServerDriverRemoteHost _remoteHost;
|
|
|
|
private bool _isClosed;
|
|
|
|
// server capability reported to the client during negotiation (not the actual capability)
|
|
private readonly RemoteSessionCapability _serverCapability;
|
|
private Runspace _rsToUseForSteppablePipeline;
|
|
|
|
// steppable pipeline event subscribers exist per-session
|
|
private readonly ServerSteppablePipelineSubscriber _eventSubscriber = new ServerSteppablePipelineSubscriber();
|
|
private PSDataCollection<object> _inputCollection; // PowerShell driver input collection
|
|
|
|
// Object to invoke nested PowerShell drivers on single pipeline worker thread.
|
|
private readonly PowerShellDriverInvoker _driverNestedInvoker;
|
|
|
|
// Remote wrapper for script debugger.
|
|
private ServerRemoteDebugger _serverRemoteDebugger;
|
|
|
|
// Version of PowerShell client.
|
|
private readonly 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 readonly string _configurationName;
|
|
|
|
/// <summary>
|
|
/// Event that get raised when the RunspacePool is closed.
|
|
/// </summary>
|
|
internal EventHandler<EventArgs> Closed;
|
|
|
|
#endregion Private Members
|
|
|
|
#region Constructors
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of 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>
|
|
/// <param name="initialLocation">Optional initial location of the powershell.</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,
|
|
string initialLocation)
|
|
{
|
|
Dbg.Assert(configData != null, "ConfigurationData cannot be null");
|
|
|
|
_serverCapability = serverCapability;
|
|
_clientPSVersion = psClientVersion;
|
|
|
|
_configurationName = configurationName;
|
|
_initialLocation = initialLocation;
|
|
|
|
// 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 ?? PSThreadOptions.UseCurrentThread;
|
|
if (threadOptions == PSThreadOptions.Default || threadOptions == serverThreadOptions)
|
|
{
|
|
RunspacePool.ThreadOptions = serverThreadOptions;
|
|
}
|
|
else
|
|
{
|
|
if (!isAdministrator)
|
|
{
|
|
throw new InvalidOperationException(PSRemotingErrorInvariants.FormatResourceString(RemotingErrorIdStrings.MustBeAdminToOverrideThreadOptions));
|
|
}
|
|
|
|
RunspacePool.ThreadOptions = threadOptions;
|
|
}
|
|
|
|
// Set Thread ApartmentState for this RunspacePool
|
|
ApartmentState serverApartmentState = configData.ShellThreadApartmentState ?? Runspace.DefaultApartmentState;
|
|
|
|
if (apartmentState == ApartmentState.Unknown || apartmentState == serverApartmentState)
|
|
{
|
|
RunspacePool.ApartmentState = serverApartmentState;
|
|
}
|
|
else
|
|
{
|
|
RunspacePool.ApartmentState = apartmentState;
|
|
}
|
|
|
|
// 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 += HandleRunspacePoolStateChanged;
|
|
|
|
// listen for events on the runspace pool
|
|
RunspacePool.ForwardEvent += HandleRunspacePoolForwardEvent;
|
|
|
|
RunspacePool.RunspaceCreated += HandleRunspaceCreated;
|
|
|
|
// register for all the events from the data structure handler
|
|
DataStructureHandler.CreateAndInvokePowerShell += HandleCreateAndInvokePowerShell;
|
|
DataStructureHandler.GetCommandMetadata += HandleGetCommandMetadata;
|
|
DataStructureHandler.HostResponseReceived += HandleHostResponseReceived;
|
|
DataStructureHandler.SetMaxRunspacesReceived += HandleSetMaxRunspacesReceived;
|
|
DataStructureHandler.SetMinRunspacesReceived += HandleSetMinRunspacesReceived;
|
|
DataStructureHandler.GetAvailableRunspacesReceived += HandleGetAvailableRunspacesReceived;
|
|
DataStructureHandler.ResetRunspaceState += 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 -= HandleRunspacePoolStateChanged;
|
|
RunspacePool.ForwardEvent -= 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.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,
|
|
args.Runspace.ApartmentState,
|
|
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 ?? 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.
|
|
}
|
|
|
|
if (!string.IsNullOrWhiteSpace(_initialLocation))
|
|
{
|
|
var setLocationCommand = new Command("Set-Location");
|
|
setLocationCommand.Parameters.Add(new CommandParameter("LiteralPath", _initialLocation));
|
|
InvokeScript(setLocationCommand, args);
|
|
}
|
|
|
|
// 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 of this event, unused.</param>
|
|
/// <param name="eventArgs">Arguments describing this event.</param>
|
|
private void HandleCreateAndInvokePowerShell(object _, 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);
|
|
|
|
ApartmentState apartmentState = RemotingDecoder.GetApartmentState(data.Data);
|
|
|
|
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;
|
|
Collection<object> preProcessOutput = new Collection<object>();
|
|
|
|
try
|
|
{
|
|
var result = PreProcessDebuggerCommand(powershell.Commands, _serverRemoteDebugger, preProcessOutput, 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;
|
|
|
|
case PreProcessCommandResult.BreakpointManagement:
|
|
terminateImmediate = true;
|
|
break;
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
terminateImmediate = true;
|
|
|
|
preProcessOutput.Add(
|
|
PSObject.AsPSObject(ex));
|
|
}
|
|
|
|
// 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,
|
|
apartmentState,
|
|
hostInfo,
|
|
streamOptions,
|
|
addToHistory,
|
|
null);
|
|
|
|
noOpDriver.RunNoOpCommand(preProcessOutput);
|
|
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)
|
|
{
|
|
// 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,
|
|
apartmentState,
|
|
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,
|
|
apartmentState,
|
|
_remoteHost,
|
|
hostInfo,
|
|
streamOptions,
|
|
addToHistory);
|
|
|
|
return;
|
|
}
|
|
else if (powershell.Commands.Commands.Count == 1 &&
|
|
!powershell.Commands.Commands[0].IsScript &&
|
|
(powershell.Commands.Commands[0].CommandText.Contains("Get-PSDebuggerStopArgs", StringComparison.OrdinalIgnoreCase) ||
|
|
powershell.Commands.Commands[0].CommandText.Contains("Set-PSDebuggerAction", StringComparison.OrdinalIgnoreCase)))
|
|
{
|
|
// 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,
|
|
apartmentState,
|
|
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,
|
|
apartmentState,
|
|
hostInfo,
|
|
streamOptions,
|
|
addToHistory,
|
|
null);
|
|
|
|
_inputCollection = driver.InputCollection;
|
|
driver.Start();
|
|
}
|
|
|
|
private bool? _initialSessionStateIncludesGetCommandWithListImportedSwitch;
|
|
private readonly 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(static 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,
|
|
ApartmentState.Unknown,
|
|
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 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,
|
|
ApartmentState.MTA,
|
|
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. "Get" commands use this so that the
|
|
/// data they retrieve can be sent back to the caller.
|
|
/// </summary>
|
|
None = 0,
|
|
|
|
/// <summary>
|
|
/// This is a valid debugger command but was not processed because
|
|
/// the debugger state was not correct.
|
|
/// </summary>
|
|
ValidNotProcessed,
|
|
|
|
/// <summary>
|
|
/// SetDebuggerAction.
|
|
/// </summary>
|
|
SetDebuggerAction,
|
|
|
|
/// <summary>
|
|
/// SetDebugMode.
|
|
/// </summary>
|
|
SetDebugMode,
|
|
|
|
/// <summary>
|
|
/// SetDebuggerStepMode.
|
|
/// </summary>
|
|
SetDebuggerStepMode,
|
|
|
|
/// <summary>
|
|
/// SetPreserveUnhandledBreakpointMode.
|
|
/// </summary>
|
|
SetPreserveUnhandledBreakpointMode,
|
|
|
|
/// <summary>
|
|
/// The PreProcessCommandResult used for managing breakpoints.
|
|
/// </summary>
|
|
BreakpointManagement,
|
|
}
|
|
|
|
private sealed 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">The PSCommand.</param>
|
|
/// <param name="serverRemoteDebugger">The debugger that can be used to invoke debug operations via API.</param>
|
|
/// <param name="preProcessOutput">A Collection that can be used to send output to the client.</param>
|
|
/// <param name="commandArgument">Command argument.</param>
|
|
/// <returns>PreProcessCommandResult type if preprocessing occurred.</returns>
|
|
private static PreProcessCommandResult PreProcessDebuggerCommand(
|
|
PSCommand commands,
|
|
ServerRemoteDebugger serverRemoteDebugger,
|
|
Collection<object> preProcessOutput,
|
|
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(RemoteDebuggingCommands.GetDebuggerStopArgs, StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
// __Get-PSDebuggerStopArgs private virtual command.
|
|
// No input parameters.
|
|
// Returns DebuggerStopEventArgs object.
|
|
|
|
// Evaluate this command only if the debugger is activated.
|
|
if (!serverRemoteDebugger.IsActive)
|
|
{
|
|
return PreProcessCommandResult.ValidNotProcessed;
|
|
}
|
|
|
|
ReplaceVirtualCommandWithScript(commands, "$host.Runspace.Debugger.GetDebuggerStopArgs()");
|
|
}
|
|
else if (commandText.Equals(RemoteDebuggingCommands.SetDebuggerAction, StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
// __Set-PSDebuggerAction private virtual command.
|
|
// DebuggerResumeAction enum input parameter.
|
|
// Returns void.
|
|
|
|
// Evaluate this command only if the debugger is activated.
|
|
if (!serverRemoteDebugger.IsActive)
|
|
{
|
|
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)
|
|
{
|
|
// Do nothing.
|
|
}
|
|
}
|
|
|
|
commandArgument.ResumeAction = resumeAction ?? throw new PSArgumentException("ResumeAction");
|
|
result = PreProcessCommandResult.SetDebuggerAction;
|
|
}
|
|
else if (commandText.Equals(RemoteDebuggingCommands.SetDebugMode, 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)
|
|
{
|
|
// Do nothing.
|
|
}
|
|
}
|
|
|
|
commandArgument.Mode = mode ?? throw new PSArgumentException("Mode");
|
|
result = PreProcessCommandResult.SetDebugMode;
|
|
}
|
|
else if (commandText.Equals(RemoteDebuggingCommands.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(RemoteDebuggingCommands.SetUnhandledBreakpointMode, 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)
|
|
{
|
|
// Do nothing.
|
|
}
|
|
}
|
|
|
|
commandArgument.UnhandledBreakpointMode = mode ?? throw new PSArgumentException("Mode");
|
|
result = PreProcessCommandResult.SetPreserveUnhandledBreakpointMode;
|
|
}
|
|
else if (commandText.Equals(RemoteDebuggingCommands.GetBreakpoint, StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
// __Get-PSBreakpoint private virtual command.
|
|
// Input parameters:
|
|
// [-Id <int>]
|
|
// Returns Breakpoint object(s).
|
|
TryGetParameter<int?>(command, "RunspaceId", out int? runspaceId);
|
|
if (TryGetParameter<int>(command, "Id", out int breakpointId))
|
|
{
|
|
preProcessOutput.Add(serverRemoteDebugger.GetBreakpoint(breakpointId, runspaceId));
|
|
}
|
|
else
|
|
{
|
|
foreach (Breakpoint breakpoint in serverRemoteDebugger.GetBreakpoints(runspaceId))
|
|
{
|
|
preProcessOutput.Add(breakpoint);
|
|
}
|
|
}
|
|
|
|
result = PreProcessCommandResult.BreakpointManagement;
|
|
}
|
|
else if (commandText.Equals(RemoteDebuggingCommands.SetBreakpoint, StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
// __Set-PSBreakpoint private virtual command.
|
|
// Input parameters:
|
|
// -Breakpoint <Breakpoint> or -BreakpointList <IEnumerable<Breakpoint>>
|
|
// [-RunspaceId <int?>]
|
|
// Returns Breakpoint object(s).
|
|
TryGetParameter<Breakpoint>(command, "Breakpoint", out Breakpoint breakpoint);
|
|
TryGetParameter<ArrayList>(command, "BreakpointList", out ArrayList breakpoints);
|
|
if (breakpoint == null && breakpoints == null)
|
|
{
|
|
throw new PSArgumentException(DebuggerStrings.BreakpointOrBreakpointListNotSpecified);
|
|
}
|
|
|
|
TryGetParameter<int?>(command, "RunspaceId", out int? runspaceId);
|
|
|
|
commands.Clear();
|
|
|
|
// Any collection comes through remoting as an ArrayList of Objects so we convert each object
|
|
// into a breakpoint and add it to the list.
|
|
var bps = new List<Breakpoint>();
|
|
if (breakpoints != null)
|
|
{
|
|
foreach (object obj in breakpoints)
|
|
{
|
|
if (!LanguagePrimitives.TryConvertTo<Breakpoint>(obj, out Breakpoint bp))
|
|
{
|
|
throw new PSArgumentException(DebuggerStrings.BreakpointListContainedANonBreakpoint);
|
|
}
|
|
|
|
bps.Add(bp);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
bps.Add(breakpoint);
|
|
}
|
|
|
|
serverRemoteDebugger.SetBreakpoints(bps, runspaceId);
|
|
|
|
foreach (var bp in bps)
|
|
{
|
|
preProcessOutput.Add(bp);
|
|
}
|
|
|
|
result = PreProcessCommandResult.BreakpointManagement;
|
|
}
|
|
else if (commandText.Equals(RemoteDebuggingCommands.RemoveBreakpoint, StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
// __Remove-PSBreakpoint private virtual command.
|
|
// Input parameters:
|
|
// -Id <int>
|
|
// [-RunspaceId <int?>]
|
|
// Returns bool.
|
|
int breakpointId = GetParameter<int>(command, "Id");
|
|
TryGetParameter<int?>(command, "RunspaceId", out int? runspaceId);
|
|
|
|
Breakpoint breakpoint = serverRemoteDebugger.GetBreakpoint(breakpointId, runspaceId);
|
|
preProcessOutput.Add(
|
|
breakpoint != null && serverRemoteDebugger.RemoveBreakpoint(breakpoint, runspaceId));
|
|
|
|
result = PreProcessCommandResult.BreakpointManagement;
|
|
}
|
|
else if (commandText.Equals(RemoteDebuggingCommands.EnableBreakpoint, StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
// __Enable-PSBreakpoint private virtual command.
|
|
// Input parameters:
|
|
// -Id <int>
|
|
// [-RunspaceId <int?>]
|
|
// Returns Breakpoint object.
|
|
int breakpointId = GetParameter<int>(command, "Id");
|
|
TryGetParameter<int?>(command, "RunspaceId", out int? runspaceId);
|
|
|
|
Breakpoint bp = serverRemoteDebugger.GetBreakpoint(breakpointId, runspaceId);
|
|
if (bp != null)
|
|
{
|
|
preProcessOutput.Add(serverRemoteDebugger.EnableBreakpoint(bp, runspaceId));
|
|
}
|
|
|
|
result = PreProcessCommandResult.BreakpointManagement;
|
|
}
|
|
else if (commandText.Equals(RemoteDebuggingCommands.DisableBreakpoint, StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
// __Disable-PSBreakpoint private virtual command.
|
|
// Input parameters:
|
|
// -Id <int>
|
|
// [-RunspaceId <int?>]
|
|
// Returns Breakpoint object.
|
|
int breakpointId = GetParameter<int>(command, "Id");
|
|
TryGetParameter<int?>(command, "RunspaceId", out int? runspaceId);
|
|
|
|
Breakpoint bp = serverRemoteDebugger.GetBreakpoint(breakpointId, runspaceId);
|
|
if (bp != null)
|
|
{
|
|
preProcessOutput.Add(serverRemoteDebugger.DisableBreakpoint(bp, runspaceId));
|
|
}
|
|
|
|
result = PreProcessCommandResult.BreakpointManagement;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
private static void ReplaceVirtualCommandWithScript(PSCommand commands, string script)
|
|
{
|
|
ScriptBlock scriptBlock = ScriptBlock.Create(script);
|
|
scriptBlock.LanguageMode = PSLanguageMode.FullLanguage;
|
|
commands.Clear();
|
|
commands.AddCommand("Invoke-Command")
|
|
.AddParameter("ScriptBlock", scriptBlock)
|
|
.AddParameter("NoNewScope", true);
|
|
}
|
|
|
|
private static T GetParameter<T>(Command command, string parameterName)
|
|
{
|
|
if (command.Parameters?.Count == 0)
|
|
{
|
|
throw new PSArgumentException(parameterName);
|
|
}
|
|
|
|
foreach (CommandParameter param in command.Parameters)
|
|
{
|
|
if (string.Equals(param.Name, parameterName, StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
return LanguagePrimitives.ConvertTo<T>(param.Value);
|
|
}
|
|
}
|
|
|
|
throw new PSArgumentException(parameterName);
|
|
}
|
|
|
|
private static bool TryGetParameter<T>(Command command, string parameterName, out T value)
|
|
{
|
|
try
|
|
{
|
|
value = GetParameter<T>(command, parameterName);
|
|
return true;
|
|
}
|
|
catch (Exception ex) when (
|
|
ex is PSArgumentException ||
|
|
ex is InvalidCastException ||
|
|
ex is PSInvalidCastException)
|
|
{
|
|
value = default(T);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
#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 readonly 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);
|
|
}
|
|
}
|
|
|
|
#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 readonly Queue<ServerPowerShellDriver> _driverInvokeQueue;
|
|
private readonly ManualResetEvent _processDrivers;
|
|
private readonly 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 readonly IRSPDriverInvoke _driverInvoker;
|
|
private readonly Runspace _runspace;
|
|
private readonly 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(nameof(driverInvoker));
|
|
}
|
|
|
|
if (runspace == null)
|
|
{
|
|
throw new PSArgumentNullException(nameof(runspace));
|
|
}
|
|
|
|
if (debugger == null)
|
|
{
|
|
throw new PSArgumentNullException(nameof(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">List of breakpoints.</param>
|
|
/// <param name="runspaceId">The runspace id of the runspace you want to interact with. A null value will use the current runspace.</param>
|
|
public override void SetBreakpoints(IEnumerable<Breakpoint> breakpoints, int? runspaceId) =>
|
|
_wrappedDebugger.Value.SetBreakpoints(breakpoints, runspaceId);
|
|
|
|
/// <summary>
|
|
/// Get a breakpoint by id, primarily for Enable/Disable/Remove-PSBreakpoint cmdlets.
|
|
/// </summary>
|
|
/// <param name="id">Id of the breakpoint you want.</param>
|
|
/// <param name="runspaceId">The runspace id of the runspace you want to interact with. A null value will use the current runspace.</param>
|
|
/// <returns>The breakpoint with the specified id.</returns>
|
|
public override Breakpoint GetBreakpoint(int id, int? runspaceId) =>
|
|
_wrappedDebugger.Value.GetBreakpoint(id, runspaceId);
|
|
|
|
/// <summary>
|
|
/// Returns breakpoints on a runspace.
|
|
/// </summary>
|
|
/// <param name="runspaceId">The runspace id of the runspace you want to interact with. A null value will use the current runspace.</param>
|
|
/// <returns>A list of breakpoints in a runspace.</returns>
|
|
public override List<Breakpoint> GetBreakpoints(int? runspaceId) =>
|
|
_wrappedDebugger.Value.GetBreakpoints(runspaceId);
|
|
|
|
/// <summary>
|
|
/// Sets a command breakpoint in the debugger.
|
|
/// </summary>
|
|
/// <param name="command">The name of the command that will trigger the breakpoint. This value may not be null.</param>
|
|
/// <param name="action">The action to take when the breakpoint is hit. If null, PowerShell will break into the debugger when the breakpoint is hit.</param>
|
|
/// <param name="path">The path to the script file where the breakpoint may be hit. If null, the breakpoint may be hit anywhere the command is invoked.</param>
|
|
/// <param name="runspaceId">The runspace id of the runspace you want to interact with. A null value will use the current runspace.</param>
|
|
/// <returns>The command breakpoint that was set.</returns>
|
|
public override CommandBreakpoint SetCommandBreakpoint(string command, ScriptBlock action, string path, int? runspaceId) =>
|
|
_wrappedDebugger.Value.SetCommandBreakpoint(command, action, path, runspaceId);
|
|
|
|
/// <summary>
|
|
/// Sets a line breakpoint in the debugger.
|
|
/// </summary>
|
|
/// <param name="path">The path to the script file where the breakpoint may be hit. This value may not be null.</param>
|
|
/// <param name="line">The line in the script file where the breakpoint may be hit. This value must be greater than or equal to 1.</param>
|
|
/// <param name="column">The column in the script file where the breakpoint may be hit. If 0, the breakpoint will trigger on any statement on the line.</param>
|
|
/// <param name="action">The action to take when the breakpoint is hit. If null, PowerShell will break into the debugger when the breakpoint is hit.</param>
|
|
/// <param name="runspaceId">The runspace id of the runspace you want to interact with. A null value will use the current runspace.</param>
|
|
/// <returns>The line breakpoint that was set.</returns>
|
|
public override LineBreakpoint SetLineBreakpoint(string path, int line, int column, ScriptBlock action, int? runspaceId) =>
|
|
_wrappedDebugger.Value.SetLineBreakpoint(path, line, column, action, runspaceId);
|
|
|
|
/// <summary>
|
|
/// Sets a variable breakpoint in the debugger.
|
|
/// </summary>
|
|
/// <param name="variableName">The name of the variable that will trigger the breakpoint. This value may not be null.</param>
|
|
/// <param name="accessMode">The variable access mode that will trigger the breakpoint.</param>
|
|
/// <param name="action">The action to take when the breakpoint is hit. If null, PowerShell will break into the debugger when the breakpoint is hit.</param>
|
|
/// <param name="path">The path to the script file where the breakpoint may be hit. If null, the breakpoint may be hit anywhere the variable is accessed using the specified access mode.</param>
|
|
/// <param name="runspaceId">The runspace id of the runspace you want to interact with. A null value will use the current runspace.</param>
|
|
/// <returns>The variable breakpoint that was set.</returns>
|
|
public override VariableBreakpoint SetVariableBreakpoint(string variableName, VariableAccessMode accessMode, ScriptBlock action, string path, int? runspaceId) =>
|
|
_wrappedDebugger.Value.SetVariableBreakpoint(variableName, accessMode, action, path, runspaceId);
|
|
|
|
/// <summary>
|
|
/// Removes a breakpoint from the debugger.
|
|
/// </summary>
|
|
/// <param name="breakpoint">The breakpoint to remove from the debugger. This value may not be null.</param>
|
|
/// <param name="runspaceId">The runspace id of the runspace you want to interact with. A null value will use the current runspace.</param>
|
|
/// <returns>True if the breakpoint was removed from the debugger; false otherwise.</returns>
|
|
public override bool RemoveBreakpoint(Breakpoint breakpoint, int? runspaceId) =>
|
|
_wrappedDebugger.Value.RemoveBreakpoint(breakpoint, runspaceId);
|
|
|
|
/// <summary>
|
|
/// Enables a breakpoint in the debugger.
|
|
/// </summary>
|
|
/// <param name="breakpoint">The breakpoint to enable in the debugger. This value may not be null.</param>
|
|
/// <param name="runspaceId">The runspace id of the runspace you want to interact with. A null value will use the current runspace.</param>
|
|
/// <returns>The updated breakpoint if it was found; null if the breakpoint was not found in the debugger.</returns>
|
|
public override Breakpoint EnableBreakpoint(Breakpoint breakpoint, int? runspaceId) =>
|
|
_wrappedDebugger.Value.EnableBreakpoint(breakpoint, runspaceId);
|
|
|
|
/// <summary>
|
|
/// Disables a breakpoint in the debugger.
|
|
/// </summary>
|
|
/// <param name="breakpoint">The breakpoint to enable in the debugger. This value may not be null.</param>
|
|
/// <param name="runspaceId">The runspace id of the runspace you want to interact with. A null value will use the current runspace.</param>
|
|
/// <returns>The updated breakpoint if it was found; null if the breakpoint was not found in the debugger.</returns>
|
|
public override Breakpoint DisableBreakpoint(Breakpoint breakpoint, int? runspaceId) =>
|
|
_wrappedDebugger.Value.DisableBreakpoint(breakpoint, runspaceId);
|
|
|
|
/// <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.
|
|
const 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>
|
|
/// <param name="breakAll">
|
|
/// If true, the debugger automatically invokes a break all when it
|
|
/// attaches to the job.
|
|
/// </param>
|
|
internal override void DebugJob(Job job, bool breakAll) =>
|
|
_wrappedDebugger.Value.DebugJob(job, breakAll);
|
|
|
|
/// <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>
|
|
/// <param name="breakAll">
|
|
/// When true, this command will invoke a BreakAll when the debugger is
|
|
/// first attached.
|
|
/// </param>
|
|
internal override void DebugRunspace(Runspace runspace, bool breakAll)
|
|
{
|
|
_wrappedDebugger.Value.DebugRunspace(runspace, breakAll);
|
|
}
|
|
|
|
/// <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();
|
|
}
|
|
|
|
internal override void Break(object triggerObject = null)
|
|
{
|
|
_wrappedDebugger.Value.Break(triggerObject);
|
|
}
|
|
|
|
#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 readonly ManualResetEventSlim _commandCompleteEvent;
|
|
private readonly Debugger _wrappedDebugger;
|
|
private readonly PSCommand _command;
|
|
private readonly 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,
|
|
ApartmentState apartmentState,
|
|
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);
|
|
|
|
const 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,
|
|
apartmentState,
|
|
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
|
|
}
|
|
}
|