diff --git a/src/PowerShellEditorServices.Protocol/DebugAdapter/AttachRequest.cs b/src/PowerShellEditorServices.Protocol/DebugAdapter/AttachRequest.cs index e9734e002..24f117699 100644 --- a/src/PowerShellEditorServices.Protocol/DebugAdapter/AttachRequest.cs +++ b/src/PowerShellEditorServices.Protocol/DebugAdapter/AttachRequest.cs @@ -21,5 +21,7 @@ public class AttachRequestArguments public string ProcessId { get; set; } public int RunspaceId { get; set; } + + public string CustomPipeName { get; set; } } } diff --git a/src/PowerShellEditorServices.Protocol/Server/DebugAdapter.cs b/src/PowerShellEditorServices.Protocol/Server/DebugAdapter.cs index 69a720f8e..88fc797e1 100644 --- a/src/PowerShellEditorServices.Protocol/Server/DebugAdapter.cs +++ b/src/PowerShellEditorServices.Protocol/Server/DebugAdapter.cs @@ -23,6 +23,8 @@ namespace Microsoft.PowerShell.EditorServices.Protocol.Server { public class DebugAdapter { + private static readonly Version _minVersionForCustomPipeName = new Version(6, 2); + private EditorSession _editorSession; private bool _noDebug; @@ -350,11 +352,17 @@ protected async Task HandleAttachRequestAsync( RegisterEventHandlers(); + bool processIdIsSet = !string.IsNullOrEmpty(attachParams.ProcessId) && attachParams.ProcessId != "undefined"; + bool customPipeNameIsSet = !string.IsNullOrEmpty(attachParams.CustomPipeName) && attachParams.CustomPipeName != "undefined"; + + PowerShellVersionDetails runspaceVersion = + _editorSession.PowerShellContext.CurrentRunspace.PowerShellVersion; + // If there are no host processes to attach to or the user cancels selection, we get a null for the process id. // This is not an error, just a request to stop the original "attach to" request. // Testing against "undefined" is a HACK because I don't know how to make "Cancel" on quick pick loading // to cancel on the VSCode side without sending an attachRequest with processId set to "undefined". - if (string.IsNullOrEmpty(attachParams.ProcessId) || (attachParams.ProcessId == "undefined")) + if (!processIdIsSet && !customPipeNameIsSet) { Logger.Write( LogLevel.Normal, @@ -370,9 +378,6 @@ await requestContext.SendErrorAsync( if (attachParams.ComputerName != null) { - PowerShellVersionDetails runspaceVersion = - _editorSession.PowerShellContext.CurrentRunspace.PowerShellVersion; - if (runspaceVersion.Version.Major < 4) { await requestContext.SendErrorAsync( @@ -403,16 +408,12 @@ await requestContext.SendErrorAsync( _isRemoteAttach = true; } - if (int.TryParse(attachParams.ProcessId, out int processId) && (processId > 0)) + if (processIdIsSet && int.TryParse(attachParams.ProcessId, out int processId) && (processId > 0)) { - PowerShellVersionDetails runspaceVersion = - _editorSession.PowerShellContext.CurrentRunspace.PowerShellVersion; - if (runspaceVersion.Version.Major < 5) { await requestContext.SendErrorAsync( $"Attaching to a process is only available with PowerShell 5 and higher (current session is {runspaceVersion.Version})."); - return; } @@ -427,20 +428,27 @@ await requestContext.SendErrorAsync( return; } + } + else if (customPipeNameIsSet) + { + if (runspaceVersion.Version < _minVersionForCustomPipeName) + { + await requestContext.SendErrorAsync( + $"Attaching to a process with CustomPipeName is only available with PowerShell 6.2 and higher (current session is {runspaceVersion.Version})."); + return; + } - // Clear any existing breakpoints before proceeding - await ClearSessionBreakpointsAsync(); - - // Execute the Debug-Runspace command but don't await it because it - // will block the debug adapter initialization process. The - // InitializedEvent will be sent as soon as the RunspaceChanged - // event gets fired with the attached runspace. - int runspaceId = attachParams.RunspaceId > 0 ? attachParams.RunspaceId : 1; - _waitingForAttach = true; - Task nonAwaitedTask = - _editorSession.PowerShellContext - .ExecuteScriptStringAsync($"\nDebug-Runspace -Id {runspaceId}") - .ContinueWith(OnExecutionCompletedAsync); + await _editorSession.PowerShellContext.ExecuteScriptStringAsync( + $"Enter-PSHostProcess -CustomPipeName {attachParams.CustomPipeName}", + errorMessages); + + if (errorMessages.Length > 0) + { + await requestContext.SendErrorAsync( + $"Could not attach to process with CustomPipeName: '{attachParams.CustomPipeName}'"); + + return; + } } else { @@ -454,6 +462,19 @@ await requestContext.SendErrorAsync( return; } + // Clear any existing breakpoints before proceeding + await ClearSessionBreakpointsAsync().ConfigureAwait(continueOnCapturedContext: false); + + // Execute the Debug-Runspace command but don't await it because it + // will block the debug adapter initialization process. The + // InitializedEvent will be sent as soon as the RunspaceChanged + // event gets fired with the attached runspace. + int runspaceId = attachParams.RunspaceId > 0 ? attachParams.RunspaceId : 1; + _waitingForAttach = true; + Task nonAwaitedTask = _editorSession.PowerShellContext + .ExecuteScriptStringAsync($"\nDebug-Runspace -Id {runspaceId}") + .ContinueWith(OnExecutionCompletedAsync); + await requestContext.SendResultAsync(null); }