diff --git a/src/PowerShellEditorServices.Hosting/Commands/StartEditorServicesCommand.cs b/src/PowerShellEditorServices.Hosting/Commands/StartEditorServicesCommand.cs index 743b6aac99..10de11254d 100644 --- a/src/PowerShellEditorServices.Hosting/Commands/StartEditorServicesCommand.cs +++ b/src/PowerShellEditorServices.Hosting/Commands/StartEditorServicesCommand.cs @@ -224,10 +224,7 @@ protected override void EndProcessing() // Create the configuration from parameters EditorServicesConfig editorServicesConfig = CreateConfigObject(); - SessionFileWriter sessionFileWriter = new(_logger, SessionDetailsPath); - _logger.Log(PsesLogLevel.Diagnostic, "Session file writer created"); - - using EditorServicesLoader psesLoader = EditorServicesLoader.Create(_logger, editorServicesConfig, sessionFileWriter, _loggerUnsubscribers); + using EditorServicesLoader psesLoader = EditorServicesLoader.Create(_logger, editorServicesConfig, SessionDetailsPath, _loggerUnsubscribers); _logger.Log(PsesLogLevel.Verbose, "Loading EditorServices"); // Synchronously start editor services and wait here until it shuts down. #pragma warning disable VSTHRD002 @@ -394,7 +391,7 @@ private string GetProfilePathFromProfileObject(PSObject profileObject, ProfileUs $"{HostProfileId}_profile.ps1"); } - // We should only use PSReadLine if we specificied that we want a console repl + // We should only use PSReadLine if we specified that we want a console repl // and we have not explicitly said to use the legacy ReadLine. // We also want it if we are either: // * On Windows on any version OR diff --git a/src/PowerShellEditorServices.Hosting/Configuration/SessionFileWriter.cs b/src/PowerShellEditorServices.Hosting/Configuration/SessionFileWriter.cs index e7765331aa..623ea8cf30 100644 --- a/src/PowerShellEditorServices.Hosting/Configuration/SessionFileWriter.cs +++ b/src/PowerShellEditorServices.Hosting/Configuration/SessionFileWriter.cs @@ -42,15 +42,19 @@ public sealed class SessionFileWriter : ISessionFileWriter private readonly string _sessionFilePath; + private readonly Version _powerShellVersion; + /// /// Construct a new session file writer for the given session file path. /// /// The logger to log actions with. /// The path to write the session file path to. - public SessionFileWriter(HostLogger logger, string sessionFilePath) + /// The process's PowerShell version object. + public SessionFileWriter(HostLogger logger, string sessionFilePath, Version powerShellVersion) { _logger = logger; _sessionFilePath = sessionFilePath; + _powerShellVersion = powerShellVersion; } /// @@ -84,11 +88,11 @@ public void WriteSessionStarted(ITransportConfig languageServiceTransport, ITran { "status", "started" }, }; - if (languageServiceTransport != null) + if (languageServiceTransport is not null) { sessionObject["languageServiceTransport"] = languageServiceTransport.SessionFileTransportName; - if (languageServiceTransport.SessionFileEntries != null) + if (languageServiceTransport.SessionFileEntries is not null) { foreach (KeyValuePair sessionEntry in languageServiceTransport.SessionFileEntries) { @@ -97,7 +101,7 @@ public void WriteSessionStarted(ITransportConfig languageServiceTransport, ITran } } - if (debugAdapterTransport != null) + if (debugAdapterTransport is not null) { sessionObject["debugServiceTransport"] = debugAdapterTransport.SessionFileTransportName; @@ -119,6 +123,8 @@ public void WriteSessionStarted(ITransportConfig languageServiceTransport, ITran /// The dictionary representing the session file. private void WriteSessionObject(Dictionary sessionObject) { + sessionObject["powerShellVersion"] = _powerShellVersion; + string psModulePath = Environment.GetEnvironmentVariable("PSModulePath"); string content = null; using (SMA.PowerShell pwsh = SMA.PowerShell.Create(RunspaceMode.NewRunspace)) diff --git a/src/PowerShellEditorServices.Hosting/EditorServicesLoader.cs b/src/PowerShellEditorServices.Hosting/EditorServicesLoader.cs index b9d4097cc9..d5772bb590 100644 --- a/src/PowerShellEditorServices.Hosting/EditorServicesLoader.cs +++ b/src/PowerShellEditorServices.Hosting/EditorServicesLoader.cs @@ -49,37 +49,28 @@ public sealed class EditorServicesLoader : IDisposable /// /// The host logger to use. /// The host configuration to start editor services with. - /// The session file writer to write the session file with. - /// - public static EditorServicesLoader Create( - HostLogger logger, - EditorServicesConfig hostConfig, - ISessionFileWriter sessionFileWriter) => Create(logger, hostConfig, sessionFileWriter, loggersToUnsubscribe: null); - - /// - /// Create a new Editor Services loader. - /// - /// The host logger to use. - /// The host configuration to start editor services with. - /// The session file writer to write the session file with. + /// Path to the session file to create on startup or startup failure. /// The loggers to unsubscribe form writing to the terminal. - /// public static EditorServicesLoader Create( HostLogger logger, EditorServicesConfig hostConfig, - ISessionFileWriter sessionFileWriter, + string sessionDetailsPath, IReadOnlyCollection loggersToUnsubscribe) { - if (logger == null) + if (logger is null) { throw new ArgumentNullException(nameof(logger)); } - if (hostConfig == null) + if (hostConfig is null) { throw new ArgumentNullException(nameof(hostConfig)); } + Version powerShellVersion = GetPSVersion(); + SessionFileWriter sessionFileWriter = new(logger, sessionDetailsPath, powerShellVersion); + logger.Log(PsesLogLevel.Diagnostic, "Session file writer created"); + #if CoreCLR // In .NET Core, we add an event here to redirect dependency loading to the new AssemblyLoadContext we load PSES' dependencies into logger.Log(PsesLogLevel.Verbose, "Adding AssemblyResolve event handler for new AssemblyLoadContext dependency loading"); @@ -167,7 +158,7 @@ public static EditorServicesLoader Create( }; #endif - return new EditorServicesLoader(logger, hostConfig, sessionFileWriter, loggersToUnsubscribe); + return new EditorServicesLoader(logger, hostConfig, sessionFileWriter, loggersToUnsubscribe, powerShellVersion); } private readonly EditorServicesConfig _hostConfig; @@ -178,33 +169,38 @@ public static EditorServicesLoader Create( private readonly IReadOnlyCollection _loggersToUnsubscribe; + private readonly Version _powerShellVersion; + private EditorServicesRunner _editorServicesRunner; private EditorServicesLoader( HostLogger logger, EditorServicesConfig hostConfig, ISessionFileWriter sessionFileWriter, - IReadOnlyCollection loggersToUnsubscribe) + IReadOnlyCollection loggersToUnsubscribe, + Version powerShellVersion) { _logger = logger; _hostConfig = hostConfig; _sessionFileWriter = sessionFileWriter; _loggersToUnsubscribe = loggersToUnsubscribe; + _powerShellVersion = powerShellVersion; } /// /// Load Editor Services and its dependencies in an isolated way and start it. /// This method's returned task will end when Editor Services shuts down. /// - /// public Task LoadAndRunEditorServicesAsync() { // Log important host information here LogHostInformation(); + CheckPowerShellVersion(); + #if !CoreCLR // Make sure the .NET Framework version supports .NET Standard 2.0 - CheckNetFxVersion(); + CheckDotNetVersion(); #endif // Add the bundled modules to the PSModulePath @@ -241,10 +237,30 @@ private static void LoadEditorServices() => // The call within this method is therefore a total no-op EditorServicesLoading.LoadEditorServicesForHost(); + private void CheckPowerShellVersion() + { + PSLanguageMode languageMode = Runspace.DefaultRunspace.SessionStateProxy.LanguageMode; + + _logger.Log(PsesLogLevel.Verbose, $@" +== PowerShell Details == +- PowerShell version: {_powerShellVersion} +- Language mode: {languageMode} +"); + + if ((_powerShellVersion < new Version(5, 1)) + || (_powerShellVersion >= new Version(6, 0) && _powerShellVersion < new Version(7, 2))) + { + _logger.Log(PsesLogLevel.Error, $"PowerShell {_powerShellVersion} is not supported, please update!"); + _sessionFileWriter.WriteSessionFailure("powerShellVersion"); + } + + // TODO: Check if language mode still matters for support. + } + #if !CoreCLR - private void CheckNetFxVersion() + private void CheckDotNetVersion() { - _logger.Log(PsesLogLevel.Diagnostic, "Checking that .NET Framework version is at least 4.8"); + _logger.Log(PsesLogLevel.Verbose, "Checking that .NET Framework version is at least 4.8"); using RegistryKey key = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\Net Framework Setup\NDP\v4\Full"); object netFxValue = key?.GetValue("Release"); if (netFxValue == null || netFxValue is not int netFxVersion) @@ -256,7 +272,8 @@ private void CheckNetFxVersion() if (netFxVersion < Net48Version) { - _logger.Log(PsesLogLevel.Warning, $".NET Framework version {netFxVersion} lower than .NET 4.8. This runtime is not supported and you may experience errors. Please update your .NET runtime version."); + _logger.Log(PsesLogLevel.Error, $".NET Framework {netFxVersion} is out-of-date, please install at least 4.8: https://dotnet.microsoft.com/en-us/download/dotnet-framework"); + _sessionFileWriter.WriteSessionFailure("dotNetVersion"); } } #endif @@ -322,30 +339,6 @@ private void LogHostInformation() - PowerShell output encoding: {GetPSOutputEncoding()} "); - LogPowerShellDetails(); - - LogOperatingSystemDetails(); - } - - private static string GetPSOutputEncoding() - { - using SMA.PowerShell pwsh = SMA.PowerShell.Create(); - return pwsh.AddScript("$OutputEncoding.EncodingName", useLocalScope: true).Invoke()[0]; - } - - private void LogPowerShellDetails() - { - PSLanguageMode languageMode = Runspace.DefaultRunspace.SessionStateProxy.LanguageMode; - - _logger.Log(PsesLogLevel.Verbose, $@" -== PowerShell Details == -- PowerShell version: {GetPSVersion()} -- Language mode: {languageMode} -"); - } - - private void LogOperatingSystemDetails() - { _logger.Log(PsesLogLevel.Verbose, $@" == Environment Details == - OS description: {RuntimeInformation.OSDescription} @@ -354,6 +347,12 @@ private void LogOperatingSystemDetails() "); } + private static string GetPSOutputEncoding() + { + using SMA.PowerShell pwsh = SMA.PowerShell.Create(); + return pwsh.AddScript("$OutputEncoding.EncodingName", useLocalScope: true).Invoke()[0]; + } + // TODO: Deduplicate this with VersionUtils. private static string GetOSArchitecture() { @@ -401,7 +400,7 @@ private void ValidateConfiguration() } } - private static object GetPSVersion() + private static Version GetPSVersion() { // In order to read the $PSVersionTable variable, // we are forced to create a new runspace to avoid concurrency issues, @@ -412,7 +411,7 @@ private static object GetPSVersion() return typeof(PSObject).Assembly .GetType("System.Management.Automation.PSVersionInfo") .GetMethod("get_PSVersion", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic) - .Invoke(null, new object[0] /* Cannot use Array.Empty, since it must work in net452 */); + .Invoke(null, new object[0] /* Cannot use Array.Empty, since it must work in net452 */) as Version; #pragma warning restore CA1825 } }