diff --git a/.editorconfig b/.editorconfig index 7f900f267..e0f9bbf3f 100644 --- a/.editorconfig +++ b/.editorconfig @@ -11,7 +11,7 @@ insert_final_newline = true indent_size = 4 trim_trailing_whitespace = true csharp_space_before_open_square_brackets = true -csharp_space_after_keywords_in_control_flow_statements = false +csharp_space_after_keywords_in_control_flow_statements = true [*.{json}] indent_size = 2 diff --git a/src/PowerShellEditorServices.Hosting/Commands/StartEditorServicesCommand.cs b/src/PowerShellEditorServices.Hosting/Commands/StartEditorServicesCommand.cs index 0e698f812..2a2ae3527 100644 --- a/src/PowerShellEditorServices.Hosting/Commands/StartEditorServicesCommand.cs +++ b/src/PowerShellEditorServices.Hosting/Commands/StartEditorServicesCommand.cs @@ -349,7 +349,16 @@ private EditorServicesConfig CreateConfigObject() var profile = (PSObject)GetVariableValue("profile"); var hostInfo = new HostInfo(HostName, HostProfileId, HostVersion); - var editorServicesConfig = new EditorServicesConfig(hostInfo, Host, SessionDetailsPath, bundledModulesPath, LogPath) + + var initialSessionState = Runspace.DefaultRunspace.InitialSessionState; + initialSessionState.LanguageMode = Runspace.DefaultRunspace.SessionStateProxy.LanguageMode; + + var editorServicesConfig = new EditorServicesConfig( + hostInfo, + Host, + SessionDetailsPath, + bundledModulesPath, + LogPath) { FeatureFlags = FeatureFlags, LogLevel = LogLevel, @@ -357,7 +366,7 @@ private EditorServicesConfig CreateConfigObject() AdditionalModules = AdditionalModules, LanguageServiceTransport = GetLanguageServiceTransport(), DebugServiceTransport = GetDebugServiceTransport(), - LanguageMode = Runspace.DefaultRunspace.SessionStateProxy.LanguageMode, + InitialSessionState = initialSessionState, ProfilePaths = new ProfilePathConfig { AllUsersAllHosts = GetProfilePathFromProfileObject(profile, ProfileUserKind.AllUsers, ProfileHostKind.AllHosts), diff --git a/src/PowerShellEditorServices.Hosting/Configuration/EditorServicesConfig.cs b/src/PowerShellEditorServices.Hosting/Configuration/EditorServicesConfig.cs index 5228ceced..2347ac4a1 100644 --- a/src/PowerShellEditorServices.Hosting/Configuration/EditorServicesConfig.cs +++ b/src/PowerShellEditorServices.Hosting/Configuration/EditorServicesConfig.cs @@ -2,8 +2,8 @@ // Licensed under the MIT License. using System.Collections.Generic; -using System.Management.Automation; using System.Management.Automation.Host; +using System.Management.Automation.Runspaces; namespace Microsoft.PowerShell.EditorServices.Hosting { @@ -111,10 +111,9 @@ public EditorServicesConfig( public ProfilePathConfig ProfilePaths { get; set; } /// - /// The language mode inherited from the orginal PowerShell process. - /// This will be used when creating runspaces so that we honor the same language mode. + /// The InitialSessionState to use when creating runspaces. LanguageMode can be set here. /// - public PSLanguageMode LanguageMode { get; internal set; } + public InitialSessionState InitialSessionState { get; internal set; } public string StartupBanner { get; set; } = @" diff --git a/src/PowerShellEditorServices.Hosting/Internal/EditorServicesRunner.cs b/src/PowerShellEditorServices.Hosting/Internal/EditorServicesRunner.cs index e49da8527..a68842876 100644 --- a/src/PowerShellEditorServices.Hosting/Internal/EditorServicesRunner.cs +++ b/src/PowerShellEditorServices.Hosting/Internal/EditorServicesRunner.cs @@ -288,7 +288,7 @@ private HostStartupInfo CreateHostStartupInfo() profilePaths, _config.FeatureFlags, _config.AdditionalModules, - _config.LanguageMode, + _config.InitialSessionState, _config.LogPath, (int)_config.LogLevel, consoleReplEnabled: _config.ConsoleRepl != ConsoleReplKind.None, diff --git a/src/PowerShellEditorServices/Hosting/HostStartupInfo.cs b/src/PowerShellEditorServices/Hosting/HostStartupInfo.cs index 9fc788e0d..ed6fb3e11 100644 --- a/src/PowerShellEditorServices/Hosting/HostStartupInfo.cs +++ b/src/PowerShellEditorServices/Hosting/HostStartupInfo.cs @@ -3,8 +3,8 @@ using System; using System.Collections.Generic; -using System.Management.Automation; using System.Management.Automation.Host; +using System.Management.Automation.Runspaces; namespace Microsoft.PowerShell.EditorServices.Hosting { @@ -92,10 +92,10 @@ public sealed class HostStartupInfo public string LogPath { get; } /// - /// The language mode inherited from the orginal PowerShell process. - /// This will be used when creating runspaces so that we honor the same language mode. + /// The InitialSessionState will be inherited from the orginal PowerShell process. This will + /// be used when creating runspaces so that we honor the same InitialSessionState. /// - public PSLanguageMode LanguageMode { get; } + public InitialSessionState InitialSessionState { get; } /// /// The minimum log level of log events to be logged. @@ -135,7 +135,7 @@ public sealed class HostStartupInfo /// The path to the user specific profile. /// Flags of features to enable. /// Names or paths of additional modules to import. - /// The language mode inherited from the orginal PowerShell process. This will be used when creating runspaces so that we honor the same language mode. + /// The language mode inherited from the orginal PowerShell process. This will be used when creating runspaces so that we honor the same initialSessionState including allowed modules, cmdlets and language mode. /// The path to log to. /// The minimum log event level. /// Enable console if true. @@ -149,7 +149,7 @@ public HostStartupInfo( ProfilePathInfo profilePaths, IReadOnlyList featureFlags, IReadOnlyList additionalModules, - PSLanguageMode languageMode, + InitialSessionState initialSessionState, string logPath, int logLevel, bool consoleReplEnabled, @@ -163,7 +163,7 @@ public HostStartupInfo( ProfilePaths = profilePaths; FeatureFlags = featureFlags ?? Array.Empty(); AdditionalModules = additionalModules ?? Array.Empty(); - LanguageMode = languageMode; + InitialSessionState = initialSessionState; LogPath = logPath; LogLevel = logLevel; ConsoleReplEnabled = consoleReplEnabled; diff --git a/src/PowerShellEditorServices/Services/PowerShellContext/PowerShellContextService.cs b/src/PowerShellEditorServices/Services/PowerShellContext/PowerShellContextService.cs index 198cd26d9..c2194a9bd 100644 --- a/src/PowerShellEditorServices/Services/PowerShellContext/PowerShellContextService.cs +++ b/src/PowerShellEditorServices/Services/PowerShellContext/PowerShellContextService.cs @@ -11,7 +11,6 @@ using System.Management.Automation.Remoting; using System.Management.Automation.Runspaces; using System.Reflection; -using System.Runtime.InteropServices; using System.Text; using System.Threading; using System.Threading.Tasks; @@ -230,7 +229,7 @@ public static PowerShellContextService Create( logger); logger.LogTrace("Creating initial PowerShell runspace"); - Runspace initialRunspace = PowerShellContextService.CreateRunspace(psHost, hostStartupInfo.LanguageMode); + Runspace initialRunspace = PowerShellContextService.CreateRunspace(psHost, hostStartupInfo.InitialSessionState); powerShellContext.Initialize(hostStartupInfo.ProfilePaths, initialRunspace, true, hostUserInterface); powerShellContext.ImportCommandsModuleAsync(); @@ -256,14 +255,17 @@ public static PowerShellContextService Create( } /// - /// + /// Only used in testing. Creates a Runspace given HostStartupInfo instead of a PSHost. /// + /// + /// TODO: We should use `CreateRunspace` in testing instead of this, if possible. + /// /// /// /// The EditorServicesPSHostUserInterface to use for this instance. /// An ILogger implementation to use for this instance. /// - public static Runspace CreateRunspace( + public static Runspace CreateTestRunspace( HostStartupInfo hostDetails, PowerShellContextService powerShellContext, EditorServicesPSHostUserInterface hostUserInterface, @@ -274,38 +276,17 @@ public static Runspace CreateRunspace( var psHost = new EditorServicesPSHost(powerShellContext, hostDetails, hostUserInterface, logger); powerShellContext.ConsoleWriter = hostUserInterface; powerShellContext.ConsoleReader = hostUserInterface; - return CreateRunspace(psHost, hostDetails.LanguageMode); + return CreateRunspace(psHost, hostDetails.InitialSessionState); } /// /// /// /// The PSHost that will be used for this Runspace. - /// The language mode inherited from the orginal PowerShell process. This will be used when creating runspaces so that we honor the same language mode. + /// This will be used when creating runspaces so that we honor the same InitialSessionState. /// - public static Runspace CreateRunspace(PSHost psHost, PSLanguageMode languageMode) + public static Runspace CreateRunspace(PSHost psHost, InitialSessionState initialSessionState) { - InitialSessionState initialSessionState; - if (Environment.GetEnvironmentVariable("PSES_TEST_USE_CREATE_DEFAULT") == "1") { - initialSessionState = InitialSessionState.CreateDefault(); - } else { - 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; - - // We set the process scope's execution policy (which is really the runspace's scope) to - // Bypass so we can import our bundled modules. This is equivalent in scope to the CLI - // argument `-Bypass`, which (for instance) the extension passes. Thus we emulate this - // behavior for consistency such that unit tests can pass in a similar environment. - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - { - initialSessionState.ExecutionPolicy = ExecutionPolicy.Bypass; - } - Runspace runspace = RunspaceFactory.CreateRunspace(psHost, initialSessionState); // Windows PowerShell must be hosted in STA mode @@ -434,10 +415,9 @@ public void Initialize( this.PromptContext = new LegacyReadLineContext(this); } - if (VersionUtils.IsWindows) - { - this.SetExecutionPolicy(); - } + // Finally, restore the runspace's execution policy to the user's policy instead of + // Bypass. + this.RestoreExecutionPolicy(); } /// @@ -2152,9 +2132,20 @@ private static string GetStringForPSCommand(PSCommand psCommand) return stringBuilder.ToString(); } - private void SetExecutionPolicy() + /// + /// This function restores the execution policy for the process by examining the user's + /// execution policy hierarchy. We do this because the process policy will always be set to + /// Bypass when initializing our runspaces. + /// + internal void RestoreExecutionPolicy() { - this.logger.LogTrace("Setting execution policy..."); + // Execution policy is a Windows-only feature. + if (!VersionUtils.IsWindows) + { + return; + } + + this.logger.LogTrace("Restoring execution policy..."); // We want to get the list hierarchy of execution policies // Calling the cmdlet is the simplest way to do that diff --git a/test/PowerShellEditorServices.Test/PowerShellContextFactory.cs b/test/PowerShellEditorServices.Test/PowerShellContextFactory.cs index 41c3e4730..422cc58a0 100644 --- a/test/PowerShellEditorServices.Test/PowerShellContextFactory.cs +++ b/test/PowerShellEditorServices.Test/PowerShellContextFactory.cs @@ -4,7 +4,7 @@ using System; using System.Collections.Generic; using System.IO; -using System.Management.Automation; +using System.Management.Automation.Runspaces; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Logging; @@ -13,6 +13,7 @@ using Microsoft.PowerShell.EditorServices.Services; using Microsoft.PowerShell.EditorServices.Services.PowerShellContext; using Microsoft.PowerShell.EditorServices.Test.Shared; +using Microsoft.PowerShell.EditorServices.Utility; namespace Microsoft.PowerShell.EditorServices.Test { @@ -40,6 +41,16 @@ internal static class PowerShellContextFactory public static PowerShellContextService Create(ILogger logger) { PowerShellContextService powerShellContext = new PowerShellContextService(logger, null, isPSReadLineEnabled: false); + var initialSessionState = InitialSessionState.CreateDefault(); + // We set the process scope's execution policy (which is really the runspace's scope) to + // `Bypass` so we can import our bundled modules. This is equivalent in scope to the CLI + // argument `-ExecutionPolicy Bypass`, which (for instance) the extension passes. Thus + // we emulate this behavior for consistency such that unit tests can pass in a similar + // environment. + if (VersionUtils.IsWindows) + { + initialSessionState.ExecutionPolicy = ExecutionPolicy.Bypass; + } HostStartupInfo testHostDetails = new HostStartupInfo( "PowerShell Editor Services Test Host", @@ -49,16 +60,14 @@ public static PowerShellContextService Create(ILogger logger) TestProfilePaths, new List(), new List(), - // TODO: We want to replace this property with an entire initial session state, - // which would then also control the process-scoped execution policy. - PSLanguageMode.FullLanguage, + initialSessionState, null, 0, consoleReplEnabled: false, usesLegacyReadLine: false, bundledModulePath: BundledModulePath); - InitialRunspace = PowerShellContextService.CreateRunspace( + InitialRunspace = PowerShellContextService.CreateTestRunspace( testHostDetails, powerShellContext, new TestPSHostUserInterface(powerShellContext, logger),