diff --git a/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/LaunchAndAttachHandler.cs b/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/LaunchAndAttachHandler.cs index 200dcc316..b9048636b 100644 --- a/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/LaunchAndAttachHandler.cs +++ b/src/PowerShellEditorServices/Services/DebugAdapter/Handlers/LaunchAndAttachHandler.cs @@ -114,6 +114,12 @@ internal class LaunchAndAttachHandler : ILaunchHandler Handle(PsesLaunchRequestArguments request, Can public async Task HandleImpl(PsesLaunchRequestArguments request, CancellationToken cancellationToken) { + EnsureDebugCommandsAvailable(); + // The debugger has officially started. We use this to later check if we should stop it. ((PsesInternalHost)_executionService).DebugContext.IsActive = true; @@ -246,6 +254,24 @@ await _executionService.ExecutePSCommandAsync( return new LaunchResponse(); } + private void EnsureDebugCommandsAvailable() + { + if (_runspaceContext.CurrentRunspace.Runspace.SessionStateProxy.LanguageMode != PSLanguageMode.ConstrainedLanguage) + { + return; + } + + foreach (string commandName in s_requiredDebugCommands) + { + if (_runspaceContext.CurrentRunspace.Runspace.SessionStateProxy.InvokeCommand.GetCommand(commandName, CommandTypes.Cmdlet) is null) + { + throw new HandlerErrorException( + "Cannot start debugging because you are running PowerShell in a constrained language mode that does not have the required debug commands available. Please contact your administrator to add the debug commands to your constrained runspace.", + new { MissingCommand = commandName, LanguageMode = _runspaceContext.CurrentRunspace.Runspace.SessionStateProxy.LanguageMode.ToString() }); + } + } + } + public async Task Handle(PsesAttachRequestArguments request, CancellationToken cancellationToken) { // We want to set this as early as possible to avoid an early `StopDebugging` call in diff --git a/test/PowerShellEditorServices.Test/Debugging/DebugServiceTests.cs b/test/PowerShellEditorServices.Test/Debugging/DebugServiceTests.cs index 3ba16008d..8aee56b36 100644 --- a/test/PowerShellEditorServices.Test/Debugging/DebugServiceTests.cs +++ b/test/PowerShellEditorServices.Test/Debugging/DebugServiceTests.cs @@ -7,18 +7,24 @@ using System.IO; using System.Linq; using System.Management.Automation; +using System.Management.Automation.Runspaces; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Logging.Abstractions; using Microsoft.PowerShell.EditorServices.Handlers; using Microsoft.PowerShell.EditorServices.Services; using Microsoft.PowerShell.EditorServices.Services.DebugAdapter; +using Microsoft.PowerShell.EditorServices.Services.PowerShell.Context; +using Microsoft.PowerShell.EditorServices.Services.PowerShell.Execution; using Microsoft.PowerShell.EditorServices.Services.PowerShell.Console; using Microsoft.PowerShell.EditorServices.Services.PowerShell.Host; +using Microsoft.PowerShell.EditorServices.Services.PowerShell; +using Microsoft.PowerShell.EditorServices.Services.PowerShell.Runspace; using Microsoft.PowerShell.EditorServices.Services.TextDocument; using Microsoft.PowerShell.EditorServices.Test; using Microsoft.PowerShell.EditorServices.Test.Shared; using Microsoft.PowerShell.EditorServices.Utility; +using SMA = System.Management.Automation; using Xunit; namespace PowerShellEditorServices.Test.Debugging @@ -32,6 +38,69 @@ internal class TestReadLine : IReadLine public void AddToHistory(string historyEntry) => history.Add(historyEntry); } + internal sealed class TestRunspaceInfo(Runspace runspace) : IRunspaceInfo + { + public RunspaceOrigin RunspaceOrigin => global::Microsoft.PowerShell.EditorServices.Services.PowerShell.Runspace.RunspaceOrigin.Local; + + public bool IsOnRemoteMachine => false; + + public PowerShellVersionDetails PowerShellVersionDetails => throw new NotImplementedException(); + + public SessionDetails SessionDetails => throw new NotImplementedException(); + + public Runspace Runspace { get; } = runspace; + } + + internal sealed class TestRunspaceContext(Runspace runspace) : IRunspaceContext + { + public IRunspaceInfo CurrentRunspace { get; } = new TestRunspaceInfo(runspace); + } + + internal sealed class ThrowingExecutionService : IInternalPowerShellExecutionService + { + public event Action RunspaceChanged + { + add { } + remove { } + } + + public Task ExecuteDelegateAsync( + string representation, + ExecutionOptions executionOptions, + Func func, + CancellationToken cancellationToken) => throw new NotImplementedException(); + + public Task ExecuteDelegateAsync( + string representation, + ExecutionOptions executionOptions, + Action action, + CancellationToken cancellationToken) => throw new NotImplementedException(); + + public Task ExecuteDelegateAsync( + string representation, + ExecutionOptions executionOptions, + Func func, + CancellationToken cancellationToken) => throw new NotImplementedException(); + + public Task ExecuteDelegateAsync( + string representation, + ExecutionOptions executionOptions, + Action action, + CancellationToken cancellationToken) => throw new NotImplementedException(); + + public Task> ExecutePSCommandAsync( + PSCommand psCommand, + CancellationToken cancellationToken, + PowerShellExecutionOptions executionOptions = null) => throw new NotImplementedException(); + + public Task ExecutePSCommandAsync( + PSCommand psCommand, + CancellationToken cancellationToken, + PowerShellExecutionOptions executionOptions = null) => throw new NotImplementedException(); + + public void CancelCurrentTask() => throw new NotImplementedException(); + } + [Trait("Category", "DebugService")] public class DebugServiceTests : IAsyncLifetime { @@ -589,6 +658,44 @@ public async Task RecordsF5CommandInPowerShellHistory() Assert.Equal(". '" + debugScriptFile.FilePath + "'", Assert.Single(testReadLine.history)); } + [Fact] + public async Task LaunchFailsInConstrainedLanguageWhenDebugCommandsAreUnavailable() + { + InitialSessionState initialSessionState = InitialSessionState.CreateDefault2(); + initialSessionState.LanguageMode = PSLanguageMode.ConstrainedLanguage; + using Runspace constrainedRunspace = RunspaceFactory.CreateRunspace(initialSessionState); + constrainedRunspace.Open(); + + Assert.Equal(PSLanguageMode.ConstrainedLanguage, constrainedRunspace.SessionStateProxy.LanguageMode); + Assert.Null(constrainedRunspace.SessionStateProxy.InvokeCommand.GetCommand("Wait-Debugger", CommandTypes.Cmdlet)); + + LaunchAndAttachHandler launchHandler = new( + NullLoggerFactory.Instance, + debugAdapterServer: null, + breakpointService: null, + debugEventHandlerService: null, + debugService, + new TestRunspaceContext(constrainedRunspace), + new ThrowingExecutionService(), + new DebugStateService(), + remoteFileManagerService: null); + + HandlerErrorException exception = await Assert.ThrowsAsync(() => + launchHandler.Handle( + new PsesLaunchRequestArguments + { + Script = debugScriptFile.FilePath, + Cwd = "", + CreateTemporaryIntegratedConsole = false, + ExecuteMode = "DotSource", + }, + CancellationToken.None)); + + Assert.Equal( + "Cannot start debugging because you are running PowerShell in a constrained language mode that does not have the required debug commands available. Please contact your administrator to add the debug commands to your constrained runspace.", + exception.Message); + } + [Fact] public async Task RecordsF8CommandInHistory() {