From 7ad5642e2df0652c5c029a126d361d3dc67abefc Mon Sep 17 00:00:00 2001 From: PaulHigin Date: Wed, 7 Dec 2016 15:03:03 -0800 Subject: [PATCH 1/7] Implementation for Invoke-Command step-in remote debugging Minor updates to test. Added It:Skip for Windows only test. More CR changes Fix for SSH remoting Ctrl+C operation Fixed comment Clean up PSObject base Implementation for Invoke-Command step-in remote debugging Remove GP cache changes Added test Fixed potential null reference error Fixed ad hoc bug Added debugger ready wait in debugger process loop Stability improvements Improve step out behavior Added event raised when internal runsapce debug processing ends Added consistent runspace clean up Some clean up --- .../engine/debugger/debugger.cs | 478 +++++++++++++++++- .../engine/remoting/client/Job.cs | 47 -- .../engine/remoting/client/remoterunspace.cs | 11 +- .../remoting/commands/InvokeCommandCommand.cs | 147 ++++-- .../remoting/commands/PSRemotingCmdlet.cs | 40 ++ .../remoting/common/RunspaceConnectionInfo.cs | 10 +- .../engine/remoting/common/throttlemanager.cs | 38 ++ .../resources/RemotingErrorIdStrings.resx | 5 +- .../InvokeCommandRemoteDebug.Tests.ps1 | 200 ++++++++ 9 files changed, 843 insertions(+), 133 deletions(-) create mode 100644 test/powershell/engine/Remoting/InvokeCommandRemoteDebug.Tests.ps1 diff --git a/src/System.Management.Automation/engine/debugger/debugger.cs b/src/System.Management.Automation/engine/debugger/debugger.cs index 4d9809acf76..117f04f1e49 100644 --- a/src/System.Management.Automation/engine/debugger/debugger.cs +++ b/src/System.Management.Automation/engine/debugger/debugger.cs @@ -104,6 +104,13 @@ public DebuggerStopEventArgs( /// DebuggerAction.StepToLine is only valid when debugging an script. /// public DebuggerResumeAction ResumeAction { get; set; } + + /// + /// This property is used internally for remote debug stops only. It is used to signal the remote debugger proxy + /// that it should *not* send a resume action to the remote debugger. This is used by runspace debug processing to + /// leave pending runspace debug sessions suspended until a debugger is attached. + /// + internal bool SuspendRemote { get; set; } }; /// @@ -253,6 +260,71 @@ private DebugSource() { } #endregion + #region Runspace Debug Processing + + /// + /// ProcessRunspaceDebug event arguments + /// + public sealed class ProcessRunspaceDebugEventArgs : EventArgs + { + /// + /// The runspace to process + /// + public Runspace Runspace + { + get; + private set; + } + + /// + /// When set to true this will cause PowerShell to handle this runspace debug session internally through its + /// internal script debugger + /// + public bool HandleInternally + { + get; + set; + } + + /// + /// Constructor + /// + public ProcessRunspaceDebugEventArgs(Runspace runspace) + { + if (runspace == null) { throw new PSArgumentNullException("runspace"); } + + Runspace = runspace; + } + } + + /// + /// ProcessRunspaceDebugEnd event arguments + /// + public sealed class ProcessRunspaceDebugEndEventArgs : EventArgs + { + /// + /// The runspace where internal debug processing has ended + /// + public Runspace Runspace + { + get; + private set; + } + + /// + /// Constructor + /// + /// + public ProcessRunspaceDebugEndEventArgs(Runspace runspace) + { + if (runspace == null) { throw new PSArgumentNullException("runspace"); } + + Runspace = runspace; + } + } + + #endregion + #endregion #region Enums @@ -327,6 +399,25 @@ public abstract class Debugger /// internal event EventHandler NestedDebuggingCancelledEvent; + #region Runspace Debug Processing Events + + /// + /// Event raised when a runspace debugger needs breakpoint processing + /// + public event EventHandler ProcessRunspaceDebug; + + /// + /// Event raised when a runspace debugger is finished being processed internally. + /// + public event EventHandler ProcessRunspaceDebugEnd; + + /// + /// Event raised when debugging session is over and runspace debuggers queued for processing should be released + /// + public event EventHandler CancelProcessRunspaceDebug; + + #endregion + #endregion #region Properties @@ -470,6 +561,47 @@ protected bool IsDebuggerBreakpointUpdatedEventSubscribed() return (BreakpointUpdated != null); } + #region Runspace Debug Processing + + /// + /// RaiseProcessRunspaceDebugEvent + /// + /// ProcessRunspaceDebugEventArgs + protected void RaiseProcessRunspaceDebugEvent(ProcessRunspaceDebugEventArgs args) + { + if (args == null) { throw new PSArgumentNullException("args"); } + ProcessRunspaceDebug.SafeInvoke(this, args); + } + + /// + /// RaiseProcessRunspaceDebugEndEvent + /// + /// ProcessRunspaceDebugEndEventArgs + protected void RaiseProcessRunspaceDebugEndEvent(ProcessRunspaceDebugEndEventArgs args) + { + if (args == null) { throw new PSArgumentNullException("args"); } + ProcessRunspaceDebugEnd.SafeInvoke(this, args); + } + + /// + /// IsProcessRunspaceDebugEventSubscribed + /// + /// True if event subscription exists + protected bool IsProcessRunspaceDebugEventSubscribed() + { + return (ProcessRunspaceDebug != null); + } + + /// + /// RaiseCancelProcessDebuggerEvent + /// + protected void RaiseCancelProcessRunspaceDebugEvent() + { + CancelProcessRunspaceDebug.SafeInvoke(this, null); + } + + #endregion + #endregion #region Public Methods @@ -712,6 +844,30 @@ internal void RaiseNestedDebuggingCancelEvent() #endregion + #region Runspace Debug Processing Methods + + /// + /// Adds the provided Runspace object to the runspace debugger processing queue. + /// The queue will then raise the ProcessRunspaceDebug events for each runspace to allow + /// a host script debugger implementation to provide an active debugging session. + /// + /// Runspace to debug + internal virtual void QueueRunspaceForDebug(Runspace runspace) + { + throw new PSNotImplementedException(); + } + + /// + /// Causes the CancelRunspaceDebugProcessing event to be raised which notifies subscribers that current debugging + /// sessions should be cancelled. + /// + public virtual void CancelDebuggerProcessing() + { + throw new PSNotImplementedException(); + } + + #endregion + #region Members internal const string CannotProcessCommandNotStopped = "Debugger:CannotProcessCommandNotStopped"; @@ -1575,6 +1731,11 @@ internal void Clear() private bool _preserveUnhandledDebugStopEvent; private ManualResetEventSlim _preserveDebugStopEvent; + // Process runspace debugger + private Lazy> _runspaceDebugQueue = new Lazy>(); + private volatile Int32 _processingRunspaceDebugQueue; + private ManualResetEventSlim _runspaceDebugCompleteEvent; + private static readonly string s_processDebugPromptMatch; #endregion private members @@ -2634,6 +2795,96 @@ internal override void StopDebugRunspace(Runspace runspace) #endregion + #region Runspace Debug Processing + + /// + /// Adds the provided Runspace object to the runspace debugger processing queue. + /// The queue will then raise the ProcessRunspaceDebug events for each runspace to allow + /// a host script debugger implementation to provide an active debugging session. + /// + /// Runspace to debug + internal override void QueueRunspaceForDebug(Runspace runspace) + { + if (runspace == null) { throw new PSArgumentNullException("runspace"); } + + runspace.StateChanged += RunspaceStateChangedHandler; + runspace.AvailabilityChanged += RunspaceAvailabilityChangedHandler; + _runspaceDebugQueue.Value.Enqueue(new ProcessRunspaceDebugEventArgs(runspace)); + StartRunspaceForDebugQueueProcessing(); + } + + /// + /// Causes the CancelRunspaceDebugProcessing event to be raised which notifies subscribers that these debugging + /// sessions should be cancelled. + /// + public override void CancelDebuggerProcessing() + { + try + { + RaiseCancelProcessRunspaceDebugEvent(); + } + catch (Exception) + { } + + ReleaseInternalRunspaceDebugProcessing(null, true); + } + + private void ReleaseInternalRunspaceDebugProcessing(object sender, bool emptyQueue = false) + { + Runspace runspace = sender as Runspace; + if (runspace != null) + { + runspace.StateChanged -= RunspaceStateChangedHandler; + runspace.AvailabilityChanged -= RunspaceAvailabilityChangedHandler; + } + + if (emptyQueue && _runspaceDebugQueue.IsValueCreated) + { + ProcessRunspaceDebugEventArgs args; + while (_runspaceDebugQueue.Value.TryDequeue(out args)) + { + args.Runspace.StateChanged -= RunspaceStateChangedHandler; + args.Runspace.AvailabilityChanged -= RunspaceAvailabilityChangedHandler; + try + { + args.Runspace.Debugger.UnhandledBreakpointMode = UnhandledBreakpointProcessingMode.Ignore; + } + catch (Exception) { } + } + } + + if (_runspaceDebugCompleteEvent != null) + { + try + { + _runspaceDebugCompleteEvent.Set(); + } + catch (ObjectDisposedException) { } + } + } + + private void RunspaceStateChangedHandler(object sender, RunspaceStateEventArgs args) + { + switch (args.RunspaceStateInfo.State) + { + case RunspaceState.Closed: + case RunspaceState.Broken: + case RunspaceState.Disconnected: + ReleaseInternalRunspaceDebugProcessing(sender); + break; + } + } + + private void RunspaceAvailabilityChangedHandler(object sender, RunspaceAvailabilityEventArgs args) + { + if (args.RunspaceAvailability == RunspaceAvailability.Available) + { + ReleaseInternalRunspaceDebugProcessing(sender); + } + } + + #endregion + #endregion #region Job debugger integration @@ -2938,6 +3189,10 @@ private Debugger PopActiveDebugger() _steppingMode = SteppingMode.StepIn; _overOrOutFrame = _nestedRunningFrame; _nestedRunningFrame = null; + if (_lastActiveDebuggerAction == DebuggerResumeAction.StepOut) + { + CancelDebuggerProcessingAsNeeded(poppedDebugger); + } break; case DebuggerResumeAction.Stop: @@ -2960,6 +3215,20 @@ private Debugger PopActiveDebugger() return poppedDebugger; } + private void CancelDebuggerProcessingAsNeeded(Debugger debugger) + { + NestedRunspaceDebugger nestedDebugger = debugger as NestedRunspaceDebugger; + if (nestedDebugger != null) + { + var callstack = nestedDebugger.GetRSCallStack(); + if (callstack.Count == 1) + { + // A final frame step-out should clear any runspace debug processing. + CancelDebuggerProcessing(); + } + } + } + private void HandleActiveJobDebuggerStop(object sender, DebuggerStopEventArgs args) { // If we are debugging nested runspaces then ignore job debugger stops @@ -3037,6 +3306,7 @@ private void HandleMonitorRunningJobsDebuggerStop(object sender, DebuggerStopEve } Debugger senderDebugger = sender as Debugger; + bool pushSucceeded = false; lock (_syncActiveDebuggerStopObject) { Debugger activeDebugger = null; @@ -3055,11 +3325,14 @@ private void HandleMonitorRunningJobsDebuggerStop(object sender, DebuggerStopEve } } - if (PushActiveDebugger(senderDebugger, _jobCallStackOffset)) - { - // Forward the debug stop event. - HandleActiveJobDebuggerStop(sender, args); - } + pushSucceeded = PushActiveDebugger(senderDebugger, _jobCallStackOffset); + } + + // Handle debugger stop outside lock. + if (pushSucceeded) + { + // Forward the debug stop event. + HandleActiveJobDebuggerStop(sender, args); } } @@ -3436,6 +3709,7 @@ private void HandleMonitorRunningRSDebuggerStop(object sender, DebuggerStopEvent if (sender == null || args == null) { return; } Debugger senderDebugger = sender as Debugger; + bool pushSucceeded = false; lock (_syncActiveDebuggerStopObject) { Debugger activeDebugger; @@ -3491,12 +3765,15 @@ private void HandleMonitorRunningRSDebuggerStop(object sender, DebuggerStopEvent args.InvocationInfo = nestedDebugger.FixupInvocationInfo(args.InvocationInfo); // Finally push the runspace debugger. - if (PushActiveDebugger(senderDebugger, _runspaceCallStackOffset)) - { - // Forward the debug stop event. - // This method will always pop the debugger after debugger stop completes. - HandleActiveRunspaceDebuggerStop(sender, args); - } + pushSucceeded = PushActiveDebugger(senderDebugger, _runspaceCallStackOffset); + } + + // Handle debugger stop outside lock. + if (pushSucceeded) + { + // Forward the debug stop event. + // This method will always pop the debugger after debugger stop completes. + HandleActiveRunspaceDebuggerStop(sender, args); } } @@ -3588,6 +3865,118 @@ private bool SetUpDebuggerOnRunspace(Runspace runspace) #endregion + #region Runspace Debug Processing + + private void StartRunspaceForDebugQueueProcessing() + { + Int32 startThread = Interlocked.CompareExchange(ref _processingRunspaceDebugQueue, 1, 0); + + if (startThread == 0) + { + var thread = new System.Threading.Thread( + new ThreadStart(DebuggerQueueThreadProc)); + thread.Start(); + } + } + + private void DebuggerQueueThreadProc() + { + ProcessRunspaceDebugEventArgs runspaceDebugProcessArgs; + while (_runspaceDebugQueue.Value.TryDequeue(out runspaceDebugProcessArgs)) + { + if (IsProcessRunspaceDebugEventSubscribed()) + { + try + { + RaiseProcessRunspaceDebugEvent(runspaceDebugProcessArgs); + } + catch (Exception) { } + } + else + { + // If there are no ProcessDebugger event subscribers then default to handling internally. + runspaceDebugProcessArgs.HandleInternally = true; + } + + // Check for internal handling request. + if (runspaceDebugProcessArgs.HandleInternally) + { + try + { + ProcessRunspaceDebugInternally(runspaceDebugProcessArgs.Runspace); + } + catch (Exception) { } + } + } + + Interlocked.CompareExchange(ref _processingRunspaceDebugQueue, 0, 1); + + if (_runspaceDebugQueue.Value.Count > 0) + { + StartRunspaceForDebugQueueProcessing(); + } + } + + private void ProcessRunspaceDebugInternally(Runspace runspace) + { + WaitForReadyDebug(); + + DebugRunspace(runspace); + + // Block this event thread until debugging has ended. + WaitForDebugComplete(); + + // Ensure runspace debugger is not stopped in break mode. + if (runspace.Debugger.InBreakpoint) + { + try + { + runspace.Debugger.UnhandledBreakpointMode = UnhandledBreakpointProcessingMode.Ignore; + } + catch (Exception) { } + } + + StopDebugRunspace(runspace); + + RaiseProcessRunspaceDebugEndEvent( + new ProcessRunspaceDebugEndEventArgs(runspace)); + } + + private void WaitForReadyDebug() + { + // Wait up to ten seconds + System.Threading.Thread.Sleep(500); + int count = 0; + bool debugReady = false; + do + { + System.Threading.Thread.Sleep(250); + debugReady = IsDebuggerReady(); + } while (!debugReady && (count++ < 40)); + + if (!debugReady) { throw new PSInvalidOperationException(); } + } + + private bool IsDebuggerReady() + { + return (!this.IsPushed && !this.InBreakpoint && (this._context._debuggingMode > -1) && (this._context.InternalHost.NestedPromptCount == 0)); + } + + private void WaitForDebugComplete() + { + if (_runspaceDebugCompleteEvent == null) + { + _runspaceDebugCompleteEvent = new ManualResetEventSlim(false); + } + else + { + _runspaceDebugCompleteEvent.Reset(); + } + _runspaceDebugCompleteEvent.Wait(); + } + + #endregion + #region IDisposable /// @@ -3753,6 +4142,7 @@ internal abstract class NestedRunspaceDebugger : Debugger, IDisposable { #region Members + private bool _isDisposed; protected Runspace _runspace; protected Debugger _wrappedDebugger; @@ -3821,6 +4211,8 @@ public NestedRunspaceDebugger( /// DebuggerCommandResults public override DebuggerCommandResults ProcessCommand(PSCommand command, PSDataCollection output) { + if (_isDisposed) { return new DebuggerCommandResults(null, false); } + // Preprocess debugger commands. String cmd = command.Commands[0].CommandText.Trim(); @@ -3909,6 +4301,8 @@ public override bool IsActive /// public virtual void Dispose() { + _isDisposed = true; + if (_wrappedDebugger != null) { _wrappedDebugger.BreakpointUpdated -= HandleBreakpointUpdated; @@ -4042,6 +4436,21 @@ internal void CheckStateAndRaiseStopEvent() } } + /// + /// Gets the callstack of the nested runspace. + /// + /// + internal PSDataCollection GetRSCallStack() + { + // Get call stack from wrapped debugger + PSCommand cmd = new PSCommand(); + cmd.AddCommand("Get-PSCallStack"); + PSDataCollection callStackOutput = new PSDataCollection(); + _wrappedDebugger.ProcessCommand(cmd, callStackOutput); + + return callStackOutput; + } + #endregion } @@ -4069,11 +4478,7 @@ public StandaloneRunspaceDebugger( protected override DebuggerCommandResults HandleCallStack(PSDataCollection output) { - // Get call stack from wrapped debugger - PSCommand cmd = new PSCommand(); - cmd.AddCommand("Get-PSCallStack"); - PSDataCollection callStackOutput = new PSDataCollection(); - _wrappedDebugger.ProcessCommand(cmd, callStackOutput); + PSDataCollection callStackOutput = GetRSCallStack(); // Display call stack info as formatted. using (PowerShell ps = PowerShell.Create()) @@ -4087,7 +4492,7 @@ protected override DebuggerCommandResults HandleCallStack(PSDataCollection - /// Removes job data aggregation callbacks. Used for jobs - /// stopped in debugger so that debugger can access data. - /// - internal void RemoveJobAggregation() - { - RemoveAggreateCallbacksFromHelper(Helper); - } - #endregion #region stop @@ -3077,32 +3068,6 @@ protected void HandleURIDirectionReported(object sender, RemoteDataEventArgs - /// Used to detect an Invoke-Command running command breakpoint hit. - /// In this case disconnect the runspace so that a debugger can be - /// attached later by the user. - /// - /// - /// - protected void HandleRunspaceAvailabilityChangedForInvoke(object sender, RunspaceAvailabilityEventArgs e) - { - RemoteRunspace remoteRunspace = sender as RemoteRunspace; - if (remoteRunspace != null && - e.RunspaceAvailability == RunspaceAvailability.RemoteDebug) - { - remoteRunspace.AvailabilityChanged -= HandleRunspaceAvailabilityChangedForInvoke; - - try - { - remoteRunspace.DisconnectAsync(); - } - catch (PSNotImplementedException) { } - catch (InvalidRunspacePoolStateException) { } - catch (InvalidRunspaceStateException) { } - catch (PSInvalidOperationException) { } - } - } - /// /// Handle method executor stream events. /// @@ -3146,10 +3111,6 @@ protected virtual void HandlePipelineStateChanged(object sender, PipelineStateEv // since we got state changed event..we dont need to listen on // URI redirections anymore ((RemoteRunspace)Runspace).URIRedirectionReported -= HandleURIDirectionReported; - - // We monitor runspace RemoteDebug availability only while - // this pipeline is running. - ((RemoteRunspace)Runspace).AvailabilityChanged -= HandleRunspaceAvailabilityChangedForInvoke; } PipelineState state = e.PipelineStateInfo.State; @@ -3628,12 +3589,6 @@ private void HandleInformationAdded(object sender, DataAddedEventArgs eventArgs) /// aggregation has to be stopped protected void StopAggregateResultsFromHelper(ExecutionCmdletHelper helper) { - // Ensure the Runspace availability handler is removed on command completion. - if (helper.PipelineRunspace != null) - { - helper.PipelineRunspace.AvailabilityChanged -= HandleRunspaceAvailabilityChangedForInvoke; - } - // Get the pipeline associated with this helper and register for appropriate events RemoveAggreateCallbacksFromHelper(helper); @@ -4123,7 +4078,6 @@ internal PSInvokeExpressionSyncJob(List operations, Throttle RemoteRunspace remoteRS = helper.Pipeline.Runspace as RemoteRunspace; if (null != remoteRS) { - remoteRS.AvailabilityChanged += HandleRunspaceAvailabilityChangedForInvoke; remoteRS.StateChanged += HandleRunspaceStateChanged; if (remoteRS.RunspaceStateInfo.State == RunspaceState.BeforeOpen) @@ -4357,7 +4311,6 @@ private void HandleRunspaceStateChanged(object sender, RunspaceStateEventArgs e) if (e.RunspaceStateInfo.State != RunspaceState.Opened) { remoteRS.StateChanged -= HandleRunspaceStateChanged; - remoteRS.AvailabilityChanged -= HandleRunspaceAvailabilityChangedForInvoke; } } } diff --git a/src/System.Management.Automation/engine/remoting/client/remoterunspace.cs b/src/System.Management.Automation/engine/remoting/client/remoterunspace.cs index 0a3c8b05985..416b03dde2d 100644 --- a/src/System.Management.Automation/engine/remoting/client/remoterunspace.cs +++ b/src/System.Management.Automation/engine/remoting/client/remoterunspace.cs @@ -1351,7 +1351,12 @@ private bool SetDebugInfo(PSPrimitiveDictionary psApplicationPrivateData) var psVersionTable = psApplicationPrivateData[PSVersionInfo.PSVersionTableName] as PSPrimitiveDictionary; if (psVersionTable.ContainsKey(PSVersionInfo.PSVersionName)) { - ServerVersion = psVersionTable[PSVersionInfo.PSVersionName] as Version; + var psVersionInfo = psVersionTable[PSVersionInfo.PSVersionName]; + var baseValue = PSObject.Base(psVersionInfo); + if (baseValue != null) + { + ServerVersion = baseValue as Version; + } } } } @@ -2435,7 +2440,7 @@ private void ProcessDebuggerStopEventProc(object state) finally { _handleDebuggerStop = false; - if (!_detachCommand) + if (!_detachCommand && !args.SuspendRemote) { SetDebuggerAction(args.ResumeAction); } @@ -2468,7 +2473,7 @@ private void ProcessDebuggerStopEventProc(object state) finally { // Restore runspace availability. - if (restoreAvailability) + if (restoreAvailability && (_runspace.RunspaceAvailability == RunspaceAvailability.RemoteDebug)) { SetRemoteDebug(false, prevAvailability); } diff --git a/src/System.Management.Automation/engine/remoting/commands/InvokeCommandCommand.cs b/src/System.Management.Automation/engine/remoting/commands/InvokeCommandCommand.cs index 41420e9b957..1637fec6773 100644 --- a/src/System.Management.Automation/engine/remoting/commands/InvokeCommandCommand.cs +++ b/src/System.Management.Automation/engine/remoting/commands/InvokeCommandCommand.cs @@ -739,6 +739,35 @@ public override Hashtable[] SSHConnection #endregion + #region Remote Debug Parameters + + /// + /// When selected this parameter causes a debugger Step-Into action for each running remote session. + /// + [Parameter(ParameterSetName = InvokeCommandCommand.ComputerNameParameterSet)] + [Parameter(ParameterSetName = InvokeCommandCommand.SessionParameterSet)] + [Parameter(ParameterSetName = InvokeCommandCommand.UriParameterSet)] + [Parameter(ParameterSetName = InvokeCommandCommand.FilePathComputerNameParameterSet)] + [Parameter(ParameterSetName = InvokeCommandCommand.FilePathSessionParameterSet)] + [Parameter(ParameterSetName = InvokeCommandCommand.FilePathUriParameterSet)] + [Parameter(ParameterSetName = InvokeCommandCommand.VMIdParameterSet)] + [Parameter(ParameterSetName = InvokeCommandCommand.VMNameParameterSet)] + [Parameter(ParameterSetName = InvokeCommandCommand.ContainerIdParameterSet)] + [Parameter(ParameterSetName = InvokeCommandCommand.FilePathVMIdParameterSet)] + [Parameter(ParameterSetName = InvokeCommandCommand.FilePathVMNameParameterSet)] + [Parameter(ParameterSetName = InvokeCommandCommand.FilePathContainerIdParameterSet)] + [Parameter(ParameterSetName = InvokeCommandCommand.SSHHostParameterSet)] + [Parameter(ParameterSetName = InvokeCommandCommand.SSHHostHashParameterSet)] + [Parameter(ParameterSetName = InvokeCommandCommand.FilePathSSHHostParameterSet)] + [Parameter(ParameterSetName = InvokeCommandCommand.FilePathSSHHostHashParameterSet)] + public virtual SwitchParameter RemoteDebug + { + get; + set; + } + + #endregion + #endregion Parameters #region Overrides @@ -767,6 +796,19 @@ protected override void BeginProcessing() throw new InvalidOperationException(RemotingErrorIdStrings.SessionNameWithoutInvokeDisconnected); } + // Adjust RemoteDebug value based on current state + var hostDebugger = GetHostDebugger(); + if (hostDebugger == null) + { + // Do not allow RemoteDebug if there is no host debugger available. Otherwise script will hang indefinitely. + RemoteDebug = false; + } + else if (hostDebugger.IsDebuggerSteppingEnabled) + { + // If host debugger is in step-in mode then always make RemoteDebug true + RemoteDebug = true; + } + // Checking session's availability and reporting errors in early stage, unless '-AsJob' is specified. // When '-AsJob' is specified, Invoke-Command should return a job object without throwing error, even // if the session is not in available state -- this is the PSv3 behavior and we should not break it. @@ -985,7 +1027,7 @@ protected override void ProcessRecord() _inputStreamClosed = true; } - if (!ParameterSetName.Equals("InProcess")) + if (!ParameterSetName.Equals(InProcParameterSet)) { // at this point there is nothing to do for // inproc case. The script block is executed @@ -1209,6 +1251,17 @@ protected override void EndProcessing() /// protected override void StopProcessing() { + // Ensure that any runspace debug processing is ended + var hostDebugger = GetHostDebugger(); + if (hostDebugger != null) + { + try + { + hostDebugger.CancelDebuggerProcessing(); + } + catch (PSNotImplementedException) { } + } + if (!ParameterSetName.Equals(InvokeCommandCommand.InProcParameterSet)) { if (!_asjob) @@ -1247,6 +1300,20 @@ protected override void StopProcessing() #region Private Methods + private Debugger GetHostDebugger() + { + Debugger hostDebugger = null; + try + { + System.Management.Automation.Internal.Host.InternalHost chost = + this.Host as System.Management.Automation.Internal.Host.InternalHost; + hostDebugger = chost.Runspace.Debugger; + } + catch (PSNotImplementedException) { } + + return hostDebugger; + } + /// /// Handle event from the throttle manager indicating that all /// operations are complete @@ -1298,11 +1365,29 @@ private void CreateAndRunSyncJob() // Add robust connection retry notification handler. AddConnectionRetryHandler(_job); + // Enable all Invoke-Command synchronous jobs for remote debugging (in case Wait-Debugger or + // or line breakpoints are set in script). + foreach (var operation in Operations) + { + operation.RunspaceDebuggingEnabled = true; + operation.RunspaceDebugStepInEnabled = RemoteDebug; + operation.RunspaceDebugStop += HandleRunspaceDebugStop; + } + _job.StartOperations(Operations); } } } + private void HandleRunspaceDebugStop(object sender, ProcessRunspaceDebugEventArgs args) + { + var hostDebugger = GetHostDebugger(); + if (hostDebugger != null) + { + hostDebugger.QueueRunspaceForDebug(args.Runspace); + } + } + private void HandleJobStateChanged(object sender, JobStateEventArgs e) { JobState state = e.JobStateInfo.State; @@ -1617,8 +1702,6 @@ private void WriteJobResults(bool nonblocking) // pipelines. _asjob = true; - List removedDebugStopJobs = new List(); - // Write warnings to user about each disconnect. foreach (var cjob in rtnJob.ChildJobs) { @@ -1629,41 +1712,17 @@ private void WriteJobResults(bool nonblocking) PSSession session = GetPSSession(childJob.Runspace.InstanceId); if (session != null) { - RemoteDebugger remoteDebugger = session.Runspace.Debugger as RemoteDebugger; - if (remoteDebugger != null && - remoteDebugger.IsRemoteDebug) - { - // The session was disconnected because it hit a debug breakpoint. + // Write network failed, auto-disconnect error + WriteNetworkFailedError(session); - // Remove child job data aggregation so debugger can show data. - childJob.RemoveJobAggregation(); - removedDebugStopJobs.Add(childJob); - - // Write appropriate warning. - WriteWarning( - StringUtil.Format(RemotingErrorIdStrings.RCDisconnectDebug, + // Session disconnected message. + WriteWarning( + StringUtil.Format(RemotingErrorIdStrings.RCDisconnectSession, session.Name, session.InstanceId, session.ComputerName)); - } - else - { - // Write network failed, auto-disconnect error - WriteNetworkFailedError(session); - - // Session disconnected message. - WriteWarning( - StringUtil.Format(RemotingErrorIdStrings.RCDisconnectSession, - session.Name, session.InstanceId, session.ComputerName)); - } } } } - // Remove debugger stopped jobs - foreach (var dJob in removedDebugStopJobs) - { - rtnJob.ChildJobs.Remove(dJob); - } - if (rtnJob.ChildJobs.Count > 0) { JobRepository.Add(rtnJob); @@ -1686,25 +1745,13 @@ private void WriteJobResults(bool nonblocking) // Add to session repository. this.RunspaceRepository.AddOrReplace(session); - RemoteRunspace remoteRunspace = session.Runspace as RemoteRunspace; - if (remoteRunspace != null && - remoteRunspace.RunspacePool.RemoteRunspacePoolInternal.IsRemoteDebugStop) - { - // The session was disconnected because it hit a debug breakpoint. - WriteWarning( - StringUtil.Format(RemotingErrorIdStrings.RCDisconnectDebug, - session.Name, session.InstanceId, session.ComputerName)); - } - else - { - // Write network failed, auto-disconnect error - WriteNetworkFailedError(session); + // Write network failed, auto-disconnect error + WriteNetworkFailedError(session); - // Session disconnected message. - WriteWarning( - StringUtil.Format(RemotingErrorIdStrings.RCDisconnectSession, - session.Name, session.InstanceId, session.ComputerName)); - } + // Session disconnected message. + WriteWarning( + StringUtil.Format(RemotingErrorIdStrings.RCDisconnectSession, + session.Name, session.InstanceId, session.ComputerName)); // Session created message. WriteWarning( diff --git a/src/System.Management.Automation/engine/remoting/commands/PSRemotingCmdlet.cs b/src/System.Management.Automation/engine/remoting/commands/PSRemotingCmdlet.cs index 772c3375205..18904bf1dee 100644 --- a/src/System.Management.Automation/engine/remoting/commands/PSRemotingCmdlet.cs +++ b/src/System.Management.Automation/engine/remoting/commands/PSRemotingCmdlet.cs @@ -3118,6 +3118,42 @@ internal Runspace PipelineRunspace set; get; } + + #region Runspace Debug + + internal void ConfigureRunspaceDebugging(Runspace runspace) + { + if (!RunspaceDebuggingEnabled || (runspace == null) || (runspace.Debugger == null)) { return; } + + runspace.Debugger.DebuggerStop += HandleDebuggerStop; + + // Configure runspace debugger to preserve unhandled stops (wait for debugger attach) + runspace.Debugger.UnhandledBreakpointMode = UnhandledBreakpointProcessingMode.Wait; + + if (RunspaceDebugStepInEnabled) + { + // Configure runspace debugger to run script in step mode + try + { + runspace.Debugger.SetDebuggerStepMode(true); + } + catch (PSInvalidOperationException) { } + } + } + + private void HandleDebuggerStop(object sender, DebuggerStopEventArgs args) + { + PipelineRunspace.Debugger.DebuggerStop -= HandleDebuggerStop; + + // Forward event + RaiseRunspaceDebugStopEvent(PipelineRunspace); + + // Signal remote session to remain stopped in debuger + args.SuspendRemote = true; + } + + #endregion + } // ExecutionCmdletHelper /// @@ -3152,6 +3188,8 @@ internal ExecutionCmdletHelperRunspace(Pipeline pipeline) /// internal override void StartOperation() { + ConfigureRunspaceDebugging(PipelineRunspace); + try { if (ShouldUseSteppablePipelineOnServer) @@ -3386,6 +3424,8 @@ private void HandleRunspaceStateChanged(object sender, case RunspaceState.Opened: { + ConfigureRunspaceDebugging(RemoteRunspace); + // if successfully opened // Call InvokeAsync() on the pipeline try diff --git a/src/System.Management.Automation/engine/remoting/common/RunspaceConnectionInfo.cs b/src/System.Management.Automation/engine/remoting/common/RunspaceConnectionInfo.cs index b50f21da228..b62337fadec 100644 --- a/src/System.Management.Automation/engine/remoting/common/RunspaceConnectionInfo.cs +++ b/src/System.Management.Automation/engine/remoting/common/RunspaceConnectionInfo.cs @@ -2139,6 +2139,10 @@ private static System.Diagnostics.Process StartSSHProcessImpl( return sshProcess; } + // Process creation flags + private const int CREATE_NEW_PROCESS_GROUP = 0x00000200; + private const int CREATE_SUSPENDED = 0x00000004; + /// /// CreateProcessWithRedirectedStd /// @@ -2205,8 +2209,12 @@ private static Process CreateProcessWithRedirectedStd( // No new window: Inherit the parent process's console window creationFlags = 0x00000000; + // Create the new process in its own group, so that Ctrl+C is not sent to ssh.exe. We want to handle this + // control signal internally so that it can be passed via PSRP to the remote session. + creationFlags |= CREATE_NEW_PROCESS_GROUP; + // Create the new process suspended so we have a chance to get a corresponding Process object in case it terminates quickly. - creationFlags |= 0x00000004; + creationFlags |= CREATE_SUSPENDED; PlatformInvokes.SECURITY_ATTRIBUTES lpProcessAttributes = new PlatformInvokes.SECURITY_ATTRIBUTES(); PlatformInvokes.SECURITY_ATTRIBUTES lpThreadAttributes = new PlatformInvokes.SECURITY_ATTRIBUTES(); diff --git a/src/System.Management.Automation/engine/remoting/common/throttlemanager.cs b/src/System.Management.Automation/engine/remoting/common/throttlemanager.cs index 785bbe252b6..1549f8f8dc4 100644 --- a/src/System.Management.Automation/engine/remoting/common/throttlemanager.cs +++ b/src/System.Management.Automation/engine/remoting/common/throttlemanager.cs @@ -130,6 +130,44 @@ internal bool IgnoreStop } } private bool _ignoreStop = false; + + #region Runspace Debug + + /// + /// When true enables runspace debugging for operations involving runspaces. + /// + internal bool RunspaceDebuggingEnabled + { + get; + set; + } + + /// + /// When true configures runspace debugging to stop at first opportunity. + /// + internal bool RunspaceDebugStepInEnabled + { + get; + set; + } + + /// + /// Event raised when operation runspace enters a debugger stopped state. + /// + internal event EventHandler RunspaceDebugStop; + + /// + /// RaiseRunspaceDebugStopEvent + /// + /// Runspace + internal void RaiseRunspaceDebugStopEvent(System.Management.Automation.Runspaces.Runspace runspace) + { + RunspaceDebugStop.SafeInvoke(this, new ProcessRunspaceDebugEventArgs(runspace)); + } + + + #endregion + } // IThrottleOperation #endregion IThrottleOperation diff --git a/src/System.Management.Automation/resources/RemotingErrorIdStrings.resx b/src/System.Management.Automation/resources/RemotingErrorIdStrings.resx index 69c3a356605..c826acf0ac5 100644 --- a/src/System.Management.Automation/resources/RemotingErrorIdStrings.resx +++ b/src/System.Management.Automation/resources/RemotingErrorIdStrings.resx @@ -936,7 +936,7 @@ Do you want to continue? {0} may need to restart the WinRM service if a configuration using this name has recently been unregistered, certain system data structures may still be cached. In that case, a restart of WinRM may be required. All WinRM sessions connected to Windows PowerShell session configurations, such as Microsoft.PowerShell and session configurations that are created with the Register-PSSessionConfiguration cmdlet, are disconnected. - + You are running in a remote session and have selected the Force option which means the WinRM service may restart.If the WinRM service restarts then this remote session will be terminated and you will need to create a new session to continue @@ -1359,9 +1359,6 @@ All WinRM sessions connected to Windows PowerShell session configurations, such The remote session command is currently stopped in the debugger. Use the Enter-PSSession cmdlet to connect interactively to the remote session and automatically enter into the console debugger. - - Session {0} with instance ID {1} on computer {2} has been disconnected because the script running on the session has stopped at a breakpoint. Use the Enter-PSSession cmdlet on this session to connect back to the session and begin interactive debugging. - The remote session to which you are connected does not support remote debugging. You must connect to a remote computer that is running Windows PowerShell {0} or greater. diff --git a/test/powershell/engine/Remoting/InvokeCommandRemoteDebug.Tests.ps1 b/test/powershell/engine/Remoting/InvokeCommandRemoteDebug.Tests.ps1 new file mode 100644 index 00000000000..2520541e22d --- /dev/null +++ b/test/powershell/engine/Remoting/InvokeCommandRemoteDebug.Tests.ps1 @@ -0,0 +1,200 @@ +## +## PowerShell Invoke-Command -RemoteDebug Tests +## + +if (-not (Get-Module TestRemoting -ErrorAction SilentlyContinue)) +{ + $remotingModule = Join-Path $PSScriptRoot "../Common/TestRemoting.psm1" + Import-Module $remotingModule +} + +$typeDef = @' +using System; +using System.Globalization; +using System.Management.Automation; +using System.Management.Automation.Runspaces; +using System.Management.Automation.Host; + +namespace TestRunner +{ + public class DummyHost : PSHost, IHostSupportsInteractiveSession + { + public Runspace _runspace; + private Guid _instanceId = Guid.NewGuid(); + + public override CultureInfo CurrentCulture + { + get { return CultureInfo.CurrentCulture; } + } + public override CultureInfo CurrentUICulture + { + get { return CultureInfo.CurrentUICulture; } + } + public override Guid InstanceId + { + get { return _instanceId; } + } + public override string Name + { + get { return "DummyTestHost"; } + } + public override PSHostUserInterface UI + { + get { return null; } + } + public override Version Version + { + get { return new Version(1, 0); } + } + public override void EnterNestedPrompt() { } + public override void ExitNestedPrompt() { } + public override void NotifyBeginApplication() { } + public override void NotifyEndApplication() { } + public override void SetShouldExit(int exitCode) { } + public void PushRunspace(Runspace runspace) { } + public void PopRunspace() { } + public bool IsRunspacePushed { get { return false; } } + public Runspace Runspace { get { return _runspace; } private set { _runspace = value; } } + } + + public class TestDebugger : Debugger + { + private Runspace _runspace; + public int DebugStopCount + { + private set; + get; + } + public int RunspaceDebugProcessingCount + { + private set; + get; + } + public bool RunspaceDebugProcessCancelled + { + private set; + get; + } + + private void HandleDebuggerStop(object sender, DebuggerStopEventArgs args) + { + DebugStopCount++; + var debugger = sender as Debugger; + var command = new PSCommand(); + command.AddScript("prompt"); + var output = new PSDataCollection(); + debugger.ProcessCommand(command, output); + } + private void HandleProcessRunspaceDebug(object sender, ProcessRunspaceDebugEventArgs args) + { + args.HandleInternally = true; + RunspaceDebugProcessingCount++; + } + private void HandleCancelProcessRunspaceDebug(object sender, EventArgs args) + { + RunspaceDebugProcessCancelled = true; + } + public TestDebugger(Runspace runspace) + { + _runspace = runspace; + _runspace.Debugger.DebuggerStop += HandleDebuggerStop; + _runspace.Debugger.ProcessRunspaceDebug += HandleProcessRunspaceDebug; + _runspace.Debugger.CancelProcessRunspaceDebug += HandleCancelProcessRunspaceDebug; + } + public void Release() + { + if (_runspace == null) { return; } + _runspace.Debugger.DebuggerStop -= HandleDebuggerStop; + _runspace.Debugger.ProcessRunspaceDebug -= HandleProcessRunspaceDebug; + _runspace.Debugger.CancelProcessRunspaceDebug -= HandleCancelProcessRunspaceDebug; + } + + public override DebuggerCommandResults ProcessCommand(PSCommand command, PSDataCollection output) { return null; } + public override void SetDebuggerAction(DebuggerResumeAction resumeAction) { } + public override DebuggerStopEventArgs GetDebuggerStopArgs() { return null; } + public override void StopProcessCommand() { } + } +} +'@ + +Describe "Invoke-Command remote debugging tests" -Tags 'Feature' { + + BeforeAll { + + if (!$IsWindows) + { + $originalDefaultParameterValues = $PSDefaultParameterValues.Clone() + $PSDefaultParameterValues["it:skip"] = $true + return + } + + $sb = [scriptblock]::Create(@' + "Hello!" +'@) + + Add-Type -TypeDefinition $typeDef -ReferencedAssemblies "System.Globalization","System.Management.Automation" + + $dummyHost = [TestRunner.DummyHost]::new() + [runspace] $rs = [runspacefactory]::CreateRunspace($dummyHost) + $rs.Open() + $dummyHost._runspace = $rs + + $testDebugger = [TestRunner.TestDebugger]::new($rs) + + [runspace] $rs2 = [runspacefactory]::CreateRunspace() + $rs2.Open() + + [powershell] $ps = [powershell]::Create() + $ps.Runspace = $rs + + [powershell] $ps2 = [powershell]::Create() + $ps2.Runspace = $rs2 + + $remoteSession = New-RemoteRunspace + } + + AfterAll { + + if (!$IsWindows) + { + $global:PSDefaultParameterValues = $originalDefaultParameterValues + return + } + + if ($testDebugger -ne $null) { $testDebugger.Release() } + if ($ps -ne $null) { $ps.Dispose() } + if ($ps2 -ne $null) { $ps2.Dispose() } + if ($rs -ne $null) { $rs.Dispose() } + if ($rs2 -ne $null) { $rs2.Dispose() } + if ($remoteSession -ne $null) { Remove-PSSession $remoteSession -ErrorAction SilentlyContinue } + } + + It "Verifies that asynchronous Invoke-Command -RemoteDebug is ignored" { + + $ps.AddCommand("Invoke-Command").AddParameter("Session", $remoteSession).AddParameter("ScriptBlock", $sb).AddParameter("RemoteDebug", $true).AddParameter("AsJob", $true) + $result = $ps.Invoke() + $testDebugger.DebugStopCount | Should Be 0 + } + + It "Verifies that synchronous Invoke-Command -RemoteDebug invokes debugger" { + + $ps.Commands.Clear() + $ps.AddCommand("Invoke-Command").AddParameter("Session", $remoteSession).AddParameter("ScriptBlock", $sb).AddParameter("RemoteDebug", $true) + $result = $ps.Invoke() + $testDebugger.RunspaceDebugProcessingCount | Should Be 1 + $testDebugger.DebugStopCount | Should Be 1 + } + + It "Verifies the debugger CancelDebuggerProcessing API method" { + + $rs.Debugger.CancelDebuggerProcessing() + $testDebugger.RunspaceDebugProcessCancelled | Should Be $true + } + + It "Verifies that Invoke-Command -RemoteDebug running in a runspace without PSHost is ignored" { + + $ps2.AddCommand("Invoke-Command").AddParameter("Session", $remoteSession).AddParameter("ScriptBlock", $sb).AddParameter("RemoteDebug", $true) + $result = $ps2.Invoke() + $result | Should Be "Hello!" + } +} From 3698549df4dc461dc7723fa997f12399ab8efb02 Mon Sep 17 00:00:00 2001 From: PaulHigin Date: Thu, 19 Jan 2017 09:15:07 -0800 Subject: [PATCH 2/7] Minor test changes based on CR --- .../InvokeCommandRemoteDebug.Tests.ps1 | 275 +++++++++--------- 1 file changed, 140 insertions(+), 135 deletions(-) diff --git a/test/powershell/engine/Remoting/InvokeCommandRemoteDebug.Tests.ps1 b/test/powershell/engine/Remoting/InvokeCommandRemoteDebug.Tests.ps1 index 2520541e22d..c4dfdcff96c 100644 --- a/test/powershell/engine/Remoting/InvokeCommandRemoteDebug.Tests.ps1 +++ b/test/powershell/engine/Remoting/InvokeCommandRemoteDebug.Tests.ps1 @@ -2,120 +2,123 @@ ## PowerShell Invoke-Command -RemoteDebug Tests ## -if (-not (Get-Module TestRemoting -ErrorAction SilentlyContinue)) +if ($IsWindows) { - $remotingModule = Join-Path $PSScriptRoot "../Common/TestRemoting.psm1" - Import-Module $remotingModule -} - -$typeDef = @' -using System; -using System.Globalization; -using System.Management.Automation; -using System.Management.Automation.Runspaces; -using System.Management.Automation.Host; - -namespace TestRunner -{ - public class DummyHost : PSHost, IHostSupportsInteractiveSession + if (-not (Get-Module TestRemoting -ErrorAction SilentlyContinue)) { - public Runspace _runspace; - private Guid _instanceId = Guid.NewGuid(); - - public override CultureInfo CurrentCulture - { - get { return CultureInfo.CurrentCulture; } - } - public override CultureInfo CurrentUICulture - { - get { return CultureInfo.CurrentUICulture; } - } - public override Guid InstanceId - { - get { return _instanceId; } - } - public override string Name - { - get { return "DummyTestHost"; } - } - public override PSHostUserInterface UI - { - get { return null; } - } - public override Version Version - { - get { return new Version(1, 0); } - } - public override void EnterNestedPrompt() { } - public override void ExitNestedPrompt() { } - public override void NotifyBeginApplication() { } - public override void NotifyEndApplication() { } - public override void SetShouldExit(int exitCode) { } - public void PushRunspace(Runspace runspace) { } - public void PopRunspace() { } - public bool IsRunspacePushed { get { return false; } } - public Runspace Runspace { get { return _runspace; } private set { _runspace = value; } } + $remotingModule = Join-Path $PSScriptRoot "../Common/TestRemoting.psm1" + Import-Module $remotingModule } - public class TestDebugger : Debugger - { - private Runspace _runspace; - public int DebugStopCount - { - private set; - get; - } - public int RunspaceDebugProcessingCount - { - private set; - get; - } - public bool RunspaceDebugProcessCancelled - { - private set; - get; - } + $typeDef = @' + using System; + using System.Globalization; + using System.Management.Automation; + using System.Management.Automation.Runspaces; + using System.Management.Automation.Host; - private void HandleDebuggerStop(object sender, DebuggerStopEventArgs args) - { - DebugStopCount++; - var debugger = sender as Debugger; - var command = new PSCommand(); - command.AddScript("prompt"); - var output = new PSDataCollection(); - debugger.ProcessCommand(command, output); - } - private void HandleProcessRunspaceDebug(object sender, ProcessRunspaceDebugEventArgs args) - { - args.HandleInternally = true; - RunspaceDebugProcessingCount++; - } - private void HandleCancelProcessRunspaceDebug(object sender, EventArgs args) - { - RunspaceDebugProcessCancelled = true; - } - public TestDebugger(Runspace runspace) - { - _runspace = runspace; - _runspace.Debugger.DebuggerStop += HandleDebuggerStop; - _runspace.Debugger.ProcessRunspaceDebug += HandleProcessRunspaceDebug; - _runspace.Debugger.CancelProcessRunspaceDebug += HandleCancelProcessRunspaceDebug; - } - public void Release() - { - if (_runspace == null) { return; } - _runspace.Debugger.DebuggerStop -= HandleDebuggerStop; - _runspace.Debugger.ProcessRunspaceDebug -= HandleProcessRunspaceDebug; - _runspace.Debugger.CancelProcessRunspaceDebug -= HandleCancelProcessRunspaceDebug; + namespace TestRunner + { + public class DummyHost : PSHost, IHostSupportsInteractiveSession + { + public Runspace _runspace; + private Guid _instanceId = Guid.NewGuid(); + + public override CultureInfo CurrentCulture + { + get { return CultureInfo.CurrentCulture; } + } + public override CultureInfo CurrentUICulture + { + get { return CultureInfo.CurrentUICulture; } + } + public override Guid InstanceId + { + get { return _instanceId; } + } + public override string Name + { + get { return "DummyTestHost"; } + } + public override PSHostUserInterface UI + { + get { return null; } + } + public override Version Version + { + get { return new Version(1, 0); } + } + public override void EnterNestedPrompt() { } + public override void ExitNestedPrompt() { } + public override void NotifyBeginApplication() { } + public override void NotifyEndApplication() { } + public override void SetShouldExit(int exitCode) { } + public void PushRunspace(Runspace runspace) { } + public void PopRunspace() { } + public bool IsRunspacePushed { get { return false; } } + public Runspace Runspace { get { return _runspace; } private set { _runspace = value; } } + } + + public class TestDebugger : Debugger + { + private Runspace _runspace; + public int DebugStopCount + { + private set; + get; + } + public int RunspaceDebugProcessingCount + { + private set; + get; + } + public bool RunspaceDebugProcessCancelled + { + private set; + get; + } + + private void HandleDebuggerStop(object sender, DebuggerStopEventArgs args) + { + DebugStopCount++; + var debugger = sender as Debugger; + var command = new PSCommand(); + command.AddScript("prompt"); + var output = new PSDataCollection(); + debugger.ProcessCommand(command, output); + } + private void HandleProcessRunspaceDebug(object sender, ProcessRunspaceDebugEventArgs args) + { + args.HandleInternally = true; + RunspaceDebugProcessingCount++; + } + private void HandleCancelProcessRunspaceDebug(object sender, EventArgs args) + { + RunspaceDebugProcessCancelled = true; + } + public TestDebugger(Runspace runspace) + { + _runspace = runspace; + _runspace.Debugger.DebuggerStop += HandleDebuggerStop; + _runspace.Debugger.ProcessRunspaceDebug += HandleProcessRunspaceDebug; + _runspace.Debugger.CancelProcessRunspaceDebug += HandleCancelProcessRunspaceDebug; + } + public void Release() + { + if (_runspace == null) { return; } + _runspace.Debugger.DebuggerStop -= HandleDebuggerStop; + _runspace.Debugger.ProcessRunspaceDebug -= HandleProcessRunspaceDebug; + _runspace.Debugger.CancelProcessRunspaceDebug -= HandleCancelProcessRunspaceDebug; + } + + public override DebuggerCommandResults ProcessCommand(PSCommand command, PSDataCollection output) { return null; } + public override void SetDebuggerAction(DebuggerResumeAction resumeAction) { } + public override DebuggerStopEventArgs GetDebuggerStopArgs() { return null; } + public override void StopProcessCommand() { } } - - public override DebuggerCommandResults ProcessCommand(PSCommand command, PSDataCollection output) { return null; } - public override void SetDebuggerAction(DebuggerResumeAction resumeAction) { } - public override DebuggerStopEventArgs GetDebuggerStopArgs() { return null; } - public override void StopProcessCommand() { } } -} '@ +} Describe "Invoke-Command remote debugging tests" -Tags 'Feature' { @@ -125,32 +128,33 @@ Describe "Invoke-Command remote debugging tests" -Tags 'Feature' { { $originalDefaultParameterValues = $PSDefaultParameterValues.Clone() $PSDefaultParameterValues["it:skip"] = $true - return } - - $sb = [scriptblock]::Create(@' - "Hello!" + else + { + $sb = [scriptblock]::Create(@' + "Hello!" '@) - Add-Type -TypeDefinition $typeDef -ReferencedAssemblies "System.Globalization","System.Management.Automation" + Add-Type -TypeDefinition $typeDef -ReferencedAssemblies "System.Globalization","System.Management.Automation" - $dummyHost = [TestRunner.DummyHost]::new() - [runspace] $rs = [runspacefactory]::CreateRunspace($dummyHost) - $rs.Open() - $dummyHost._runspace = $rs + $dummyHost = [TestRunner.DummyHost]::new() + [runspace] $rs = [runspacefactory]::CreateRunspace($dummyHost) + $rs.Open() + $dummyHost._runspace = $rs - $testDebugger = [TestRunner.TestDebugger]::new($rs) + $testDebugger = [TestRunner.TestDebugger]::new($rs) - [runspace] $rs2 = [runspacefactory]::CreateRunspace() - $rs2.Open() + [runspace] $rs2 = [runspacefactory]::CreateRunspace() + $rs2.Open() - [powershell] $ps = [powershell]::Create() - $ps.Runspace = $rs + [powershell] $ps = [powershell]::Create() + $ps.Runspace = $rs - [powershell] $ps2 = [powershell]::Create() - $ps2.Runspace = $rs2 + [powershell] $ps2 = [powershell]::Create() + $ps2.Runspace = $rs2 - $remoteSession = New-RemoteRunspace + $remoteSession = New-RemoteRunspace + } } AfterAll { @@ -158,25 +162,26 @@ Describe "Invoke-Command remote debugging tests" -Tags 'Feature' { if (!$IsWindows) { $global:PSDefaultParameterValues = $originalDefaultParameterValues - return } - - if ($testDebugger -ne $null) { $testDebugger.Release() } - if ($ps -ne $null) { $ps.Dispose() } - if ($ps2 -ne $null) { $ps2.Dispose() } - if ($rs -ne $null) { $rs.Dispose() } - if ($rs2 -ne $null) { $rs2.Dispose() } - if ($remoteSession -ne $null) { Remove-PSSession $remoteSession -ErrorAction SilentlyContinue } + else + { + if ($testDebugger -ne $null) { $testDebugger.Release() } + if ($ps -ne $null) { $ps.Dispose() } + if ($ps2 -ne $null) { $ps2.Dispose() } + if ($rs -ne $null) { $rs.Dispose() } + if ($rs2 -ne $null) { $rs2.Dispose() } + if ($remoteSession -ne $null) { Remove-PSSession $remoteSession -ErrorAction SilentlyContinue } + } } - It "Verifies that asynchronous Invoke-Command -RemoteDebug is ignored" { + It "Verifies that asynchronous 'Invoke-Command -RemoteDebug' is ignored" { $ps.AddCommand("Invoke-Command").AddParameter("Session", $remoteSession).AddParameter("ScriptBlock", $sb).AddParameter("RemoteDebug", $true).AddParameter("AsJob", $true) $result = $ps.Invoke() $testDebugger.DebugStopCount | Should Be 0 } - It "Verifies that synchronous Invoke-Command -RemoteDebug invokes debugger" { + It "Verifies that synchronous 'Invoke-Command -RemoteDebug' invokes debugger" { $ps.Commands.Clear() $ps.AddCommand("Invoke-Command").AddParameter("Session", $remoteSession).AddParameter("ScriptBlock", $sb).AddParameter("RemoteDebug", $true) @@ -185,13 +190,13 @@ Describe "Invoke-Command remote debugging tests" -Tags 'Feature' { $testDebugger.DebugStopCount | Should Be 1 } - It "Verifies the debugger CancelDebuggerProcessing API method" { + It "Verifies the debugger 'CancelDebuggerProcessing' API method" { $rs.Debugger.CancelDebuggerProcessing() $testDebugger.RunspaceDebugProcessCancelled | Should Be $true } - It "Verifies that Invoke-Command -RemoteDebug running in a runspace without PSHost is ignored" { + It "Verifies that 'Invoke-Command -RemoteDebug' running in a runspace without PSHost is ignored" { $ps2.AddCommand("Invoke-Command").AddParameter("Session", $remoteSession).AddParameter("ScriptBlock", $sb).AddParameter("RemoteDebug", $true) $result = $ps2.Invoke() From 4396b0d79089e35f21c823d9bc00909bfceafa6f Mon Sep 17 00:00:00 2001 From: PaulHigin Date: Mon, 23 Jan 2017 09:08:47 -0800 Subject: [PATCH 3/7] Added runspace event clean up to fix problems when reusing sessions --- .../engine/remoting/commands/InvokeCommandCommand.cs | 3 +++ .../engine/remoting/commands/PSRemotingCmdlet.cs | 9 +++++++++ 2 files changed, 12 insertions(+) diff --git a/src/System.Management.Automation/engine/remoting/commands/InvokeCommandCommand.cs b/src/System.Management.Automation/engine/remoting/commands/InvokeCommandCommand.cs index 1637fec6773..6adbfe47470 100644 --- a/src/System.Management.Automation/engine/remoting/commands/InvokeCommandCommand.cs +++ b/src/System.Management.Automation/engine/remoting/commands/InvokeCommandCommand.cs @@ -1381,6 +1381,9 @@ private void CreateAndRunSyncJob() private void HandleRunspaceDebugStop(object sender, ProcessRunspaceDebugEventArgs args) { + var operation = sender as IThrottleOperation; + operation.RunspaceDebugStop -= HandleRunspaceDebugStop; + var hostDebugger = GetHostDebugger(); if (hostDebugger != null) { diff --git a/src/System.Management.Automation/engine/remoting/commands/PSRemotingCmdlet.cs b/src/System.Management.Automation/engine/remoting/commands/PSRemotingCmdlet.cs index 18904bf1dee..cf94a916d31 100644 --- a/src/System.Management.Automation/engine/remoting/commands/PSRemotingCmdlet.cs +++ b/src/System.Management.Automation/engine/remoting/commands/PSRemotingCmdlet.cs @@ -3141,6 +3141,13 @@ internal void ConfigureRunspaceDebugging(Runspace runspace) } } + internal void CleanupRunspaceDebugging(Runspace runspace) + { + if ((runspace == null) || (runspace.Debugger == null)) { return; } + + runspace.Debugger.DebuggerStop -= HandleDebuggerStop; + } + private void HandleDebuggerStop(object sender, DebuggerStopEventArgs args) { PipelineRunspace.Debugger.DebuggerStop -= HandleDebuggerStop; @@ -3282,6 +3289,8 @@ private void RaiseOperationCompleteEvent() /// raises this operation complete private void RaiseOperationCompleteEvent(EventArgs baseEventArgs) { + CleanupRunspaceDebugging(PipelineRunspace); + if (pipeline != null) { // Dispose the pipeline object and release data and remoting resources. From 9db8fdf22a73cb6b9763919b4c609340c029caaf Mon Sep 17 00:00:00 2001 From: PaulHigin Date: Wed, 1 Feb 2017 13:43:59 -0800 Subject: [PATCH 4/7] Changes from Code Review --- .../engine/debugger/debugger.cs | 110 +++++++----------- .../engine/remoting/client/remoterunspace.cs | 7 +- .../remoting/commands/InvokeCommandCommand.cs | 2 +- .../engine/remoting/common/throttlemanager.cs | 4 +- .../InvokeCommandRemoteDebug.Tests.ps1 | 32 +++-- 5 files changed, 65 insertions(+), 90 deletions(-) diff --git a/src/System.Management.Automation/engine/debugger/debugger.cs b/src/System.Management.Automation/engine/debugger/debugger.cs index 117f04f1e49..ef3237e7e5b 100644 --- a/src/System.Management.Automation/engine/debugger/debugger.cs +++ b/src/System.Management.Automation/engine/debugger/debugger.cs @@ -263,13 +263,11 @@ private DebugSource() { } #region Runspace Debug Processing /// - /// ProcessRunspaceDebug event arguments + /// StartRunspaceDebugProcessing event arguments /// - public sealed class ProcessRunspaceDebugEventArgs : EventArgs + public sealed class StartRunspaceDebugProcessingEventArgs : EventArgs { - /// - /// The runspace to process - /// + /// The runspace to process public Runspace Runspace { get; @@ -277,10 +275,11 @@ public Runspace Runspace } /// - /// When set to true this will cause PowerShell to handle this runspace debug session internally through its - /// internal script debugger + /// When set to true this will cause PowerShell to process this runspace debug session through its + /// script debugger. To use the default processing return from this event call after setting + /// this property to true. /// - public bool HandleInternally + public bool UseDefaultProcessing { get; set; @@ -289,7 +288,7 @@ public bool HandleInternally /// /// Constructor /// - public ProcessRunspaceDebugEventArgs(Runspace runspace) + public StartRunspaceDebugProcessingEventArgs(Runspace runspace) { if (runspace == null) { throw new PSArgumentNullException("runspace"); } @@ -402,19 +401,20 @@ public abstract class Debugger #region Runspace Debug Processing Events /// - /// Event raised when a runspace debugger needs breakpoint processing + /// Event raised when a runspace debugger needs breakpoint processing. /// - public event EventHandler ProcessRunspaceDebug; + public event EventHandler StartRunspaceDebugProcessing; /// - /// Event raised when a runspace debugger is finished being processed internally. + /// Event raised when a runspace debugger is finished being processed. /// - public event EventHandler ProcessRunspaceDebugEnd; + public event EventHandler RunspaceDebugProcessingCompleted; /// - /// Event raised when debugging session is over and runspace debuggers queued for processing should be released + /// Event raised to indicate that the debugging session is over and runspace debuggers queued for + /// processing should be released. /// - public event EventHandler CancelProcessRunspaceDebug; + public event EventHandler CancelRunspaceDebugProcessing; #endregion @@ -563,41 +563,30 @@ protected bool IsDebuggerBreakpointUpdatedEventSubscribed() #region Runspace Debug Processing - /// - /// RaiseProcessRunspaceDebugEvent - /// - /// ProcessRunspaceDebugEventArgs - protected void RaiseProcessRunspaceDebugEvent(ProcessRunspaceDebugEventArgs args) + /// + protected void RaiseStartRunspaceDebugProcessingEvent(StartRunspaceDebugProcessingEventArgs args) { if (args == null) { throw new PSArgumentNullException("args"); } - ProcessRunspaceDebug.SafeInvoke(this, args); + StartRunspaceDebugProcessing.SafeInvoke(this, args); } - /// - /// RaiseProcessRunspaceDebugEndEvent - /// - /// ProcessRunspaceDebugEndEventArgs - protected void RaiseProcessRunspaceDebugEndEvent(ProcessRunspaceDebugEndEventArgs args) + /// + protected void RaiseRunspaceProcessingCompletedEvent(ProcessRunspaceDebugEndEventArgs args) { if (args == null) { throw new PSArgumentNullException("args"); } - ProcessRunspaceDebugEnd.SafeInvoke(this, args); + RunspaceDebugProcessingCompleted.SafeInvoke(this, args); } - /// - /// IsProcessRunspaceDebugEventSubscribed - /// - /// True if event subscription exists + /// protected bool IsProcessRunspaceDebugEventSubscribed() { - return (ProcessRunspaceDebug != null); + return (StartRunspaceDebugProcessing != null); } - /// - /// RaiseCancelProcessDebuggerEvent - /// - protected void RaiseCancelProcessRunspaceDebugEvent() + /// + protected void RaiseCancelRunspaceDebugProcessingEvent() { - CancelProcessRunspaceDebug.SafeInvoke(this, null); + CancelRunspaceDebugProcessing.SafeInvoke(this, null); } #endregion @@ -848,7 +837,7 @@ internal void RaiseNestedDebuggingCancelEvent() /// /// Adds the provided Runspace object to the runspace debugger processing queue. - /// The queue will then raise the ProcessRunspaceDebug events for each runspace to allow + /// The queue will then raise the StartRunspaceDebugProcessing events for each runspace to allow /// a host script debugger implementation to provide an active debugging session. /// /// Runspace to debug @@ -1732,7 +1721,7 @@ internal void Clear() private ManualResetEventSlim _preserveDebugStopEvent; // Process runspace debugger - private Lazy> _runspaceDebugQueue = new Lazy>(); + private Lazy> _runspaceDebugQueue = new Lazy>(); private volatile Int32 _processingRunspaceDebugQueue; private ManualResetEventSlim _runspaceDebugCompleteEvent; @@ -2799,17 +2788,15 @@ internal override void StopDebugRunspace(Runspace runspace) /// /// Adds the provided Runspace object to the runspace debugger processing queue. - /// The queue will then raise the ProcessRunspaceDebug events for each runspace to allow + /// The queue will then raise the StartRunspaceDebugProcessing events for each runspace to allow /// a host script debugger implementation to provide an active debugging session. /// /// Runspace to debug internal override void QueueRunspaceForDebug(Runspace runspace) { - if (runspace == null) { throw new PSArgumentNullException("runspace"); } - runspace.StateChanged += RunspaceStateChangedHandler; runspace.AvailabilityChanged += RunspaceAvailabilityChangedHandler; - _runspaceDebugQueue.Value.Enqueue(new ProcessRunspaceDebugEventArgs(runspace)); + _runspaceDebugQueue.Value.Enqueue(new StartRunspaceDebugProcessingEventArgs(runspace)); StartRunspaceForDebugQueueProcessing(); } @@ -2819,14 +2806,15 @@ internal override void QueueRunspaceForDebug(Runspace runspace) /// public override void CancelDebuggerProcessing() { + // Empty runspace debugger processing queue and then notify any subscribers. + ReleaseInternalRunspaceDebugProcessing(null, true); + try { - RaiseCancelProcessRunspaceDebugEvent(); + RaiseCancelRunspaceDebugProcessingEvent(); } catch (Exception) { } - - ReleaseInternalRunspaceDebugProcessing(null, true); } private void ReleaseInternalRunspaceDebugProcessing(object sender, bool emptyQueue = false) @@ -2840,7 +2828,7 @@ private void ReleaseInternalRunspaceDebugProcessing(object sender, bool emptyQue if (emptyQueue && _runspaceDebugQueue.IsValueCreated) { - ProcessRunspaceDebugEventArgs args; + StartRunspaceDebugProcessingEventArgs args; while (_runspaceDebugQueue.Value.TryDequeue(out args)) { args.Runspace.StateChanged -= RunspaceStateChangedHandler; @@ -3189,10 +3177,6 @@ private Debugger PopActiveDebugger() _steppingMode = SteppingMode.StepIn; _overOrOutFrame = _nestedRunningFrame; _nestedRunningFrame = null; - if (_lastActiveDebuggerAction == DebuggerResumeAction.StepOut) - { - CancelDebuggerProcessingAsNeeded(poppedDebugger); - } break; case DebuggerResumeAction.Stop: @@ -3215,20 +3199,6 @@ private Debugger PopActiveDebugger() return poppedDebugger; } - private void CancelDebuggerProcessingAsNeeded(Debugger debugger) - { - NestedRunspaceDebugger nestedDebugger = debugger as NestedRunspaceDebugger; - if (nestedDebugger != null) - { - var callstack = nestedDebugger.GetRSCallStack(); - if (callstack.Count == 1) - { - // A final frame step-out should clear any runspace debug processing. - CancelDebuggerProcessing(); - } - } - } - private void HandleActiveJobDebuggerStop(object sender, DebuggerStopEventArgs args) { // If we are debugging nested runspaces then ignore job debugger stops @@ -3881,25 +3851,25 @@ private void StartRunspaceForDebugQueueProcessing() private void DebuggerQueueThreadProc() { - ProcessRunspaceDebugEventArgs runspaceDebugProcessArgs; + StartRunspaceDebugProcessingEventArgs runspaceDebugProcessArgs; while (_runspaceDebugQueue.Value.TryDequeue(out runspaceDebugProcessArgs)) { if (IsProcessRunspaceDebugEventSubscribed()) { try { - RaiseProcessRunspaceDebugEvent(runspaceDebugProcessArgs); + RaiseStartRunspaceDebugProcessingEvent(runspaceDebugProcessArgs); } catch (Exception) { } } else { // If there are no ProcessDebugger event subscribers then default to handling internally. - runspaceDebugProcessArgs.HandleInternally = true; + runspaceDebugProcessArgs.UseDefaultProcessing = true; } // Check for internal handling request. - if (runspaceDebugProcessArgs.HandleInternally) + if (runspaceDebugProcessArgs.UseDefaultProcessing) { try { @@ -3938,7 +3908,7 @@ private void ProcessRunspaceDebugInternally(Runspace runspace) StopDebugRunspace(runspace); - RaiseProcessRunspaceDebugEndEvent( + RaiseRunspaceProcessingCompletedEvent( new ProcessRunspaceDebugEndEventArgs(runspace)); } diff --git a/src/System.Management.Automation/engine/remoting/client/remoterunspace.cs b/src/System.Management.Automation/engine/remoting/client/remoterunspace.cs index 416b03dde2d..5e9cc54a540 100644 --- a/src/System.Management.Automation/engine/remoting/client/remoterunspace.cs +++ b/src/System.Management.Automation/engine/remoting/client/remoterunspace.cs @@ -1351,12 +1351,7 @@ private bool SetDebugInfo(PSPrimitiveDictionary psApplicationPrivateData) var psVersionTable = psApplicationPrivateData[PSVersionInfo.PSVersionTableName] as PSPrimitiveDictionary; if (psVersionTable.ContainsKey(PSVersionInfo.PSVersionName)) { - var psVersionInfo = psVersionTable[PSVersionInfo.PSVersionName]; - var baseValue = PSObject.Base(psVersionInfo); - if (baseValue != null) - { - ServerVersion = baseValue as Version; - } + ServerVersion = PSObject.Base(psVersionTable[PSVersionInfo.PSVersionName]) as Version; } } } diff --git a/src/System.Management.Automation/engine/remoting/commands/InvokeCommandCommand.cs b/src/System.Management.Automation/engine/remoting/commands/InvokeCommandCommand.cs index 6adbfe47470..0b7640c97c3 100644 --- a/src/System.Management.Automation/engine/remoting/commands/InvokeCommandCommand.cs +++ b/src/System.Management.Automation/engine/remoting/commands/InvokeCommandCommand.cs @@ -1379,7 +1379,7 @@ private void CreateAndRunSyncJob() } } - private void HandleRunspaceDebugStop(object sender, ProcessRunspaceDebugEventArgs args) + private void HandleRunspaceDebugStop(object sender, StartRunspaceDebugProcessingEventArgs args) { var operation = sender as IThrottleOperation; operation.RunspaceDebugStop -= HandleRunspaceDebugStop; diff --git a/src/System.Management.Automation/engine/remoting/common/throttlemanager.cs b/src/System.Management.Automation/engine/remoting/common/throttlemanager.cs index 1549f8f8dc4..808916d9d14 100644 --- a/src/System.Management.Automation/engine/remoting/common/throttlemanager.cs +++ b/src/System.Management.Automation/engine/remoting/common/throttlemanager.cs @@ -154,7 +154,7 @@ internal bool RunspaceDebugStepInEnabled /// /// Event raised when operation runspace enters a debugger stopped state. /// - internal event EventHandler RunspaceDebugStop; + internal event EventHandler RunspaceDebugStop; /// /// RaiseRunspaceDebugStopEvent @@ -162,7 +162,7 @@ internal bool RunspaceDebugStepInEnabled /// Runspace internal void RaiseRunspaceDebugStopEvent(System.Management.Automation.Runspaces.Runspace runspace) { - RunspaceDebugStop.SafeInvoke(this, new ProcessRunspaceDebugEventArgs(runspace)); + RunspaceDebugStop.SafeInvoke(this, new StartRunspaceDebugProcessingEventArgs(runspace)); } diff --git a/test/powershell/engine/Remoting/InvokeCommandRemoteDebug.Tests.ps1 b/test/powershell/engine/Remoting/InvokeCommandRemoteDebug.Tests.ps1 index c4dfdcff96c..7bed91b6c1e 100644 --- a/test/powershell/engine/Remoting/InvokeCommandRemoteDebug.Tests.ps1 +++ b/test/powershell/engine/Remoting/InvokeCommandRemoteDebug.Tests.ps1 @@ -87,12 +87,12 @@ if ($IsWindows) var output = new PSDataCollection(); debugger.ProcessCommand(command, output); } - private void HandleProcessRunspaceDebug(object sender, ProcessRunspaceDebugEventArgs args) + private void HandleStartRunspaceDebugProcessing(object sender, StartRunspaceDebugProcessingEventArgs args) { - args.HandleInternally = true; + args.UseDefaultProcessing = true; RunspaceDebugProcessingCount++; } - private void HandleCancelProcessRunspaceDebug(object sender, EventArgs args) + private void HandleCancelRunspaceDebugProcessing(object sender, EventArgs args) { RunspaceDebugProcessCancelled = true; } @@ -100,15 +100,15 @@ if ($IsWindows) { _runspace = runspace; _runspace.Debugger.DebuggerStop += HandleDebuggerStop; - _runspace.Debugger.ProcessRunspaceDebug += HandleProcessRunspaceDebug; - _runspace.Debugger.CancelProcessRunspaceDebug += HandleCancelProcessRunspaceDebug; + _runspace.Debugger.StartRunspaceDebugProcessing += HandleStartRunspaceDebugProcessing; + _runspace.Debugger.CancelRunspaceDebugProcessing += HandleCancelRunspaceDebugProcessing; } public void Release() { if (_runspace == null) { return; } _runspace.Debugger.DebuggerStop -= HandleDebuggerStop; - _runspace.Debugger.ProcessRunspaceDebug -= HandleProcessRunspaceDebug; - _runspace.Debugger.CancelProcessRunspaceDebug -= HandleCancelProcessRunspaceDebug; + _runspace.Debugger.StartRunspaceDebugProcessing -= HandleStartRunspaceDebugProcessing; + _runspace.Debugger.CancelRunspaceDebugProcessing -= HandleCancelRunspaceDebugProcessing; } public override DebuggerCommandResults ProcessCommand(PSCommand command, PSDataCollection output) { return null; } @@ -127,7 +127,7 @@ Describe "Invoke-Command remote debugging tests" -Tags 'Feature' { if (!$IsWindows) { $originalDefaultParameterValues = $PSDefaultParameterValues.Clone() - $PSDefaultParameterValues["it:skip"] = $true + $PSDefaultParameterValues["it:Pending"] = $true } else { @@ -176,7 +176,11 @@ Describe "Invoke-Command remote debugging tests" -Tags 'Feature' { It "Verifies that asynchronous 'Invoke-Command -RemoteDebug' is ignored" { - $ps.AddCommand("Invoke-Command").AddParameter("Session", $remoteSession).AddParameter("ScriptBlock", $sb).AddParameter("RemoteDebug", $true).AddParameter("AsJob", $true) + $ps.AddCommand("Invoke-Command"). + AddParameter("Session", $remoteSession). + AddParameter("ScriptBlock", $sb). + AddParameter("RemoteDebug", $true). + AddParameter("AsJob", $true) $result = $ps.Invoke() $testDebugger.DebugStopCount | Should Be 0 } @@ -184,7 +188,10 @@ Describe "Invoke-Command remote debugging tests" -Tags 'Feature' { It "Verifies that synchronous 'Invoke-Command -RemoteDebug' invokes debugger" { $ps.Commands.Clear() - $ps.AddCommand("Invoke-Command").AddParameter("Session", $remoteSession).AddParameter("ScriptBlock", $sb).AddParameter("RemoteDebug", $true) + $ps.AddCommand("Invoke-Command"). + AddParameter("Session", $remoteSession). + AddParameter("ScriptBlock", $sb). + AddParameter("RemoteDebug", $true) $result = $ps.Invoke() $testDebugger.RunspaceDebugProcessingCount | Should Be 1 $testDebugger.DebugStopCount | Should Be 1 @@ -198,7 +205,10 @@ Describe "Invoke-Command remote debugging tests" -Tags 'Feature' { It "Verifies that 'Invoke-Command -RemoteDebug' running in a runspace without PSHost is ignored" { - $ps2.AddCommand("Invoke-Command").AddParameter("Session", $remoteSession).AddParameter("ScriptBlock", $sb).AddParameter("RemoteDebug", $true) + $ps2.AddCommand("Invoke-Command"). + AddParameter("Session", $remoteSession). + AddParameter("ScriptBlock", $sb). + AddParameter("RemoteDebug", $true) $result = $ps2.Invoke() $result | Should Be "Hello!" } From 984cffa1ee9a348eb9032010c9fe043b1997fd73 Mon Sep 17 00:00:00 2001 From: PaulHigin Date: Wed, 1 Feb 2017 13:57:04 -0800 Subject: [PATCH 5/7] Missed one case from Code Review comments --- src/System.Management.Automation/engine/debugger/debugger.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/System.Management.Automation/engine/debugger/debugger.cs b/src/System.Management.Automation/engine/debugger/debugger.cs index ef3237e7e5b..33cc6c1fdcd 100644 --- a/src/System.Management.Automation/engine/debugger/debugger.cs +++ b/src/System.Management.Automation/engine/debugger/debugger.cs @@ -578,7 +578,7 @@ protected void RaiseRunspaceProcessingCompletedEvent(ProcessRunspaceDebugEndEven } /// - protected bool IsProcessRunspaceDebugEventSubscribed() + protected bool IsStartRunspaceDebugProcessingEventSubscribed() { return (StartRunspaceDebugProcessing != null); } @@ -3854,7 +3854,7 @@ private void DebuggerQueueThreadProc() StartRunspaceDebugProcessingEventArgs runspaceDebugProcessArgs; while (_runspaceDebugQueue.Value.TryDequeue(out runspaceDebugProcessArgs)) { - if (IsProcessRunspaceDebugEventSubscribed()) + if (IsStartRunspaceDebugProcessingEventSubscribed()) { try { From e49287901089dd1cef8c9d58f08091fb943b181f Mon Sep 17 00:00:00 2001 From: PaulHigin Date: Fri, 10 Feb 2017 11:14:53 -0800 Subject: [PATCH 6/7] Fixed step-out being ignored because debugger is disabled. --- .../engine/debugger/debugger.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/System.Management.Automation/engine/debugger/debugger.cs b/src/System.Management.Automation/engine/debugger/debugger.cs index 33cc6c1fdcd..d8338324ba9 100644 --- a/src/System.Management.Automation/engine/debugger/debugger.cs +++ b/src/System.Management.Automation/engine/debugger/debugger.cs @@ -3908,6 +3908,13 @@ private void ProcessRunspaceDebugInternally(Runspace runspace) StopDebugRunspace(runspace); + // If we return to local script execution in step mode then ensure the debugger is enabled. + _nestedDebuggerStop = false; + if ((_steppingMode == SteppingMode.StepIn) && (_currentDebuggerAction != DebuggerResumeAction.Stop) && (_context._debuggingMode == 0)) + { + SetInternalDebugMode(InternalDebugMode.Enabled); + } + RaiseRunspaceProcessingCompletedEvent( new ProcessRunspaceDebugEndEventArgs(runspace)); } From a343799bc2f3b361a1fdc9a99fe3a39f13b537ea Mon Sep 17 00:00:00 2001 From: PaulHigin Date: Fri, 10 Feb 2017 15:18:36 -0800 Subject: [PATCH 7/7] CR test feedback --- .../Remoting/InvokeCommandRemoteDebug.Tests.ps1 | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/test/powershell/engine/Remoting/InvokeCommandRemoteDebug.Tests.ps1 b/test/powershell/engine/Remoting/InvokeCommandRemoteDebug.Tests.ps1 index 7bed91b6c1e..dfcd9decc8e 100644 --- a/test/powershell/engine/Remoting/InvokeCommandRemoteDebug.Tests.ps1 +++ b/test/powershell/engine/Remoting/InvokeCommandRemoteDebug.Tests.ps1 @@ -4,11 +4,8 @@ if ($IsWindows) { - if (-not (Get-Module TestRemoting -ErrorAction SilentlyContinue)) - { - $remotingModule = Join-Path $PSScriptRoot "../Common/TestRemoting.psm1" - Import-Module $remotingModule - } + $remotingModule = Join-Path $PSScriptRoot "../Common/TestRemoting.psm1" + Import-Module $remotingModule -ErrorAction SilentlyContinue $typeDef = @' using System; @@ -174,6 +171,11 @@ Describe "Invoke-Command remote debugging tests" -Tags 'Feature' { } } + AfterEach { + $ps.Commands.Clear() + $ps2.Commands.Clear() + } + It "Verifies that asynchronous 'Invoke-Command -RemoteDebug' is ignored" { $ps.AddCommand("Invoke-Command"). @@ -187,7 +189,6 @@ Describe "Invoke-Command remote debugging tests" -Tags 'Feature' { It "Verifies that synchronous 'Invoke-Command -RemoteDebug' invokes debugger" { - $ps.Commands.Clear() $ps.AddCommand("Invoke-Command"). AddParameter("Session", $remoteSession). AddParameter("ScriptBlock", $sb).