Skip to content

Commit f9073ef

Browse files
authored
Guard constrained debug launch
1 parent 8c15a29 commit f9073ef

2 files changed

Lines changed: 133 additions & 0 deletions

File tree

src/PowerShellEditorServices/Services/DebugAdapter/Handlers/LaunchAndAttachHandler.cs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,12 @@ internal class LaunchAndAttachHandler : ILaunchHandler<PsesLaunchRequestArgument
114114
private readonly DebugEventHandlerService _debugEventHandlerService;
115115
private readonly IDebugAdapterServerFacade _debugAdapterServer;
116116
private readonly RemoteFileManagerService _remoteFileManagerService;
117+
private static readonly string[] s_requiredDebugCommands =
118+
[
119+
"Get-Variable",
120+
"Wait-Debugger",
121+
"Get-Runspace",
122+
];
117123

118124
public LaunchAndAttachHandler(
119125
ILoggerFactory factory,
@@ -154,6 +160,8 @@ public async Task<LaunchResponse> Handle(PsesLaunchRequestArguments request, Can
154160

155161
public async Task<LaunchResponse> HandleImpl(PsesLaunchRequestArguments request, CancellationToken cancellationToken)
156162
{
163+
EnsureDebugCommandsAvailable();
164+
157165
// The debugger has officially started. We use this to later check if we should stop it.
158166
((PsesInternalHost)_executionService).DebugContext.IsActive = true;
159167

@@ -246,6 +254,24 @@ await _executionService.ExecutePSCommandAsync(
246254
return new LaunchResponse();
247255
}
248256

257+
private void EnsureDebugCommandsAvailable()
258+
{
259+
if (_runspaceContext.CurrentRunspace.Runspace.SessionStateProxy.LanguageMode != PSLanguageMode.ConstrainedLanguage)
260+
{
261+
return;
262+
}
263+
264+
foreach (string commandName in s_requiredDebugCommands)
265+
{
266+
if (_runspaceContext.CurrentRunspace.Runspace.SessionStateProxy.InvokeCommand.GetCommand(commandName, CommandTypes.Cmdlet) is null)
267+
{
268+
throw new HandlerErrorException(
269+
"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.",
270+
new { MissingCommand = commandName, LanguageMode = _runspaceContext.CurrentRunspace.Runspace.SessionStateProxy.LanguageMode.ToString() });
271+
}
272+
}
273+
}
274+
249275
public async Task<AttachResponse> Handle(PsesAttachRequestArguments request, CancellationToken cancellationToken)
250276
{
251277
// We want to set this as early as possible to avoid an early `StopDebugging` call in

test/PowerShellEditorServices.Test/Debugging/DebugServiceTests.cs

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,18 +7,24 @@
77
using System.IO;
88
using System.Linq;
99
using System.Management.Automation;
10+
using System.Management.Automation.Runspaces;
1011
using System.Threading;
1112
using System.Threading.Tasks;
1213
using Microsoft.Extensions.Logging.Abstractions;
1314
using Microsoft.PowerShell.EditorServices.Handlers;
1415
using Microsoft.PowerShell.EditorServices.Services;
1516
using Microsoft.PowerShell.EditorServices.Services.DebugAdapter;
17+
using Microsoft.PowerShell.EditorServices.Services.PowerShell.Context;
18+
using Microsoft.PowerShell.EditorServices.Services.PowerShell.Execution;
1619
using Microsoft.PowerShell.EditorServices.Services.PowerShell.Console;
1720
using Microsoft.PowerShell.EditorServices.Services.PowerShell.Host;
21+
using Microsoft.PowerShell.EditorServices.Services.PowerShell;
22+
using Microsoft.PowerShell.EditorServices.Services.PowerShell.Runspace;
1823
using Microsoft.PowerShell.EditorServices.Services.TextDocument;
1924
using Microsoft.PowerShell.EditorServices.Test;
2025
using Microsoft.PowerShell.EditorServices.Test.Shared;
2126
using Microsoft.PowerShell.EditorServices.Utility;
27+
using SMA = System.Management.Automation;
2228
using Xunit;
2329

2430
namespace PowerShellEditorServices.Test.Debugging
@@ -32,6 +38,69 @@ internal class TestReadLine : IReadLine
3238
public void AddToHistory(string historyEntry) => history.Add(historyEntry);
3339
}
3440

41+
internal sealed class TestRunspaceInfo(Runspace runspace) : IRunspaceInfo
42+
{
43+
public RunspaceOrigin RunspaceOrigin => global::Microsoft.PowerShell.EditorServices.Services.PowerShell.Runspace.RunspaceOrigin.Local;
44+
45+
public bool IsOnRemoteMachine => false;
46+
47+
public PowerShellVersionDetails PowerShellVersionDetails => throw new NotImplementedException();
48+
49+
public SessionDetails SessionDetails => throw new NotImplementedException();
50+
51+
public Runspace Runspace { get; } = runspace;
52+
}
53+
54+
internal sealed class TestRunspaceContext(Runspace runspace) : IRunspaceContext
55+
{
56+
public IRunspaceInfo CurrentRunspace { get; } = new TestRunspaceInfo(runspace);
57+
}
58+
59+
internal sealed class ThrowingExecutionService : IInternalPowerShellExecutionService
60+
{
61+
public event Action<object, RunspaceChangedEventArgs> RunspaceChanged
62+
{
63+
add { }
64+
remove { }
65+
}
66+
67+
public Task<TResult> ExecuteDelegateAsync<TResult>(
68+
string representation,
69+
ExecutionOptions executionOptions,
70+
Func<SMA.PowerShell, CancellationToken, TResult> func,
71+
CancellationToken cancellationToken) => throw new NotImplementedException();
72+
73+
public Task ExecuteDelegateAsync(
74+
string representation,
75+
ExecutionOptions executionOptions,
76+
Action<SMA.PowerShell, CancellationToken> action,
77+
CancellationToken cancellationToken) => throw new NotImplementedException();
78+
79+
public Task<TResult> ExecuteDelegateAsync<TResult>(
80+
string representation,
81+
ExecutionOptions executionOptions,
82+
Func<CancellationToken, TResult> func,
83+
CancellationToken cancellationToken) => throw new NotImplementedException();
84+
85+
public Task ExecuteDelegateAsync(
86+
string representation,
87+
ExecutionOptions executionOptions,
88+
Action<CancellationToken> action,
89+
CancellationToken cancellationToken) => throw new NotImplementedException();
90+
91+
public Task<IReadOnlyList<TResult>> ExecutePSCommandAsync<TResult>(
92+
PSCommand psCommand,
93+
CancellationToken cancellationToken,
94+
PowerShellExecutionOptions executionOptions = null) => throw new NotImplementedException();
95+
96+
public Task ExecutePSCommandAsync(
97+
PSCommand psCommand,
98+
CancellationToken cancellationToken,
99+
PowerShellExecutionOptions executionOptions = null) => throw new NotImplementedException();
100+
101+
public void CancelCurrentTask() => throw new NotImplementedException();
102+
}
103+
35104
[Trait("Category", "DebugService")]
36105
public class DebugServiceTests : IAsyncLifetime
37106
{
@@ -589,6 +658,44 @@ public async Task RecordsF5CommandInPowerShellHistory()
589658
Assert.Equal(". '" + debugScriptFile.FilePath + "'", Assert.Single(testReadLine.history));
590659
}
591660

661+
[Fact]
662+
public async Task LaunchFailsInConstrainedLanguageWhenDebugCommandsAreUnavailable()
663+
{
664+
InitialSessionState initialSessionState = InitialSessionState.CreateDefault2();
665+
initialSessionState.LanguageMode = PSLanguageMode.ConstrainedLanguage;
666+
using Runspace constrainedRunspace = RunspaceFactory.CreateRunspace(initialSessionState);
667+
constrainedRunspace.Open();
668+
669+
Assert.Equal(PSLanguageMode.ConstrainedLanguage, constrainedRunspace.SessionStateProxy.LanguageMode);
670+
Assert.Null(constrainedRunspace.SessionStateProxy.InvokeCommand.GetCommand("Wait-Debugger", CommandTypes.Cmdlet));
671+
672+
LaunchAndAttachHandler launchHandler = new(
673+
NullLoggerFactory.Instance,
674+
debugAdapterServer: null,
675+
breakpointService: null,
676+
debugEventHandlerService: null,
677+
debugService,
678+
new TestRunspaceContext(constrainedRunspace),
679+
new ThrowingExecutionService(),
680+
new DebugStateService(),
681+
remoteFileManagerService: null);
682+
683+
HandlerErrorException exception = await Assert.ThrowsAsync<HandlerErrorException>(() =>
684+
launchHandler.Handle(
685+
new PsesLaunchRequestArguments
686+
{
687+
Script = debugScriptFile.FilePath,
688+
Cwd = "",
689+
CreateTemporaryIntegratedConsole = false,
690+
ExecuteMode = "DotSource",
691+
},
692+
CancellationToken.None));
693+
694+
Assert.Equal(
695+
"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.",
696+
exception.Message);
697+
}
698+
592699
[Fact]
593700
public async Task RecordsF8CommandInHistory()
594701
{

0 commit comments

Comments
 (0)