Skip to content

Commit 12adcc0

Browse files
committed
Fix up debugger attach handlers
First off, the error messages were never actually displayed to the user because the RpcErrorException constructor takes three arguments. Secondly, we do not support attaching to PowerShell Editor Services. It sure looked like we did (because we had special logic for it) but once attached, nothing worked. So it was half-baked. Now we throw an error if the user is trying to do that. Thirdly, because of that half-baked implementation, the process ID field was typed as a string (to support "current" as a shortcut)but that caused a mess here and an error in the VS Code client. Now it's just always an integer. (Same for the runspace ID.) Fourthly, a big mess was cleaned up by refactoring using functions, who'd have thought? Fifth and finally, superfluous version checking around PowerShell <5.1 was removed (as those versions are no longer supported whatsoever).
1 parent 4c39342 commit 12adcc0

File tree

6 files changed

+179
-179
lines changed

6 files changed

+179
-179
lines changed

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ public override Task<PauseResponse> Handle(PauseArguments request, CancellationT
5151
}
5252
catch (NotSupportedException e)
5353
{
54-
throw new RpcErrorException(0, e.Message);
54+
throw new RpcErrorException(0, e, e.Message);
5555
}
5656
}
5757
}

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

+113-118
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;
@@ -190,7 +191,7 @@ public async Task<LaunchResponse> Handle(PsesLaunchRequestArguments request, Can
190191
&& !string.IsNullOrEmpty(request.Script)
191192
&& ScriptFile.IsUntitledPath(request.Script))
192193
{
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.");
194195
}
195196

196197
// If the current session is remote, map the script path to the remote
@@ -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
@@ -258,40 +255,12 @@ private async Task<AttachResponse> HandleImpl(PsesAttachRequestArguments request
258255
_logger.LogInformation(
259256
$"Attach request aborted, received {request.ProcessId} for processId.");
260257

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.");
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,38 @@ 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+
// TODO: Implement support for breakpoints in runspaces in the extension terminal.
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}).");
282+
throw new RpcErrorException(0, null, $"Attaching to the Extension Terminal is not supported!");
315283
}
316284

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);
335288
}
336289
else if (customPipeNameIsSet)
337290
{
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);
361294
}
362-
else if (request.ProcessId != "current")
295+
else
363296
{
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!");
368298
}
369299

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

372301
// 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
374303
// InitializedEvent will be sent as soon as the RunspaceChanged
375304
// event gets fired with the attached runspace.
376-
377305
PSCommand debugRunspaceCmd = new PSCommand().AddCommand("Debug-Runspace");
378-
if (request.RunspaceName != null)
306+
if (!string.IsNullOrEmpty(request.RunspaceName))
379307
{
380-
PSCommand getRunspaceIdCommand = new PSCommand()
308+
PSCommand psCommand = new PSCommand()
381309
.AddCommand(@"Microsoft.PowerShell.Utility\Get-Runspace")
382310
.AddParameter("Name", request.RunspaceName)
383311
.AddCommand(@"Microsoft.PowerShell.Utility\Select-Object")
@@ -386,7 +314,7 @@ await _executionService.ExecutePSCommandAsync(
386314
try
387315
{
388316
IEnumerable<int?> ids = await _executionService.ExecutePSCommandAsync<int?>(
389-
getRunspaceIdCommand,
317+
psCommand,
390318
cancellationToken)
391319
.ConfigureAwait(false);
392320

@@ -395,38 +323,27 @@ await _executionService.ExecutePSCommandAsync(
395323
_debugStateService.RunspaceId = id;
396324
break;
397325

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!
399327
}
400328
}
401-
catch (Exception getRunspaceException)
329+
catch (Exception e)
402330
{
403331
_logger.LogError(
404-
getRunspaceException,
332+
e,
405333
"Unable to determine runspace to attach to. Message: {message}",
406-
getRunspaceException.Message);
334+
e.Message);
407335
}
408336

409-
// TODO: We have the ID, why not just use that?
410337
debugRunspaceCmd.AddParameter("Name", request.RunspaceName);
411338
}
412-
else if (request.RunspaceId != null)
339+
else if (request.RunspaceId > 0)
413340
{
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);
425343
}
426344
else
427345
{
428346
_debugStateService.RunspaceId = 1;
429-
430347
debugRunspaceCmd.AddParameter("Id", 1);
431348
}
432349

@@ -438,11 +355,89 @@ await _executionService.ExecutePSCommandAsync(
438355
.ExecutePSCommandAsync(debugRunspaceCmd, CancellationToken.None, PowerShellExecutionOptions.ImmediateInteractive)
439356
.ContinueWith(OnExecutionCompletedAsync, TaskScheduler.Default);
440357

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)
442368
{
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);
444440
}
445-
return new AttachResponse();
446441
}
447442

448443
// PSES follows the following flow:

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

+7-7
Original file line numberDiff line numberDiff line change
@@ -38,20 +38,20 @@ await _debugService.SetVariableAsync(
3838

3939
return new SetVariableResponse { Value = updatedValue };
4040
}
41-
catch (Exception ex) when (ex is ArgumentTransformationMetadataException or
41+
catch (Exception e) when (e is ArgumentTransformationMetadataException or
4242
InvalidPowerShellExpressionException or
4343
SessionStateUnauthorizedAccessException)
4444
{
4545
// Catch common, innocuous errors caused by the user supplying a value that can't be converted or the variable is not settable.
46-
_logger.LogTrace($"Failed to set variable: {ex.Message}");
47-
throw new RpcErrorException(0, ex.Message);
46+
_logger.LogTrace($"Failed to set variable: {e.Message}");
47+
throw new RpcErrorException(0, e, e.Message);
4848
}
49-
catch (Exception ex)
49+
catch (Exception e)
5050
{
51-
_logger.LogError($"Unexpected error setting variable: {ex.Message}");
51+
_logger.LogError($"Unexpected error setting variable: {e.Message}");
5252
string msg =
53-
$"Unexpected error: {ex.GetType().Name} - {ex.Message} Please report this error to the PowerShellEditorServices project on GitHub.";
54-
throw new RpcErrorException(0, msg);
53+
$"Unexpected error: {e.GetType().Name} - {e.Message} Please report this error to the PowerShellEditorServices project on GitHub.";
54+
throw new RpcErrorException(0, e, msg);
5555
}
5656
}
5757
}

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)