Skip to content

Commit

Permalink
Support ConstrainedLanguage mode (#1269)
Browse files Browse the repository at this point in the history
* 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>
  • Loading branch information
TylerLeonhardt and TylerLeonhardt authored Apr 28, 2020
1 parent fc1a95a commit 03bca8d
Show file tree
Hide file tree
Showing 18 changed files with 132 additions and 50 deletions.
10 changes: 10 additions & 0 deletions PowerShellEditorServices.build.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,16 @@ task TestE2E {

$env:PWSH_EXE_NAME = if ($IsCoreCLR) { "pwsh" } else { "powershell" }
exec { & $script:dotnetExe test --logger trx -f $script:NetRuntime.Core (DotNetTestFilter) }

# Run E2E tests in ConstrainedLanguage mode.
if (!$script:IsUnix) {
try {
[System.Environment]::SetEnvironmentVariable("__PSLockdownPolicy", "0x80000007", [System.EnvironmentVariableTarget]::Machine);
exec { & $script:dotnetExe test --logger trx -f $script:NetRuntime.Core (DotNetTestFilter) }
} finally {
[System.Environment]::SetEnvironmentVariable("__PSLockdownPolicy", $null, [System.EnvironmentVariableTarget]::Machine);
}
}
}

task LayoutModule -After Build {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@ function Clear-Host {
param()

__clearhost
$psEditor.Window.Terminal.Clear()
if ($host.Runspace.LanguageMode -eq [System.Management.Automation.PSLanguageMode]::FullLanguage) {
$psEditor.Window.Terminal.Clear()
}
}

if (!$IsMacOS -or $IsLinux) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
using System.Management.Automation;
using System.Reflection;
using SMA = System.Management.Automation;
using System.Management.Automation.Runspaces;
using Microsoft.PowerShell.EditorServices.Hosting;
using System.Globalization;
using System.Collections;
Expand Down Expand Up @@ -358,6 +359,7 @@ private EditorServicesConfig CreateConfigObject()
AdditionalModules = AdditionalModules,
LanguageServiceTransport = GetLanguageServiceTransport(),
DebugServiceTransport = GetDebugServiceTransport(),
LanguageMode = Runspace.DefaultRunspace.SessionStateProxy.LanguageMode,
ProfilePaths = new ProfilePathConfig
{
AllUsersAllHosts = GetProfilePathFromProfileObject(profile, ProfileUserKind.AllUsers, ProfileHostKind.AllHosts),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
//

using System.Collections.Generic;
using System.Management.Automation;
using System.Management.Automation.Host;

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

/// <summary>
/// The language mode inherited from the orginal PowerShell process.
/// This will be used when creating runspaces so that we honor the same language mode.
/// </summary>
public PSLanguageMode LanguageMode { get; internal set; }

public string StartupBanner { get; set; } = @"
=====> PowerShell Integrated Console <=====
Expand Down
17 changes: 1 addition & 16 deletions src/PowerShellEditorServices.Hosting/EditorServicesLoader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -190,8 +190,6 @@ public Task LoadAndRunEditorServicesAsync()
// Make sure the .NET Framework version supports .NET Standard 2.0
CheckNetFxVersion();
#endif
// Ensure the language mode allows us to run
CheckLanguageMode();

// Add the bundled modules to the PSModulePath
UpdatePSModulePath();
Expand Down Expand Up @@ -250,19 +248,6 @@ private void CheckNetFxVersion()
}
#endif

/// <summary>
/// PSES currently does not work in Constrained Language Mode, because PSReadLine script invocations won't work in it.
/// Ideally we can find a better way so that PSES will work in CLM.
/// </summary>
private void CheckLanguageMode()
{
_logger.Log(PsesLogLevel.Diagnostic, "Checking that PSES is running in FullLanguage mode");
if (Runspace.DefaultRunspace.SessionStateProxy.LanguageMode != PSLanguageMode.FullLanguage)
{
throw new InvalidOperationException("Cannot start PowerShell Editor Services in Constrained Language Mode");
}
}

private void UpdatePSModulePath()
{
if (string.IsNullOrEmpty(_hostConfig.BundledModulePath))
Expand Down Expand Up @@ -332,7 +317,7 @@ private string GetPSOutputEncoding()
{
using (var pwsh = SMA.PowerShell.Create())
{
return pwsh.AddScript("$OutputEncoding.EncodingName").Invoke<string>()[0];
return pwsh.AddScript("$OutputEncoding.EncodingName", useLocalScope: true).Invoke<string>()[0];
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,7 @@ private HostStartupInfo CreateHostStartupInfo()
profilePaths,
_config.FeatureFlags,
_config.AdditionalModules,
_config.LanguageMode,
_config.LogPath,
(int)_config.LogLevel,
consoleReplEnabled: _config.ConsoleRepl != ConsoleReplKind.None,
Expand Down
10 changes: 10 additions & 0 deletions src/PowerShellEditorServices/Hosting/HostStartupInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

using System;
using System.Collections.Generic;
using System.Management.Automation;
using System.Management.Automation.Host;

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

/// <summary>
/// The language mode inherited from the orginal PowerShell process.
/// This will be used when creating runspaces so that we honor the same language mode.
/// </summary>
public PSLanguageMode LanguageMode { get; }

/// <summary>
/// The minimum log level of log events to be logged.
/// </summary>
Expand Down Expand Up @@ -117,6 +124,7 @@ public sealed class HostStartupInfo
/// <param name="currentUsersProfilePath">The path to the user specific profile.</param>
/// <param name="featureFlags">Flags of features to enable.</param>
/// <param name="additionalModules">Names or paths of additional modules to import.</param>
/// <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>
/// <param name="logPath">The path to log to.</param>
/// <param name="logLevel">The minimum log event level.</param>
/// <param name="consoleReplEnabled">Enable console if true.</param>
Expand All @@ -129,6 +137,7 @@ public HostStartupInfo(
ProfilePathInfo profilePaths,
IReadOnlyList<string> featureFlags,
IReadOnlyList<string> additionalModules,
PSLanguageMode languageMode,
string logPath,
int logLevel,
bool consoleReplEnabled,
Expand All @@ -141,6 +150,7 @@ public HostStartupInfo(
ProfilePaths = profilePaths;
FeatureFlags = featureFlags ?? Array.Empty<string>();
AdditionalModules = additionalModules ?? Array.Empty<string>();
LanguageMode = languageMode;
LogPath = logPath;
LogLevel = logLevel;
ConsoleReplEnabled = consoleReplEnabled;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,7 @@ public static PowerShellContextService Create(
hostUserInterface,
logger);

Runspace initialRunspace = PowerShellContextService.CreateRunspace(psHost);
Runspace initialRunspace = PowerShellContextService.CreateRunspace(psHost, hostStartupInfo.LanguageMode);
powerShellContext.Initialize(hostStartupInfo.ProfilePaths, initialRunspace, true, hostUserInterface);

powerShellContext.ImportCommandsModuleAsync();
Expand Down Expand Up @@ -244,9 +244,7 @@ public static PowerShellContextService Create(
/// </summary>
/// <param name="hostDetails"></param>
/// <param name="powerShellContext"></param>
/// <param name="hostUserInterface">
/// The EditorServicesPSHostUserInterface to use for this instance.
/// </param>
/// <param name="hostUserInterface">The EditorServicesPSHostUserInterface to use for this instance.</param>
/// <param name="logger">An ILogger implementation to use for this instance.</param>
/// <returns></returns>
public static Runspace CreateRunspace(
Expand All @@ -260,15 +258,16 @@ public static Runspace CreateRunspace(
var psHost = new EditorServicesPSHost(powerShellContext, hostDetails, hostUserInterface, logger);
powerShellContext.ConsoleWriter = hostUserInterface;
powerShellContext.ConsoleReader = hostUserInterface;
return CreateRunspace(psHost);
return CreateRunspace(psHost, hostDetails.LanguageMode);
}

/// <summary>
///
/// </summary>
/// <param name="psHost"></param>
/// <param name="psHost">The PSHost that will be used for this Runspace.</param>
/// <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>
/// <returns></returns>
public static Runspace CreateRunspace(PSHost psHost)
public static Runspace CreateRunspace(PSHost psHost, PSLanguageMode languageMode)
{
InitialSessionState initialSessionState;
if (Environment.GetEnvironmentVariable("PSES_TEST_USE_CREATE_DEFAULT") == "1") {
Expand All @@ -277,6 +276,11 @@ public static Runspace CreateRunspace(PSHost psHost)
initialSessionState = InitialSessionState.CreateDefault2();
}

// Create and initialize a new Runspace while honoring the LanguageMode of the original runspace
// that started PowerShell Editor Services. This is because the PowerShell Integrated Console
// should have the same LanguageMode of whatever is set by the system.
initialSessionState.LanguageMode = languageMode;

Runspace runspace = RunspaceFactory.CreateRunspace(psHost, initialSessionState);

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

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

bool hadErrors = false;
Expand Down Expand Up @@ -686,7 +692,7 @@ public async Task<IEnumerable<TResult>> ExecuteCommandAsync<TResult>(
if (executionOptions.WriteInputToHost)
{
this.WriteOutput(
psCommand.Commands[0].CommandText,
executionOptions.InputString ?? psCommand.Commands[0].CommandText,
includeNewLine: true);
}

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

internal static TResult ExecuteScriptAndGetItem<TResult>(string scriptToExecute, Runspace runspace, TResult defaultValue = default)
internal static TResult ExecuteScriptAndGetItem<TResult>(
string scriptToExecute,
Runspace runspace,
TResult defaultValue = default,
bool useLocalScope = false)
{
using (PowerShell pwsh = PowerShell.Create())
{
pwsh.Runspace = runspace;
IEnumerable<TResult> results = pwsh.AddScript(scriptToExecute).Invoke<TResult>();
IEnumerable<TResult> results = pwsh.AddScript(scriptToExecute, useLocalScope).Invoke<TResult>();
return results.DefaultIfEmpty(defaultValue).First();
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -121,14 +121,17 @@ public static DscBreakpointCapability CheckForCapability(
runspaceDetails.AddCapability(capability);

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

// Get the list of DSC resource paths
powerShell.Commands.Clear();
powerShell.AddCommand("Get-DscResource");
powerShell.AddCommand("Select-Object");
powerShell.AddParameter("ExpandProperty", "ParentPath");
powerShell
.AddCommand("Get-DscResource")
.AddCommand("Select-Object")
.AddParameter("ExpandProperty", "ParentPath");

Collection<PSObject> resourcePaths = null;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,18 @@ internal class ExecutionOptions
/// </summary>
public bool WriteInputToHost { get; set; }

/// <summary>
/// If this is set, we will use this string for history and writing to the host
/// instead of grabbing the command from the PSCommand.
/// </summary>
public string InputString { get; set; }

/// <summary>
/// If this is set, we will use this string for history and writing to the host
/// instead of grabbing the command from the PSCommand.
/// </summary>
public bool UseNewScope { get; set; }

/// <summary>
/// Gets or sets a value indicating whether the command to
/// be executed is a console input prompt, such as the
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ internal static bool TryGetPSReadLineProxy(
{
pwsh.Runspace = runspace;
var psReadLineType = pwsh
.AddScript(ReadLineInitScript)
.AddScript(ReadLineInitScript, useLocalScope: true)
.Invoke<Type>()
.FirstOrDefault();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ public static PowerShellVersionDetails GetVersionDetails(Runspace runspace, ILog

try
{
var psVersionTable = PowerShellContextService.ExecuteScriptAndGetItem<Hashtable>("$PSVersionTable", runspace);
var psVersionTable = PowerShellContextService.ExecuteScriptAndGetItem<Hashtable>("$PSVersionTable", runspace, useLocalScope: true);
if (psVersionTable != null)
{
var edition = psVersionTable["PSEdition"] as string;
Expand Down Expand Up @@ -134,7 +134,7 @@ public static PowerShellVersionDetails GetVersionDetails(Runspace runspace, ILog
versionString = powerShellVersion.ToString();
}

var arch = PowerShellContextService.ExecuteScriptAndGetItem<string>("$env:PROCESSOR_ARCHITECTURE", runspace);
var arch = PowerShellContextService.ExecuteScriptAndGetItem<string>("$env:PROCESSOR_ARCHITECTURE", runspace, useLocalScope: true);
if (arch != null)
{
if (string.Equals(arch, "AMD64", StringComparison.CurrentCultureIgnoreCase))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,8 @@ internal static RunspaceDetails CreateFromRunspace(
PowerShellContextService.ExecuteScriptAndGetItem<string>(
"$Host.Name",
runspace,
defaultValue: string.Empty);
defaultValue: string.Empty,
useLocalScope: true);

// hostname is 'ServerRemoteHost' when the user enters a session.
// ex. Enter-PSSession
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,8 @@ public static PSCommand GetDetailsCommand()
{
PSCommand infoCommand = new PSCommand();
infoCommand.AddScript(
"@{ 'computerName' = if ([Environment]::MachineName) {[Environment]::MachineName} else {'localhost'}; 'processId' = $PID; 'instanceId' = $host.InstanceId }");
"@{ 'computerName' = if ([Environment]::MachineName) {[Environment]::MachineName} else {'localhost'}; 'processId' = $PID; 'instanceId' = $host.InstanceId }",
useLocalScope: true);

return infoCommand;
}
Expand Down
Loading

0 comments on commit 03bca8d

Please sign in to comment.