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
            });
        }
Beispiel #2
0
        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);
        }