Skip to content

Commit 03bca8d

Browse files
Support ConstrainedLanguage mode (#1269)
* change some to AddScript useLocalScope * initial support of constrainedlanguage mode * polish ConstrainedLanguage mode * useLocalScope * have e2e tests also run in CLM Co-authored-by: Tyler Leonhardt (POWERSHELL) <tyleonha@microsoft.com>
1 parent fc1a95a commit 03bca8d

File tree

18 files changed

+132
-50
lines changed

18 files changed

+132
-50
lines changed

PowerShellEditorServices.build.ps1

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,16 @@ task TestE2E {
274274

275275
$env:PWSH_EXE_NAME = if ($IsCoreCLR) { "pwsh" } else { "powershell" }
276276
exec { & $script:dotnetExe test --logger trx -f $script:NetRuntime.Core (DotNetTestFilter) }
277+
278+
# Run E2E tests in ConstrainedLanguage mode.
279+
if (!$script:IsUnix) {
280+
try {
281+
[System.Environment]::SetEnvironmentVariable("__PSLockdownPolicy", "0x80000007", [System.EnvironmentVariableTarget]::Machine);
282+
exec { & $script:dotnetExe test --logger trx -f $script:NetRuntime.Core (DotNetTestFilter) }
283+
} finally {
284+
[System.Environment]::SetEnvironmentVariable("__PSLockdownPolicy", $null, [System.EnvironmentVariableTarget]::Machine);
285+
}
286+
}
277287
}
278288

279289
task LayoutModule -After Build {

module/PowerShellEditorServices/Commands/Public/Clear-Host.ps1

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,9 @@ function Clear-Host {
1010
param()
1111

1212
__clearhost
13-
$psEditor.Window.Terminal.Clear()
13+
if ($host.Runspace.LanguageMode -eq [System.Management.Automation.PSLanguageMode]::FullLanguage) {
14+
$psEditor.Window.Terminal.Clear()
15+
}
1416
}
1517

1618
if (!$IsMacOS -or $IsLinux) {

src/PowerShellEditorServices.Hosting/Commands/StartEditorServicesCommand.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
using System.Management.Automation;
1212
using System.Reflection;
1313
using SMA = System.Management.Automation;
14+
using System.Management.Automation.Runspaces;
1415
using Microsoft.PowerShell.EditorServices.Hosting;
1516
using System.Globalization;
1617
using System.Collections;
@@ -358,6 +359,7 @@ private EditorServicesConfig CreateConfigObject()
358359
AdditionalModules = AdditionalModules,
359360
LanguageServiceTransport = GetLanguageServiceTransport(),
360361
DebugServiceTransport = GetDebugServiceTransport(),
362+
LanguageMode = Runspace.DefaultRunspace.SessionStateProxy.LanguageMode,
361363
ProfilePaths = new ProfilePathConfig
362364
{
363365
AllUsersAllHosts = GetProfilePathFromProfileObject(profile, ProfileUserKind.AllUsers, ProfileHostKind.AllHosts),

src/PowerShellEditorServices.Hosting/Configuration/EditorServicesConfig.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
//
55

66
using System.Collections.Generic;
7+
using System.Management.Automation;
78
using System.Management.Automation.Host;
89

910
namespace Microsoft.PowerShell.EditorServices.Hosting
@@ -111,6 +112,12 @@ public EditorServicesConfig(
111112
/// </summary>
112113
public ProfilePathConfig ProfilePaths { get; set; }
113114

115+
/// <summary>
116+
/// The language mode inherited from the orginal PowerShell process.
117+
/// This will be used when creating runspaces so that we honor the same language mode.
118+
/// </summary>
119+
public PSLanguageMode LanguageMode { get; internal set; }
120+
114121
public string StartupBanner { get; set; } = @"
115122
116123
=====> PowerShell Integrated Console <=====

src/PowerShellEditorServices.Hosting/EditorServicesLoader.cs

Lines changed: 1 addition & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -190,8 +190,6 @@ public Task LoadAndRunEditorServicesAsync()
190190
// Make sure the .NET Framework version supports .NET Standard 2.0
191191
CheckNetFxVersion();
192192
#endif
193-
// Ensure the language mode allows us to run
194-
CheckLanguageMode();
195193

196194
// Add the bundled modules to the PSModulePath
197195
UpdatePSModulePath();
@@ -250,19 +248,6 @@ private void CheckNetFxVersion()
250248
}
251249
#endif
252250

253-
/// <summary>
254-
/// PSES currently does not work in Constrained Language Mode, because PSReadLine script invocations won't work in it.
255-
/// Ideally we can find a better way so that PSES will work in CLM.
256-
/// </summary>
257-
private void CheckLanguageMode()
258-
{
259-
_logger.Log(PsesLogLevel.Diagnostic, "Checking that PSES is running in FullLanguage mode");
260-
if (Runspace.DefaultRunspace.SessionStateProxy.LanguageMode != PSLanguageMode.FullLanguage)
261-
{
262-
throw new InvalidOperationException("Cannot start PowerShell Editor Services in Constrained Language Mode");
263-
}
264-
}
265-
266251
private void UpdatePSModulePath()
267252
{
268253
if (string.IsNullOrEmpty(_hostConfig.BundledModulePath))
@@ -332,7 +317,7 @@ private string GetPSOutputEncoding()
332317
{
333318
using (var pwsh = SMA.PowerShell.Create())
334319
{
335-
return pwsh.AddScript("$OutputEncoding.EncodingName").Invoke<string>()[0];
320+
return pwsh.AddScript("$OutputEncoding.EncodingName", useLocalScope: true).Invoke<string>()[0];
336321
}
337322
}
338323

src/PowerShellEditorServices.Hosting/Internal/EditorServicesRunner.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,7 @@ private HostStartupInfo CreateHostStartupInfo()
237237
profilePaths,
238238
_config.FeatureFlags,
239239
_config.AdditionalModules,
240+
_config.LanguageMode,
240241
_config.LogPath,
241242
(int)_config.LogLevel,
242243
consoleReplEnabled: _config.ConsoleRepl != ConsoleReplKind.None,

src/PowerShellEditorServices/Hosting/HostStartupInfo.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
using System;
77
using System.Collections.Generic;
8+
using System.Management.Automation;
89
using System.Management.Automation.Host;
910

1011
namespace Microsoft.PowerShell.EditorServices.Hosting
@@ -89,6 +90,12 @@ public sealed class HostStartupInfo
8990
/// </summary>
9091
public string LogPath { get; }
9192

93+
/// <summary>
94+
/// The language mode inherited from the orginal PowerShell process.
95+
/// This will be used when creating runspaces so that we honor the same language mode.
96+
/// </summary>
97+
public PSLanguageMode LanguageMode { get; }
98+
9299
/// <summary>
93100
/// The minimum log level of log events to be logged.
94101
/// </summary>
@@ -117,6 +124,7 @@ public sealed class HostStartupInfo
117124
/// <param name="currentUsersProfilePath">The path to the user specific profile.</param>
118125
/// <param name="featureFlags">Flags of features to enable.</param>
119126
/// <param name="additionalModules">Names or paths of additional modules to import.</param>
127+
/// <param name="languageMode">The language mode inherited from the orginal PowerShell process. This will be used when creating runspaces so that we honor the same language mode.</param>
120128
/// <param name="logPath">The path to log to.</param>
121129
/// <param name="logLevel">The minimum log event level.</param>
122130
/// <param name="consoleReplEnabled">Enable console if true.</param>
@@ -129,6 +137,7 @@ public HostStartupInfo(
129137
ProfilePathInfo profilePaths,
130138
IReadOnlyList<string> featureFlags,
131139
IReadOnlyList<string> additionalModules,
140+
PSLanguageMode languageMode,
132141
string logPath,
133142
int logLevel,
134143
bool consoleReplEnabled,
@@ -141,6 +150,7 @@ public HostStartupInfo(
141150
ProfilePaths = profilePaths;
142151
FeatureFlags = featureFlags ?? Array.Empty<string>();
143152
AdditionalModules = additionalModules ?? Array.Empty<string>();
153+
LanguageMode = languageMode;
144154
LogPath = logPath;
145155
LogLevel = logLevel;
146156
ConsoleReplEnabled = consoleReplEnabled;

src/PowerShellEditorServices/Services/PowerShellContext/PowerShellContextService.cs

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -213,7 +213,7 @@ public static PowerShellContextService Create(
213213
hostUserInterface,
214214
logger);
215215

216-
Runspace initialRunspace = PowerShellContextService.CreateRunspace(psHost);
216+
Runspace initialRunspace = PowerShellContextService.CreateRunspace(psHost, hostStartupInfo.LanguageMode);
217217
powerShellContext.Initialize(hostStartupInfo.ProfilePaths, initialRunspace, true, hostUserInterface);
218218

219219
powerShellContext.ImportCommandsModuleAsync();
@@ -244,9 +244,7 @@ public static PowerShellContextService Create(
244244
/// </summary>
245245
/// <param name="hostDetails"></param>
246246
/// <param name="powerShellContext"></param>
247-
/// <param name="hostUserInterface">
248-
/// The EditorServicesPSHostUserInterface to use for this instance.
249-
/// </param>
247+
/// <param name="hostUserInterface">The EditorServicesPSHostUserInterface to use for this instance.</param>
250248
/// <param name="logger">An ILogger implementation to use for this instance.</param>
251249
/// <returns></returns>
252250
public static Runspace CreateRunspace(
@@ -260,15 +258,16 @@ public static Runspace CreateRunspace(
260258
var psHost = new EditorServicesPSHost(powerShellContext, hostDetails, hostUserInterface, logger);
261259
powerShellContext.ConsoleWriter = hostUserInterface;
262260
powerShellContext.ConsoleReader = hostUserInterface;
263-
return CreateRunspace(psHost);
261+
return CreateRunspace(psHost, hostDetails.LanguageMode);
264262
}
265263

266264
/// <summary>
267265
///
268266
/// </summary>
269-
/// <param name="psHost"></param>
267+
/// <param name="psHost">The PSHost that will be used for this Runspace.</param>
268+
/// <param name="languageMode">The language mode inherited from the orginal PowerShell process. This will be used when creating runspaces so that we honor the same language mode.</param>
270269
/// <returns></returns>
271-
public static Runspace CreateRunspace(PSHost psHost)
270+
public static Runspace CreateRunspace(PSHost psHost, PSLanguageMode languageMode)
272271
{
273272
InitialSessionState initialSessionState;
274273
if (Environment.GetEnvironmentVariable("PSES_TEST_USE_CREATE_DEFAULT") == "1") {
@@ -277,6 +276,11 @@ public static Runspace CreateRunspace(PSHost psHost)
277276
initialSessionState = InitialSessionState.CreateDefault2();
278277
}
279278

279+
// Create and initialize a new Runspace while honoring the LanguageMode of the original runspace
280+
// that started PowerShell Editor Services. This is because the PowerShell Integrated Console
281+
// should have the same LanguageMode of whatever is set by the system.
282+
initialSessionState.LanguageMode = languageMode;
283+
280284
Runspace runspace = RunspaceFactory.CreateRunspace(psHost, initialSessionState);
281285

282286
// Windows PowerShell must be hosted in STA mode
@@ -410,6 +414,8 @@ public void Initialize(
410414

411415
if (powerShellVersion.Major >= 5 &&
412416
this.isPSReadLineEnabled &&
417+
// TODO: Figure out why PSReadLine isn't working in ConstrainedLanguage mode.
418+
initialRunspace.SessionStateProxy.LanguageMode == PSLanguageMode.FullLanguage &&
413419
PSReadLinePromptContext.TryGetPSReadLineProxy(logger, initialRunspace, out PSReadLineProxy proxy))
414420
{
415421
this.PromptContext = new PSReadLinePromptContext(
@@ -597,7 +603,7 @@ public async Task<IEnumerable<TResult>> ExecuteCommandAsync<TResult>(
597603
// cancelled prompt when it's called again.
598604
if (executionOptions.AddToHistory)
599605
{
600-
this.PromptContext.AddToHistory(psCommand.Commands[0].CommandText);
606+
this.PromptContext.AddToHistory(executionOptions.InputString ?? psCommand.Commands[0].CommandText);
601607
}
602608

603609
bool hadErrors = false;
@@ -686,7 +692,7 @@ public async Task<IEnumerable<TResult>> ExecuteCommandAsync<TResult>(
686692
if (executionOptions.WriteInputToHost)
687693
{
688694
this.WriteOutput(
689-
psCommand.Commands[0].CommandText,
695+
executionOptions.InputString ?? psCommand.Commands[0].CommandText,
690696
includeNewLine: true);
691697
}
692698

@@ -1156,12 +1162,16 @@ internal async Task<string> InvokeReadLineAsync(bool isCommandLine, Cancellation
11561162
cancellationToken).ConfigureAwait(false);
11571163
}
11581164

1159-
internal static TResult ExecuteScriptAndGetItem<TResult>(string scriptToExecute, Runspace runspace, TResult defaultValue = default)
1165+
internal static TResult ExecuteScriptAndGetItem<TResult>(
1166+
string scriptToExecute,
1167+
Runspace runspace,
1168+
TResult defaultValue = default,
1169+
bool useLocalScope = false)
11601170
{
11611171
using (PowerShell pwsh = PowerShell.Create())
11621172
{
11631173
pwsh.Runspace = runspace;
1164-
IEnumerable<TResult> results = pwsh.AddScript(scriptToExecute).Invoke<TResult>();
1174+
IEnumerable<TResult> results = pwsh.AddScript(scriptToExecute, useLocalScope).Invoke<TResult>();
11651175
return results.DefaultIfEmpty(defaultValue).First();
11661176
}
11671177
}

src/PowerShellEditorServices/Services/PowerShellContext/Session/Capabilities/DscBreakpointCapability.cs

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -121,14 +121,17 @@ public static DscBreakpointCapability CheckForCapability(
121121
runspaceDetails.AddCapability(capability);
122122

123123
powerShell.Commands.Clear();
124-
powerShell.AddScript("Write-Host \"Gathering DSC resource paths, this may take a while...\"");
125-
powerShell.Invoke();
124+
powerShell
125+
.AddCommand("Microsoft.PowerShell.Utility\\Write-Host")
126+
.AddArgument("Gathering DSC resource paths, this may take a while...")
127+
.Invoke();
126128

127129
// Get the list of DSC resource paths
128130
powerShell.Commands.Clear();
129-
powerShell.AddCommand("Get-DscResource");
130-
powerShell.AddCommand("Select-Object");
131-
powerShell.AddParameter("ExpandProperty", "ParentPath");
131+
powerShell
132+
.AddCommand("Get-DscResource")
133+
.AddCommand("Select-Object")
134+
.AddParameter("ExpandProperty", "ParentPath");
132135

133136
Collection<PSObject> resourcePaths = null;
134137

src/PowerShellEditorServices/Services/PowerShellContext/Session/ExecutionOptions.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,18 @@ internal class ExecutionOptions
4646
/// </summary>
4747
public bool WriteInputToHost { get; set; }
4848

49+
/// <summary>
50+
/// If this is set, we will use this string for history and writing to the host
51+
/// instead of grabbing the command from the PSCommand.
52+
/// </summary>
53+
public string InputString { get; set; }
54+
55+
/// <summary>
56+
/// If this is set, we will use this string for history and writing to the host
57+
/// instead of grabbing the command from the PSCommand.
58+
/// </summary>
59+
public bool UseNewScope { get; set; }
60+
4961
/// <summary>
5062
/// Gets or sets a value indicating whether the command to
5163
/// be executed is a console input prompt, such as the

src/PowerShellEditorServices/Services/PowerShellContext/Session/PSReadLinePromptContext.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ internal static bool TryGetPSReadLineProxy(
9191
{
9292
pwsh.Runspace = runspace;
9393
var psReadLineType = pwsh
94-
.AddScript(ReadLineInitScript)
94+
.AddScript(ReadLineInitScript, useLocalScope: true)
9595
.Invoke<Type>()
9696
.FirstOrDefault();
9797

src/PowerShellEditorServices/Services/PowerShellContext/Session/PowerShellVersionDetails.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ public static PowerShellVersionDetails GetVersionDetails(Runspace runspace, ILog
101101

102102
try
103103
{
104-
var psVersionTable = PowerShellContextService.ExecuteScriptAndGetItem<Hashtable>("$PSVersionTable", runspace);
104+
var psVersionTable = PowerShellContextService.ExecuteScriptAndGetItem<Hashtable>("$PSVersionTable", runspace, useLocalScope: true);
105105
if (psVersionTable != null)
106106
{
107107
var edition = psVersionTable["PSEdition"] as string;
@@ -134,7 +134,7 @@ public static PowerShellVersionDetails GetVersionDetails(Runspace runspace, ILog
134134
versionString = powerShellVersion.ToString();
135135
}
136136

137-
var arch = PowerShellContextService.ExecuteScriptAndGetItem<string>("$env:PROCESSOR_ARCHITECTURE", runspace);
137+
var arch = PowerShellContextService.ExecuteScriptAndGetItem<string>("$env:PROCESSOR_ARCHITECTURE", runspace, useLocalScope: true);
138138
if (arch != null)
139139
{
140140
if (string.Equals(arch, "AMD64", StringComparison.CurrentCultureIgnoreCase))

src/PowerShellEditorServices/Services/PowerShellContext/Session/RunspaceDetails.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -223,7 +223,8 @@ internal static RunspaceDetails CreateFromRunspace(
223223
PowerShellContextService.ExecuteScriptAndGetItem<string>(
224224
"$Host.Name",
225225
runspace,
226-
defaultValue: string.Empty);
226+
defaultValue: string.Empty,
227+
useLocalScope: true);
227228

228229
// hostname is 'ServerRemoteHost' when the user enters a session.
229230
// ex. Enter-PSSession

src/PowerShellEditorServices/Services/PowerShellContext/Session/SessionDetails.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,8 @@ public static PSCommand GetDetailsCommand()
5656
{
5757
PSCommand infoCommand = new PSCommand();
5858
infoCommand.AddScript(
59-
"@{ 'computerName' = if ([Environment]::MachineName) {[Environment]::MachineName} else {'localhost'}; 'processId' = $PID; 'instanceId' = $host.InstanceId }");
59+
"@{ 'computerName' = if ([Environment]::MachineName) {[Environment]::MachineName} else {'localhost'}; 'processId' = $PID; 'instanceId' = $host.InstanceId }",
60+
useLocalScope: true);
6061

6162
return infoCommand;
6263
}

0 commit comments

Comments
 (0)