Skip to content

Commit cbb9e09

Browse files
committed
WIP: Fix up debugger attach etc.
Also such a mess.
1 parent 4c39342 commit cbb9e09

File tree

4 files changed

+151
-171
lines changed

4 files changed

+151
-171
lines changed

src/PowerShellEditorServices/Services/DebugAdapter/Handlers/LaunchAndAttachHandler.cs

+110-117
Original file line numberDiff line numberDiff line change
@@ -76,17 +76,18 @@ internal record PsesAttachRequestArguments : AttachRequestArguments
7676
{
7777
public string ComputerName { get; set; }
7878

79-
public string ProcessId { get; set; }
79+
public int ProcessId { get; set; }
8080

81-
public string RunspaceId { get; set; }
81+
public int RunspaceId { get; set; }
8282

8383
public string RunspaceName { get; set; }
8484

8585
public string CustomPipeName { get; set; }
8686
}
8787

8888
internal class LaunchAndAttachHandler : ILaunchHandler<PsesLaunchRequestArguments>, IAttachHandler<PsesAttachRequestArguments>, IOnDebugAdapterServerStarted
89-
{
89+
{
90+
private static readonly int currentProcessId = System.Diagnostics.Process.GetCurrentProcess().Id;
9091
private static readonly Version s_minVersionForCustomPipeName = new(6, 2);
9192
private readonly ILogger<LaunchAndAttachHandler> _logger;
9293
private readonly BreakpointService _breakpointService;
@@ -239,16 +240,12 @@ private async Task<AttachResponse> HandleImpl(PsesAttachRequestArguments request
239240
{
240241
// The debugger has officially started. We use this to later check if we should stop it.
241242
((PsesInternalHost)_executionService).DebugContext.IsActive = true;
242-
243243
_debugStateService.IsAttachSession = true;
244-
245244
_debugEventHandlerService.RegisterEventHandlers();
246245

247-
bool processIdIsSet = !string.IsNullOrEmpty(request.ProcessId) && request.ProcessId != "undefined";
246+
bool processIdIsSet = request.ProcessId != 0;
248247
bool customPipeNameIsSet = !string.IsNullOrEmpty(request.CustomPipeName) && request.CustomPipeName != "undefined";
249248

250-
PowerShellVersionDetails runspaceVersion = _runspaceContext.CurrentRunspace.PowerShellVersionDetails;
251-
252249
// If there are no host processes to attach to or the user cancels selection, we get a null for the process id.
253250
// This is not an error, just a request to stop the original "attach to" request.
254251
// 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
261258
throw new RpcErrorException(0, "User aborted attach to PowerShell host process.");
262259
}
263260

264-
if (request.ComputerName != null)
261+
if (!string.IsNullOrEmpty(request.ComputerName))
265262
{
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);
295264
}
296265

297266
// Set up a temporary runspace changed event handler so we can ensure
@@ -305,79 +274,36 @@ void RunspaceChangedHandler(object s, RunspaceChangedEventArgs _)
305274
runspaceChanged.TrySetResult(true);
306275
}
307276

308-
_executionService.RunspaceChanged += RunspaceChangedHandler;
309-
310-
if (processIdIsSet && int.TryParse(request.ProcessId, out int processId) && (processId > 0))
277+
if (processIdIsSet)
311278
{
312-
if (runspaceVersion.Version.Major < 5)
279+
// Skip this if we're targetting ourself.
280+
if (request.ProcessId != currentProcessId)
313281
{
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);
334285
}
335286
}
336287
else if (customPipeNameIsSet)
337288
{
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);
361292
}
362-
else if (request.ProcessId != "current")
293+
else
363294
{
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!");
368296
}
369297

370-
await runspaceChanged.Task.ConfigureAwait(false);
371298

372299
// 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
374301
// InitializedEvent will be sent as soon as the RunspaceChanged
375302
// event gets fired with the attached runspace.
376-
377303
PSCommand debugRunspaceCmd = new PSCommand().AddCommand("Debug-Runspace");
378-
if (request.RunspaceName != null)
304+
if (!string.IsNullOrEmpty(request.RunspaceName))
379305
{
380-
PSCommand getRunspaceIdCommand = new PSCommand()
306+
PSCommand psCommand = new PSCommand()
381307
.AddCommand(@"Microsoft.PowerShell.Utility\Get-Runspace")
382308
.AddParameter("Name", request.RunspaceName)
383309
.AddCommand(@"Microsoft.PowerShell.Utility\Select-Object")
@@ -386,7 +312,7 @@ await _executionService.ExecutePSCommandAsync(
386312
try
387313
{
388314
IEnumerable<int?> ids = await _executionService.ExecutePSCommandAsync<int?>(
389-
getRunspaceIdCommand,
315+
psCommand,
390316
cancellationToken)
391317
.ConfigureAwait(false);
392318

@@ -395,38 +321,27 @@ await _executionService.ExecutePSCommandAsync(
395321
_debugStateService.RunspaceId = id;
396322
break;
397323

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!
399325
}
400326
}
401-
catch (Exception getRunspaceException)
327+
catch (Exception e)
402328
{
403329
_logger.LogError(
404-
getRunspaceException,
330+
e,
405331
"Unable to determine runspace to attach to. Message: {message}",
406-
getRunspaceException.Message);
332+
e.Message);
407333
}
408334

409-
// TODO: We have the ID, why not just use that?
410335
debugRunspaceCmd.AddParameter("Name", request.RunspaceName);
411336
}
412-
else if (request.RunspaceId != null)
337+
else if (request.RunspaceId > 0)
413338
{
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);
425341
}
426342
else
427343
{
428344
_debugStateService.RunspaceId = 1;
429-
430345
debugRunspaceCmd.AddParameter("Id", 1);
431346
}
432347

@@ -438,11 +353,89 @@ await _executionService.ExecutePSCommandAsync(
438353
.ExecutePSCommandAsync(debugRunspaceCmd, CancellationToken.None, PowerShellExecutionOptions.ImmediateInteractive)
439354
.ContinueWith(OnExecutionCompletedAsync, TaskScheduler.Default);
440355

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)
442366
{
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);
444438
}
445-
return new AttachResponse();
446439
}
447440

448441
// PSES follows the following flow:

src/PowerShellEditorServices/Services/PowerShell/Handlers/IGetRunspaceHandler.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ internal interface IGetRunspaceHandler : IJsonRpcRequestHandler<GetRunspaceParam
1111

1212
internal class GetRunspaceParams : IRequest<RunspaceResponse[]>
1313
{
14-
public string ProcessId { get; set; }
14+
public int ProcessId { get; set; }
1515
}
1616

1717
internal class RunspaceResponse

0 commit comments

Comments
 (0)