Skip to content

Commit 8c9b903

Browse files
Merge pull request #2018 from PowerShell/andschwa/write-session-failure
Set session failure with reason when applicable
2 parents 708759b + 9c0bea2 commit 8c9b903

File tree

3 files changed

+65
-70
lines changed

3 files changed

+65
-70
lines changed

src/PowerShellEditorServices.Hosting/Commands/StartEditorServicesCommand.cs

+2-5
Original file line numberDiff line numberDiff line change
@@ -224,10 +224,7 @@ protected override void EndProcessing()
224224
// Create the configuration from parameters
225225
EditorServicesConfig editorServicesConfig = CreateConfigObject();
226226

227-
SessionFileWriter sessionFileWriter = new(_logger, SessionDetailsPath);
228-
_logger.Log(PsesLogLevel.Diagnostic, "Session file writer created");
229-
230-
using EditorServicesLoader psesLoader = EditorServicesLoader.Create(_logger, editorServicesConfig, sessionFileWriter, _loggerUnsubscribers);
227+
using EditorServicesLoader psesLoader = EditorServicesLoader.Create(_logger, editorServicesConfig, SessionDetailsPath, _loggerUnsubscribers);
231228
_logger.Log(PsesLogLevel.Verbose, "Loading EditorServices");
232229
// Synchronously start editor services and wait here until it shuts down.
233230
#pragma warning disable VSTHRD002
@@ -394,7 +391,7 @@ private string GetProfilePathFromProfileObject(PSObject profileObject, ProfileUs
394391
$"{HostProfileId}_profile.ps1");
395392
}
396393

397-
// We should only use PSReadLine if we specificied that we want a console repl
394+
// We should only use PSReadLine if we specified that we want a console repl
398395
// and we have not explicitly said to use the legacy ReadLine.
399396
// We also want it if we are either:
400397
// * On Windows on any version OR

src/PowerShellEditorServices.Hosting/Configuration/SessionFileWriter.cs

+12-13
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,7 @@ public interface ISessionFileWriter
2020
/// Write a session file describing a failed startup.
2121
/// </summary>
2222
/// <param name="reason">The reason for the startup failure.</param>
23-
/// <param name="details">Any details to accompany the reason.</param>
24-
void WriteSessionFailure(string reason, object details);
23+
void WriteSessionFailure(string reason);
2524

2625
/// <summary>
2726
/// Write a session file describing a successful startup.
@@ -43,23 +42,26 @@ public sealed class SessionFileWriter : ISessionFileWriter
4342

4443
private readonly string _sessionFilePath;
4544

45+
private readonly Version _powerShellVersion;
46+
4647
/// <summary>
4748
/// Construct a new session file writer for the given session file path.
4849
/// </summary>
4950
/// <param name="logger">The logger to log actions with.</param>
5051
/// <param name="sessionFilePath">The path to write the session file path to.</param>
51-
public SessionFileWriter(HostLogger logger, string sessionFilePath)
52+
/// <param name="powerShellVersion">The process's PowerShell version object.</param>
53+
public SessionFileWriter(HostLogger logger, string sessionFilePath, Version powerShellVersion)
5254
{
5355
_logger = logger;
5456
_sessionFilePath = sessionFilePath;
57+
_powerShellVersion = powerShellVersion;
5558
}
5659

5760
/// <summary>
5861
/// Write a startup failure to the session file.
5962
/// </summary>
6063
/// <param name="reason">The reason for the startup failure.</param>
61-
/// <param name="details">Any extra details, which will be serialized as JSON.</param>
62-
public void WriteSessionFailure(string reason, object details)
64+
public void WriteSessionFailure(string reason)
6365
{
6466
_logger.Log(PsesLogLevel.Diagnostic, "Writing session failure");
6567

@@ -69,11 +71,6 @@ public void WriteSessionFailure(string reason, object details)
6971
{ "reason", reason },
7072
};
7173

72-
if (details != null)
73-
{
74-
sessionObject["details"] = details;
75-
}
76-
7774
WriteSessionObject(sessionObject);
7875
}
7976

@@ -91,11 +88,11 @@ public void WriteSessionStarted(ITransportConfig languageServiceTransport, ITran
9188
{ "status", "started" },
9289
};
9390

94-
if (languageServiceTransport != null)
91+
if (languageServiceTransport is not null)
9592
{
9693
sessionObject["languageServiceTransport"] = languageServiceTransport.SessionFileTransportName;
9794

98-
if (languageServiceTransport.SessionFileEntries != null)
95+
if (languageServiceTransport.SessionFileEntries is not null)
9996
{
10097
foreach (KeyValuePair<string, object> sessionEntry in languageServiceTransport.SessionFileEntries)
10198
{
@@ -104,7 +101,7 @@ public void WriteSessionStarted(ITransportConfig languageServiceTransport, ITran
104101
}
105102
}
106103

107-
if (debugAdapterTransport != null)
104+
if (debugAdapterTransport is not null)
108105
{
109106
sessionObject["debugServiceTransport"] = debugAdapterTransport.SessionFileTransportName;
110107

@@ -126,6 +123,8 @@ public void WriteSessionStarted(ITransportConfig languageServiceTransport, ITran
126123
/// <param name="sessionObject">The dictionary representing the session file.</param>
127124
private void WriteSessionObject(Dictionary<string, object> sessionObject)
128125
{
126+
sessionObject["powerShellVersion"] = _powerShellVersion.ToString();
127+
129128
string psModulePath = Environment.GetEnvironmentVariable("PSModulePath");
130129
string content = null;
131130
using (SMA.PowerShell pwsh = SMA.PowerShell.Create(RunspaceMode.NewRunspace))

src/PowerShellEditorServices.Hosting/EditorServicesLoader.cs

+51-52
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ public sealed class EditorServicesLoader : IDisposable
3333
{
3434
#if !CoreCLR
3535
// See https://docs.microsoft.com/en-us/dotnet/framework/migration-guide/how-to-determine-which-versions-are-installed
36-
private const int Net462Version = 394802;
36+
private const int Net48Version = 528040;
3737

3838
private static readonly string s_psesBaseDirPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
3939
#endif
@@ -49,37 +49,28 @@ public sealed class EditorServicesLoader : IDisposable
4949
/// </summary>
5050
/// <param name="logger">The host logger to use.</param>
5151
/// <param name="hostConfig">The host configuration to start editor services with.</param>
52-
/// <param name="sessionFileWriter">The session file writer to write the session file with.</param>
53-
/// <returns></returns>
54-
public static EditorServicesLoader Create(
55-
HostLogger logger,
56-
EditorServicesConfig hostConfig,
57-
ISessionFileWriter sessionFileWriter) => Create(logger, hostConfig, sessionFileWriter, loggersToUnsubscribe: null);
58-
59-
/// <summary>
60-
/// Create a new Editor Services loader.
61-
/// </summary>
62-
/// <param name="logger">The host logger to use.</param>
63-
/// <param name="hostConfig">The host configuration to start editor services with.</param>
64-
/// <param name="sessionFileWriter">The session file writer to write the session file with.</param>
52+
/// <param name="sessionDetailsPath">Path to the session file to create on startup or startup failure.</param>
6553
/// <param name="loggersToUnsubscribe">The loggers to unsubscribe form writing to the terminal.</param>
66-
/// <returns></returns>
6754
public static EditorServicesLoader Create(
6855
HostLogger logger,
6956
EditorServicesConfig hostConfig,
70-
ISessionFileWriter sessionFileWriter,
57+
string sessionDetailsPath,
7158
IReadOnlyCollection<IDisposable> loggersToUnsubscribe)
7259
{
73-
if (logger == null)
60+
if (logger is null)
7461
{
7562
throw new ArgumentNullException(nameof(logger));
7663
}
7764

78-
if (hostConfig == null)
65+
if (hostConfig is null)
7966
{
8067
throw new ArgumentNullException(nameof(hostConfig));
8168
}
8269

70+
Version powerShellVersion = GetPSVersion();
71+
SessionFileWriter sessionFileWriter = new(logger, sessionDetailsPath, powerShellVersion);
72+
logger.Log(PsesLogLevel.Diagnostic, "Session file writer created");
73+
8374
#if CoreCLR
8475
// In .NET Core, we add an event here to redirect dependency loading to the new AssemblyLoadContext we load PSES' dependencies into
8576
logger.Log(PsesLogLevel.Verbose, "Adding AssemblyResolve event handler for new AssemblyLoadContext dependency loading");
@@ -167,7 +158,7 @@ public static EditorServicesLoader Create(
167158
};
168159
#endif
169160

170-
return new EditorServicesLoader(logger, hostConfig, sessionFileWriter, loggersToUnsubscribe);
161+
return new EditorServicesLoader(logger, hostConfig, sessionFileWriter, loggersToUnsubscribe, powerShellVersion);
171162
}
172163

173164
private readonly EditorServicesConfig _hostConfig;
@@ -178,33 +169,38 @@ public static EditorServicesLoader Create(
178169

179170
private readonly IReadOnlyCollection<IDisposable> _loggersToUnsubscribe;
180171

172+
private readonly Version _powerShellVersion;
173+
181174
private EditorServicesRunner _editorServicesRunner;
182175

183176
private EditorServicesLoader(
184177
HostLogger logger,
185178
EditorServicesConfig hostConfig,
186179
ISessionFileWriter sessionFileWriter,
187-
IReadOnlyCollection<IDisposable> loggersToUnsubscribe)
180+
IReadOnlyCollection<IDisposable> loggersToUnsubscribe,
181+
Version powerShellVersion)
188182
{
189183
_logger = logger;
190184
_hostConfig = hostConfig;
191185
_sessionFileWriter = sessionFileWriter;
192186
_loggersToUnsubscribe = loggersToUnsubscribe;
187+
_powerShellVersion = powerShellVersion;
193188
}
194189

195190
/// <summary>
196191
/// Load Editor Services and its dependencies in an isolated way and start it.
197192
/// This method's returned task will end when Editor Services shuts down.
198193
/// </summary>
199-
/// <returns></returns>
200194
public Task LoadAndRunEditorServicesAsync()
201195
{
202196
// Log important host information here
203197
LogHostInformation();
204198

199+
CheckPowerShellVersion();
200+
205201
#if !CoreCLR
206202
// Make sure the .NET Framework version supports .NET Standard 2.0
207-
CheckNetFxVersion();
203+
CheckDotNetVersion();
208204
#endif
209205

210206
// Add the bundled modules to the PSModulePath
@@ -241,10 +237,30 @@ private static void LoadEditorServices() =>
241237
// The call within this method is therefore a total no-op
242238
EditorServicesLoading.LoadEditorServicesForHost();
243239

240+
private void CheckPowerShellVersion()
241+
{
242+
PSLanguageMode languageMode = Runspace.DefaultRunspace.SessionStateProxy.LanguageMode;
243+
244+
_logger.Log(PsesLogLevel.Verbose, $@"
245+
== PowerShell Details ==
246+
- PowerShell version: {_powerShellVersion}
247+
- Language mode: {languageMode}
248+
");
249+
250+
if ((_powerShellVersion < new Version(5, 1))
251+
|| (_powerShellVersion >= new Version(6, 0) && _powerShellVersion < new Version(7, 2)))
252+
{
253+
_logger.Log(PsesLogLevel.Error, $"PowerShell {_powerShellVersion} is not supported, please update!");
254+
_sessionFileWriter.WriteSessionFailure("powerShellVersion");
255+
}
256+
257+
// TODO: Check if language mode still matters for support.
258+
}
259+
244260
#if !CoreCLR
245-
private void CheckNetFxVersion()
261+
private void CheckDotNetVersion()
246262
{
247-
_logger.Log(PsesLogLevel.Diagnostic, "Checking that .NET Framework version is at least 4.6.2");
263+
_logger.Log(PsesLogLevel.Verbose, "Checking that .NET Framework version is at least 4.8");
248264
using RegistryKey key = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\Net Framework Setup\NDP\v4\Full");
249265
object netFxValue = key?.GetValue("Release");
250266
if (netFxValue == null || netFxValue is not int netFxVersion)
@@ -254,9 +270,10 @@ private void CheckNetFxVersion()
254270

255271
_logger.Log(PsesLogLevel.Verbose, $".NET registry version: {netFxVersion}");
256272

257-
if (netFxVersion < Net462Version)
273+
if (netFxVersion < Net48Version)
258274
{
259-
_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.");
275+
_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");
276+
_sessionFileWriter.WriteSessionFailure("dotNetVersion");
260277
}
261278
}
262279
#endif
@@ -322,30 +339,6 @@ private void LogHostInformation()
322339
- PowerShell output encoding: {GetPSOutputEncoding()}
323340
");
324341

325-
LogPowerShellDetails();
326-
327-
LogOperatingSystemDetails();
328-
}
329-
330-
private static string GetPSOutputEncoding()
331-
{
332-
using SMA.PowerShell pwsh = SMA.PowerShell.Create();
333-
return pwsh.AddScript("$OutputEncoding.EncodingName", useLocalScope: true).Invoke<string>()[0];
334-
}
335-
336-
private void LogPowerShellDetails()
337-
{
338-
PSLanguageMode languageMode = Runspace.DefaultRunspace.SessionStateProxy.LanguageMode;
339-
340-
_logger.Log(PsesLogLevel.Verbose, $@"
341-
== PowerShell Details ==
342-
- PowerShell version: {GetPSVersion()}
343-
- Language mode: {languageMode}
344-
");
345-
}
346-
347-
private void LogOperatingSystemDetails()
348-
{
349342
_logger.Log(PsesLogLevel.Verbose, $@"
350343
== Environment Details ==
351344
- OS description: {RuntimeInformation.OSDescription}
@@ -354,6 +347,12 @@ private void LogOperatingSystemDetails()
354347
");
355348
}
356349

350+
private static string GetPSOutputEncoding()
351+
{
352+
using SMA.PowerShell pwsh = SMA.PowerShell.Create();
353+
return pwsh.AddScript("$OutputEncoding.EncodingName", useLocalScope: true).Invoke<string>()[0];
354+
}
355+
357356
// TODO: Deduplicate this with VersionUtils.
358357
private static string GetOSArchitecture()
359358
{
@@ -401,7 +400,7 @@ private void ValidateConfiguration()
401400
}
402401
}
403402

404-
private static object GetPSVersion()
403+
private static Version GetPSVersion()
405404
{
406405
// In order to read the $PSVersionTable variable,
407406
// we are forced to create a new runspace to avoid concurrency issues,
@@ -412,7 +411,7 @@ private static object GetPSVersion()
412411
return typeof(PSObject).Assembly
413412
.GetType("System.Management.Automation.PSVersionInfo")
414413
.GetMethod("get_PSVersion", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)
415-
.Invoke(null, new object[0] /* Cannot use Array.Empty, since it must work in net452 */);
414+
.Invoke(null, new object[0] /* Cannot use Array.Empty, since it must work in net452 */) as Version;
416415
#pragma warning restore CA1825
417416
}
418417
}

0 commit comments

Comments
 (0)