Skip to content

Remove the MSI install logic (it's unreliable) #4570

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
May 10, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
90 changes: 6 additions & 84 deletions src/features/UpdatePowerShell.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,13 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

import { spawn } from "child_process";
import * as fs from "fs"; // TODO: Remove, but it's for a stream.
import fetch from "node-fetch";
import * as os from "os";
import * as path from "path";
import { SemVer } from "semver";
import * as stream from "stream";
import * as util from "util";
import vscode = require("vscode");

import { ILogger } from "../logging";
import { IPowerShellVersionDetails, SessionManager } from "../session";
import { IPowerShellVersionDetails } from "../session";
import { changeSetting, Settings } from "../settings";
import { isWindows } from "../utils";

const streamPipeline = util.promisify(stream.pipeline);

interface IUpdateMessageItem extends vscode.MessageItem {
id: number;
Expand All @@ -29,7 +20,6 @@ export class UpdatePowerShell {
private static LTSBuildInfoURL = "https://aka.ms/pwsh-buildinfo-lts";
private static StableBuildInfoURL = "https://aka.ms/pwsh-buildinfo-stable";
private static PreviewBuildInfoURL = "https://aka.ms/pwsh-buildinfo-preview";
private static GitHubAPIReleaseURL = "https://api.github.com/repos/PowerShell/PowerShell/releases/tags/";
private static GitHubWebReleaseURL = "https://github.com/PowerShell/PowerShell/releases/tag/";
private static promptOptions: IUpdateMessageItem[] = [
{
Expand All @@ -46,10 +36,8 @@ export class UpdatePowerShell {
},
];
private localVersion: SemVer;
private architecture: string;

constructor(
private sessionManager: SessionManager,
private sessionSettings: Settings,
private logger: ILogger,
versionDetails: IPowerShellVersionDetails) {
Expand All @@ -58,7 +46,6 @@ export class UpdatePowerShell {
// 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();
}

private shouldCheckForUpdate(): boolean {
Expand Down Expand Up @@ -173,74 +160,13 @@ export class UpdatePowerShell {
await vscode.env.openExternal(url);
}

private async updateWindows(tag: string): Promise<void> {
let msiMatcher: string;
if (this.architecture === "x64") {
msiMatcher = "win-x64.msi";
} else if (this.architecture === "x86") {
msiMatcher = "win-x86.msi";
} else {
// We shouldn't get here, but do something sane anyway.
return this.openReleaseInBrowser(tag);
}

let response = await fetch(UpdatePowerShell.GitHubAPIReleaseURL + tag);
if (!response.ok) {
throw new Error("Failed to fetch GitHub release info!");
}
const release = await response.json();

// eslint-disable-next-line @typescript-eslint/no-explicit-any
const asset = release.assets.filter((a: any) => a.name.indexOf(msiMatcher) >= 0)[0];
const msiDownloadPath = path.join(os.tmpdir(), asset.name);

response = await fetch(asset.browser_download_url);
if (!response.ok) {
throw new Error("Failed to fetch MSI!");
}

const progressOptions = {
title: "Downloading PowerShell Installer...",
location: vscode.ProgressLocation.Notification,
cancellable: false,
};
// Streams the body of the request to a file.
await vscode.window.withProgress(progressOptions,
async () => { await streamPipeline(response.body, fs.createWriteStream(msiDownloadPath)); });

// Stop the session because Windows likes to hold on to files.
this.logger.writeDiagnostic("MSI downloaded, stopping session and closing terminals!");
await this.sessionManager.stop();

// Close all terminals with the name "pwsh" in the current VS Code session.
// This will encourage folks to not close the instance of VS Code that spawned
// the MSI process.
for (const terminal of vscode.window.terminals) {
if (terminal.name === "pwsh") {
terminal.dispose();
}
}

// Invoke the MSI via cmd.
this.logger.writeDiagnostic(`Running '${msiDownloadPath}' to update PowerShell...`);
const msi = spawn("msiexec", ["/i", msiDownloadPath]);

msi.on("close", () => {
// Now that the MSI is finished, restart the session.
this.logger.writeDiagnostic("MSI installation finished, restarting session.");
void this.sessionManager.start();
fs.unlinkSync(msiDownloadPath);
});
}

private async installUpdate(tag: string): Promise<void> {
const releaseVersion = new SemVer(tag);
const result = await vscode.window.showInformationMessage(
`You have an old version of PowerShell (${this.localVersion.version}). The current latest release is ${releaseVersion.version}.
Would you like to update the version? ${isWindows
? "This will close ALL pwsh terminals running in this VS Code session!"
: "We can't update you automatically, but we can open the latest release in your browser!"
}`, ...UpdatePowerShell.promptOptions);
`You have an old version of PowerShell (${this.localVersion.version}).
The current latest release is ${releaseVersion.version}.
Would you like to open the GitHub release in your browser?`,
...UpdatePowerShell.promptOptions);

// If the user cancels the notification.
if (!result) {
Expand All @@ -253,11 +179,7 @@ export class UpdatePowerShell {
switch (result.id) {
// Yes
case 0:
if (isWindows && (this.architecture === "x64" || this.architecture === "x86")) {
await this.updateWindows(tag);
} else {
await this.openReleaseInBrowser(tag);
}
await this.openReleaseInBrowser(tag);
break;
// Not Now
case 1:
Expand Down
4 changes: 2 additions & 2 deletions src/session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -479,7 +479,7 @@ export class SessionManager implements Middleware {
"commit": version, // Actually used by UpdatePowerShell
"architecture": process.arch // Best guess based off Code's architecture
};
const updater = new UpdatePowerShell(this, this.sessionSettings, this.logger, versionDetails);
const updater = new UpdatePowerShell(this.sessionSettings, this.logger, versionDetails);
void updater.checkForUpdate();
}
return;
Expand Down Expand Up @@ -735,7 +735,7 @@ Type 'help' to get help.
// We haven't "started" until we're done getting the version information.
this.started = true;

const updater = new UpdatePowerShell(this, this.sessionSettings, this.logger, this.versionDetails);
const updater = new UpdatePowerShell(this.sessionSettings, this.logger, this.versionDetails);
// NOTE: We specifically don't want to wait for this.
void updater.checkForUpdate();
}
Expand Down
24 changes: 8 additions & 16 deletions test/features/UpdatePowerShell.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,7 @@ describe("UpdatePowerShell feature", function () {
"commit": "7.3.0",
"architecture": "X64"
};
// @ts-expect-error testing doesn't require all arguments.
const updater = new UpdatePowerShell(undefined, settings, testLogger, version);
const updater = new UpdatePowerShell(settings, testLogger, version);
// @ts-expect-error method is private.
assert(!updater.shouldCheckForUpdate());
});
Expand All @@ -46,8 +45,7 @@ describe("UpdatePowerShell feature", function () {
"commit": "5.1.22621",
"architecture": "X64"
};
// @ts-expect-error testing doesn't require all arguments.
const updater = new UpdatePowerShell(undefined, settings, testLogger, version);
const updater = new UpdatePowerShell(settings, testLogger, version);
// @ts-expect-error method is private.
assert(!updater.shouldCheckForUpdate());
});
Expand All @@ -59,8 +57,7 @@ describe("UpdatePowerShell feature", function () {
"commit": "7.3.0-preview.3-508-g07175ae0ff8eb7306fe0b0fc7d19bdef4fbf2d67",
"architecture": "Arm64"
};
// @ts-expect-error testing doesn't require all arguments.
const updater = new UpdatePowerShell(undefined, settings, testLogger, version);
const updater = new UpdatePowerShell(settings, testLogger, version);
// @ts-expect-error method is private.
assert(!updater.shouldCheckForUpdate());
});
Expand All @@ -72,8 +69,7 @@ describe("UpdatePowerShell feature", function () {
"commit": "7.3.0-daily20221206.1",
"architecture": "Arm64"
};
// @ts-expect-error testing doesn't require all arguments.
const updater = new UpdatePowerShell(undefined, settings, testLogger, version);
const updater = new UpdatePowerShell(settings, testLogger, version);
// @ts-expect-error method is private.
assert(!updater.shouldCheckForUpdate());
});
Expand All @@ -86,8 +82,7 @@ describe("UpdatePowerShell feature", function () {
"commit": "7.3.0",
"architecture": "X64"
};
// @ts-expect-error testing doesn't require all arguments.
const updater = new UpdatePowerShell(undefined, settings, testLogger, version);
const updater = new UpdatePowerShell(settings, testLogger, version);
// @ts-expect-error method is private.
assert(!updater.shouldCheckForUpdate());
});
Expand All @@ -99,8 +94,7 @@ describe("UpdatePowerShell feature", function () {
"commit": "7.3.0",
"architecture": "X64"
};
// @ts-expect-error testing doesn't require all arguments.
const updater = new UpdatePowerShell(undefined, settings, testLogger, version);
const updater = new UpdatePowerShell(settings, testLogger, version);
// @ts-expect-error method is private.
assert(updater.shouldCheckForUpdate());
});
Expand All @@ -115,8 +109,7 @@ describe("UpdatePowerShell feature", function () {
"commit": "7.0.0",
"architecture": "X64"
};
// @ts-expect-error testing doesn't require all arguments.
const updater = new UpdatePowerShell(undefined, settings, testLogger, version);
const updater = new UpdatePowerShell(settings, testLogger, version);
// @ts-expect-error method is private.
const tag: string | undefined = await updater.maybeGetNewRelease();
// NOTE: This will need to be updated each new major LTS.
Expand All @@ -130,8 +123,7 @@ describe("UpdatePowerShell feature", function () {
"commit": "7.0.0",
"architecture": "X64"
};
// @ts-expect-error testing doesn't require all arguments.
const updater = new UpdatePowerShell(undefined, settings, testLogger, version);
const updater = new UpdatePowerShell(settings, testLogger, version);
// @ts-expect-error method is private.
const tag: string | undefined = await updater.maybeGetNewRelease();
// NOTE: This will need to be updated each new major stable.
Expand Down