Skip to content

Commit 0942b21

Browse files
Merge pull request #4543 from PowerShell/andschwa/failure-reasons
Update startup logic to handle session failure reasons
2 parents 9cf9c5d + 2d74846 commit 0942b21

File tree

2 files changed

+43
-30
lines changed

2 files changed

+43
-30
lines changed

src/process.ts

Lines changed: 22 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -111,28 +111,28 @@ export class PowerShellProcess {
111111
hideFromUser: this.sessionSettings.integratedConsole.startInBackground,
112112
};
113113

114+
// Subscribe a log event for when the terminal closes (this fires for
115+
// all terminals and the event itself checks if it's our terminal). This
116+
// subscription should happen before we create the terminal so if it
117+
// fails immediately, the event fires.
118+
this.consoleCloseSubscription = vscode.window.onDidCloseTerminal((terminal) => this.onTerminalClose(terminal));
119+
114120
this.consoleTerminal = vscode.window.createTerminal(terminalOptions);
115121

116122
const pwshName = path.basename(this.exePath);
117123
this.logger.write(`${pwshName} started.`);
118124

125+
// Log that the PowerShell terminal process has been started
126+
const pid = await this.getPid();
127+
this.logTerminalPid(pid ?? 0, pwshName);
128+
119129
if (this.sessionSettings.integratedConsole.showOnStartup
120130
&& !this.sessionSettings.integratedConsole.startInBackground) {
121131
// We still need to run this to set the active terminal to the extension terminal.
122132
this.consoleTerminal.show(true);
123133
}
124134

125-
// Start the language client
126-
const sessionDetails = await this.waitForSessionFile();
127-
128-
// Subscribe a log event for when the terminal closes
129-
this.consoleCloseSubscription = vscode.window.onDidCloseTerminal((terminal) => this.onTerminalClose(terminal));
130-
131-
// Log that the PowerShell terminal process has been started
132-
const pid = await this.getPid();
133-
this.logTerminalPid(pid ?? 0, pwshName);
134-
135-
return sessionDetails;
135+
return await this.waitForSessionFile();
136136
}
137137

138138
// This function should only be used after a failure has occurred because it is slow!
@@ -154,7 +154,7 @@ export class PowerShellProcess {
154154

155155
public async dispose(): Promise<void> {
156156
// Clean up the session file
157-
this.logger.write("Terminating PowerShell process...");
157+
this.logger.write("Disposing PowerShell Extension Terminal...");
158158

159159
this.consoleTerminal?.dispose();
160160
this.consoleTerminal = undefined;
@@ -199,8 +199,8 @@ export class PowerShellProcess {
199199
private static async deleteSessionFile(sessionFilePath: vscode.Uri): Promise<void> {
200200
try {
201201
await vscode.workspace.fs.delete(sessionFilePath);
202-
} catch (e) {
203-
// TODO: Be more specific about what we're catching
202+
} catch {
203+
// We don't care about any reasons for it to fail.
204204
}
205205
}
206206

@@ -212,6 +212,12 @@ export class PowerShellProcess {
212212
// Check every 2 seconds
213213
this.logger.write("Waiting for session file...");
214214
for (let i = numOfTries; i > 0; i--) {
215+
if (this.consoleTerminal === undefined) {
216+
const err = "PowerShell Extension Terminal didn't start!";
217+
this.logger.write(err);
218+
throw new Error(err);
219+
}
220+
215221
if (await utils.checkIfFileExists(this.sessionFilePath)) {
216222
this.logger.write("Session file found!");
217223
const sessionDetails = await PowerShellProcess.readSessionFile(this.sessionFilePath);
@@ -237,7 +243,8 @@ export class PowerShellProcess {
237243
return;
238244
}
239245

240-
this.logger.write("powershell.exe terminated or terminal UI was closed");
246+
this.logger.write("PowerShell process terminated or Extension Terminal was closed!");
241247
this.onExitedEmitter.fire();
248+
void this.dispose();
242249
}
243250
}

src/session.ts

Lines changed: 21 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,6 @@ export enum RunspaceType {
4444
export interface IEditorServicesSessionDetails {
4545
status: string;
4646
reason: string;
47-
detail: string;
4847
powerShellVersion: string;
4948
channel: string;
5049
languageServicePort: number;
@@ -430,7 +429,7 @@ export class SessionManager implements Middleware {
430429

431430
private async startPowerShell(): Promise<PowerShellProcess | undefined> {
432431
if (this.PowerShellExeDetails === undefined) {
433-
this.setSessionFailure("Unable to find PowerShell.");
432+
void this.setSessionFailedGetPowerShell("Unable to find PowerShell, try installing it?");
434433
return;
435434
}
436435

@@ -458,9 +457,6 @@ export class SessionManager implements Middleware {
458457
try {
459458
this.sessionDetails = await languageServerProcess.start("EditorServices");
460459
} catch (err) {
461-
// We should kill the process in case it's stuck.
462-
void languageServerProcess.dispose();
463-
464460
// PowerShell never started, probably a bad version!
465461
const version = await languageServerProcess.getVersionCli();
466462
let shouldUpdate = true;
@@ -498,10 +494,10 @@ export class SessionManager implements Middleware {
498494
void this.setSessionFailedOpenBug("Language client failed to start: " + (err instanceof Error ? err.message : "unknown"));
499495
}
500496
} else if (this.sessionDetails.status === "failed") { // Server started but indicated it failed
501-
if (this.sessionDetails.reason === "unsupported") {
497+
if (this.sessionDetails.reason === "powerShellVersion") {
502498
void this.setSessionFailedGetPowerShell(`PowerShell ${this.sessionDetails.powerShellVersion} is not supported, please update!`);
503-
} else if (this.sessionDetails.reason === "languageMode") {
504-
this.setSessionFailure(`PowerShell language features are disabled due to an unsupported LanguageMode: ${this.sessionDetails.detail}`);
499+
} else if (this.sessionDetails.reason === "dotNetVersion") { // Only applies to PowerShell 5.1
500+
void this.setSessionFailedGetDotNet(".NET Framework is out-of-date, please install at least 4.8!");
505501
} else {
506502
void this.setSessionFailedOpenBug(`PowerShell could not be started for an unknown reason: ${this.sessionDetails.reason}`);
507503
}
@@ -728,7 +724,7 @@ Type 'help' to get help.
728724
try {
729725
await this.languageClient.start();
730726
} catch (err) {
731-
this.setSessionFailure("Could not start language service: ", err instanceof Error ? err.message : "unknown");
727+
void this.setSessionFailedOpenBug("Could not start language service: " + (err instanceof Error ? err.message : "unknown"));
732728
return;
733729
}
734730

@@ -761,6 +757,9 @@ Type 'help' to get help.
761757
const semver = new SemVer(this.versionDetails.version);
762758
this.languageStatusItem.text = `$(terminal-powershell) ${semver.major}.${semver.minor}`;
763759
this.languageStatusItem.detail += ` ${this.versionDetails.commit} (${this.versionDetails.architecture.toLowerCase()})`;
760+
} else if (this.PowerShellExeDetails?.displayName) { // In case it didn't start.
761+
this.languageStatusItem.text = `$(terminal-powershell) ${this.PowerShellExeDetails.displayName}`;
762+
this.languageStatusItem.detail += `: ${this.PowerShellExeDetails.exePath}`;
764763
}
765764

766765
if (statusText) {
@@ -799,11 +798,6 @@ Type 'help' to get help.
799798
this.setSessionStatus("Executing...", SessionStatus.Busy);
800799
}
801800

802-
private setSessionFailure(message: string, ...additionalMessages: string[]): void {
803-
this.setSessionStatus("Initialization Error!", SessionStatus.Failed);
804-
void this.logger.writeAndShowError(message, ...additionalMessages);
805-
}
806-
807801
private async setSessionFailedOpenBug(message: string): Promise<void> {
808802
this.setSessionStatus("Initialization Error!", SessionStatus.Failed);
809803
await this.logger.writeAndShowErrorWithActions(message, [{
@@ -825,6 +819,17 @@ Type 'help' to get help.
825819
);
826820
}
827821

822+
private async setSessionFailedGetDotNet(message: string): Promise<void> {
823+
this.setSessionStatus("Initialization Error!", SessionStatus.Failed);
824+
await this.logger.writeAndShowErrorWithActions(message, [{
825+
prompt: "Open .NET Framework Documentation",
826+
action: async (): Promise<void> => {
827+
await vscode.env.openExternal(
828+
vscode.Uri.parse("https://dotnet.microsoft.com/en-us/download/dotnet-framework"));
829+
}}]
830+
);
831+
}
832+
828833
private async changePowerShellDefaultVersion(exePath: IPowerShellExeDetails): Promise<void> {
829834
this.suppressRestartPrompt = true;
830835
try {
@@ -857,7 +862,8 @@ Type 'help' to get help.
857862
private async showSessionMenu(): Promise<void> {
858863
const powershellExeFinder = new PowerShellExeFinder(
859864
this.platformDetails,
860-
this.sessionSettings.powerShellAdditionalExePaths,
865+
// We don't pull from session settings because we want them fresh!
866+
getSettings().powerShellAdditionalExePaths,
861867
this.logger);
862868
const availablePowerShellExes = await powershellExeFinder.getAllAvailablePowerShellInstallations();
863869

0 commit comments

Comments
 (0)