@@ -18,6 +18,154 @@ namespace Microsoft.PowerShell.EditorServices.Services
1818{
1919 internal class BreakpointService
2020 {
21+ private const string _getPSBreakpointLegacy = @"
22+ [CmdletBinding()]
23+ param (
24+ [Parameter()]
25+ [string]
26+ $Script,
27+
28+ [Parameter()]
29+ [int]
30+ $RunspaceId = [Runspace]::DefaultRunspace.Id
31+ )
32+
33+ $runspace = if ($PSBoundParameters.ContainsKey('RunspaceId')) {
34+ Get-Runspace -Id $RunspaceId
35+ $null = $PSBoundParameters.Remove('RunspaceId')
36+ }
37+ else {
38+ [Runspace]::DefaultRunspace
39+ }
40+
41+ $debugger = $runspace.Debugger
42+ $getBreakpointsMeth = $debugger.GetType().GetMethod(
43+ 'GetBreakpoints',
44+ [System.Reflection.BindingFlags]'NonPublic, Public, Instance',
45+ $null,
46+ [type[]]@(),
47+ $null)
48+
49+ $runspaceIdProp = [System.Management.Automation.PSNoteProperty]::new(
50+ 'RunspaceId',
51+ $runspaceId)
52+
53+ @(
54+ if (-not $getBreakpointsMeth) {
55+ if ($RunspaceId -ne [Runspace]::DefaultRunspace.Id) {
56+ throw 'Failed to find GetBreakpoints method on Debugger.'
57+ }
58+
59+ Microsoft.PowerShell.Utility\Get-PSBreakpoint @PSBoundParameters
60+ }
61+ else {
62+ $getBreakpointsMeth.Invoke($debugger, @()) | Where-Object {
63+ if ($Script) {
64+ $_.Script -eq $Script
65+ }
66+ else {
67+ $true
68+ }
69+ }
70+ }
71+ ) | ForEach-Object {
72+ $_.PSObject.Properties.Add($runspaceIdProp)
73+ $_
74+ }
75+ " ;
76+
77+ private const string _removePSBreakpointLegacy = @"
78+ [CmdletBinding(DefaultParameterSetName = 'Breakpoint')]
79+ param (
80+ [Parameter(Mandatory, ValueFromPipeline, ParameterSetName = 'Breakpoint')]
81+ [System.Management.Automation.Breakpoint[]]
82+ $Breakpoint,
83+
84+ [Parameter(Mandatory, ValueFromPipeline, ParameterSetName = 'Id')]
85+ [int[]]
86+ $Id,
87+
88+ [Parameter(ParameterSetName = 'Id')]
89+ [int]
90+ $RunspaceId = [Runspace]::DefaultRunspace.Id
91+ )
92+
93+ begin {
94+ $removeBreakpointMeth = [Runspace]::DefaultRunspace.Debugger.GetType().GetMethod(
95+ 'RemoveBreakpoint',
96+ [System.Reflection.BindingFlags]'NonPublic, Public, Instance',
97+ $null,
98+ [type[]]@([System.Management.Automation.Breakpoint]),
99+ $null)
100+ $getBreakpointMeth = [Runspace]::DefaultRunspace.Debugger.GetType().GetMethod(
101+ 'GetBreakpoint',
102+ [System.Reflection.BindingFlags]'NonPublic, Public, Instance',
103+ $null,
104+ [type[]]@([int]),
105+ $null)
106+
107+ $breakpointCollection = [System.Collections.Generic.List[System.Management.Automation.Breakpoint]]::new()
108+ }
109+
110+ process {
111+ if ($PSCmdlet.ParameterSetName -eq 'Id') {
112+ $runspace = Get-Runspace -Id $RunspaceId
113+ $runspaceProp = [System.Management.Automation.PSNoteProperty]::new(
114+ 'Runspace',
115+ $Runspace)
116+
117+ $breakpoints = if ($getBreakpointMeth) {
118+ foreach ($breakpointId in $Id) {
119+ $getBreakpointMeth.Invoke($runspace.Debugger, @($breakpointId))
120+ }
121+ }
122+ elseif ($runspace -eq [Runspace]::DefaultRunspace) {
123+ Microsoft.PowerShell.Utility\Get-PSBreakpoint -Id $Id
124+ }
125+ else {
126+ throw 'Failed to find GetBreakpoint method on Debugger.'
127+ }
128+
129+ $breakpoints | ForEach-Object {
130+ $_.PSObject.Properties.Add($runspaceProp)
131+ $breakpointCollection.Add($_)
132+ }
133+ }
134+ else {
135+ foreach ($b in $Breakpoint) {
136+ # RunspaceId may be set by _getPSBreakpointLegacy when
137+ # targeting a breakpoint in a specific runspace.
138+ $runspace = if ($b.PSObject.Properties.Match('RunspaceId')) {
139+ Get-Runspace -Id $b.RunspaceId
140+ }
141+ else {
142+ [Runspace]::DefaultRunspace
143+ }
144+
145+ $b.PSObject.Properties.Add(
146+ [System.Management.Automation.PSNoteProperty]::new('Runspace', $runspace))
147+ $breakpointCollection.Add($b)
148+ }
149+ }
150+ }
151+
152+ end {
153+ foreach ($b in $breakpointCollection) {
154+ if ($removeBreakpointMeth) {
155+ $removeBreakpointMeth.Invoke($b.Runspace.Debugger, @($b))
156+ }
157+ elseif ($b.Runspace -eq [Runspace]::DefaultRunspace) {
158+ # If we don't have the method, we can only remove breakpoints
159+ # from the default runspace using Remove-PSBreakpoint.
160+ $b | Microsoft.PowerShell.Utility\Remove-PSBreakpoint
161+ }
162+ else {
163+ throw 'Failed to find RemoveBreakpoint method on Debugger.'
164+ }
165+ }
166+ }
167+ " ;
168+
21169 /// <summary>
22170 /// Code used on WinPS 5.1 to set breakpoints without Script path validation.
23171 /// It uses reflection because the APIs were not public until 7.0 but just in
@@ -45,7 +193,11 @@ internal class BreakpointService
45193
46194 [Parameter(ParameterSetName = 'Command', Mandatory = $true)]
47195 [string]
48- $Command
196+ $Command,
197+
198+ [Parameter()]
199+ [int]
200+ $RunspaceId
49201 )
50202
51203 if ($Script) {
@@ -65,6 +217,9 @@ internal class BreakpointService
65217 $null)
66218
67219 if (-not $cmdCtor) {
220+ if ($PSBoundParameters.ContainsKey('RunspaceId')) {
221+ throw 'Failed to find constructor for CommandBreakpoint.'
222+ }
68223 Microsoft.PowerShell.Utility\Set-PSBreakpoint @PSBoundParameters
69224 return
70225 }
@@ -82,15 +237,24 @@ internal class BreakpointService
82237 $null)
83238
84239 if (-not $lineCtor) {
240+ if ($PSBoundParameters.ContainsKey('RunspaceId')) {
241+ throw 'Failed to find constructor for LineBreakpoint.'
242+ }
85243 Microsoft.PowerShell.Utility\Set-PSBreakpoint @PSBoundParameters
86244 return
87245 }
88246
89247 $b = $lineCtor.Invoke(@($Script, $Line, $Column, $Action))
90248 }
91249
92- [Runspace]::DefaultRunspace.Debugger.SetBreakpoints(
93- [System.Management.Automation.Breakpoint[]]@($b))
250+ $runspace = if ($PSBoundParameters.ContainsKey('RunspaceId')) {
251+ Get-Runspace -Id $RunspaceId
252+ }
253+ else {
254+ [Runspace]::DefaultRunspace
255+ }
256+
257+ $runspace.Debugger.SetBreakpoints([System.Management.Automation.Breakpoint[]]@($b))
94258
95259 $b
96260 " ;
@@ -128,10 +292,14 @@ public async Task<IReadOnlyList<Breakpoint>> GetBreakpointsAsync()
128292 }
129293
130294 // Legacy behavior
131- PSCommand psCommand = new PSCommand ( ) . AddCommand ( @"Microsoft.PowerShell.Utility\Get-PSBreakpoint" ) ;
295+ PSCommand psCommand = new PSCommand ( ) . AddScript ( _getPSBreakpointLegacy , useLocalScope : true ) ;
296+ if ( _debugStateService . RunspaceId is not null )
297+ {
298+ psCommand . AddParameter ( "RunspaceId" , _debugStateService . RunspaceId . Value ) ;
299+ }
132300 return await _executionService
133- . ExecutePSCommandAsync < Breakpoint > ( psCommand , CancellationToken . None )
134- . ConfigureAwait ( false ) ;
301+ . ExecutePSCommandAsync < Breakpoint > ( psCommand , CancellationToken . None )
302+ . ConfigureAwait ( false ) ;
135303 }
136304
137305 public async Task < IReadOnlyList < BreakpointDetails > > SetBreakpointsAsync ( IReadOnlyList < BreakpointDetails > breakpoints )
@@ -211,6 +379,11 @@ public async Task<IReadOnlyList<BreakpointDetails>> SetBreakpointsAsync(IReadOnl
211379 {
212380 psCommand . AddParameter ( "Action" , actionScriptBlock ) ;
213381 }
382+
383+ if ( _debugStateService . RunspaceId is not null )
384+ {
385+ psCommand . AddParameter ( "RunspaceId" , _debugStateService . RunspaceId . Value ) ;
386+ }
214387 }
215388
216389 // If no PSCommand was created then there are no breakpoints to set.
@@ -335,14 +508,17 @@ public async Task RemoveAllBreakpointsAsync(string scriptPath = null)
335508 }
336509
337510 // Legacy behavior
338- PSCommand psCommand = new PSCommand ( ) . AddCommand ( @"Microsoft.PowerShell.Utility\Get-PSBreakpoint" ) ;
339-
511+ PSCommand psCommand = new PSCommand ( ) . AddScript ( _getPSBreakpointLegacy , useLocalScope : true ) ;
512+ if ( _debugStateService . RunspaceId is not null )
513+ {
514+ psCommand . AddParameter ( "RunspaceId" , _debugStateService . RunspaceId . Value ) ;
515+ }
340516 if ( ! string . IsNullOrEmpty ( scriptPath ) )
341517 {
342518 psCommand . AddParameter ( "Script" , scriptPath ) ;
343519 }
344520
345- psCommand . AddCommand ( @"Microsoft.PowerShell.Utility\Remove-PSBreakpoint" ) ;
521+ psCommand . AddScript ( _removePSBreakpointLegacy , useLocalScope : true ) ;
346522 await _executionService . ExecutePSCommandAsync < object > ( psCommand , CancellationToken . None ) . ConfigureAwait ( false ) ;
347523 }
348524 catch ( Exception e )
@@ -378,8 +554,12 @@ public async Task RemoveBreakpointsAsync(IEnumerable<Breakpoint> breakpoints)
378554 if ( breakpointIds . Any ( ) )
379555 {
380556 PSCommand psCommand = new PSCommand ( )
381- . AddCommand ( @"Microsoft.PowerShell.Utility\Remove-PSBreakpoint" )
557+ . AddScript ( _removePSBreakpointLegacy , useLocalScope : true )
382558 . AddParameter ( "Id" , breakpoints . Select ( b => b . Id ) . ToArray ( ) ) ;
559+ if ( _debugStateService . RunspaceId is not null )
560+ {
561+ psCommand . AddParameter ( "RunspaceId" , _debugStateService . RunspaceId . Value ) ;
562+ }
383563 await _executionService . ExecutePSCommandAsync < object > ( psCommand , CancellationToken . None ) . ConfigureAwait ( false ) ;
384564 }
385565 }
0 commit comments