Skip to content

Commit 79ff7dd

Browse files
jborean93Copilot
andauthored
Add Start-DebugAttachSession function (#2249)
* Add Start-DebugAttachSession function Adds the `Start-DebugAttachSession` cmdlet which can be used to launch a new attach session debug request inside an existing launched debug session. This allows a script being debugged to launch a child debugging session using more dynamic environment information without requiring end users to manually setup the launch.json configuration. * Apply suggestions from code review Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Remote mandatory param and update logic to exclude temp consoles * Update module/PowerShellEditorServices/Commands/Public/Start-DebugAttachSession.ps1 * Add startDebugging tests * Skip test in CLM * Add test timeout and -AsJob test --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
1 parent 48c996c commit 79ff7dd

File tree

8 files changed

+527
-2
lines changed

8 files changed

+527
-2
lines changed

module/PowerShellEditorServices/Commands/PowerShellEditorServices.Commands.psd1

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,8 @@ FunctionsToExport = @('Register-EditorCommand',
7979
'Test-ScriptExtent',
8080
'Open-EditorFile',
8181
'New-EditorFile',
82-
'Clear-Host')
82+
'Clear-Host',
83+
'Start-DebugAttachSession')
8384

8485
# Cmdlets to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no cmdlets to export.
8586
CmdletsToExport = @()
Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
# Copyright (c) Microsoft Corporation.
2+
# Licensed under the MIT License.
3+
4+
using namespace System.Collections
5+
using namespace System.Management.Automation
6+
using namespace System.Reflection
7+
using namespace System.Threading
8+
using namespace System.Threading.Tasks
9+
10+
function Start-DebugAttachSession {
11+
<#
12+
.EXTERNALHELP ..\PowerShellEditorServices.Commands-help.xml
13+
#>
14+
[OutputType([System.Management.Automation.Job2])]
15+
[CmdletBinding(DefaultParameterSetName = 'ProcessId')]
16+
param(
17+
[Parameter()]
18+
[string]
19+
$Name,
20+
21+
[Parameter(ParameterSetName = 'ProcessId')]
22+
[int]
23+
$ProcessId,
24+
25+
[Parameter(ParameterSetName = 'CustomPipeName')]
26+
[string]
27+
$CustomPipeName,
28+
29+
[Parameter()]
30+
[string]
31+
$RunspaceName,
32+
33+
[Parameter()]
34+
[int]
35+
$RunspaceId,
36+
37+
[Parameter()]
38+
[string]
39+
$ComputerName,
40+
41+
[Parameter()]
42+
[switch]
43+
$AsJob
44+
)
45+
46+
$ErrorActionPreference = 'Stop'
47+
48+
try {
49+
if ($PSBoundParameters.ContainsKey('RunspaceId') -and $RunspaceName) {
50+
$err = [ErrorRecord]::new(
51+
[ArgumentException]::new("Cannot specify both RunspaceId and RunspaceName parameters"),
52+
"InvalidRunspaceParameters",
53+
[ErrorCategory]::InvalidArgument,
54+
$null)
55+
$err.ErrorDetails = [ErrorDetails]::new("")
56+
$err.ErrorDetails.RecommendedAction = 'Specify only one of RunspaceId or RunspaceName.'
57+
$PSCmdlet.WriteError($err)
58+
return
59+
}
60+
61+
# Var will be set by PSES in configurationDone before launching script
62+
$debugServer = Get-Variable -Name __psEditorServices_DebugServer -ValueOnly -ErrorAction Ignore
63+
if (-not $debugServer) {
64+
$err = [ErrorRecord]::new(
65+
[Exception]::new("Cannot start a new attach debug session unless running in an existing launch debug session not in a temporary console"),
66+
"NoDebugSession",
67+
[ErrorCategory]::InvalidOperation,
68+
$null)
69+
$err.ErrorDetails = [ErrorDetails]::new("")
70+
$err.ErrorDetails.RecommendedAction = 'Launch script with debugging to ensure the debug session is available.'
71+
$PSCmdlet.WriteError($err)
72+
return
73+
}
74+
75+
if ($AsJob -and -not (Get-Command -Name Start-ThreadJob -ErrorAction Ignore)) {
76+
$err = [ErrorRecord]::new(
77+
[Exception]::new("Cannot use the -AsJob parameter unless running on PowerShell 7+ or the ThreadJob module is present"),
78+
"NoThreadJob",
79+
[ErrorCategory]::InvalidArgument,
80+
$null)
81+
$err.ErrorDetails = [ErrorDetails]::new("")
82+
$err.ErrorDetails.RecommendedAction = 'Install the ThreadJob module or run on PowerShell 7+.'
83+
$PSCmdlet.WriteError($err)
84+
return
85+
}
86+
87+
$configuration = @{
88+
type = 'PowerShell'
89+
request = 'attach'
90+
# A temp console is also needed as the current one is busy running
91+
# this code. Failing to set this will cause a deadlock.
92+
createTemporaryIntegratedConsole = $true
93+
}
94+
95+
if ($ProcessId) {
96+
if ($ProcessId -eq $PID) {
97+
$err = [ErrorRecord]::new(
98+
[ArgumentException]::new("PSES does not support attaching to the current editor process"),
99+
"AttachToCurrentProcess",
100+
[ErrorCategory]::InvalidArgument,
101+
$PID)
102+
$err.ErrorDetails = [ErrorDetails]::new("")
103+
$err.ErrorDetails.RecommendedAction = 'Specify a different process id.'
104+
$PSCmdlet.WriteError($err)
105+
return
106+
}
107+
108+
$configuration.name = "Attach Process $ProcessId"
109+
$configuration.processId = $ProcessId
110+
}
111+
elseif ($CustomPipeName) {
112+
$configuration.name = "Attach Pipe $CustomPipeName"
113+
$configuration.customPipeName = $CustomPipeName
114+
}
115+
else {
116+
$configuration.name = 'Attach Session'
117+
}
118+
119+
if ($ComputerName) {
120+
$configuration.computerName = $ComputerName
121+
}
122+
123+
if ($PSBoundParameters.ContainsKey('RunspaceId')) {
124+
$configuration.runspaceId = $RunspaceId
125+
}
126+
elseif ($RunspaceName) {
127+
$configuration.runspaceName = $RunspaceName
128+
}
129+
130+
# https://microsoft.github.io/debug-adapter-protocol/specification#Reverse_Requests_StartDebugging
131+
$resp = $debugServer.SendRequest(
132+
'startDebugging',
133+
@{
134+
configuration = $configuration
135+
request = 'attach'
136+
}
137+
)
138+
139+
# PipelineStopToken added in pwsh 7.6
140+
$cancelToken = if ($PSCmdlet.PipelineStopToken) {
141+
$PSCmdlet.PipelineStopToken
142+
}
143+
else {
144+
[CancellationToken]::new($false)
145+
}
146+
147+
# There is no response for a startDebugging request
148+
$task = $resp.ReturningVoid($cancelToken)
149+
150+
$waitTask = {
151+
[CmdletBinding()]
152+
param ([Parameter(Mandatory)][Task]$Task)
153+
154+
while (-not $Task.AsyncWaitHandle.WaitOne(300)) {}
155+
$null = $Task.GetAwaiter().GetResult()
156+
}
157+
158+
if ($AsJob) {
159+
# Using the Ast to build the scriptblock allows the job to inherit
160+
# the using namespace entries and include the proper line/script
161+
# paths in any error traces that are emitted.
162+
Start-ThreadJob -ScriptBlock {
163+
& ($args[0]).Ast.GetScriptBlock() $args[1]
164+
} -ArgumentList $waitTask, $task
165+
}
166+
else {
167+
& $waitTask $task
168+
}
169+
}
170+
catch {
171+
$PSCmdlet.WriteError($_)
172+
return
173+
}
174+
}

module/docs/PowerShellEditorServices.Commands.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,10 @@ The Set-ScriptExtent function can insert or replace text at a specified position
4646

4747
You can use the Find-Ast function to easily find the desired extent.
4848

49+
### [Start-DebugAttachSession](Start-DebugAttachSession.md)
50+
51+
The Start-DebugAttachSession function can start a new debug session that is attached to the specified PowerShell instance.
52+
4953
### [Test-ScriptExtent](Test-ScriptExtent.md)
5054

5155
The Test-ScriptExtent function can be used to determine if a ScriptExtent object is before, after, or inside another ScriptExtent object. You can also test for any combination of these with separate ScriptExtent objects to test against.

0 commit comments

Comments
 (0)