public async Task <InitializeResponse> Handle(InitializeRequestArguments request, CancellationToken cancellationToken) { // Clear any existing breakpoints before proceeding await _breakpointService.RemoveAllBreakpointsAsync().ConfigureAwait(false); // Now send the Initialize response to continue setup return(new InitializeResponse { SupportsConditionalBreakpoints = true, SupportsConfigurationDoneRequest = true, SupportsFunctionBreakpoints = true, SupportsHitConditionalBreakpoints = true, SupportsLogPoints = true, SupportsSetVariable = true }); }
public async Task <Unit> Handle(PsesAttachRequestArguments request, CancellationToken cancellationToken) { _debugStateService.IsAttachSession = true; _debugEventHandlerService.RegisterEventHandlers(); bool processIdIsSet = !string.IsNullOrEmpty(request.ProcessId) && request.ProcessId != "undefined"; bool customPipeNameIsSet = !string.IsNullOrEmpty(request.CustomPipeName) && request.CustomPipeName != "undefined"; PowerShellVersionDetails runspaceVersion = _powerShellContextService.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 (!processIdIsSet && !customPipeNameIsSet) { _logger.LogInformation( $"Attach request aborted, received {request.ProcessId} for processId."); throw new RpcErrorException(0, "User aborted attach to PowerShell host process."); } StringBuilder errorMessages = new StringBuilder(); if (request.ComputerName != null) { if (runspaceVersion.Version.Major < 4) { throw new RpcErrorException(0, $"Remote sessions are only available with PowerShell 4 and higher (current session is {runspaceVersion.Version})."); } else if (_powerShellContextService.CurrentRunspace.Location == RunspaceLocation.Remote) { throw new RpcErrorException(0, $"Cannot attach to a process in a remote session when already in a remote session."); } await _powerShellContextService.ExecuteScriptStringAsync( $"Enter-PSSession -ComputerName \"{request.ComputerName}\"", errorMessages).ConfigureAwait(false); if (errorMessages.Length > 0) { throw new RpcErrorException(0, $"Could not establish remote session to computer '{request.ComputerName}'"); } _debugStateService.IsRemoteAttach = true; } if (processIdIsSet && int.TryParse(request.ProcessId, out int processId) && (processId > 0)) { if (runspaceVersion.Version.Major < 5) { throw new RpcErrorException(0, $"Attaching to a process is only available with PowerShell 5 and higher (current session is {runspaceVersion.Version})."); } await _powerShellContextService.ExecuteScriptStringAsync( $"Enter-PSHostProcess -Id {processId}", errorMessages).ConfigureAwait(false); if (errorMessages.Length > 0) { throw new RpcErrorException(0, $"Could not attach to process '{processId}'"); } } else if (customPipeNameIsSet) { if (runspaceVersion.Version < s_minVersionForCustomPipeName) { throw new RpcErrorException(0, $"Attaching to a process with CustomPipeName is only available with PowerShell 6.2 and higher (current session is {runspaceVersion.Version})."); } await _powerShellContextService.ExecuteScriptStringAsync( $"Enter-PSHostProcess -CustomPipeName {request.CustomPipeName}", errorMessages).ConfigureAwait(false); if (errorMessages.Length > 0) { throw new RpcErrorException(0, $"Could not attach to process with CustomPipeName: '{request.CustomPipeName}'"); } } else if (request.ProcessId != "current") { _logger.LogError( $"Attach request failed, '{request.ProcessId}' is an invalid value for the processId."); throw new RpcErrorException(0, "A positive integer must be specified for the processId field."); } // 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. string debugRunspaceCmd; if (request.RunspaceName != null) { IEnumerable <int?> ids = await _powerShellContextService.ExecuteCommandAsync <int?>(new PSCommand() .AddCommand("Microsoft.PowerShell.Utility\\Get-Runspace") .AddParameter("Name", request.RunspaceName) .AddCommand("Microsoft.PowerShell.Utility\\Select-Object") .AddParameter("ExpandProperty", "Id")); foreach (var id in ids) { _debugStateService.RunspaceId = id; break; } debugRunspaceCmd = $"\nDebug-Runspace -Name '{request.RunspaceName}'"; } else if (request.RunspaceId != null) { if (!int.TryParse(request.RunspaceId, out int runspaceId) || runspaceId <= 0) { _logger.LogError( $"Attach request failed, '{request.RunspaceId}' is an invalid value for the processId."); throw new RpcErrorException(0, "A positive integer must be specified for the RunspaceId field."); } _debugStateService.RunspaceId = runspaceId; debugRunspaceCmd = $"\nDebug-Runspace -Id {runspaceId}"; } else { _debugStateService.RunspaceId = 1; debugRunspaceCmd = "\nDebug-Runspace -Id 1"; } // Clear any existing breakpoints before proceeding await _breakpointService.RemoveAllBreakpointsAsync().ConfigureAwait(continueOnCapturedContext: false); _debugStateService.WaitingForAttach = true; Task nonAwaitedTask = _powerShellContextService .ExecuteScriptStringAsync(debugRunspaceCmd) .ContinueWith(OnExecutionCompletedAsync); if (runspaceVersion.Version.Major >= 7) { _jsonRpcServer.SendNotification(EventNames.Initialized); } return(Unit.Value); }
/// <summary> /// Start the debug server listening. /// </summary> /// <returns>A task that completes when the server is ready.</returns> public async Task StartAsync() { _debugAdapterServer = await DebugAdapterServer.From(options => { // We need to let the PowerShell Context Service know that we are in a debug session // so that it doesn't send the powerShell/startDebugger message. _psesHost = ServiceProvider.GetService <PsesInternalHost>(); _psesHost.DebugContext.IsDebugServerActive = true; options .WithInput(_inputStream) .WithOutput(_outputStream) .WithServices(serviceCollection => serviceCollection .AddLogging() .AddOptions() .AddPsesDebugServices(ServiceProvider, this)) // TODO: Consider replacing all WithHandler with AddSingleton .WithHandler <LaunchAndAttachHandler>() .WithHandler <DisconnectHandler>() .WithHandler <BreakpointHandlers>() .WithHandler <ConfigurationDoneHandler>() .WithHandler <ThreadsHandler>() .WithHandler <StackTraceHandler>() .WithHandler <ScopesHandler>() .WithHandler <VariablesHandler>() .WithHandler <ContinueHandler>() .WithHandler <NextHandler>() .WithHandler <PauseHandler>() .WithHandler <StepInHandler>() .WithHandler <StepOutHandler>() .WithHandler <SourceHandler>() .WithHandler <SetVariableHandler>() .WithHandler <DebugEvaluateHandler>() // The OnInitialize delegate gets run when we first receive the _Initialize_ request: // https://microsoft.github.io/debug-adapter-protocol/specification#Requests_Initialize .OnInitialize(async(server, request, cancellationToken) => { // We need to make sure the host has been started _startedPses = !await _psesHost.TryStartAsync(new HostStartOptions(), CancellationToken.None).ConfigureAwait(false); // Ensure the debugger mode is set correctly - this is required for remote debugging to work _psesHost.DebugContext.EnableDebugMode(); BreakpointService breakpointService = server.GetService <BreakpointService>(); // Clear any existing breakpoints before proceeding await breakpointService.RemoveAllBreakpointsAsync().ConfigureAwait(false); }) // The OnInitialized delegate gets run right before the server responds to the _Initialize_ request: // https://microsoft.github.io/debug-adapter-protocol/specification#Requests_Initialize .OnInitialized((server, request, response, cancellationToken) => { response.SupportsConditionalBreakpoints = true; response.SupportsConfigurationDoneRequest = true; response.SupportsFunctionBreakpoints = true; response.SupportsHitConditionalBreakpoints = true; response.SupportsLogPoints = true; response.SupportsSetVariable = true; return(Task.CompletedTask); }); }).ConfigureAwait(false); }