diff --git a/PowerShellEditorServices.Common.props b/PowerShellEditorServices.Common.props
index 2ab30ebc7..cdb72f19d 100644
--- a/PowerShellEditorServices.Common.props
+++ b/PowerShellEditorServices.Common.props
@@ -11,6 +11,5 @@
git
https://github.com/PowerShell/PowerShellEditorServices
portable
- $(DefineConstants);$(ExtraDefineConstants)
diff --git a/PowerShellEditorServices.build.ps1 b/PowerShellEditorServices.build.ps1
index 8df6c5fc1..fbd126708 100644
--- a/PowerShellEditorServices.build.ps1
+++ b/PowerShellEditorServices.build.ps1
@@ -259,20 +259,20 @@ task TestServer TestServerWinPS,TestServerPS7,TestServerPS72
task TestServerWinPS -If (-not $script:IsNix) {
Set-Location .\test\PowerShellEditorServices.Test\
- exec { & $script:dotnetExe test -p:ExtraDefineConstants=TEST --logger trx -f $script:NetRuntime.Desktop (DotNetTestFilter) }
+ exec { & $script:dotnetExe test --logger trx -f $script:NetRuntime.Desktop (DotNetTestFilter) }
}
task TestServerPS7 -If (-not $script:IsRosetta) {
Set-Location .\test\PowerShellEditorServices.Test\
Invoke-WithCreateDefaultHook -NewModulePath $script:PSCoreModulePath {
- exec { & $script:dotnetExe test -p:ExtraDefineConstants=TEST --logger trx -f $script:NetRuntime.PS7 (DotNetTestFilter) }
+ exec { & $script:dotnetExe test --logger trx -f $script:NetRuntime.PS7 (DotNetTestFilter) }
}
}
task TestServerPS72 {
Set-Location .\test\PowerShellEditorServices.Test\
Invoke-WithCreateDefaultHook -NewModulePath $script:PSCoreModulePath {
- exec { & $script:dotnetExe test -p:ExtraDefineConstants=TEST --logger trx -f $script:NetRuntime.PS72 (DotNetTestFilter) }
+ exec { & $script:dotnetExe test --logger trx -f $script:NetRuntime.PS72 (DotNetTestFilter) }
}
}
@@ -281,13 +281,13 @@ task TestE2E {
$env:PWSH_EXE_NAME = if ($IsCoreCLR) { "pwsh" } else { "powershell" }
$NetRuntime = if ($IsRosetta) { $script:NetRuntime.PS72 } else { $script:NetRuntime.PS7 }
- exec { & $script:dotnetExe test -p:ExtraDefineConstants=TEST --logger trx -f $NetRuntime (DotNetTestFilter) }
+ exec { & $script:dotnetExe test --logger trx -f $NetRuntime (DotNetTestFilter) }
# Run E2E tests in ConstrainedLanguage mode.
if (!$script:IsNix) {
try {
[System.Environment]::SetEnvironmentVariable("__PSLockdownPolicy", "0x80000007", [System.EnvironmentVariableTarget]::Machine);
- exec { & $script:dotnetExe test -p:ExtraDefineConstants=TEST --logger trx -f $script:NetRuntime.PS7 (DotNetTestFilter) }
+ exec { & $script:dotnetExe test --logger trx -f $script:NetRuntime.PS7 (DotNetTestFilter) }
} finally {
[System.Environment]::SetEnvironmentVariable("__PSLockdownPolicy", $null, [System.EnvironmentVariableTarget]::Machine);
}
diff --git a/src/PowerShellEditorServices.Hosting/Internal/EditorServicesRunner.cs b/src/PowerShellEditorServices.Hosting/Internal/EditorServicesRunner.cs
index b4dbf7c63..e49da8527 100644
--- a/src/PowerShellEditorServices.Hosting/Internal/EditorServicesRunner.cs
+++ b/src/PowerShellEditorServices.Hosting/Internal/EditorServicesRunner.cs
@@ -292,7 +292,8 @@ private HostStartupInfo CreateHostStartupInfo()
_config.LogPath,
(int)_config.LogLevel,
consoleReplEnabled: _config.ConsoleRepl != ConsoleReplKind.None,
- usesLegacyReadLine: _config.ConsoleRepl == ConsoleReplKind.LegacyReadLine);
+ usesLegacyReadLine: _config.ConsoleRepl == ConsoleReplKind.LegacyReadLine,
+ bundledModulePath: _config.BundledModulePath);
}
private void WriteStartupBanner()
diff --git a/src/PowerShellEditorServices/Hosting/HostStartupInfo.cs b/src/PowerShellEditorServices/Hosting/HostStartupInfo.cs
index 5f3ae7647..9fc788e0d 100644
--- a/src/PowerShellEditorServices/Hosting/HostStartupInfo.cs
+++ b/src/PowerShellEditorServices/Hosting/HostStartupInfo.cs
@@ -107,6 +107,11 @@ public sealed class HostStartupInfo
///
public int LogLevel { get; }
+ ///
+ /// The path to find the bundled modules. User configurable for advanced usage.
+ ///
+ public string BundledModulePath { get; }
+
#endregion
#region Constructors
@@ -135,6 +140,7 @@ public sealed class HostStartupInfo
/// The minimum log event level.
/// Enable console if true.
/// Use PSReadLine if false, otherwise use the legacy readline implementation.
+ /// A custom path to the expected bundled modules.
public HostStartupInfo(
string name,
string profileId,
@@ -147,7 +153,8 @@ public HostStartupInfo(
string logPath,
int logLevel,
bool consoleReplEnabled,
- bool usesLegacyReadLine)
+ bool usesLegacyReadLine,
+ string bundledModulePath)
{
Name = name ?? DefaultHostName;
ProfileId = profileId ?? DefaultHostProfileId;
@@ -161,6 +168,7 @@ public HostStartupInfo(
LogLevel = logLevel;
ConsoleReplEnabled = consoleReplEnabled;
UsesLegacyReadLine = usesLegacyReadLine;
+ BundledModulePath = bundledModulePath;
}
#endregion
diff --git a/src/PowerShellEditorServices/Services/PowerShellContext/PowerShellContextService.cs b/src/PowerShellEditorServices/Services/PowerShellContext/PowerShellContextService.cs
index f226dd8d3..198cd26d9 100644
--- a/src/PowerShellEditorServices/Services/PowerShellContext/PowerShellContextService.cs
+++ b/src/PowerShellEditorServices/Services/PowerShellContext/PowerShellContextService.cs
@@ -11,6 +11,7 @@
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;
@@ -32,10 +33,18 @@ namespace Microsoft.PowerShell.EditorServices.Services
///
internal class PowerShellContextService : IHostSupportsInteractiveSession
{
- private static readonly string s_commandsModulePath = Path.GetFullPath(
- Path.Combine(
- Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location),
- "../../Commands/PowerShellEditorServices.Commands.psd1"));
+ // This is a default that can be overriden at runtime by the user or tests.
+ private static string s_bundledModulePath = Path.GetFullPath(Path.Combine(
+ Path.GetDirectoryName(typeof(PowerShellContextService).Assembly.Location),
+ "..",
+ "..",
+ ".."));
+
+ private static string s_commandsModulePath => Path.GetFullPath(Path.Combine(
+ s_bundledModulePath,
+ "PowerShellEditorServices",
+ "Commands",
+ "PowerShellEditorServices.Commands.psd1"));
private static readonly Action s_runspaceApartmentStateSetter;
private static readonly PropertyInfo s_writeStreamProperty;
@@ -189,9 +198,16 @@ public static PowerShellContextService Create(
OmniSharp.Extensions.LanguageServer.Protocol.Server.ILanguageServerFacade languageServer,
HostStartupInfo hostStartupInfo)
{
+ var logger = factory.CreateLogger();
+
Validate.IsNotNull(nameof(hostStartupInfo), hostStartupInfo);
- var logger = factory.CreateLogger();
+ // Respect a user provided bundled module path.
+ if (Directory.Exists(hostStartupInfo.BundledModulePath))
+ {
+ logger.LogTrace($"Using new bundled module path: {hostStartupInfo.BundledModulePath}");
+ s_bundledModulePath = hostStartupInfo.BundledModulePath;
+ }
bool shouldUsePSReadLine = hostStartupInfo.ConsoleReplEnabled
&& !hostStartupInfo.UsesLegacyReadLine;
@@ -281,6 +297,15 @@ public static Runspace CreateRunspace(PSHost psHost, PSLanguageMode languageMode
// 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
@@ -396,7 +421,7 @@ public void Initialize(
if (powerShellVersion.Major >= 5 &&
this.isPSReadLineEnabled &&
- PSReadLinePromptContext.TryGetPSReadLineProxy(logger, initialRunspace, out PSReadLineProxy proxy))
+ PSReadLinePromptContext.TryGetPSReadLineProxy(logger, initialRunspace, s_bundledModulePath, out PSReadLineProxy proxy))
{
this.PromptContext = new PSReadLinePromptContext(
this,
@@ -420,15 +445,13 @@ public void Initialize(
/// the runspace. This method will be moved somewhere else soon.
///
///
- public Task ImportCommandsModuleAsync() => ImportCommandsModuleAsync(s_commandsModulePath);
-
- public Task ImportCommandsModuleAsync(string path)
+ public Task ImportCommandsModuleAsync()
{
- this.logger.LogTrace($"Importing PowershellEditorServices commands from {path}");
+ this.logger.LogTrace($"Importing PowershellEditorServices commands from {s_commandsModulePath}");
PSCommand importCommand = new PSCommand()
.AddCommand("Import-Module")
- .AddArgument(path);
+ .AddArgument(s_commandsModulePath);
return this.ExecuteCommandAsync(importCommand, sendOutputToHost: false, sendErrorToHost: false);
}
diff --git a/src/PowerShellEditorServices/Services/PowerShellContext/Session/PSReadLinePromptContext.cs b/src/PowerShellEditorServices/Services/PowerShellContext/Session/PSReadLinePromptContext.cs
index 5f4931854..ee4c50634 100644
--- a/src/PowerShellEditorServices/Services/PowerShellContext/Session/PSReadLinePromptContext.cs
+++ b/src/PowerShellEditorServices/Services/PowerShellContext/Session/PSReadLinePromptContext.cs
@@ -17,20 +17,6 @@ namespace Microsoft.PowerShell.EditorServices.Services.PowerShellContext
internal class PSReadLinePromptContext : IPromptContext
{
- private static readonly string _psReadLineModulePath = Path.Combine(
- Path.GetDirectoryName(typeof(PSReadLinePromptContext).Assembly.Location),
- "..",
- "..",
- "..",
-#if TEST
- // When using xUnit (dotnet test) the assemblies are deployed to the
- // test project folder, invalidating our relative path assumption.
- "..",
- "..",
- "module",
-#endif
- "PSReadLine");
-
private static readonly Lazy s_lazyInvokeReadLineForEditorServicesCmdletInfo = new Lazy(() =>
{
var type = Type.GetType("Microsoft.PowerShell.EditorServices.Commands.InvokeReadLineForEditorServicesCommand, Microsoft.PowerShell.EditorServices.Hosting");
@@ -79,6 +65,7 @@ internal PSReadLinePromptContext(
internal static bool TryGetPSReadLineProxy(
ILogger logger,
Runspace runspace,
+ string bundledModulePath,
out PSReadLineProxy readLineProxy)
{
readLineProxy = null;
@@ -87,15 +74,33 @@ internal static bool TryGetPSReadLineProxy(
{
pwsh.Runspace = runspace;
pwsh.AddCommand("Microsoft.PowerShell.Core\\Import-Module")
- .AddParameter("Name", _psReadLineModulePath)
+ .AddParameter("Name", Path.Combine(bundledModulePath, "PSReadLine"))
.Invoke();
+ if (pwsh.HadErrors)
+ {
+ logger.LogWarning("PSConsoleReadline type not found: {Reason}", pwsh.Streams.Error[0].ToString());
+ return false;
+ }
+
var psReadLineType = Type.GetType("Microsoft.PowerShell.PSConsoleReadLine, Microsoft.PowerShell.PSReadLine2");
if (psReadLineType == null)
{
- logger.LogWarning("PSConsoleReadline type not found: {Reason}", pwsh.HadErrors ? pwsh.Streams.Error[0].ToString() : "");
- return false;
+ // NOTE: For some reason `Type.GetType(...)` can fail to find the type,
+ // and in that case, this search through the `AppDomain` for some reason will succeed.
+ // It's slower, but only happens when needed.
+ logger.LogTrace("PSConsoleReadline type not found using Type.GetType(), searching all loaded assemblies...");
+ psReadLineType = AppDomain.CurrentDomain
+ .GetAssemblies()
+ .FirstOrDefault(asm => asm.GetName().Name.Equals("Microsoft.PowerShell.PSReadLine2"))
+ ?.ExportedTypes
+ ?.FirstOrDefault(type => type.FullName.Equals("Microsoft.PowerShell.PSConsoleReadLine"));
+ if (psReadLineType == null)
+ {
+ logger.LogWarning("PSConsoleReadLine type not found anywhere!");
+ return false;
+ }
}
try
diff --git a/test/PowerShellEditorServices.Test.E2E/PowerShellEditorServices.Test.E2E.csproj b/test/PowerShellEditorServices.Test.E2E/PowerShellEditorServices.Test.E2E.csproj
index 2c443e008..57a0514b6 100644
--- a/test/PowerShellEditorServices.Test.E2E/PowerShellEditorServices.Test.E2E.csproj
+++ b/test/PowerShellEditorServices.Test.E2E/PowerShellEditorServices.Test.E2E.csproj
@@ -22,8 +22,6 @@
-
- PreserveNewest
-
+
diff --git a/test/PowerShellEditorServices.Test.E2E/xunit.runner.json b/test/PowerShellEditorServices.Test.E2E/xunit.runner.json
index 79d1ad980..3f3645a0a 100644
--- a/test/PowerShellEditorServices.Test.E2E/xunit.runner.json
+++ b/test/PowerShellEditorServices.Test.E2E/xunit.runner.json
@@ -1,4 +1,6 @@
{
- "parallelizeTestCollections": false
+ "$schema": "https://xunit.net/schema/current/xunit.runner.schema.json",
+ "appDomain": "denied",
+ "parallelizeTestCollections": false,
+ "methodDisplay": "method"
}
-
diff --git a/test/PowerShellEditorServices.Test/App.config b/test/PowerShellEditorServices.Test/App.config
deleted file mode 100644
index 9735dc735..000000000
--- a/test/PowerShellEditorServices.Test/App.config
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/test/PowerShellEditorServices.Test/Language/SemanticTokenTest.cs b/test/PowerShellEditorServices.Test/Language/SemanticTokenTest.cs
index 42fbaacb2..2111a5e88 100644
--- a/test/PowerShellEditorServices.Test/Language/SemanticTokenTest.cs
+++ b/test/PowerShellEditorServices.Test/Language/SemanticTokenTest.cs
@@ -17,7 +17,7 @@ namespace Microsoft.PowerShell.EditorServices.Test.Language
public class SemanticTokenTest
{
[Fact]
- public async Task TokenizesFunctionElements()
+ public void TokenizesFunctionElements()
{
string text = @"
function Get-Sum {
@@ -59,7 +59,7 @@ function Get-Sum {
}
[Fact]
- public async Task TokenizesStringExpansion()
+ public void TokenizesStringExpansion()
{
string text = "Write-Host \"$(Test-Property Get-Whatever) $(Get-Whatever)\"";
ScriptFile scriptFile = new ScriptFile(
@@ -82,7 +82,7 @@ public async Task TokenizesStringExpansion()
}
[Fact]
- public async Task RecognizesTokensWithAsterisk()
+ public void RecognizesTokensWithAsterisk()
{
string text = @"
function Get-A*A {
@@ -111,7 +111,7 @@ function Get-A*A {
}
[Fact]
- public async Task RecognizesArrayPropertyInExpandableString()
+ public void RecognizesArrayPropertyInExpandableString()
{
string text = "\"$(@($Array).Count) OtherText\"";
ScriptFile scriptFile = new ScriptFile(
@@ -136,7 +136,7 @@ public async Task RecognizesArrayPropertyInExpandableString()
}
[Fact]
- public async Task RecognizesCurlyQuotedString()
+ public void RecognizesCurlyQuotedString()
{
string text = "“^[-'a-z]*”";
ScriptFile scriptFile = new ScriptFile(
@@ -150,7 +150,7 @@ public async Task RecognizesCurlyQuotedString()
}
[Fact]
- public async Task RecognizeEnum()
+ public void RecognizeEnum()
{
string text = @"
enum MyEnum{
diff --git a/test/PowerShellEditorServices.Test/PowerShellContextFactory.cs b/test/PowerShellEditorServices.Test/PowerShellContextFactory.cs
index 4eb93395a..41c3e4730 100644
--- a/test/PowerShellEditorServices.Test/PowerShellContextFactory.cs
+++ b/test/PowerShellEditorServices.Test/PowerShellContextFactory.cs
@@ -32,7 +32,10 @@ internal static class PowerShellContextFactory
Path.GetFullPath(
TestUtilities.NormalizePath("../../../../PowerShellEditorServices.Test.Shared/ProfileTest.ps1")));
- public static System.Management.Automation.Runspaces.Runspace initialRunspace;
+ public static readonly string BundledModulePath = Path.GetFullPath(
+ TestUtilities.NormalizePath("../../../../../module"));
+
+ public static System.Management.Automation.Runspaces.Runspace InitialRunspace;
public static PowerShellContextService Create(ILogger logger)
{
@@ -46,13 +49,16 @@ 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,
null,
0,
consoleReplEnabled: false,
- usesLegacyReadLine: false);
+ usesLegacyReadLine: false,
+ bundledModulePath: BundledModulePath);
- initialRunspace = PowerShellContextService.CreateRunspace(
+ InitialRunspace = PowerShellContextService.CreateRunspace(
testHostDetails,
powerShellContext,
new TestPSHostUserInterface(powerShellContext, logger),
@@ -60,7 +66,7 @@ public static PowerShellContextService Create(ILogger logger)
powerShellContext.Initialize(
TestProfilePaths,
- initialRunspace,
+ InitialRunspace,
ownsInitialRunspace: true,
consoleHost: null);
diff --git a/test/PowerShellEditorServices.Test/PowerShellEditorServices.Test.csproj b/test/PowerShellEditorServices.Test/PowerShellEditorServices.Test.csproj
index 70115e8f7..bbf7c3814 100644
--- a/test/PowerShellEditorServices.Test/PowerShellEditorServices.Test.csproj
+++ b/test/PowerShellEditorServices.Test/PowerShellEditorServices.Test.csproj
@@ -1,27 +1,34 @@
+
net6.0;netcoreapp3.1;net461
Microsoft.PowerShell.EditorServices.Test
x64
+
true
true
+
+
+
+
+
@@ -30,14 +37,21 @@
+
+
PreserveNewest
+
+
+
+
+
$(DefineConstants);CoreCLR
diff --git a/test/PowerShellEditorServices.Test/Session/PowerShellContextTests.cs b/test/PowerShellEditorServices.Test/Session/PowerShellContextTests.cs
index 4cf1fef8c..ba1e07611 100644
--- a/test/PowerShellEditorServices.Test/Session/PowerShellContextTests.cs
+++ b/test/PowerShellEditorServices.Test/Session/PowerShellContextTests.cs
@@ -3,7 +3,6 @@
using System;
using System.Collections.Generic;
-using System.IO;
using System.Linq;
using System.Management.Automation;
using System.Runtime.InteropServices;
@@ -148,13 +147,13 @@ await this.powerShellContext.ExecuteCommandAsync(
}
[Trait("Category", "PSReadLine")]
- [SkippableFact]
- public async Task CanGetPSReadLineProxy()
+ [Fact]
+ public void CanGetPSReadLineProxy()
{
- Skip.If(IsWindows, "This test doesn't work on Windows for some reason.");
Assert.True(PSReadLinePromptContext.TryGetPSReadLineProxy(
NullLogger.Instance,
- PowerShellContextFactory.initialRunspace,
+ PowerShellContextFactory.InitialRunspace,
+ PowerShellContextFactory.BundledModulePath,
out PSReadLineProxy proxy));
}
diff --git a/test/PowerShellEditorServices.Test/xunit.runner.json b/test/PowerShellEditorServices.Test/xunit.runner.json
index 0b4dfc597..f8b76c8fc 100644
--- a/test/PowerShellEditorServices.Test/xunit.runner.json
+++ b/test/PowerShellEditorServices.Test/xunit.runner.json
@@ -1,4 +1,6 @@
{
+ "$schema": "https://xunit.net/schema/current/xunit.runner.schema.json",
+ "appDomain": "denied",
"parallelizeTestCollections": false,
"methodDisplay": "method"
}