Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,12 @@ internal class LaunchAndAttachHandler : ILaunchHandler<PsesLaunchRequestArgument
private readonly DebugEventHandlerService _debugEventHandlerService;
private readonly IDebugAdapterServerFacade _debugAdapterServer;
private readonly RemoteFileManagerService _remoteFileManagerService;
private static readonly string[] s_requiredDebugCommands =
[
"Get-Variable",
"Wait-Debugger",
"Get-Runspace",
];

public LaunchAndAttachHandler(
ILoggerFactory factory,
Expand Down Expand Up @@ -154,6 +160,8 @@ public async Task<LaunchResponse> Handle(PsesLaunchRequestArguments request, Can

public async Task<LaunchResponse> 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;

Expand Down Expand Up @@ -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<AttachResponse> Handle(PsesAttachRequestArguments request, CancellationToken cancellationToken)
{
// We want to set this as early as possible to avoid an early `StopDebugging` call in
Expand Down
107 changes: 107 additions & 0 deletions test/PowerShellEditorServices.Test/Debugging/DebugServiceTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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<object, RunspaceChangedEventArgs> RunspaceChanged
{
add { }
remove { }
}

public Task<TResult> ExecuteDelegateAsync<TResult>(
string representation,
ExecutionOptions executionOptions,
Func<SMA.PowerShell, CancellationToken, TResult> func,
CancellationToken cancellationToken) => throw new NotImplementedException();

public Task ExecuteDelegateAsync(
string representation,
ExecutionOptions executionOptions,
Action<SMA.PowerShell, CancellationToken> action,
CancellationToken cancellationToken) => throw new NotImplementedException();

public Task<TResult> ExecuteDelegateAsync<TResult>(
string representation,
ExecutionOptions executionOptions,
Func<CancellationToken, TResult> func,
CancellationToken cancellationToken) => throw new NotImplementedException();

public Task ExecuteDelegateAsync(
string representation,
ExecutionOptions executionOptions,
Action<CancellationToken> action,
CancellationToken cancellationToken) => throw new NotImplementedException();

public Task<IReadOnlyList<TResult>> ExecutePSCommandAsync<TResult>(
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
{
Expand Down Expand Up @@ -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<HandlerErrorException>(() =>
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()
{
Expand Down
Loading