Skip to content

Commit fc3d4a9

Browse files
Initialize runspaces with InitialSessionState object (#1526)
Instead of passing values for `LanguageMode` and `ExecutionPolicy` as fields that are set on an `InitialSessionState` object that's eventually created, we now pass a fully initialized `InitialSessionState` object from the start. We also rename `SetExecutionPolicy` to `RestoreExecutionPolicy` to reflect what it actually does: restores the user's policy after we overrode it with `Bypass` while initializing the integrated console's runspace. We override it because we need to be able to load `PSReadLine` etc. without policy issues. Finally, we fix the EditorConfig setting to enforce spaces after control-flow keywords (such as `if`) because, while our codebase is inconsistent, it is our preferred style going forward. Co-authored-by: Andrew Schwartzmeyer <andrew@schwartzmeyer.com> Co-authored-by: Darren Kattan <dkattan@immense.net>
1 parent 99f6824 commit fc3d4a9

File tree

7 files changed

+62
-54
lines changed

7 files changed

+62
-54
lines changed

.editorconfig

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ insert_final_newline = true
1111
indent_size = 4
1212
trim_trailing_whitespace = true
1313
csharp_space_before_open_square_brackets = true
14-
csharp_space_after_keywords_in_control_flow_statements = false
14+
csharp_space_after_keywords_in_control_flow_statements = true
1515

1616
[*.{json}]
1717
indent_size = 2

src/PowerShellEditorServices.Hosting/Commands/StartEditorServicesCommand.cs

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -349,15 +349,24 @@ private EditorServicesConfig CreateConfigObject()
349349
var profile = (PSObject)GetVariableValue("profile");
350350

351351
var hostInfo = new HostInfo(HostName, HostProfileId, HostVersion);
352-
var editorServicesConfig = new EditorServicesConfig(hostInfo, Host, SessionDetailsPath, bundledModulesPath, LogPath)
352+
353+
var initialSessionState = Runspace.DefaultRunspace.InitialSessionState;
354+
initialSessionState.LanguageMode = Runspace.DefaultRunspace.SessionStateProxy.LanguageMode;
355+
356+
var editorServicesConfig = new EditorServicesConfig(
357+
hostInfo,
358+
Host,
359+
SessionDetailsPath,
360+
bundledModulesPath,
361+
LogPath)
353362
{
354363
FeatureFlags = FeatureFlags,
355364
LogLevel = LogLevel,
356365
ConsoleRepl = GetReplKind(),
357366
AdditionalModules = AdditionalModules,
358367
LanguageServiceTransport = GetLanguageServiceTransport(),
359368
DebugServiceTransport = GetDebugServiceTransport(),
360-
LanguageMode = Runspace.DefaultRunspace.SessionStateProxy.LanguageMode,
369+
InitialSessionState = initialSessionState,
361370
ProfilePaths = new ProfilePathConfig
362371
{
363372
AllUsersAllHosts = GetProfilePathFromProfileObject(profile, ProfileUserKind.AllUsers, ProfileHostKind.AllHosts),

src/PowerShellEditorServices.Hosting/Configuration/EditorServicesConfig.cs

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22
// Licensed under the MIT License.
33

44
using System.Collections.Generic;
5-
using System.Management.Automation;
65
using System.Management.Automation.Host;
6+
using System.Management.Automation.Runspaces;
77

88
namespace Microsoft.PowerShell.EditorServices.Hosting
99
{
@@ -111,10 +111,9 @@ public EditorServicesConfig(
111111
public ProfilePathConfig ProfilePaths { get; set; }
112112

113113
/// <summary>
114-
/// The language mode inherited from the orginal PowerShell process.
115-
/// This will be used when creating runspaces so that we honor the same language mode.
114+
/// The InitialSessionState to use when creating runspaces. LanguageMode can be set here.
116115
/// </summary>
117-
public PSLanguageMode LanguageMode { get; internal set; }
116+
public InitialSessionState InitialSessionState { get; internal set; }
118117

119118
public string StartupBanner { get; set; } = @"
120119

src/PowerShellEditorServices.Hosting/Internal/EditorServicesRunner.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -288,7 +288,7 @@ private HostStartupInfo CreateHostStartupInfo()
288288
profilePaths,
289289
_config.FeatureFlags,
290290
_config.AdditionalModules,
291-
_config.LanguageMode,
291+
_config.InitialSessionState,
292292
_config.LogPath,
293293
(int)_config.LogLevel,
294294
consoleReplEnabled: _config.ConsoleRepl != ConsoleReplKind.None,

src/PowerShellEditorServices/Hosting/HostStartupInfo.cs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33

44
using System;
55
using System.Collections.Generic;
6-
using System.Management.Automation;
76
using System.Management.Automation.Host;
7+
using System.Management.Automation.Runspaces;
88

99
namespace Microsoft.PowerShell.EditorServices.Hosting
1010
{
@@ -92,10 +92,10 @@ public sealed class HostStartupInfo
9292
public string LogPath { get; }
9393

9494
/// <summary>
95-
/// The language mode inherited from the orginal PowerShell process.
96-
/// This will be used when creating runspaces so that we honor the same language mode.
95+
/// The InitialSessionState will be inherited from the orginal PowerShell process. This will
96+
/// be used when creating runspaces so that we honor the same InitialSessionState.
9797
/// </summary>
98-
public PSLanguageMode LanguageMode { get; }
98+
public InitialSessionState InitialSessionState { get; }
9999

100100
/// <summary>
101101
/// The minimum log level of log events to be logged.
@@ -135,7 +135,7 @@ public sealed class HostStartupInfo
135135
/// <param name="currentUsersProfilePath">The path to the user specific profile.</param>
136136
/// <param name="featureFlags">Flags of features to enable.</param>
137137
/// <param name="additionalModules">Names or paths of additional modules to import.</param>
138-
/// <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>
138+
/// <param name="initialSessionState">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.</param>
139139
/// <param name="logPath">The path to log to.</param>
140140
/// <param name="logLevel">The minimum log event level.</param>
141141
/// <param name="consoleReplEnabled">Enable console if true.</param>
@@ -149,7 +149,7 @@ public HostStartupInfo(
149149
ProfilePathInfo profilePaths,
150150
IReadOnlyList<string> featureFlags,
151151
IReadOnlyList<string> additionalModules,
152-
PSLanguageMode languageMode,
152+
InitialSessionState initialSessionState,
153153
string logPath,
154154
int logLevel,
155155
bool consoleReplEnabled,
@@ -163,7 +163,7 @@ public HostStartupInfo(
163163
ProfilePaths = profilePaths;
164164
FeatureFlags = featureFlags ?? Array.Empty<string>();
165165
AdditionalModules = additionalModules ?? Array.Empty<string>();
166-
LanguageMode = languageMode;
166+
InitialSessionState = initialSessionState;
167167
LogPath = logPath;
168168
LogLevel = logLevel;
169169
ConsoleReplEnabled = consoleReplEnabled;

src/PowerShellEditorServices/Services/PowerShellContext/PowerShellContextService.cs

Lines changed: 25 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111
using System.Management.Automation.Remoting;
1212
using System.Management.Automation.Runspaces;
1313
using System.Reflection;
14-
using System.Runtime.InteropServices;
1514
using System.Text;
1615
using System.Threading;
1716
using System.Threading.Tasks;
@@ -230,7 +229,7 @@ public static PowerShellContextService Create(
230229
logger);
231230

232231
logger.LogTrace("Creating initial PowerShell runspace");
233-
Runspace initialRunspace = PowerShellContextService.CreateRunspace(psHost, hostStartupInfo.LanguageMode);
232+
Runspace initialRunspace = PowerShellContextService.CreateRunspace(psHost, hostStartupInfo.InitialSessionState);
234233
powerShellContext.Initialize(hostStartupInfo.ProfilePaths, initialRunspace, true, hostUserInterface);
235234
powerShellContext.ImportCommandsModuleAsync();
236235

@@ -256,14 +255,17 @@ public static PowerShellContextService Create(
256255
}
257256

258257
/// <summary>
259-
///
258+
/// Only used in testing. Creates a Runspace given HostStartupInfo instead of a PSHost.
260259
/// </summary>
260+
/// <remarks>
261+
/// TODO: We should use `CreateRunspace` in testing instead of this, if possible.
262+
/// </remarks>
261263
/// <param name="hostDetails"></param>
262264
/// <param name="powerShellContext"></param>
263265
/// <param name="hostUserInterface">The EditorServicesPSHostUserInterface to use for this instance.</param>
264266
/// <param name="logger">An ILogger implementation to use for this instance.</param>
265267
/// <returns></returns>
266-
public static Runspace CreateRunspace(
268+
public static Runspace CreateTestRunspace(
267269
HostStartupInfo hostDetails,
268270
PowerShellContextService powerShellContext,
269271
EditorServicesPSHostUserInterface hostUserInterface,
@@ -274,38 +276,17 @@ public static Runspace CreateRunspace(
274276
var psHost = new EditorServicesPSHost(powerShellContext, hostDetails, hostUserInterface, logger);
275277
powerShellContext.ConsoleWriter = hostUserInterface;
276278
powerShellContext.ConsoleReader = hostUserInterface;
277-
return CreateRunspace(psHost, hostDetails.LanguageMode);
279+
return CreateRunspace(psHost, hostDetails.InitialSessionState);
278280
}
279281

280282
/// <summary>
281283
///
282284
/// </summary>
283285
/// <param name="psHost">The PSHost that will be used for this Runspace.</param>
284-
/// <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>
286+
/// <param name="initialSessionState">This will be used when creating runspaces so that we honor the same InitialSessionState.</param>
285287
/// <returns></returns>
286-
public static Runspace CreateRunspace(PSHost psHost, PSLanguageMode languageMode)
288+
public static Runspace CreateRunspace(PSHost psHost, InitialSessionState initialSessionState)
287289
{
288-
InitialSessionState initialSessionState;
289-
if (Environment.GetEnvironmentVariable("PSES_TEST_USE_CREATE_DEFAULT") == "1") {
290-
initialSessionState = InitialSessionState.CreateDefault();
291-
} else {
292-
initialSessionState = InitialSessionState.CreateDefault2();
293-
}
294-
295-
// Create and initialize a new Runspace while honoring the LanguageMode of the original runspace
296-
// that started PowerShell Editor Services. This is because the PowerShell Integrated Console
297-
// should have the same LanguageMode of whatever is set by the system.
298-
initialSessionState.LanguageMode = languageMode;
299-
300-
// We set the process scope's execution policy (which is really the runspace's scope) to
301-
// Bypass so we can import our bundled modules. This is equivalent in scope to the CLI
302-
// argument `-Bypass`, which (for instance) the extension passes. Thus we emulate this
303-
// behavior for consistency such that unit tests can pass in a similar environment.
304-
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
305-
{
306-
initialSessionState.ExecutionPolicy = ExecutionPolicy.Bypass;
307-
}
308-
309290
Runspace runspace = RunspaceFactory.CreateRunspace(psHost, initialSessionState);
310291

311292
// Windows PowerShell must be hosted in STA mode
@@ -434,10 +415,9 @@ public void Initialize(
434415
this.PromptContext = new LegacyReadLineContext(this);
435416
}
436417

437-
if (VersionUtils.IsWindows)
438-
{
439-
this.SetExecutionPolicy();
440-
}
418+
// Finally, restore the runspace's execution policy to the user's policy instead of
419+
// Bypass.
420+
this.RestoreExecutionPolicy();
441421
}
442422

443423
/// <summary>
@@ -2152,9 +2132,20 @@ private static string GetStringForPSCommand(PSCommand psCommand)
21522132
return stringBuilder.ToString();
21532133
}
21542134

2155-
private void SetExecutionPolicy()
2135+
/// <summary>
2136+
/// This function restores the execution policy for the process by examining the user's
2137+
/// execution policy hierarchy. We do this because the process policy will always be set to
2138+
/// Bypass when initializing our runspaces.
2139+
/// </summary>
2140+
internal void RestoreExecutionPolicy()
21562141
{
2157-
this.logger.LogTrace("Setting execution policy...");
2142+
// Execution policy is a Windows-only feature.
2143+
if (!VersionUtils.IsWindows)
2144+
{
2145+
return;
2146+
}
2147+
2148+
this.logger.LogTrace("Restoring execution policy...");
21582149

21592150
// We want to get the list hierarchy of execution policies
21602151
// Calling the cmdlet is the simplest way to do that

test/PowerShellEditorServices.Test/PowerShellContextFactory.cs

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
using System;
55
using System.Collections.Generic;
66
using System.IO;
7-
using System.Management.Automation;
7+
using System.Management.Automation.Runspaces;
88
using System.Threading;
99
using System.Threading.Tasks;
1010
using Microsoft.Extensions.Logging;
@@ -13,6 +13,7 @@
1313
using Microsoft.PowerShell.EditorServices.Services;
1414
using Microsoft.PowerShell.EditorServices.Services.PowerShellContext;
1515
using Microsoft.PowerShell.EditorServices.Test.Shared;
16+
using Microsoft.PowerShell.EditorServices.Utility;
1617

1718
namespace Microsoft.PowerShell.EditorServices.Test
1819
{
@@ -40,6 +41,16 @@ internal static class PowerShellContextFactory
4041
public static PowerShellContextService Create(ILogger logger)
4142
{
4243
PowerShellContextService powerShellContext = new PowerShellContextService(logger, null, isPSReadLineEnabled: false);
44+
var initialSessionState = InitialSessionState.CreateDefault();
45+
// We set the process scope's execution policy (which is really the runspace's scope) to
46+
// `Bypass` so we can import our bundled modules. This is equivalent in scope to the CLI
47+
// argument `-ExecutionPolicy Bypass`, which (for instance) the extension passes. Thus
48+
// we emulate this behavior for consistency such that unit tests can pass in a similar
49+
// environment.
50+
if (VersionUtils.IsWindows)
51+
{
52+
initialSessionState.ExecutionPolicy = ExecutionPolicy.Bypass;
53+
}
4354

4455
HostStartupInfo testHostDetails = new HostStartupInfo(
4556
"PowerShell Editor Services Test Host",
@@ -49,16 +60,14 @@ public static PowerShellContextService Create(ILogger logger)
4960
TestProfilePaths,
5061
new List<string>(),
5162
new List<string>(),
52-
// TODO: We want to replace this property with an entire initial session state,
53-
// which would then also control the process-scoped execution policy.
54-
PSLanguageMode.FullLanguage,
63+
initialSessionState,
5564
null,
5665
0,
5766
consoleReplEnabled: false,
5867
usesLegacyReadLine: false,
5968
bundledModulePath: BundledModulePath);
6069

61-
InitialRunspace = PowerShellContextService.CreateRunspace(
70+
InitialRunspace = PowerShellContextService.CreateTestRunspace(
6271
testHostDetails,
6372
powerShellContext,
6473
new TestPSHostUserInterface(powerShellContext, logger),

0 commit comments

Comments
 (0)