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
}
}