@@ -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 ;
@@ -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
@@ -261,37 +258,9 @@ private async Task<AttachResponse> HandleImpl(PsesAttachRequestArguments request
261
258
throw new RpcErrorException ( 0 , "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,36 @@ 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
+ // Skip this if we're targetting ourself.
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 } ).") ;
315
- }
316
-
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 ) ;
282
+ _executionService . RunspaceChanged += RunspaceChangedHandler ;
283
+ await AttachToProcess ( request . ProcessId , cancellationToken ) . ConfigureAwait ( false ) ;
284
+ await runspaceChanged . Task . ConfigureAwait ( false ) ;
334
285
}
335
286
}
336
287
else if ( customPipeNameIsSet )
337
288
{
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
- }
289
+ _executionService . RunspaceChanged += RunspaceChangedHandler ;
290
+ await AttachToPipe ( request . CustomPipeName , cancellationToken ) . ConfigureAwait ( false ) ;
291
+ await runspaceChanged . Task . ConfigureAwait ( false ) ;
361
292
}
362
- else if ( request . ProcessId != "current" )
293
+ else
363
294
{
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." ) ;
295
+ throw new RpcErrorException ( 0 , "Invalid configuration with no process ID nor custom pipe name!" ) ;
368
296
}
369
297
370
- await runspaceChanged . Task . ConfigureAwait ( false ) ;
371
298
372
299
// Execute the Debug-Runspace command but don't await it because it
373
- // will block the debug adapter initialization process. The
300
+ // will block the debug adapter initialization process. The
374
301
// InitializedEvent will be sent as soon as the RunspaceChanged
375
302
// event gets fired with the attached runspace.
376
-
377
303
PSCommand debugRunspaceCmd = new PSCommand ( ) . AddCommand ( "Debug-Runspace" ) ;
378
- if ( request . RunspaceName != null )
304
+ if ( ! string . IsNullOrEmpty ( request . RunspaceName ) )
379
305
{
380
- PSCommand getRunspaceIdCommand = new PSCommand ( )
306
+ PSCommand psCommand = new PSCommand ( )
381
307
. AddCommand ( @"Microsoft.PowerShell.Utility\Get-Runspace" )
382
308
. AddParameter ( "Name" , request . RunspaceName )
383
309
. AddCommand ( @"Microsoft.PowerShell.Utility\Select-Object" )
@@ -386,7 +312,7 @@ await _executionService.ExecutePSCommandAsync(
386
312
try
387
313
{
388
314
IEnumerable < int ? > ids = await _executionService . ExecutePSCommandAsync < int ? > (
389
- getRunspaceIdCommand ,
315
+ psCommand ,
390
316
cancellationToken )
391
317
. ConfigureAwait ( false ) ;
392
318
@@ -395,38 +321,27 @@ await _executionService.ExecutePSCommandAsync(
395
321
_debugStateService . RunspaceId = id ;
396
322
break ;
397
323
398
- // TODO: If we don't end up setting this, we should throw
324
+ // TODO: If we don't end up setting this, we should throw!
399
325
}
400
326
}
401
- catch ( Exception getRunspaceException )
327
+ catch ( Exception e )
402
328
{
403
329
_logger . LogError (
404
- getRunspaceException ,
330
+ e ,
405
331
"Unable to determine runspace to attach to. Message: {message}" ,
406
- getRunspaceException . Message ) ;
332
+ e . Message ) ;
407
333
}
408
334
409
- // TODO: We have the ID, why not just use that?
410
335
debugRunspaceCmd . AddParameter ( "Name" , request . RunspaceName ) ;
411
336
}
412
- else if ( request . RunspaceId != null )
337
+ else if ( request . RunspaceId > 0 )
413
338
{
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 ) ;
339
+ _debugStateService . RunspaceId = request . RunspaceId ;
340
+ debugRunspaceCmd . AddParameter ( "Id" , request . RunspaceId ) ;
425
341
}
426
342
else
427
343
{
428
344
_debugStateService . RunspaceId = 1 ;
429
-
430
345
debugRunspaceCmd . AddParameter ( "Id" , 1 ) ;
431
346
}
432
347
@@ -438,11 +353,89 @@ await _executionService.ExecutePSCommandAsync(
438
353
. ExecutePSCommandAsync ( debugRunspaceCmd , CancellationToken . None , PowerShellExecutionOptions . ImmediateInteractive )
439
354
. ContinueWith ( OnExecutionCompletedAsync , TaskScheduler . Default ) ;
440
355
441
- if ( runspaceVersion . Version . Major >= 7 )
356
+ _debugStateService . ServerStarted . TrySetResult ( true ) ;
357
+
358
+ return new AttachResponse ( ) ;
359
+ }
360
+
361
+ private async Task AttachToComputer ( string computerName , CancellationToken cancellationToken )
362
+ {
363
+ _debugStateService . IsRemoteAttach = true ;
364
+
365
+ if ( _runspaceContext . CurrentRunspace . RunspaceOrigin != RunspaceOrigin . Local )
442
366
{
443
- _debugStateService . ServerStarted . TrySetResult ( true ) ;
367
+ throw new RpcErrorException ( 0 , "Cannot attach to a process in a remote session when already in a remote session." ) ;
368
+ }
369
+
370
+ PSCommand psCommand = new PSCommand ( )
371
+ . AddCommand ( "Enter-PSSession" )
372
+ . AddParameter ( "ComputerName" , computerName ) ;
373
+
374
+ try
375
+ {
376
+ await _executionService . ExecutePSCommandAsync (
377
+ psCommand ,
378
+ cancellationToken ,
379
+ PowerShellExecutionOptions . ImmediateInteractive )
380
+ . ConfigureAwait ( false ) ;
381
+ }
382
+ catch ( Exception e )
383
+ {
384
+ string msg = $ "Could not establish remote session to computer { computerName } ";
385
+ _logger . LogError ( e , msg ) ;
386
+ throw new RpcErrorException ( 0 , msg ) ;
387
+ }
388
+ }
389
+
390
+ private async Task AttachToProcess ( int processId , CancellationToken cancellationToken )
391
+ {
392
+ PSCommand enterPSHostProcessCommand = new PSCommand ( )
393
+ . AddCommand ( @"Microsoft.PowerShell.Core\Enter-PSHostProcess" )
394
+ . AddParameter ( "Id" , processId ) ;
395
+
396
+ try
397
+ {
398
+ await _executionService . ExecutePSCommandAsync (
399
+ enterPSHostProcessCommand ,
400
+ cancellationToken ,
401
+ PowerShellExecutionOptions . ImmediateInteractive )
402
+ . ConfigureAwait ( false ) ;
403
+ }
404
+ catch ( Exception e )
405
+ {
406
+ string msg = $ "Could not attach to process with ID: { processId } ";
407
+ _logger . LogError ( e , msg ) ;
408
+ throw new RpcErrorException ( 0 , msg ) ;
409
+ }
410
+ }
411
+
412
+ private async Task AttachToPipe ( string pipeName , CancellationToken cancellationToken )
413
+ {
414
+ PowerShellVersionDetails runspaceVersion = _runspaceContext . CurrentRunspace . PowerShellVersionDetails ;
415
+
416
+ if ( runspaceVersion . Version < s_minVersionForCustomPipeName )
417
+ {
418
+ throw new RpcErrorException ( 0 , $ "Attaching to a process with CustomPipeName is only available with PowerShell 6.2 and higher (current session is { runspaceVersion . Version } ).") ;
419
+ }
420
+
421
+ PSCommand enterPSHostProcessCommand = new PSCommand ( )
422
+ . AddCommand ( @"Microsoft.PowerShell.Core\Enter-PSHostProcess" )
423
+ . AddParameter ( "CustomPipeName" , pipeName ) ;
424
+
425
+ try
426
+ {
427
+ await _executionService . ExecutePSCommandAsync (
428
+ enterPSHostProcessCommand ,
429
+ cancellationToken ,
430
+ PowerShellExecutionOptions . ImmediateInteractive )
431
+ . ConfigureAwait ( false ) ;
432
+ }
433
+ catch ( Exception e )
434
+ {
435
+ string msg = $ "Could not attach to process with CustomPipeName: { pipeName } ";
436
+ _logger . LogError ( e , msg ) ;
437
+ throw new RpcErrorException ( 0 , msg ) ;
444
438
}
445
- return new AttachResponse ( ) ;
446
439
}
447
440
448
441
// PSES follows the following flow:
0 commit comments