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