From 3adbd64bdcdb82ca0cbbeb15688e16a4c5e27c26 Mon Sep 17 00:00:00 2001 From: Jordan Borean Date: Tue, 29 Jul 2025 10:34:20 +1000 Subject: [PATCH 1/2] Add property and event for debug attach Adds the `IsRemoteDebuggerAttached` property to `Runspace` that indicates whether a debugger is attached to the Runspace through the `Debug-Runspace` cmdlet. Also adds a new engine event `PowerShell.OnDebugAttach` that is emitted when the debugger is attached. --- .../commands/utility/DebugRunspaceCommand.cs | 10 +++++++ .../engine/EventManager.cs | 7 ++++- .../engine/hostifaces/Connection.cs | 6 +++++ .../Debug-Runspace.Tests.ps1 | 27 +++++++++++++++++++ 4 files changed, 49 insertions(+), 1 deletion(-) diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/DebugRunspaceCommand.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/DebugRunspaceCommand.cs index f5d84dfd195..701ba91f857 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/DebugRunspaceCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/DebugRunspaceCommand.cs @@ -265,6 +265,15 @@ private void WaitAndReceiveRunspaceOutput() // Set up host script debugger to debug the runspace. _debugger.DebugRunspace(_runspace, breakAll: BreakAll); + _runspace.IsRemoteDebuggerAttached = true; + _runspace.Events?.GenerateEvent( + PSEngineEvent.OnDebugAttach, + null, + Array.Empty(), + null, + true, + false); + while (_debugging) { // Wait for running script. @@ -307,6 +316,7 @@ private void WaitAndReceiveRunspaceOutput() { _runspace.AvailabilityChanged -= HandleRunspaceAvailabilityChanged; _debugger.NestedDebuggingCancelledEvent -= HandleDebuggerNestedDebuggingCancelledEvent; + _runspace.IsRemoteDebuggerAttached = false; _debugger.StopDebugRunspace(_runspace); _newRunningScriptEvent.Dispose(); } diff --git a/src/System.Management.Automation/engine/EventManager.cs b/src/System.Management.Automation/engine/EventManager.cs index 75538135b92..c9cafb12782 100644 --- a/src/System.Management.Automation/engine/EventManager.cs +++ b/src/System.Management.Automation/engine/EventManager.cs @@ -1830,6 +1830,11 @@ private PSEngineEvent() { } /// public const string OnIdle = "PowerShell.OnIdle"; + /// + /// Called when Debug-Runspace has attached a debugger to the current runspace. + /// + public const string OnDebugAttach = "PowerShell.OnDebugAttach"; + /// /// Called during scriptblock invocation. /// @@ -1843,7 +1848,7 @@ private PSEngineEvent() { } /// /// A HashSet that contains all engine event names. /// - internal static readonly HashSet EngineEvents = new HashSet(StringComparer.OrdinalIgnoreCase) { Exiting, OnIdle, OnScriptBlockInvoke }; + internal static readonly HashSet EngineEvents = new HashSet(StringComparer.OrdinalIgnoreCase) { Exiting, OnIdle, OnDebugAttach, OnScriptBlockInvoke }; } /// diff --git a/src/System.Management.Automation/engine/hostifaces/Connection.cs b/src/System.Management.Automation/engine/hostifaces/Connection.cs index 3f5911d1c62..ccb918c7dd9 100644 --- a/src/System.Management.Automation/engine/hostifaces/Connection.cs +++ b/src/System.Management.Automation/engine/hostifaces/Connection.cs @@ -761,6 +761,12 @@ public string Name /// Gets the Runspace Id. /// public int Id { get; } + + /// + /// Gets and sets a boolean indicating whether the runspace has a + /// debugger attached with Debug-Runspace. + /// + public bool IsRemoteDebuggerAttached { get; internal set; } /// /// Returns protocol version that the remote server uses for PS remoting. diff --git a/test/powershell/Modules/Microsoft.PowerShell.Utility/Debug-Runspace.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Utility/Debug-Runspace.Tests.ps1 index 8aa28b00aef..2b517e4f301 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Utility/Debug-Runspace.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Utility/Debug-Runspace.Tests.ps1 @@ -31,6 +31,33 @@ Describe "Debug-Runspace" -Tag "CI" { $rs1.Debugger.SetDebugMode("None") { Debug-Runspace -Runspace $rs1 -ErrorAction stop } | Should -Throw -ErrorId "InvalidOperation,Microsoft.PowerShell.Commands.DebugRunspaceCommand" } + + It "Should write attach event and mark runspace as having a remote debugger attached" { + $onAttachName = [System.Management.Automation.PSEngineEvent]::OnDebugAttach + + $debugTarget = [PowerShell]::Create() + $null = $debugTarget.AddCommand('Wait-Event').AddParameter('SourceIdentifier', $onAttachName) + $waitTask = $debugTarget.BeginInvoke() + $debugTarget.Runspace.IsRemoteDebuggerAttached | Should -BeFalse + + $debugger = [PowerShell]::Create() + $null = $debugger.AddCommand('Debug-Runspace').AddParameter('Id', $debugTarget.Runspace.Id) + $debugTask = $debugger.BeginInvoke() + + $waitTask.AsyncWaitHandle.WaitOne(5000) | Should -BeTrue + $waitInfo = $debugTarget.EndInvoke($waitTask) + $waitInfo.SourceIdentifier | Should -Be $onAttachName + + $debugTarget.Runspace.IsRemoteDebuggerAttached | Should -BeTrue + + $debugger.Stop() + $exp = { + $debugger.EndInvoke($debugTask) + } | Should -Throw -PassThru + $exp.FullyQualifiedErrorId | Should -Be "PipelineStoppedException" + + $debugTarget.Runspace.IsRemoteDebuggerAttached | Should -BeFalse + } } From 5c0cd99da8a1304d95429956fa9c5b0e362f2541 Mon Sep 17 00:00:00 2001 From: Jordan Borean Date: Wed, 8 Oct 2025 07:22:15 +1000 Subject: [PATCH 2/2] Apply suggestions from code review Co-authored-by: Patrick Meinecke --- .../commands/utility/DebugRunspaceCommand.cs | 10 +++++----- .../engine/EventManager.cs | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/DebugRunspaceCommand.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/DebugRunspaceCommand.cs index 701ba91f857..756afff13de 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/DebugRunspaceCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/DebugRunspaceCommand.cs @@ -268,11 +268,11 @@ private void WaitAndReceiveRunspaceOutput() _runspace.IsRemoteDebuggerAttached = true; _runspace.Events?.GenerateEvent( PSEngineEvent.OnDebugAttach, - null, - Array.Empty(), - null, - true, - false); + sender: null, + args: Array.Empty(), + extraData: null, + processInCurrentThread: true, + waitForCompletionInCurrentThread: false); while (_debugging) { diff --git a/src/System.Management.Automation/engine/EventManager.cs b/src/System.Management.Automation/engine/EventManager.cs index c9cafb12782..e72a34cef1c 100644 --- a/src/System.Management.Automation/engine/EventManager.cs +++ b/src/System.Management.Automation/engine/EventManager.cs @@ -1831,7 +1831,7 @@ private PSEngineEvent() { } public const string OnIdle = "PowerShell.OnIdle"; /// - /// Called when Debug-Runspace has attached a debugger to the current runspace. + /// Called when Debug-Runspace has attached a debugger to the current runspace. /// public const string OnDebugAttach = "PowerShell.OnDebugAttach";