@@ -76,17 +76,18 @@ internal record PsesAttachRequestArguments : AttachRequestArguments
76
76
{
77
77
public string ComputerName { get ; set ; }
78
78
79
- public string ProcessId { get ; set ; }
79
+ public int ProcessId { get ; set ; }
80
80
81
- public string RunspaceId { get ; set ; }
81
+ public int RunspaceId { get ; set ; }
82
82
83
83
public string RunspaceName { get ; set ; }
84
84
85
85
public string CustomPipeName { get ; set ; }
86
86
}
87
87
88
88
internal class LaunchAndAttachHandler : ILaunchHandler < PsesLaunchRequestArguments > , IAttachHandler < PsesAttachRequestArguments > , IOnDebugAdapterServerStarted
89
- {
89
+ {
90
+ private static readonly int currentProcessId = System . Diagnostics . Process . GetCurrentProcess ( ) . Id ;
90
91
private static readonly Version s_minVersionForCustomPipeName = new ( 6 , 2 ) ;
91
92
private readonly ILogger < LaunchAndAttachHandler > _logger ;
92
93
private readonly BreakpointService _breakpointService ;
@@ -190,7 +191,7 @@ public async Task<LaunchResponse> Handle(PsesLaunchRequestArguments request, Can
190
191
&& ! string . IsNullOrEmpty ( request . Script )
191
192
&& ScriptFile . IsUntitledPath ( request . Script ) )
192
193
{
193
- throw new RpcErrorException ( 0 , "Running an Untitled file in a temporary Extension Terminal is currently not supported." ) ;
194
+ throw new RpcErrorException ( 0 , null , "Running an Untitled file in a temporary Extension Terminal is currently not supported." ) ;
194
195
}
195
196
196
197
// If the current session is remote, map the script path to the remote
@@ -239,16 +240,12 @@ private async Task<AttachResponse> HandleImpl(PsesAttachRequestArguments request
239
240
{
240
241
// The debugger has officially started. We use this to later check if we should stop it.
241
242
( ( PsesInternalHost ) _executionService ) . DebugContext . IsActive = true ;
242
-
243
243
_debugStateService . IsAttachSession = true ;
244
-
245
244
_debugEventHandlerService . RegisterEventHandlers ( ) ;
246
245
247
- bool processIdIsSet = ! string . IsNullOrEmpty ( request . ProcessId ) && request . ProcessId != "undefined" ;
246
+ bool processIdIsSet = request . ProcessId != 0 ;
248
247
bool customPipeNameIsSet = ! string . IsNullOrEmpty ( request . CustomPipeName ) && request . CustomPipeName != "undefined" ;
249
248
250
- PowerShellVersionDetails runspaceVersion = _runspaceContext . CurrentRunspace . PowerShellVersionDetails ;
251
-
252
249
// If there are no host processes to attach to or the user cancels selection, we get a null for the process id.
253
250
// This is not an error, just a request to stop the original "attach to" request.
254
251
// Testing against "undefined" is a HACK because I don't know how to make "Cancel" on quick pick loading
@@ -258,40 +255,12 @@ private async Task<AttachResponse> HandleImpl(PsesAttachRequestArguments request
258
255
_logger . LogInformation (
259
256
$ "Attach request aborted, received { request . ProcessId } for processId.") ;
260
257
261
- throw new RpcErrorException ( 0 , "User aborted attach to PowerShell host process." ) ;
258
+ throw new RpcErrorException ( 0 , null , "User aborted attach to PowerShell host process." ) ;
262
259
}
263
260
264
- if ( request . ComputerName != null )
261
+ if ( ! string . IsNullOrEmpty ( request . ComputerName ) )
265
262
{
266
- if ( runspaceVersion . Version . Major < 4 )
267
- {
268
- throw new RpcErrorException ( 0 , $ "Remote sessions are only available with PowerShell 4 and higher (current session is { runspaceVersion . Version } ).") ;
269
- }
270
- else if ( _runspaceContext . CurrentRunspace . RunspaceOrigin != RunspaceOrigin . Local )
271
- {
272
- throw new RpcErrorException ( 0 , "Cannot attach to a process in a remote session when already in a remote session." ) ;
273
- }
274
-
275
- PSCommand enterPSSessionCommand = new PSCommand ( )
276
- . AddCommand ( "Enter-PSSession" )
277
- . AddParameter ( "ComputerName" , request . ComputerName ) ;
278
-
279
- try
280
- {
281
- await _executionService . ExecutePSCommandAsync (
282
- enterPSSessionCommand ,
283
- cancellationToken ,
284
- PowerShellExecutionOptions . ImmediateInteractive )
285
- . ConfigureAwait ( false ) ;
286
- }
287
- catch ( Exception e )
288
- {
289
- string msg = $ "Could not establish remote session to computer '{ request . ComputerName } '";
290
- _logger . LogError ( e , msg ) ;
291
- throw new RpcErrorException ( 0 , msg ) ;
292
- }
293
-
294
- _debugStateService . IsRemoteAttach = true ;
263
+ await AttachToComputer ( request . ComputerName , cancellationToken ) . ConfigureAwait ( false ) ;
295
264
}
296
265
297
266
// Set up a temporary runspace changed event handler so we can ensure
@@ -305,79 +274,38 @@ void RunspaceChangedHandler(object s, RunspaceChangedEventArgs _)
305
274
runspaceChanged . TrySetResult ( true ) ;
306
275
}
307
276
308
- _executionService . RunspaceChanged += RunspaceChangedHandler ;
309
-
310
- if ( processIdIsSet && int . TryParse ( request . ProcessId , out int processId ) && ( processId > 0 ) )
277
+ if ( processIdIsSet )
311
278
{
312
- if ( runspaceVersion . Version . Major < 5 )
279
+ // TODO: Implement support for breakpoints in runspaces in the extension terminal.
280
+ if ( request . ProcessId == currentProcessId )
313
281
{
314
- throw new RpcErrorException ( 0 , $ "Attaching to a process is only available with PowerShell 5 and higher (current session is { runspaceVersion . Version } ). ") ;
282
+ throw new RpcErrorException ( 0 , null , $ "Attaching to the Extension Terminal is not supported! ") ;
315
283
}
316
284
317
- PSCommand enterPSHostProcessCommand = new PSCommand ( )
318
- . AddCommand ( "Enter-PSHostProcess" )
319
- . AddParameter ( "Id" , processId ) ;
320
-
321
- try
322
- {
323
- await _executionService . ExecutePSCommandAsync (
324
- enterPSHostProcessCommand ,
325
- cancellationToken ,
326
- PowerShellExecutionOptions . ImmediateInteractive )
327
- . ConfigureAwait ( false ) ;
328
- }
329
- catch ( Exception e )
330
- {
331
- string msg = $ "Could not attach to process with Id: '{ request . ProcessId } '";
332
- _logger . LogError ( e , msg ) ;
333
- throw new RpcErrorException ( 0 , msg ) ;
334
- }
285
+ _executionService . RunspaceChanged += RunspaceChangedHandler ;
286
+ await AttachToProcess ( request . ProcessId , cancellationToken ) . ConfigureAwait ( false ) ;
287
+ await runspaceChanged . Task . ConfigureAwait ( false ) ;
335
288
}
336
289
else if ( customPipeNameIsSet )
337
290
{
338
- if ( runspaceVersion . Version < s_minVersionForCustomPipeName )
339
- {
340
- throw new RpcErrorException ( 0 , $ "Attaching to a process with CustomPipeName is only available with PowerShell 6.2 and higher (current session is { runspaceVersion . Version } ).") ;
341
- }
342
-
343
- PSCommand enterPSHostProcessCommand = new PSCommand ( )
344
- . AddCommand ( "Enter-PSHostProcess" )
345
- . AddParameter ( "CustomPipeName" , request . CustomPipeName ) ;
346
-
347
- try
348
- {
349
- await _executionService . ExecutePSCommandAsync (
350
- enterPSHostProcessCommand ,
351
- cancellationToken ,
352
- PowerShellExecutionOptions . ImmediateInteractive )
353
- . ConfigureAwait ( false ) ;
354
- }
355
- catch ( Exception e )
356
- {
357
- string msg = $ "Could not attach to process with CustomPipeName: '{ request . CustomPipeName } '";
358
- _logger . LogError ( e , msg ) ;
359
- throw new RpcErrorException ( 0 , msg ) ;
360
- }
291
+ _executionService . RunspaceChanged += RunspaceChangedHandler ;
292
+ await AttachToPipe ( request . CustomPipeName , cancellationToken ) . ConfigureAwait ( false ) ;
293
+ await runspaceChanged . Task . ConfigureAwait ( false ) ;
361
294
}
362
- else if ( request . ProcessId != "current" )
295
+ else
363
296
{
364
- _logger . LogError (
365
- $ "Attach request failed, '{ request . ProcessId } ' is an invalid value for the processId.") ;
366
-
367
- throw new RpcErrorException ( 0 , "A positive integer must be specified for the processId field." ) ;
297
+ throw new RpcErrorException ( 0 , null , "Invalid configuration with no process ID nor custom pipe name!" ) ;
368
298
}
369
299
370
- await runspaceChanged . Task . ConfigureAwait ( false ) ;
371
300
372
301
// Execute the Debug-Runspace command but don't await it because it
373
- // will block the debug adapter initialization process. The
302
+ // will block the debug adapter initialization process. The
374
303
// InitializedEvent will be sent as soon as the RunspaceChanged
375
304
// event gets fired with the attached runspace.
376
-
377
305
PSCommand debugRunspaceCmd = new PSCommand ( ) . AddCommand ( "Debug-Runspace" ) ;
378
- if ( request . RunspaceName != null )
306
+ if ( ! string . IsNullOrEmpty ( request . RunspaceName ) )
379
307
{
380
- PSCommand getRunspaceIdCommand = new PSCommand ( )
308
+ PSCommand psCommand = new PSCommand ( )
381
309
. AddCommand ( @"Microsoft.PowerShell.Utility\Get-Runspace" )
382
310
. AddParameter ( "Name" , request . RunspaceName )
383
311
. AddCommand ( @"Microsoft.PowerShell.Utility\Select-Object" )
@@ -386,7 +314,7 @@ await _executionService.ExecutePSCommandAsync(
386
314
try
387
315
{
388
316
IEnumerable < int ? > ids = await _executionService . ExecutePSCommandAsync < int ? > (
389
- getRunspaceIdCommand ,
317
+ psCommand ,
390
318
cancellationToken )
391
319
. ConfigureAwait ( false ) ;
392
320
@@ -395,38 +323,27 @@ await _executionService.ExecutePSCommandAsync(
395
323
_debugStateService . RunspaceId = id ;
396
324
break ;
397
325
398
- // TODO: If we don't end up setting this, we should throw
326
+ // TODO: If we don't end up setting this, we should throw!
399
327
}
400
328
}
401
- catch ( Exception getRunspaceException )
329
+ catch ( Exception e )
402
330
{
403
331
_logger . LogError (
404
- getRunspaceException ,
332
+ e ,
405
333
"Unable to determine runspace to attach to. Message: {message}" ,
406
- getRunspaceException . Message ) ;
334
+ e . Message ) ;
407
335
}
408
336
409
- // TODO: We have the ID, why not just use that?
410
337
debugRunspaceCmd . AddParameter ( "Name" , request . RunspaceName ) ;
411
338
}
412
- else if ( request . RunspaceId != null )
339
+ else if ( request . RunspaceId > 0 )
413
340
{
414
- if ( ! int . TryParse ( request . RunspaceId , out int runspaceId ) || runspaceId <= 0 )
415
- {
416
- _logger . LogError (
417
- $ "Attach request failed, '{ request . RunspaceId } ' is an invalid value for the processId.") ;
418
-
419
- throw new RpcErrorException ( 0 , "A positive integer must be specified for the RunspaceId field." ) ;
420
- }
421
-
422
- _debugStateService . RunspaceId = runspaceId ;
423
-
424
- debugRunspaceCmd . AddParameter ( "Id" , runspaceId ) ;
341
+ _debugStateService . RunspaceId = request . RunspaceId ;
342
+ debugRunspaceCmd . AddParameter ( "Id" , request . RunspaceId ) ;
425
343
}
426
344
else
427
345
{
428
346
_debugStateService . RunspaceId = 1 ;
429
-
430
347
debugRunspaceCmd . AddParameter ( "Id" , 1 ) ;
431
348
}
432
349
@@ -438,11 +355,89 @@ await _executionService.ExecutePSCommandAsync(
438
355
. ExecutePSCommandAsync ( debugRunspaceCmd , CancellationToken . None , PowerShellExecutionOptions . ImmediateInteractive )
439
356
. ContinueWith ( OnExecutionCompletedAsync , TaskScheduler . Default ) ;
440
357
441
- if ( runspaceVersion . Version . Major >= 7 )
358
+ _debugStateService . ServerStarted . TrySetResult ( true ) ;
359
+
360
+ return new AttachResponse ( ) ;
361
+ }
362
+
363
+ private async Task AttachToComputer ( string computerName , CancellationToken cancellationToken )
364
+ {
365
+ _debugStateService . IsRemoteAttach = true ;
366
+
367
+ if ( _runspaceContext . CurrentRunspace . RunspaceOrigin != RunspaceOrigin . Local )
442
368
{
443
- _debugStateService . ServerStarted . TrySetResult ( true ) ;
369
+ throw new RpcErrorException ( 0 , null , "Cannot attach to a process in a remote session when already in a remote session." ) ;
370
+ }
371
+
372
+ PSCommand psCommand = new PSCommand ( )
373
+ . AddCommand ( "Enter-PSSession" )
374
+ . AddParameter ( "ComputerName" , computerName ) ;
375
+
376
+ try
377
+ {
378
+ await _executionService . ExecutePSCommandAsync (
379
+ psCommand ,
380
+ cancellationToken ,
381
+ PowerShellExecutionOptions . ImmediateInteractive )
382
+ . ConfigureAwait ( false ) ;
383
+ }
384
+ catch ( Exception e )
385
+ {
386
+ string msg = $ "Could not establish remote session to computer { computerName } ";
387
+ _logger . LogError ( e , msg ) ;
388
+ throw new RpcErrorException ( 0 , e , msg ) ;
389
+ }
390
+ }
391
+
392
+ private async Task AttachToProcess ( int processId , CancellationToken cancellationToken )
393
+ {
394
+ PSCommand enterPSHostProcessCommand = new PSCommand ( )
395
+ . AddCommand ( @"Microsoft.PowerShell.Core\Enter-PSHostProcess" )
396
+ . AddParameter ( "Id" , processId ) ;
397
+
398
+ try
399
+ {
400
+ await _executionService . ExecutePSCommandAsync (
401
+ enterPSHostProcessCommand ,
402
+ cancellationToken ,
403
+ PowerShellExecutionOptions . ImmediateInteractive )
404
+ . ConfigureAwait ( false ) ;
405
+ }
406
+ catch ( Exception e )
407
+ {
408
+ string msg = $ "Could not attach to process with ID: { processId } ";
409
+ _logger . LogError ( e , msg ) ;
410
+ throw new RpcErrorException ( 0 , e , msg ) ;
411
+ }
412
+ }
413
+
414
+ private async Task AttachToPipe ( string pipeName , CancellationToken cancellationToken )
415
+ {
416
+ PowerShellVersionDetails runspaceVersion = _runspaceContext . CurrentRunspace . PowerShellVersionDetails ;
417
+
418
+ if ( runspaceVersion . Version < s_minVersionForCustomPipeName )
419
+ {
420
+ throw new RpcErrorException ( 0 , null , $ "Attaching to a process with CustomPipeName is only available with PowerShell 6.2 and higher (current session is { runspaceVersion . Version } ).") ;
421
+ }
422
+
423
+ PSCommand enterPSHostProcessCommand = new PSCommand ( )
424
+ . AddCommand ( @"Microsoft.PowerShell.Core\Enter-PSHostProcess" )
425
+ . AddParameter ( "CustomPipeName" , pipeName ) ;
426
+
427
+ try
428
+ {
429
+ await _executionService . ExecutePSCommandAsync (
430
+ enterPSHostProcessCommand ,
431
+ cancellationToken ,
432
+ PowerShellExecutionOptions . ImmediateInteractive )
433
+ . ConfigureAwait ( false ) ;
434
+ }
435
+ catch ( Exception e )
436
+ {
437
+ string msg = $ "Could not attach to process with CustomPipeName: { pipeName } ";
438
+ _logger . LogError ( e , msg ) ;
439
+ throw new RpcErrorException ( 0 , e , msg ) ;
444
440
}
445
- return new AttachResponse ( ) ;
446
441
}
447
442
448
443
// PSES follows the following flow:
0 commit comments