Skip to content

Commit

Permalink
Handle end-of-support PowerShell with error message
Browse files Browse the repository at this point in the history
Significantly improves our handling of PowerShell failing to start because it is unsupported.
  • Loading branch information
andyleejordan committed Apr 21, 2023
1 parent e108877 commit 3559aed
Show file tree
Hide file tree
Showing 3 changed files with 45 additions and 13 deletions.
11 changes: 8 additions & 3 deletions src/features/UpdatePowerShell.ts
Expand Up @@ -52,13 +52,18 @@ export class UpdatePowerShell {
private sessionManager: SessionManager,
private sessionSettings: Settings,
private logger: ILogger,
versionDetails: IPowerShellVersionDetails) {
versionDetails: IPowerShellVersionDetails | SemVer) {
// We use the commit field as it's like
// '7.3.0-preview.3-508-g07175ae0ff8eb7306fe0b0fc7d...' which translates
// to SemVer. The version handler in PSES handles Windows PowerShell and
// just returns the first three fields like '5.1.22621'.
this.localVersion = new SemVer(versionDetails.commit);
this.architecture = versionDetails.architecture.toLowerCase();
if (versionDetails instanceof SemVer) {
this.localVersion = versionDetails;
this.architecture = "";
} else {
this.localVersion = new SemVer(versionDetails.commit);
this.architecture = versionDetails.architecture.toLowerCase();
}
}

private shouldCheckForUpdate(): boolean {
Expand Down
16 changes: 13 additions & 3 deletions src/process.ts
Expand Up @@ -8,6 +8,8 @@ import { ILogger } from "./logging";
import Settings = require("./settings");
import utils = require("./utils");
import { IEditorServicesSessionDetails } from "./session";
import { promisify } from "util";
import { SemVer } from "semver";

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

// This function should only be used after a failure has occurred because it is slow!
public async getVersionCli(): Promise<SemVer> {
const exec = promisify(cp.execFile);
// TODO: This should have a timeout.
const { stdout } = await exec(this.exePath, ["-Login", "-NoProfile", "-NoLogo", "-Command", "$PSVersionTable.PSVersion.ToString()"]);
return new SemVer(stdout.trim());
}

// Returns the process Id of the consoleTerminal
public async getPid(): Promise<number | undefined> {
if (!this.consoleTerminal) { return undefined; }
Expand All @@ -148,13 +158,13 @@ export class PowerShellProcess {
// Clean up the session file
this.logger.write("Terminating PowerShell process...");

await PowerShellProcess.deleteSessionFile(this.sessionFilePath);
this.consoleTerminal?.dispose();
this.consoleTerminal = undefined;

this.consoleCloseSubscription?.dispose();
this.consoleCloseSubscription = undefined;

this.consoleTerminal?.dispose();
this.consoleTerminal = undefined;
await PowerShellProcess.deleteSessionFile(this.sessionFilePath);
}

public sendKeyPress(): void {
Expand Down
31 changes: 24 additions & 7 deletions src/session.ts
Expand Up @@ -24,7 +24,7 @@ import {
OperatingSystem, PowerShellExeFinder
} from "./platform";
import { LanguageClientConsumer } from "./languageClientConsumer";
import { SemVer } from "semver";
import { SemVer, satisfies } from "semver";

export enum SessionStatus {
NeverStarted,
Expand Down Expand Up @@ -458,17 +458,36 @@ export class SessionManager implements Middleware {
try {
this.sessionDetails = await languageServerProcess.start("EditorServices");
} catch (err) {
this.setSessionFailure("PowerShell process failed to start: ", err instanceof Error ? err.message : "unknown");
// We should kill the process in case it's stuck.
void languageServerProcess.dispose();

// PowerShell never started, probably a bad version!
const version = await languageServerProcess.getVersionCli();
if (version.major === 5) {
this.setSessionFailure(
"It looks like you're trying to use Windows PowerShell, which is supported on a best-effort basis. Can you try PowerShell 7?");
} else if (satisfies(version, ">=6.0.0 <7.2.0")) {
this.setSessionFailure(
`PowerShell ${version} has reached end-of-support, please update! Also see: https://aka.ms/pwsh-support-lifecycle`);
const updater = new UpdatePowerShell(this, this.sessionSettings, this.logger, version);
// NOTE: We specifically don't want to wait for this.
void updater.checkForUpdate();
} else {
this.setSessionFailure(
"PowerShell process didn't start! Would you like to open an issue?");
}
return;
}

if (this.sessionDetails?.status === "started") {
if (this.sessionDetails.status === "started") { // Successful start with a session file
this.logger.write("Language server started.");
try {
await this.startLanguageClient(this.sessionDetails);
return languageServerProcess;
} catch (err) {
this.setSessionFailure("Language client failed to start: ", err instanceof Error ? err.message : "unknown");
}
} else if (this.sessionDetails?.status === "failed") {
} else if (this.sessionDetails.status === "failed") { // PowerShell started but indicated it failed
if (this.sessionDetails.reason === "unsupported") {
this.setSessionFailure(
"PowerShell language features are only supported on PowerShell version 5.1 and 7+. " +
Expand All @@ -483,10 +502,8 @@ export class SessionManager implements Middleware {
}
} else {
this.setSessionFailure(
`Unknown session status '${this.sessionDetails?.status}' with reason '${this.sessionDetails?.reason}`);
`Unknown session status '${this.sessionDetails.status}' with reason '${this.sessionDetails.reason}`);
}

return languageServerProcess;
}

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

0 comments on commit 3559aed

Please sign in to comment.