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 (Mandatory )]
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 ($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 debug session" ),
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 ($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+ }
0 commit comments