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..7612cd5a0 100644 --- a/src/PowerShellEditorServices/Hosting/HostStartupInfo.cs +++ b/src/PowerShellEditorServices/Hosting/HostStartupInfo.cs @@ -106,7 +106,10 @@ public sealed class HostStartupInfo /// cref="Serilog.Events.LogEventLevel"/>, hence it is an int. /// public int LogLevel { get; } - + /// + /// The path to find the Bundled Modules /// + /// + public string BundledModulePath { get; } #endregion #region Constructors @@ -135,6 +138,7 @@ public sealed class HostStartupInfo /// The minimum log event level. /// Enable console if true. /// Use PSReadLine if false, otherwise use the legacy readline implementation. + /// The path to the Modules folder, helps with loading the bundled PSReadLine and other included modules public HostStartupInfo( string name, string profileId, @@ -147,7 +151,9 @@ public HostStartupInfo( string logPath, int logLevel, bool consoleReplEnabled, - bool usesLegacyReadLine) + bool usesLegacyReadLine, + string bundledModulePath + ) { Name = name ?? DefaultHostName; ProfileId = profileId ?? DefaultHostProfileId; @@ -161,6 +167,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..b98754951 100644 --- a/src/PowerShellEditorServices/Services/PowerShellContext/PowerShellContextService.cs +++ b/src/PowerShellEditorServices/Services/PowerShellContext/PowerShellContextService.cs @@ -32,10 +32,18 @@ namespace Microsoft.PowerShell.EditorServices.Services /// 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), - "../../Commands/PowerShellEditorServices.Commands.psd1")); + s_bundledModulesPath, + "PSReadLine")); private static readonly Action s_runspaceApartmentStateSetter; private static readonly PropertyInfo s_writeStreamProperty; @@ -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) + ? hostStartupInfo.BundledModulePath + : s_bundledModulesPath; + var logger = factory.CreateLogger(); bool shouldUsePSReadLine = hostStartupInfo.ConsoleReplEnabled @@ -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(); + if(hostStartupInfo.ConsoleReplEnabled) + { + modulesToImport.Add(_psReadLineModulePath); + } + modulesToImport.AddRange(hostStartupInfo.AdditionalModules); + foreach (string module in modulesToImport) { var command = new PSCommand() @@ -305,7 +322,7 @@ public static Runspace CreateRunspace(PSHost psHost, PSLanguageMode languageMode /// If true, the PowerShellContext owns this runspace. /// An IHostOutput implementation. Optional. public void Initialize( - ProfilePathInfo profilePaths, + HostStartupInfo hostStartupInfo, Runspace initialRunspace, bool ownsInitialRunspace, IHostOutput consoleHost) @@ -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); @@ -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, @@ -409,10 +431,6 @@ public void Initialize( this.PromptContext = new LegacyReadLineContext(this); } - if (VersionUtils.IsWindows) - { - this.SetExecutionPolicy(); - } } /// diff --git a/src/PowerShellEditorServices/Services/PowerShellContext/Session/PSReadLinePromptContext.cs b/src/PowerShellEditorServices/Services/PowerShellContext/Session/PSReadLinePromptContext.cs index 5f4931854..3633ad1d3 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,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()) { @@ -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() : ""); - return false; + logger.LogWarning("PSConsoleReadline type not found using Type.GetType(): {Reason}", pwsh.HadErrors ? pwsh.Streams.Error[0].ToString() : ""); + 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."); + psReadLineType = type; + } + else + { + logger.LogError("Unable to find PSConsoleReadLine in loaded assembles."); + return false; + } } + try { readLineProxy = new PSReadLineProxy(psReadLineType, logger); @@ -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; } } diff --git a/test/PowerShellEditorServices.Test/PowerShellContextFactory.cs b/test/PowerShellEditorServices.Test/PowerShellContextFactory.cs index 4eb93395a..5fd8ecbd1 100644 --- a/test/PowerShellEditorServices.Test/PowerShellContextFactory.cs +++ b/test/PowerShellEditorServices.Test/PowerShellContextFactory.cs @@ -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; @@ -50,7 +53,8 @@ public static PowerShellContextService Create(ILogger logger) null, 0, consoleReplEnabled: false, - usesLegacyReadLine: false); + usesLegacyReadLine: false, + bundledModulePath: BundledModulesPath); initialRunspace = PowerShellContextService.CreateRunspace( testHostDetails, @@ -59,7 +63,7 @@ public static PowerShellContextService Create(ILogger logger) logger); powerShellContext.Initialize( - TestProfilePaths, + testHostDetails, initialRunspace, ownsInitialRunspace: true, consoleHost: null); diff --git a/test/PowerShellEditorServices.Test/Session/PowerShellContextTests.cs b/test/PowerShellEditorServices.Test/Session/PowerShellContextTests.cs index 4cf1fef8c..16043aaac 100644 --- a/test/PowerShellEditorServices.Test/Session/PowerShellContextTests.cs +++ b/test/PowerShellEditorServices.Test/Session/PowerShellContextTests.cs @@ -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; @@ -151,11 +152,20 @@ await this.powerShellContext.ExecuteCommandAsync( [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