Skip to content

Commit 3559aed

Browse files
committed
Handle end-of-support PowerShell with error message
Significantly improves our handling of PowerShell failing to start because it is unsupported.
1 parent e108877 commit 3559aed

File tree

3 files changed

+45
-13
lines changed

3 files changed

+45
-13
lines changed

src/features/UpdatePowerShell.ts

+8-3
Original file line numberDiff line numberDiff line change
@@ -52,13 +52,18 @@ export class UpdatePowerShell {
5252
private sessionManager: SessionManager,
5353
private sessionSettings: Settings,
5454
private logger: ILogger,
55-
versionDetails: IPowerShellVersionDetails) {
55+
versionDetails: IPowerShellVersionDetails | SemVer) {
5656
// We use the commit field as it's like
5757
// '7.3.0-preview.3-508-g07175ae0ff8eb7306fe0b0fc7d...' which translates
5858
// to SemVer. The version handler in PSES handles Windows PowerShell and
5959
// just returns the first three fields like '5.1.22621'.
60-
this.localVersion = new SemVer(versionDetails.commit);
61-
this.architecture = versionDetails.architecture.toLowerCase();
60+
if (versionDetails instanceof SemVer) {
61+
this.localVersion = versionDetails;
62+
this.architecture = "";
63+
} else {
64+
this.localVersion = new SemVer(versionDetails.commit);
65+
this.architecture = versionDetails.architecture.toLowerCase();
66+
}
6267
}
6368

6469
private shouldCheckForUpdate(): boolean {

src/process.ts

+13-3
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ import { ILogger } from "./logging";
88
import Settings = require("./settings");
99
import utils = require("./utils");
1010
import { IEditorServicesSessionDetails } from "./session";
11+
import { promisify } from "util";
12+
import { SemVer } from "semver";
1113

1214
export class PowerShellProcess {
1315
// This is used to warn the user that the extension is taking longer than expected to startup.
@@ -134,6 +136,14 @@ export class PowerShellProcess {
134136
return sessionDetails;
135137
}
136138

139+
// This function should only be used after a failure has occurred because it is slow!
140+
public async getVersionCli(): Promise<SemVer> {
141+
const exec = promisify(cp.execFile);
142+
// TODO: This should have a timeout.
143+
const { stdout } = await exec(this.exePath, ["-Login", "-NoProfile", "-NoLogo", "-Command", "$PSVersionTable.PSVersion.ToString()"]);
144+
return new SemVer(stdout.trim());
145+
}
146+
137147
// Returns the process Id of the consoleTerminal
138148
public async getPid(): Promise<number | undefined> {
139149
if (!this.consoleTerminal) { return undefined; }
@@ -148,13 +158,13 @@ export class PowerShellProcess {
148158
// Clean up the session file
149159
this.logger.write("Terminating PowerShell process...");
150160

151-
await PowerShellProcess.deleteSessionFile(this.sessionFilePath);
161+
this.consoleTerminal?.dispose();
162+
this.consoleTerminal = undefined;
152163

153164
this.consoleCloseSubscription?.dispose();
154165
this.consoleCloseSubscription = undefined;
155166

156-
this.consoleTerminal?.dispose();
157-
this.consoleTerminal = undefined;
167+
await PowerShellProcess.deleteSessionFile(this.sessionFilePath);
158168
}
159169

160170
public sendKeyPress(): void {

src/session.ts

+24-7
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ import {
2424
OperatingSystem, PowerShellExeFinder
2525
} from "./platform";
2626
import { LanguageClientConsumer } from "./languageClientConsumer";
27-
import { SemVer } from "semver";
27+
import { SemVer, satisfies } from "semver";
2828

2929
export enum SessionStatus {
3030
NeverStarted,
@@ -458,17 +458,36 @@ export class SessionManager implements Middleware {
458458
try {
459459
this.sessionDetails = await languageServerProcess.start("EditorServices");
460460
} catch (err) {
461-
this.setSessionFailure("PowerShell process failed to start: ", err instanceof Error ? err.message : "unknown");
461+
// We should kill the process in case it's stuck.
462+
void languageServerProcess.dispose();
463+
464+
// PowerShell never started, probably a bad version!
465+
const version = await languageServerProcess.getVersionCli();
466+
if (version.major === 5) {
467+
this.setSessionFailure(
468+
"It looks like you're trying to use Windows PowerShell, which is supported on a best-effort basis. Can you try PowerShell 7?");
469+
} else if (satisfies(version, ">=6.0.0 <7.2.0")) {
470+
this.setSessionFailure(
471+
`PowerShell ${version} has reached end-of-support, please update! Also see: https://aka.ms/pwsh-support-lifecycle`);
472+
const updater = new UpdatePowerShell(this, this.sessionSettings, this.logger, version);
473+
// NOTE: We specifically don't want to wait for this.
474+
void updater.checkForUpdate();
475+
} else {
476+
this.setSessionFailure(
477+
"PowerShell process didn't start! Would you like to open an issue?");
478+
}
479+
return;
462480
}
463481

464-
if (this.sessionDetails?.status === "started") {
482+
if (this.sessionDetails.status === "started") { // Successful start with a session file
465483
this.logger.write("Language server started.");
466484
try {
467485
await this.startLanguageClient(this.sessionDetails);
486+
return languageServerProcess;
468487
} catch (err) {
469488
this.setSessionFailure("Language client failed to start: ", err instanceof Error ? err.message : "unknown");
470489
}
471-
} else if (this.sessionDetails?.status === "failed") {
490+
} else if (this.sessionDetails.status === "failed") { // PowerShell started but indicated it failed
472491
if (this.sessionDetails.reason === "unsupported") {
473492
this.setSessionFailure(
474493
"PowerShell language features are only supported on PowerShell version 5.1 and 7+. " +
@@ -483,10 +502,8 @@ export class SessionManager implements Middleware {
483502
}
484503
} else {
485504
this.setSessionFailure(
486-
`Unknown session status '${this.sessionDetails?.status}' with reason '${this.sessionDetails?.reason}`);
505+
`Unknown session status '${this.sessionDetails.status}' with reason '${this.sessionDetails.reason}`);
487506
}
488-
489-
return languageServerProcess;
490507
}
491508

492509
private async findPowerShell(): Promise<IPowerShellExeDetails | undefined> {

0 commit comments

Comments
 (0)