From b1e468fecd27803b525c2b8e00d9565e087df792 Mon Sep 17 00:00:00 2001 From: Andy Jordan Date: Mon, 24 Apr 2023 11:49:51 -0700 Subject: [PATCH 1/3] Update minimum supported .NET check to 4.8 This is not updating our requirement, that apparently already changed to 4.8 as determined by testing. --- .../EditorServicesLoader.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/PowerShellEditorServices.Hosting/EditorServicesLoader.cs b/src/PowerShellEditorServices.Hosting/EditorServicesLoader.cs index 2f9b8f8a6..b9d4097cc 100644 --- a/src/PowerShellEditorServices.Hosting/EditorServicesLoader.cs +++ b/src/PowerShellEditorServices.Hosting/EditorServicesLoader.cs @@ -33,7 +33,7 @@ public sealed class EditorServicesLoader : IDisposable { #if !CoreCLR // See https://docs.microsoft.com/en-us/dotnet/framework/migration-guide/how-to-determine-which-versions-are-installed - private const int Net462Version = 394802; + private const int Net48Version = 528040; private static readonly string s_psesBaseDirPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); #endif @@ -244,7 +244,7 @@ private static void LoadEditorServices() => #if !CoreCLR private void CheckNetFxVersion() { - _logger.Log(PsesLogLevel.Diagnostic, "Checking that .NET Framework version is at least 4.6.2"); + _logger.Log(PsesLogLevel.Diagnostic, "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) @@ -254,9 +254,9 @@ private void CheckNetFxVersion() _logger.Log(PsesLogLevel.Verbose, $".NET registry version: {netFxVersion}"); - if (netFxVersion < Net462Version) + if (netFxVersion < Net48Version) { - _logger.Log(PsesLogLevel.Warning, $".NET Framework version {netFxVersion} lower than .NET 4.6.2. This runtime is not supported and you may experience errors. Please update your .NET runtime version."); + _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."); } } #endif From 4223eecd1b2892235daa4455989d0a63c1ea300e Mon Sep 17 00:00:00 2001 From: Andy Jordan Date: Mon, 24 Apr 2023 11:51:14 -0700 Subject: [PATCH 2/3] Remove `details` from `WriteSessionFailure` Which is currently unused. It must have been in use previously because the client is expecting specific reasons. Since we need to redo this, let's remove the excess. --- .../Configuration/SessionFileWriter.cs | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/src/PowerShellEditorServices.Hosting/Configuration/SessionFileWriter.cs b/src/PowerShellEditorServices.Hosting/Configuration/SessionFileWriter.cs index 207ef67ab..e7765331a 100644 --- a/src/PowerShellEditorServices.Hosting/Configuration/SessionFileWriter.cs +++ b/src/PowerShellEditorServices.Hosting/Configuration/SessionFileWriter.cs @@ -20,8 +20,7 @@ public interface ISessionFileWriter /// Write a session file describing a failed startup. /// /// The reason for the startup failure. - /// Any details to accompany the reason. - void WriteSessionFailure(string reason, object details); + void WriteSessionFailure(string reason); /// /// Write a session file describing a successful startup. @@ -58,8 +57,7 @@ public SessionFileWriter(HostLogger logger, string sessionFilePath) /// Write a startup failure to the session file. /// /// The reason for the startup failure. - /// Any extra details, which will be serialized as JSON. - public void WriteSessionFailure(string reason, object details) + public void WriteSessionFailure(string reason) { _logger.Log(PsesLogLevel.Diagnostic, "Writing session failure"); @@ -69,11 +67,6 @@ public void WriteSessionFailure(string reason, object details) { "reason", reason }, }; - if (details != null) - { - sessionObject["details"] = details; - } - WriteSessionObject(sessionObject); } From 9c0bea28e9d28e6ea3d1b49e7c8a9738c835f822 Mon Sep 17 00:00:00 2001 From: Andy Jordan Date: Mon, 24 Apr 2023 13:07:28 -0700 Subject: [PATCH 3/3] Set session failure with reason when applicable --- .../Commands/StartEditorServicesCommand.cs | 7 +- .../Configuration/SessionFileWriter.cs | 14 ++- .../EditorServicesLoader.cs | 99 +++++++++---------- 3 files changed, 61 insertions(+), 59 deletions(-) diff --git a/src/PowerShellEditorServices.Hosting/Commands/StartEditorServicesCommand.cs b/src/PowerShellEditorServices.Hosting/Commands/StartEditorServicesCommand.cs index 743b6aac9..10de11254 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 e7765331a..c5f351f91 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.ToString(); + 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 b9d4097cc..d5772bb59 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 } }