diff --git a/src/PowerShellEditorServices.Hosting/Commands/StartEditorServicesCommand.cs b/src/PowerShellEditorServices.Hosting/Commands/StartEditorServicesCommand.cs index 0e698f812..acc52700a 100644 --- a/src/PowerShellEditorServices.Hosting/Commands/StartEditorServicesCommand.cs +++ b/src/PowerShellEditorServices.Hosting/Commands/StartEditorServicesCommand.cs @@ -349,6 +349,8 @@ private EditorServicesConfig CreateConfigObject() var profile = (PSObject)GetVariableValue("profile"); var hostInfo = new HostInfo(HostName, HostProfileId, HostVersion); + var iss = Runspace.DefaultRunspace.InitialSessionState; + iss.LanguageMode = Runspace.DefaultRunspace.SessionStateProxy.LanguageMode; var editorServicesConfig = new EditorServicesConfig(hostInfo, Host, SessionDetailsPath, bundledModulesPath, LogPath) { FeatureFlags = FeatureFlags, @@ -357,7 +359,7 @@ private EditorServicesConfig CreateConfigObject() AdditionalModules = AdditionalModules, LanguageServiceTransport = GetLanguageServiceTransport(), DebugServiceTransport = GetDebugServiceTransport(), - LanguageMode = Runspace.DefaultRunspace.SessionStateProxy.LanguageMode, + InitialSessionState = iss, 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..79d49b83c 100644 --- a/src/PowerShellEditorServices.Hosting/Configuration/EditorServicesConfig.cs +++ b/src/PowerShellEditorServices.Hosting/Configuration/EditorServicesConfig.cs @@ -4,6 +4,7 @@ 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 +112,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..be1491471 100644 --- a/src/PowerShellEditorServices/Hosting/HostStartupInfo.cs +++ b/src/PowerShellEditorServices/Hosting/HostStartupInfo.cs @@ -5,6 +5,7 @@ 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 +93,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 including allowed modules, cmdlets and language mode. /// - public PSLanguageMode LanguageMode { get; } + public InitialSessionState InitialSessionState { get; } /// /// The minimum log level of log events to be logged. @@ -135,7 +136,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 +150,7 @@ public HostStartupInfo( ProfilePathInfo profilePaths, IReadOnlyList featureFlags, IReadOnlyList additionalModules, - PSLanguageMode languageMode, + InitialSessionState initialSessionState, string logPath, int logLevel, bool consoleReplEnabled, @@ -163,7 +164,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..f04e9c8ea 100644 --- a/src/PowerShellEditorServices/Services/PowerShellContext/PowerShellContextService.cs +++ b/src/PowerShellEditorServices/Services/PowerShellContext/PowerShellContextService.cs @@ -58,7 +58,7 @@ static PowerShellContextService() { MethodInfo setterInfo = typeof(Runspace).GetProperty("ApartmentState").GetSetMethod(); Delegate setter = Delegate.CreateDelegate(typeof(Action), firstArgument: null, method: setterInfo); - s_runspaceApartmentStateSetter = (Action)setter; + s_runspaceApartmentStateSetter = (Action) setter; } if (VersionUtils.IsPS7OrGreater) @@ -230,7 +230,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(); @@ -251,7 +251,7 @@ public static PowerShellContextService Create( sendErrorToHost: true); #pragma warning restore CS4014 } - + return powerShellContext; } @@ -263,7 +263,7 @@ public static PowerShellContextService Create( /// 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,7 +274,7 @@ 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); } /// @@ -283,29 +283,8 @@ public static Runspace CreateRunspace( /// 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. /// - 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 @@ -433,11 +412,6 @@ public void Initialize( { this.PromptContext = new LegacyReadLineContext(this); } - - if (VersionUtils.IsWindows) - { - this.SetExecutionPolicy(); - } } /// @@ -1014,7 +988,7 @@ public Task> ExecuteScriptStringAsync( Validate.IsNotNull(nameof(scriptString), scriptString); PSCommand command = null; - if(CurrentRunspace.Runspace.SessionStateProxy.LanguageMode != PSLanguageMode.FullLanguage) + if (CurrentRunspace.Runspace.SessionStateProxy.LanguageMode != PSLanguageMode.FullLanguage) { try { @@ -1029,7 +1003,7 @@ public Task> ExecuteScriptStringAsync( } // fall back to old behavior - if(command == null) + if (command == null) { command = new PSCommand().AddScript(scriptString.Trim()); } @@ -2152,7 +2126,7 @@ private static string GetStringForPSCommand(PSCommand psCommand) return stringBuilder.ToString(); } - private void SetExecutionPolicy() + internal void RestoreExecutionPolicy() { this.logger.LogTrace("Setting execution policy..."); @@ -2178,14 +2152,14 @@ private void SetExecutionPolicy() // set to expected values, so we must sift through those. ExecutionPolicy policyToSet = ExecutionPolicy.Bypass; - var currentUserPolicy = (ExecutionPolicy)policies[policies.Count - 2].Members["ExecutionPolicy"].Value; + var currentUserPolicy = (ExecutionPolicy) policies[policies.Count - 2].Members["ExecutionPolicy"].Value; if (currentUserPolicy != ExecutionPolicy.Undefined) { policyToSet = currentUserPolicy; } else { - var localMachinePolicy = (ExecutionPolicy)policies[policies.Count - 1].Members["ExecutionPolicy"].Value; + var localMachinePolicy = (ExecutionPolicy) policies[policies.Count - 1].Members["ExecutionPolicy"].Value; if (localMachinePolicy != ExecutionPolicy.Undefined) { policyToSet = localMachinePolicy; @@ -2357,7 +2331,7 @@ private void HandleRunspaceStateChanged(object sender, RunspaceStateEventArgs ar case RunspaceState.Closed: case RunspaceState.Broken: // If the runspace closes or fails, pop the runspace - ((IHostSupportsInteractiveSession)this).PopRunspace(); + ((IHostSupportsInteractiveSession) this).PopRunspace(); break; } } @@ -2369,7 +2343,7 @@ private static IEnumerable GetLoadableProfilePaths(ProfilePathInfo profi yield break; } - foreach (string path in new [] { profilePaths.AllUsersAllHosts, profilePaths.AllUsersCurrentHost, profilePaths.CurrentUserAllHosts, profilePaths.CurrentUserCurrentHost }) + foreach (string path in new[] { profilePaths.AllUsersAllHosts, profilePaths.AllUsersCurrentHost, profilePaths.CurrentUserAllHosts, profilePaths.CurrentUserCurrentHost }) { if (path != null && File.Exists(path)) { @@ -2401,12 +2375,12 @@ private void StartCommandLoopOnRunspaceAvailable() void availabilityChangedHandler(object runspace, RunspaceAvailabilityEventArgs eventArgs) { if (eventArgs.RunspaceAvailability != RunspaceAvailability.Available || - this.versionSpecificOperations.IsDebuggerStopped(this.PromptNest, (Runspace)runspace)) + this.versionSpecificOperations.IsDebuggerStopped(this.PromptNest, (Runspace) runspace)) { return; } - ((Runspace)runspace).AvailabilityChanged -= availabilityChangedHandler; + ((Runspace) runspace).AvailabilityChanged -= availabilityChangedHandler; Interlocked.Exchange(ref this.isCommandLoopRestarterSet, 0); this.ConsoleReader?.StartCommandLoop(); } diff --git a/test/PowerShellEditorServices.Test.E2E/PowerShellEditorServices.Test.E2E.csproj b/test/PowerShellEditorServices.Test.E2E/PowerShellEditorServices.Test.E2E.csproj index 57a0514b6..c50895f2e 100644 --- a/test/PowerShellEditorServices.Test.E2E/PowerShellEditorServices.Test.E2E.csproj +++ b/test/PowerShellEditorServices.Test.E2E/PowerShellEditorServices.Test.E2E.csproj @@ -9,7 +9,7 @@ - + diff --git a/test/PowerShellEditorServices.Test/PowerShellContextFactory.cs b/test/PowerShellEditorServices.Test/PowerShellContextFactory.cs index 41c3e4730..801d8f2f3 100644 --- a/test/PowerShellEditorServices.Test/PowerShellContextFactory.cs +++ b/test/PowerShellEditorServices.Test/PowerShellContextFactory.cs @@ -5,6 +5,8 @@ using System.Collections.Generic; using System.IO; using System.Management.Automation; +using System.Management.Automation.Runspaces; +using System.Runtime.InteropServices; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Logging; @@ -13,6 +15,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 { @@ -36,11 +39,19 @@ internal static class PowerShellContextFactory TestUtilities.NormalizePath("../../../../../module")); public static System.Management.Automation.Runspaces.Runspace InitialRunspace; - + 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 `-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; + } HostStartupInfo testHostDetails = new HostStartupInfo( "PowerShell Editor Services Test Host", "Test.PowerShellEditorServices", @@ -49,27 +60,28 @@ 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), logger); - + powerShellContext.Initialize( TestProfilePaths, InitialRunspace, ownsInitialRunspace: true, consoleHost: null); - + if(VersionUtils.IsWindows) + { + powerShellContext.RestoreExecutionPolicy(); + } return powerShellContext; } }