Skip to content

Honor BundledModulesPath when loading modules in PowerShellContextService #1516

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
26818b3
Added BundledModulesPath to HostStartupInfo object and if it is valid…
dkattan Jun 28, 2021
2888045
Fixed s_bundledModulesPath assignment.
dkattan Jun 30, 2021
d0bc52d
Merge branch 'PowerShell:master' into honor-bundledmodulespath-parameter
dkattan Jul 6, 2021
e590fad
Fixed missing BundledModulesPath parameter assignment
dkattan Jul 6, 2021
52b16ca
Restored PowerShellEditorServices.Commands.psd1
dkattan Jul 6, 2021
d8738f0
removed #if TEST from PowerShellContextService.cs and instead provide…
dkattan Jul 6, 2021
ee154b0
removed #if TEST from PowerShellContextService.cs and instead provide…
dkattan Jul 6, 2021
b91382b
Merge branch 'honor-bundledmodulespath-parameter' of github.com:dkatt…
dkattan Jul 6, 2021
77186f0
Added logging to CanGetPSReadLineProxy test
dkattan Jul 6, 2021
48d1a96
Adjusted BundledModulesPath, re-enabled Windows test.
dkattan Jul 6, 2021
7037115
Added more logging to tests, implemented additional mechanism to find…
dkattan Jul 6, 2021
a0fa3c5
Implemented fix to find the non VSS cached location of the executing …
dkattan Jul 6, 2021
1443baa
Implemented fix to find the non VSS cached location of the executing …
dkattan Jul 6, 2021
bda11be
Attempted different fix for relative path to module directory.
dkattan Jul 6, 2021
bda40ea
Cleaned up logging
dkattan Jul 6, 2021
01957e6
Removed TEST property. Moved Set-ExecutionPolicy to before attempted …
dkattan Jul 7, 2021
9a045d9
Fixed formatting in PowerShellEditorServices.build.ps1
dkattan Jul 7, 2021
e1e70cf
Fixed issue where BundledModulePath was _not_ actually getting set.
dkattan Jul 7, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion PowerShellEditorServices.Common.props
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,5 @@
<RepositoryType>git</RepositoryType>
<RepositoryUrl>https://github.com/PowerShell/PowerShellEditorServices</RepositoryUrl>
<DebugType>portable</DebugType>
<DefineConstants Condition=" '$(ExtraDefineConstants)' != '' ">$(DefineConstants);$(ExtraDefineConstants)</DefineConstants>
</PropertyGroup>
</Project>
10 changes: 5 additions & 5 deletions PowerShellEditorServices.build.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -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) }
}
}

Expand All @@ -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);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
11 changes: 9 additions & 2 deletions src/PowerShellEditorServices/Hosting/HostStartupInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,10 @@ public sealed class HostStartupInfo
/// cref="Serilog.Events.LogEventLevel"/>, hence it is an <c>int</c>.
/// </remarks>
public int LogLevel { get; }

/// <summary>
/// The path to find the Bundled Modules ///
/// </summary>
public string BundledModulePath { get; }
#endregion

#region Constructors
Expand Down Expand Up @@ -135,6 +138,7 @@ public sealed class HostStartupInfo
/// <param name="logLevel">The minimum log event level.</param>
/// <param name="consoleReplEnabled">Enable console if true.</param>
/// <param name="usesLegacyReadLine">Use PSReadLine if false, otherwise use the legacy readline implementation.</param>
/// <param name="bundledModulePath">The path to the Modules folder, helps with loading the bundled PSReadLine and other included modules</param>
public HostStartupInfo(
string name,
string profileId,
Expand All @@ -147,7 +151,9 @@ public HostStartupInfo(
string logPath,
int logLevel,
bool consoleReplEnabled,
bool usesLegacyReadLine)
bool usesLegacyReadLine,
string bundledModulePath
)
{
Name = name ?? DefaultHostName;
ProfileId = profileId ?? DefaultHostProfileId;
Expand All @@ -161,6 +167,7 @@ public HostStartupInfo(
LogLevel = logLevel;
ConsoleReplEnabled = consoleReplEnabled;
UsesLegacyReadLine = usesLegacyReadLine;
BundledModulePath = bundledModulePath;
}

#endregion
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,18 @@ namespace Microsoft.PowerShell.EditorServices.Services
/// </summary>
internal class PowerShellContextService : IHostSupportsInteractiveSession
{
private static readonly string s_commandsModulePath = Path.GetFullPath(
private static string s_bundledModulesPath = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "..", "..");

private static string s_commandsModulePath => Path.GetFullPath(
Path.Combine(
s_bundledModulesPath,
"PowerShellEditorServices",
"Commands",
"PowerShellEditorServices.Commands.psd1"));
private static string _psReadLineModulePath => Path.GetFullPath(
Path.Combine(
Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location),
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is impetus for the change. We are referencing a path relative to the executing assembly instead of honoring the BundledModulePath parameter.

"../../Commands/PowerShellEditorServices.Commands.psd1"));
s_bundledModulesPath,
"PSReadLine"));

private static readonly Action<Runspace, ApartmentState> s_runspaceApartmentStateSetter;
private static readonly PropertyInfo s_writeStreamProperty;
Expand Down Expand Up @@ -190,7 +198,10 @@ public static PowerShellContextService Create(
HostStartupInfo hostStartupInfo)
{
Validate.IsNotNull(nameof(hostStartupInfo), hostStartupInfo);

s_bundledModulesPath = !string.IsNullOrEmpty(hostStartupInfo.BundledModulePath) && Directory.Exists(hostStartupInfo.BundledModulePath)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here if a valid path was specified, we use it, otherwise we fallback to the previous logic.

? hostStartupInfo.BundledModulePath
: s_bundledModulesPath;

var logger = factory.CreateLogger<PowerShellContextService>();

bool shouldUsePSReadLine = hostStartupInfo.ConsoleReplEnabled
Expand All @@ -215,12 +226,18 @@ public static PowerShellContextService Create(

logger.LogTrace("Creating initial PowerShell runspace");
Runspace initialRunspace = PowerShellContextService.CreateRunspace(psHost, hostStartupInfo.LanguageMode);
powerShellContext.Initialize(hostStartupInfo.ProfilePaths, initialRunspace, true, hostUserInterface);
powerShellContext.Initialize(hostStartupInfo, initialRunspace, true, hostUserInterface);
powerShellContext.ImportCommandsModuleAsync();

// TODO: This can be moved to the point after the $psEditor object
// gets initialized when that is done earlier than LanguageServer.Initialize
foreach (string module in hostStartupInfo.AdditionalModules)
var modulesToImport = new List<string>();
if(hostStartupInfo.ConsoleReplEnabled)
{
modulesToImport.Add(_psReadLineModulePath);
}
modulesToImport.AddRange(hostStartupInfo.AdditionalModules);
foreach (string module in modulesToImport)
{
var command =
new PSCommand()
Expand Down Expand Up @@ -305,7 +322,7 @@ public static Runspace CreateRunspace(PSHost psHost, PSLanguageMode languageMode
/// <param name="ownsInitialRunspace">If true, the PowerShellContext owns this runspace.</param>
/// <param name="consoleHost">An IHostOutput implementation. Optional.</param>
public void Initialize(
ProfilePathInfo profilePaths,
HostStartupInfo hostStartupInfo,
Runspace initialRunspace,
bool ownsInitialRunspace,
IHostOutput consoleHost)
Expand Down Expand Up @@ -359,7 +376,7 @@ public void Initialize(
this.ConfigureRunspaceCapabilities(this.CurrentRunspace);

// Set the $profile variable in the runspace
this.profilePaths = profilePaths;
this.profilePaths = hostStartupInfo.ProfilePaths;
if (profilePaths != null)
{
this.SetProfileVariableInCurrentRunspace(profilePaths);
Expand Down Expand Up @@ -394,9 +411,14 @@ public void Initialize(
this.versionSpecificOperations);
this.InvocationEventQueue = InvocationEventQueue.Create(this, this.PromptNest);

if (VersionUtils.IsWindows)
{
this.SetExecutionPolicy();
}

if (powerShellVersion.Major >= 5 &&
this.isPSReadLineEnabled &&
PSReadLinePromptContext.TryGetPSReadLineProxy(logger, initialRunspace, out PSReadLineProxy proxy))
PSReadLinePromptContext.TryGetPSReadLineProxy(logger, initialRunspace, hostStartupInfo.BundledModulePath, out PSReadLineProxy proxy))
{
this.PromptContext = new PSReadLinePromptContext(
this,
Expand All @@ -409,10 +431,6 @@ public void Initialize(
this.PromptContext = new LegacyReadLineContext(this);
}

if (VersionUtils.IsWindows)
{
this.SetExecutionPolicy();
}
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<CmdletInfo> s_lazyInvokeReadLineForEditorServicesCmdletInfo = new Lazy<CmdletInfo>(() =>
{
var type = Type.GetType("Microsoft.PowerShell.EditorServices.Commands.InvokeReadLineForEditorServicesCommand, Microsoft.PowerShell.EditorServices.Hosting");
Expand Down Expand Up @@ -79,9 +65,11 @@ internal PSReadLinePromptContext(
internal static bool TryGetPSReadLineProxy(
ILogger logger,
Runspace runspace,
string bundledModulePath,
out PSReadLineProxy readLineProxy)
{
readLineProxy = null;
string _psReadLineModulePath = Path.Combine(bundledModulePath, "PSReadLine");
logger.LogTrace("Attempting to load PSReadLine");
using (var pwsh = PowerShell.Create())
{
Expand All @@ -94,10 +82,24 @@ internal static bool TryGetPSReadLineProxy(

if (psReadLineType == null)
{
logger.LogWarning("PSConsoleReadline type not found: {Reason}", pwsh.HadErrors ? pwsh.Streams.Error[0].ToString() : "<Unknown reason>");
return false;
logger.LogWarning("PSConsoleReadline type not found using Type.GetType(): {Reason}", pwsh.HadErrors ? pwsh.Streams.Error[0].ToString() : "<Unknown reason>");
logger.LogWarning("Searching loaded assemblies...");
var allAssemblies = AppDomain.CurrentDomain.GetAssemblies();
var assemblies = allAssemblies.FirstOrDefault(a => a.FullName.Contains("Microsoft.PowerShell.PSReadLine2"));
var type = assemblies?.ExportedTypes?.FirstOrDefault(a => a.FullName == "Microsoft.PowerShell.PSConsoleReadLine");
if(type is not null)
{
logger.LogInformation("Found PSConsoleReadLine in loaded assemblies.");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In your testing has it ever been found in the loaded assemblies?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like it. Hmm...

psReadLineType = type;
}
else
{
logger.LogError("Unable to find PSConsoleReadLine in loaded assembles.");
return false;
}
}


try
{
readLineProxy = new PSReadLineProxy(psReadLineType, logger);
Expand All @@ -108,6 +110,7 @@ internal static bool TryGetPSReadLineProxy(
// Could be an older version, a custom build, or something a newer version with
// breaking changes.
logger.LogWarning("PSReadLineProxy unable to be initialized: {Reason}", e);
System.Console.WriteLine("PSReadLineProxy unable to be initialized");
return false;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ internal static class PowerShellContextFactory
TestUtilities.NormalizePath("../../../../PowerShellEditorServices.Test.Shared/Test.PowerShellEditorServices_profile.ps1")),
Path.GetFullPath(
TestUtilities.NormalizePath("../../../../PowerShellEditorServices.Test.Shared/ProfileTest.ps1")));
public static readonly string BundledModulesPath =
Path.GetFullPath(
TestUtilities.NormalizePath("../../../../../module"));

public static System.Management.Automation.Runspaces.Runspace initialRunspace;

Expand All @@ -50,7 +53,8 @@ public static PowerShellContextService Create(ILogger logger)
null,
0,
consoleReplEnabled: false,
usesLegacyReadLine: false);
usesLegacyReadLine: false,
bundledModulePath: BundledModulesPath);

initialRunspace = PowerShellContextService.CreateRunspace(
testHostDetails,
Expand All @@ -59,7 +63,7 @@ public static PowerShellContextService Create(ILogger logger)
logger);

powerShellContext.Initialize(
TestProfilePaths,
testHostDetails,
initialRunspace,
ownsInitialRunspace: true,
consoleHost: null);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using System.IO;
using System.Linq;
using System.Management.Automation;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging.Abstractions;
Expand Down Expand Up @@ -151,11 +152,20 @@ await this.powerShellContext.ExecuteCommandAsync<string>(
[SkippableFact]
public async Task CanGetPSReadLineProxy()
{
Skip.If(IsWindows, "This test doesn't work on Windows for some reason.");
var directory = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, AppDomain.CurrentDomain.RelativeSearchPath ?? "");
string s_bundledModulesPath = Path.GetFullPath(Path.Combine(directory,
"..",
"..",
"..",
"..",
"..",
"module"
));
Assert.True(PSReadLinePromptContext.TryGetPSReadLineProxy(
NullLogger.Instance,
PowerShellContextFactory.initialRunspace,
out PSReadLineProxy proxy));
NullLogger.Instance,
PowerShellContextFactory.initialRunspace,
s_bundledModulesPath,
out PSReadLineProxy proxy));
}

#region Helper Methods
Expand Down