From e89e1aceb950e09603daacbdceaabd130a8c2e94 Mon Sep 17 00:00:00 2001 From: Rob Holt Date: Tue, 8 Oct 2019 10:16:42 -0700 Subject: [PATCH 01/46] Fix original startup issue --- src/platform.ts | 204 ++++++++++++++++++++++++++++++------------------ src/session.ts | 7 +- 2 files changed, 133 insertions(+), 78 deletions(-) diff --git a/src/platform.ts b/src/platform.ts index 2cede17892..77b244af87 100644 --- a/src/platform.ts +++ b/src/platform.ts @@ -2,10 +2,24 @@ * Copyright (C) Microsoft Corporation. All rights reserved. *--------------------------------------------------------*/ -import fs = require("fs"); -import path = require("path"); -import process = require("process"); -import Settings = require("./settings"); +import * as child_process from "child_process"; +import * as fs from "fs"; +import * as os from "os"; +import * as path from "path"; +import * as process from "process"; +import * as Settings from "./settings"; + +export const System32PowerShellPath = getWindowsSystemPowerShellPath("System32"); +export const SysnativePowerShellPath = getWindowsSystemPowerShellPath("Sysnative"); +export const SysWow64PowerShellPath = getWindowsSystemPowerShellPath("SysWow64"); + +export const WindowsPowerShell64BitLabel = "Windows PowerShell (x64)"; +export const WindowsPowerShell32BitLabel = "Windows PowerShell (x86)"; + +const powerShell64BitPathOn32Bit = SysnativePowerShellPath.toLocaleLowerCase(); +const powerShell32BitPathOn64Bit = SysWow64PowerShellPath.toLocaleLowerCase(); + +const dotnetGlobalToolExePath = path.join(os.homedir(), ".dotnet", "tools", "pwsh.exe"); const linuxExePath = "/usr/bin/pwsh"; const linuxPreviewExePath = "/usr/bin/pwsh-preview"; @@ -54,9 +68,19 @@ export function getPlatformDetails(): IPlatformDetails { }; } +export class PowerShellNotFoundError extends Error { + public readonly powershellPath: string; + + constructor(powershellPath: string) { + super(`Unable to find PowerShell installation at path '${powershellPath}'`); + this.powershellPath = powershellPath; + } +} + /** - * Gets the default instance of PowerShell for the specified platform. - * On Windows, the default version of PowerShell is "Windows PowerShell". + * Gets the default instance of PowerShell for the specified platform, + * depending on the operating system and the bitness required. + * If a stable PowerShell Core installation can be found, this will be preferred. * @param platformDetails Specifies information about the platform - primarily the operating system. * @param use32Bit On Windows, this boolean determines whether the 32-bit version of Windows PowerShell is returned. * @returns A string containing the path of the default version of PowerShell. @@ -65,86 +89,21 @@ export function getDefaultPowerShellPath( platformDetails: IPlatformDetails, use32Bit: boolean = false): string | null { - let powerShellExePath; - let psCoreInstallPath; - - // Find the path to the powershell executable based on the current platform - // and the user's desire to run the x86 version of PowerShell - if (platformDetails.operatingSystem === OperatingSystem.Windows) { - if (use32Bit) { - psCoreInstallPath = - (platformDetails.isProcess64Bit ? process.env["ProgramFiles(x86)"] : process.env.ProgramFiles) - + "\\PowerShell"; - } else { - psCoreInstallPath = - (platformDetails.isProcess64Bit ? process.env.ProgramFiles : process.env.ProgramW6432) + "\\PowerShell"; - } - - if (fs.existsSync(psCoreInstallPath)) { - const arch = platformDetails.isProcess64Bit ? "(x64)" : "(x86)"; - const psCorePaths = - fs.readdirSync(psCoreInstallPath) - .map((item) => path.join(psCoreInstallPath, item)) - .filter((item) => { - const exePath = path.join(item, "pwsh.exe"); - return fs.lstatSync(item).isDirectory() && fs.existsSync(exePath); - }) - .map((item) => ({ - versionName: `PowerShell ${path.parse(item).base} ${arch}`, - exePath: path.join(item, "pwsh.exe"), - })); - - if (psCorePaths) { - return powerShellExePath = psCorePaths[0].exePath; - } - } - - // No PowerShell 6+ detected so use Windows PowerShell. - if (use32Bit) { - return platformDetails.isOS64Bit && platformDetails.isProcess64Bit - ? SysWow64PowerShellPath - : System32PowerShellPath; - } - return !platformDetails.isOS64Bit || platformDetails.isProcess64Bit - ? System32PowerShellPath - : SysnativePowerShellPath; - } - if (platformDetails.operatingSystem === OperatingSystem.MacOS) { - // Always default to the stable version of PowerShell (if installed) but handle case of only Preview installed - powerShellExePath = macOSExePath; - if (!fs.existsSync(macOSExePath) && fs.existsSync(macOSPreviewExePath)) { - powerShellExePath = macOSPreviewExePath; - } - } else if (platformDetails.operatingSystem === OperatingSystem.Linux) { - // Always default to the stable version of PowerShell (if installed) but handle case of only Preview installed - // as well as the Snaps case - https://snapcraft.io/ - powerShellExePath = linuxExePath; - if (!fs.existsSync(linuxExePath) && fs.existsSync(linuxPreviewExePath)) { - powerShellExePath = linuxPreviewExePath; - } else if (fs.existsSync(snapExePath)) { - powerShellExePath = snapExePath; - } else if (fs.existsSync(snapPreviewExePath)) { - powerShellExePath = snapPreviewExePath; + let pwshPath: string; + for (pwshPath of enumeratePowerShellInstallationPaths(platformDetails, use32Bit)) { + // TODO: check if the file is executable (complicated stat logic...) + if (fs.existsSync(pwshPath)) { + return pwshPath; } } - return powerShellExePath; + throw new PowerShellNotFoundError(pwshPath); } export function getWindowsSystemPowerShellPath(systemFolderName: string) { return `${process.env.windir}\\${systemFolderName}\\WindowsPowerShell\\v1.0\\powershell.exe`; } -export const System32PowerShellPath = getWindowsSystemPowerShellPath("System32"); -export const SysnativePowerShellPath = getWindowsSystemPowerShellPath("Sysnative"); -export const SysWow64PowerShellPath = getWindowsSystemPowerShellPath("SysWow64"); - -export const WindowsPowerShell64BitLabel = "Windows PowerShell (x64)"; -export const WindowsPowerShell32BitLabel = "Windows PowerShell (x86)"; - -const powerShell64BitPathOn32Bit = SysnativePowerShellPath.toLocaleLowerCase(); -const powerShell32BitPathOn64Bit = SysWow64PowerShellPath.toLocaleLowerCase(); - export function fixWindowsPowerShellPath(powerShellExePath: string, platformDetails: IPlatformDetails): string { const lowerCasedPath = powerShellExePath.toLocaleLowerCase(); @@ -248,3 +207,94 @@ export function getAvailablePowerShellExes( return paths; } + +function enumeratePowerShellInstallationPaths( + platformDetails: IPlatformDetails, + use32Bit: boolean): Iterable { + + switch (platformDetails.operatingSystem) { + case OperatingSystem.Windows: + return enumeratePowerShellInstallationPathsForWindows(platformDetails, use32Bit); + + case OperatingSystem.MacOS: + return enumeratePowerShellInstallationPathsForMacOS(); + + case OperatingSystem.Linux: + return enumeratePowerShellInstallationPathsForLinux(); + + default: + return []; + } +} + +function *enumeratePowerShellInstallationPathsForWindows( + platformDetails: IPlatformDetails, + use32Bit: boolean): Iterable { + let psCoreInstallDirPath: string; + + if (use32Bit) { + psCoreInstallDirPath = platformDetails.isProcess64Bit + ? process.env["ProgramFiles(x86)"] + : process.env.ProgramFiles; + } else { + psCoreInstallDirPath = platformDetails.isProcess64Bit + ? process.env.ProgramFiles + : process.env.ProgramW6432; + } + + psCoreInstallDirPath = path.join(psCoreInstallDirPath, "PowerShell"); + + // Search for PS 6/7 paths + // These look like "%ProgramFiles%\PowerShell\\pwsh.exe" + if (fs.existsSync(psCoreInstallDirPath) && fs.lstatSync(psCoreInstallDirPath).isDirectory()) { + for (const item of fs.readdirSync(psCoreInstallDirPath)) { + if (parseInt(item, 10)) { + yield path.join(psCoreInstallDirPath, item, "pwsh.exe"); + } + } + } + + // Now try the MSIX path + yield getPowerShellMsixPath(platformDetails, use32Bit); + + // Now try the .NET global tool pwsh.exe + yield dotnetGlobalToolExePath; + + // Finally find Windows PowerShell, which should always succeed (so don't look for pwsh-preview.exe) + yield getWindowsPowerShellPath(platformDetails, use32Bit); +} + +function *enumeratePowerShellInstallationPathsForMacOS(): Iterable { + yield macOSExePath; + yield dotnetGlobalToolExePath; + yield macOSPreviewExePath; +} + +function *enumeratePowerShellInstallationPathsForLinux(): Iterable { + yield linuxExePath; + yield snapExePath; + yield dotnetGlobalToolExePath; + yield linuxPreviewExePath; + yield snapPreviewExePath; +} + +function getWindowsPowerShellPath(platformDetails: IPlatformDetails, use32Bit: boolean): string { + if (use32Bit) { + return platformDetails.isProcess64Bit && platformDetails.isOS64Bit + ? powerShell32BitPathOn64Bit + : System32PowerShellPath; + } + + return platformDetails.isProcess64Bit && platformDetails.isOS64Bit + ? System32PowerShellPath + : powerShell64BitPathOn32Bit; +} + +function getPowerShellMsixPath(platformDetails: IPlatformDetails, use32Bit: boolean): string { + const winPSPath = getWindowsPowerShellPath(platformDetails, use32Bit); + const msixDir = child_process.execSync(`"${winPSPath}" -Command "(Get-AppxPackage -Name Microsoft.PowerShell).InstallLocation"`) + .toString("utf8") + .trim(); + + return path.join(msixDir, "pwsh.exe"); +} diff --git a/src/session.ts b/src/session.ts index 5a0598f287..82b6337d6d 100644 --- a/src/session.ts +++ b/src/session.ts @@ -110,7 +110,12 @@ export class SessionManager implements Middleware { this.createStatusBarItem(); - this.powerShellExePath = this.getPowerShellExePath(); + try { + this.powerShellExePath = this.getPowerShellExePath(); + } catch (e) { + vscode.window.showErrorMessage("Unable to find PowerShell. Do you have PowerShell installed? See logs for more details."); + this.log.writeError(`Unable to find PowerShell executable:\n${e}`); + } this.suppressRestartPrompt = false; From b115bb27584e901c7f8ef201ca174baa08505d4d Mon Sep 17 00:00:00 2001 From: Rob Holt Date: Wed, 9 Oct 2019 13:34:14 -0700 Subject: [PATCH 02/46] Add finder class --- src/platform.ts | 446 ++++++++++++++++++++++++++++++++++-------- src/session.ts | 4 +- test/platform.test.ts | 10 +- 3 files changed, 366 insertions(+), 94 deletions(-) diff --git a/src/platform.ts b/src/platform.ts index 77b244af87..e94262e903 100644 --- a/src/platform.ts +++ b/src/platform.ts @@ -8,6 +8,7 @@ import * as os from "os"; import * as path from "path"; import * as process from "process"; import * as Settings from "./settings"; +import { runInThisContext } from "vm"; export const System32PowerShellPath = getWindowsSystemPowerShellPath("System32"); export const SysnativePowerShellPath = getWindowsSystemPowerShellPath("Sysnative"); @@ -16,19 +17,17 @@ export const SysWow64PowerShellPath = getWindowsSystemPowerShellPath("SysWow64") export const WindowsPowerShell64BitLabel = "Windows PowerShell (x64)"; export const WindowsPowerShell32BitLabel = "Windows PowerShell (x86)"; -const powerShell64BitPathOn32Bit = SysnativePowerShellPath.toLocaleLowerCase(); -const powerShell32BitPathOn64Bit = SysWow64PowerShellPath.toLocaleLowerCase(); +const WinPS64BitPathOn32Bit = SysnativePowerShellPath.toLocaleLowerCase(); +const WinPS32BitPathOn64Bit = SysWow64PowerShellPath.toLocaleLowerCase(); -const dotnetGlobalToolExePath = path.join(os.homedir(), ".dotnet", "tools", "pwsh.exe"); +const LinuxExePath = "/usr/bin/pwsh"; +const LinuxPreviewExePath = "/usr/bin/pwsh-preview"; -const linuxExePath = "/usr/bin/pwsh"; -const linuxPreviewExePath = "/usr/bin/pwsh-preview"; +const SnapExePath = "/snap/bin/pwsh"; +const SnapPreviewExePath = "/snap/bin/pwsh-preview"; -const snapExePath = "/snap/bin/pwsh"; -const snapPreviewExePath = "/snap/bin/pwsh-preview"; - -const macOSExePath = "/usr/local/bin/pwsh"; -const macOSPreviewExePath = "/usr/local/bin/pwsh-preview"; +const MacOSExePath = "/usr/local/bin/pwsh"; +const MacOSPreviewExePath = "/usr/local/bin/pwsh-preview"; export enum OperatingSystem { Unknown, @@ -44,8 +43,9 @@ export interface IPlatformDetails { } export interface IPowerShellExeDetails { - versionName: string; - exePath: string; + readonly displayName: string; + readonly exePath: string; + readonly version: string; } export function getPlatformDetails(): IPlatformDetails { @@ -107,8 +107,8 @@ export function getWindowsSystemPowerShellPath(systemFolderName: string) { export function fixWindowsPowerShellPath(powerShellExePath: string, platformDetails: IPlatformDetails): string { const lowerCasedPath = powerShellExePath.toLocaleLowerCase(); - if ((platformDetails.isProcess64Bit && (lowerCasedPath === powerShell64BitPathOn32Bit)) || - (!platformDetails.isProcess64Bit && (lowerCasedPath === powerShell32BitPathOn64Bit))) { + if ((platformDetails.isProcess64Bit && (lowerCasedPath === WinPS64BitPathOn32Bit)) || + (!platformDetails.isProcess64Bit && (lowerCasedPath === WinPS32BitPathOn64Bit))) { return System32PowerShellPath; } @@ -131,24 +131,24 @@ export function getAvailablePowerShellExes( if (platformDetails.operatingSystem === OperatingSystem.Windows) { if (platformDetails.isProcess64Bit) { paths.push({ - versionName: WindowsPowerShell64BitLabel, + displayName: WindowsPowerShell64BitLabel, exePath: System32PowerShellPath, }); paths.push({ - versionName: WindowsPowerShell32BitLabel, + displayName: WindowsPowerShell32BitLabel, exePath: SysWow64PowerShellPath, }); } else { if (platformDetails.isOS64Bit) { paths.push({ - versionName: WindowsPowerShell64BitLabel, + displayName: WindowsPowerShell64BitLabel, exePath: SysnativePowerShellPath, }); } paths.push({ - versionName: WindowsPowerShell32BitLabel, + displayName: WindowsPowerShell32BitLabel, exePath: System32PowerShellPath, }); } @@ -179,15 +179,15 @@ export function getAvailablePowerShellExes( let exePaths: string[]; if (platformDetails.operatingSystem === OperatingSystem.Linux) { - exePaths = [ linuxExePath, snapExePath, linuxPreviewExePath, snapPreviewExePath ]; + exePaths = [ LinuxExePath, SnapExePath, LinuxPreviewExePath, SnapPreviewExePath ]; } else { - exePaths = [ macOSExePath, macOSPreviewExePath ]; + exePaths = [ MacOSExePath, MacOSPreviewExePath ]; } exePaths.forEach((exePath) => { if (fs.existsSync(exePath)) { paths.push({ - versionName: "PowerShell" + (/-preview/.test(exePath) ? " Preview" : ""), + displayName: "PowerShell" + (/-preview/.test(exePath) ? " Preview" : ""), exePath, }); } @@ -199,7 +199,7 @@ export function getAvailablePowerShellExes( // Add additional PowerShell paths as configured in settings for (const additionalPowerShellExePath of sessionSettings.powerShellAdditionalExePaths) { paths.push({ - versionName: additionalPowerShellExePath.versionName, + displayName: additionalPowerShellExePath.versionName, exePath: additionalPowerShellExePath.exePath, }); } @@ -208,93 +208,365 @@ export function getAvailablePowerShellExes( return paths; } -function enumeratePowerShellInstallationPaths( - platformDetails: IPlatformDetails, - use32Bit: boolean): Iterable { +interface IPossiblePowerShellExe extends IPowerShellExeDetails { + readonly exists: boolean; + readonly is32Bit: boolean; +} + +abstract class PossiblePowerShellExe implements IPossiblePowerShellExe { + private readonly pathToExe: string; + private readonly installationName: string; + private readonly is32BitExe: boolean; + + private knownToExist: boolean = undefined; + + constructor( + pathToExe: string, + installationName: string, + options?: { knownToExist?: boolean, is32Bit?: boolean }) { + + this.pathToExe = pathToExe; + this.installationName = installationName; - switch (platformDetails.operatingSystem) { - case OperatingSystem.Windows: - return enumeratePowerShellInstallationPathsForWindows(platformDetails, use32Bit); + if (options) { - case OperatingSystem.MacOS: - return enumeratePowerShellInstallationPathsForMacOS(); + if (options.knownToExist) { + this.knownToExist = options.knownToExist; + } + + options.is32Bit = !!options.is32Bit; + } + } - case OperatingSystem.Linux: - return enumeratePowerShellInstallationPathsForLinux(); + abstract get version(): string; - default: - return []; + get is32Bit(): boolean { + return this.is32BitExe; + } + + get exePath(): string { + return this.pathToExe; + } + + get exists(): boolean { + if (this.knownToExist === undefined) { + this.knownToExist = fs.existsSync(this.exePath); + } + return this.knownToExist; + } + + get displayName(): string { + return this.installationName; } } -function *enumeratePowerShellInstallationPathsForWindows( - platformDetails: IPlatformDetails, - use32Bit: boolean): Iterable { - let psCoreInstallDirPath: string; +class WinPSExe extends PossiblePowerShellExe { + private psVersion: string; - if (use32Bit) { - psCoreInstallDirPath = platformDetails.isProcess64Bit - ? process.env["ProgramFiles(x86)"] - : process.env.ProgramFiles; - } else { - psCoreInstallDirPath = platformDetails.isProcess64Bit - ? process.env.ProgramFiles - : process.env.ProgramW6432; + get version(): string { + if (!this.psVersion) { + this.psVersion = child_process.execFileSync(this.exePath, ["-c", "$PSVersionTable.PSVersion.ToString()"]); + } + return this.psVersion; + } +} + +class PSCoreExe extends PossiblePowerShellExe { + private psVersion: string; + + get version(): string { + if (!this.psVersion) { + this.psVersion = child_process.execFileSync(this.exePath, ["-v"]); + } + return this.psVersion; + } +} + +class Lazy { + private readonly factory: () => T; + private constructed: boolean; + private underlyingValue: T; + + constructor(factory: () => T) { + this.constructed = false; + this.underlyingValue = undefined; + this.factory = factory; + } + + public get value() { + if (!this.constructed) { + this.constructed = true; + this.underlyingValue = this.factory(); } + return this.underlyingValue; + } +} - psCoreInstallDirPath = path.join(psCoreInstallDirPath, "PowerShell"); +class PowerShellExeFinder { + private readonly platformDetails: IPlatformDetails; + + // PowerShell 6+ installation + private stablePwshExeVal: Lazy; + private previewPwshExeVal: Lazy; + + // 32-bit PowerShell 6+ installation + private stable32BitPwshExeVal: Lazy; + private preview32BitPwshExeVal: Lazy; + + // .NET Global Tool pwsh installation + private dotnetGlobalToolExeVal: Lazy; + + // MSIX/UWP installation + private msixExeVal: Lazy; + + // Snap pwsh installations on Linux + private stableSnapExeVal: Lazy; + private previewSnapExeVal: Lazy; + + // Windows PowerShell installations + private sys32WinPSExeVal: Lazy; + private sysWow64WinPSExeVal: Lazy; + private sysNativeWinPSExeVal: Lazy; + + constructor(platformDetails: IPlatformDetails) { + this.platformDetails = platformDetails; + + this.stablePwshExeVal = new Lazy(this.findPSCoreStable); + this.stable32BitPwshExeVal = new Lazy(this.findPSCore32BitStable); + this.preview32BitPwshExeVal = new Lazy(this.findPSCore32BitPreview); + this.previewPwshExeVal = new Lazy(this.findPSCorePreview); + this.dotnetGlobalToolExeVal = new Lazy(this.findPSCoreDotnetGlobalTool); + this.msixExeVal = new Lazy(this.findPSCoreMsix); + this.stableSnapExeVal = new Lazy(this.findPSCoreStableSnap); + this.previewSnapExeVal = new Lazy(this.findPSCorePreviewSnap); + this.sys32WinPSExeVal = new Lazy(this.findSys32WinPS); + this.sysWow64WinPSExeVal = new Lazy(this.findSysWow64WinPS); + this.sysNativeWinPSExeVal = new Lazy(this.findSysNativeWinPS); + } + + public *enumeratePowerShellInstallations(): Iterable { + // Find PSCore stable first + if (this.pwshStable) { + yield this.pwshStable; + } - // Search for PS 6/7 paths - // These look like "%ProgramFiles%\PowerShell\\pwsh.exe" - if (fs.existsSync(psCoreInstallDirPath) && fs.lstatSync(psCoreInstallDirPath).isDirectory()) { - for (const item of fs.readdirSync(psCoreInstallDirPath)) { - if (parseInt(item, 10)) { - yield path.join(psCoreInstallDirPath, item, "pwsh.exe"); + switch (this.platformDetails.operatingSystem) { + case OperatingSystem.Windows: + // Windows may have a 32-bit pwsh.exe + if (this.pwsh32Stable) { + yield this.pwsh32Stable; + } + // Also look for the MSIX/UWP installation + yield this.pwshMsix; + break; + + case OperatingSystem.Linux: + // On Linux, find the snap + yield this.pwshSnapStable; + break; + } + + // Look for the .NET global tool + yield this.pwshDotnetGlobalTool; + + // Look for PSCore preview + if (this.pwshPreview) { + yield this.pwshPreview; + } + + switch (this.platformDetails.operatingSystem) { + // On Linux, there might be a preview snap + case OperatingSystem.Linux: + yield this.pwshSnapPreview; + break; + + // Finally, on Windows, get Windows PowerShell + case OperatingSystem.Windows: + if (this.pwsh32Preview) { + yield this.pwsh32Preview; + } + // Get the natural Windows PowerShell for the process bitness + yield this.sys32WinPS; + + if (this.platformDetails.isProcess64Bit) { + // If this is a 64-bit process (which must be on a 64-bit OS), + // look for a 32-bit in SysWoW WinPS as well + yield this.sysWoW64WinPS; + } else if (this.platformDetails.isOS64Bit) { + // If this is a 32-bit process on a 64-bit operating system, + // look for the the system-native 64-bit WinPS too + yield this.sysnativeWinPS; + } + break; + } + } + + private get pwshStable(): IPossiblePowerShellExe { + return this.stablePwshExeVal.value; + } + + private get pwshPreview(): IPossiblePowerShellExe { + return this.previewPwshExeVal.value; + } + + private get pwsh32Stable(): IPossiblePowerShellExe { + return this.stable32BitPwshExeVal.value; + } + + private get pwsh32Preview(): IPossiblePowerShellExe { + return this.preview32BitPwshExeVal.value; + } + + private get pwshMsix(): IPossiblePowerShellExe { + return this.msixExeVal.value; + } + + private get pwshSnapStable(): IPossiblePowerShellExe { + return this.stableSnapExeVal.value; + } + + private get pwshSnapPreview(): IPossiblePowerShellExe { + return this.previewSnapExeVal.value; + } + + private get pwshDotnetGlobalTool(): IPossiblePowerShellExe { + return this.dotnetGlobalToolExeVal.value; + } + + private get sys32WinPS(): IPossiblePowerShellExe { + return this.sys32WinPSExeVal.value; + } + + private get sysWoW64WinPS(): IPossiblePowerShellExe { + return this.sysWow64WinPSExeVal.value; + } + + private get sysnativeWinPS(): IPossiblePowerShellExe { + return this.sysNativeWinPSExeVal.value; + } + + private findPSCoreStable(): IPossiblePowerShellExe { + switch (this.platformDetails.operatingSystem) { + case OperatingSystem.Linux: + return new PSCoreExe(LinuxExePath, "PowerShell"); + + case OperatingSystem.MacOS: + return new PSCoreExe(MacOSExePath, "PowerShell"); + + case OperatingSystem.Windows: + return this.findPSCoreWindows(); + } + } + + private findPSCorePreview(): IPossiblePowerShellExe { + switch (this.platformDetails.operatingSystem) { + case OperatingSystem.Linux: + return new PSCoreExe(LinuxPreviewExePath, "PowerShell Preview"); + + case OperatingSystem.MacOS: + return new PSCoreExe(MacOSPreviewExePath, "PowerShell Preview"); + + case OperatingSystem.Windows: + return this.findPSCoreWindows({ findPreview: true }); + } + } + + private findPSCore32BitStable(): IPossiblePowerShellExe { + return this.findPSCoreWindows({ use32Bit: true }); + } + + private findPSCore32BitPreview(): IPossiblePowerShellExe { + return this.findPSCoreWindows({ use32Bit: true }); + } + + // Search for PS 6/7 under "%ProgramFiles%\PowerShell\[-preview]\pwsh.exe" + private findPSCoreWindows(options?: { use32Bit?: boolean, findPreview?: boolean }): IPossiblePowerShellExe { + + const use32Bit: boolean = options && options.use32Bit; + + const programFilesPath: string = use32Bit + ? process.env["ProgramFiles(x86)"] + : process.env.ProgramFiles; + + // Path to "%ProgramFiles%\PowerShell" + const psCoreInstallDirPath: string = path.join(programFilesPath, "PowerShell"); + + // Make sure it exists and is a directory + if (!(fs.existsSync(psCoreInstallDirPath) && fs.lstatSync(psCoreInstallDirPath).isDirectory())) { + return undefined; + } + + // Look for folders that match the number[-preview] scheme + for (const item of fs.readdirSync(psCoreInstallDirPath)) { + if (options && options.findPreview) { + // Look for something like "7-preview" + const dashIndex = item.indexOf("-"); + if (dashIndex <= 0 || !parseInt(item.substring(0, dashIndex), 10) || item.substring(dashIndex + 1) !== "preview") { + continue; + } + } else { + // Look for something like "6" + if (!parseInt(item, 10)) { + continue; } } + + const exePath = path.join(psCoreInstallDirPath, item, "pwsh.exe"); + return new PSCoreExe(exePath, "PowerShell", { is32Bit: use32Bit }); } - // Now try the MSIX path - yield getPowerShellMsixPath(platformDetails, use32Bit); + // This method may not find any installation + // Callers should be aware of this + return undefined; + } + + private findPSCoreDotnetGlobalTool(): IPossiblePowerShellExe { + const exeName: string = this.platformDetails.operatingSystem === OperatingSystem.Windows + ? "pwsh.exe" + : "pwsh"; - // Now try the .NET global tool pwsh.exe - yield dotnetGlobalToolExePath; + const dotnetGlobalToolExePath: string = path.join(os.homedir(), ".dotnet", "tools", exeName); - // Finally find Windows PowerShell, which should always succeed (so don't look for pwsh-preview.exe) - yield getWindowsPowerShellPath(platformDetails, use32Bit); -} + return new PSCoreExe(dotnetGlobalToolExePath, ".NET Core PowerShell Global Tool"); + } -function *enumeratePowerShellInstallationPathsForMacOS(): Iterable { - yield macOSExePath; - yield dotnetGlobalToolExePath; - yield macOSPreviewExePath; -} + private findPSCoreMsix(): IPossiblePowerShellExe { + const winPSPath: string = this.findSys32WinPS().exePath; + const msixDir: string = child_process.execFileSync(winPSPath, ["-c", "(Get-AppxPackage -Name Microsoft.PowerShell).InstallLocation"]) + .toString("utf8") + .trim(); -function *enumeratePowerShellInstallationPathsForLinux(): Iterable { - yield linuxExePath; - yield snapExePath; - yield dotnetGlobalToolExePath; - yield linuxPreviewExePath; - yield snapPreviewExePath; -} + const msixExePath = path.join(msixDir, "pwsh.exe"); + return new PSCoreExe(msixExePath, "PowerShell MSIX"); + } -function getWindowsPowerShellPath(platformDetails: IPlatformDetails, use32Bit: boolean): string { - if (use32Bit) { - return platformDetails.isProcess64Bit && platformDetails.isOS64Bit - ? powerShell32BitPathOn64Bit - : System32PowerShellPath; + private findPSCoreStableSnap(): IPossiblePowerShellExe { + return new PSCoreExe(SnapExePath, "PowerShell Snap"); } - return platformDetails.isProcess64Bit && platformDetails.isOS64Bit - ? System32PowerShellPath - : powerShell64BitPathOn32Bit; -} + private findPSCorePreviewSnap(): IPossiblePowerShellExe { + return new PSCoreExe(SnapExePath, "PowerShell Preview Snap"); + } + + private findSys32WinPS(): IPossiblePowerShellExe { + const displayName: string = this.platformDetails.isOS64Bit + ? WindowsPowerShell64BitLabel + : WindowsPowerShell32BitLabel; + + return new WinPSExe( + System32PowerShellPath, + displayName, + { knownToExist: true, is32Bit: !this.platformDetails.isOS64Bit }); + } -function getPowerShellMsixPath(platformDetails: IPlatformDetails, use32Bit: boolean): string { - const winPSPath = getWindowsPowerShellPath(platformDetails, use32Bit); - const msixDir = child_process.execSync(`"${winPSPath}" -Command "(Get-AppxPackage -Name Microsoft.PowerShell).InstallLocation"`) - .toString("utf8") - .trim(); + private findSysWow64WinPS(): IPossiblePowerShellExe { + return new WinPSExe(WinPS32BitPathOn64Bit, WindowsPowerShell64BitLabel); + } - return path.join(msixDir, "pwsh.exe"); + private findSysNativeWinPS(): IPossiblePowerShellExe { + return new WinPSExe( + WinPS64BitPathOn32Bit, + WindowsPowerShell32BitLabel, + { knownToExist: true, is32Bit: true }); + } } diff --git a/src/session.ts b/src/session.ts index 82b6337d6d..94c27c9ead 100644 --- a/src/session.ts +++ b/src/session.ts @@ -261,7 +261,7 @@ export class SessionManager implements Middleware { if ((this.sessionStatus === SessionStatus.NeverStarted) && this.sessionSettings.powerShellDefaultVersion) { const powerShellExePaths = getAvailablePowerShellExes(this.platformDetails, this.sessionSettings); const powerShellDefaultVersion = - powerShellExePaths.find((item) => item.versionName === this.sessionSettings.powerShellDefaultVersion); + powerShellExePaths.find((item) => item.displayName === this.sessionSettings.powerShellDefaultVersion); if (powerShellDefaultVersion) { powerShellExePath = powerShellDefaultVersion.exePath; @@ -751,7 +751,7 @@ export class SessionManager implements Middleware { const powerShellSessionName = currentPowerShellExe ? - currentPowerShellExe.versionName : + currentPowerShellExe.displayName : `PowerShell ${this.versionDetails.displayVersion} ` + `(${this.versionDetails.architecture}) ${this.versionDetails.edition} Edition ` + `[${this.versionDetails.version}]`; diff --git a/test/platform.test.ts b/test/platform.test.ts index 398ca7560b..dcd5128f00 100644 --- a/test/platform.test.ts +++ b/test/platform.test.ts @@ -52,11 +52,11 @@ suite("Platform module", () => { platformDetails, [ { - versionName: platform.WindowsPowerShell64BitLabel, + displayName: platform.WindowsPowerShell64BitLabel, exePath: platform.System32PowerShellPath, }, { - versionName: platform.WindowsPowerShell32BitLabel, + displayName: platform.WindowsPowerShell32BitLabel, exePath: platform.SysWow64PowerShellPath, }, ]); @@ -82,11 +82,11 @@ suite("Platform module", () => { platformDetails, [ { - versionName: platform.WindowsPowerShell64BitLabel, + displayName: platform.WindowsPowerShell64BitLabel, exePath: platform.SysnativePowerShellPath, }, { - versionName: platform.WindowsPowerShell32BitLabel, + displayName: platform.WindowsPowerShell32BitLabel, exePath: platform.System32PowerShellPath, }, ]); @@ -112,7 +112,7 @@ suite("Platform module", () => { platformDetails, [ { - versionName: platform.WindowsPowerShell32BitLabel, + displayName: platform.WindowsPowerShell32BitLabel, exePath: platform.System32PowerShellPath, }, ]); From 190a9ff3e4d81b010e9ced120c66c92454ee1c4b Mon Sep 17 00:00:00 2001 From: Rob Holt Date: Wed, 9 Oct 2019 15:07:57 -0700 Subject: [PATCH 03/46] Hook up new PowerShell searcher --- src/platform.ts | 435 +++++++++++++++++------------------------- src/session.ts | 198 +++++++++---------- test/platform.test.ts | 20 +- 3 files changed, 292 insertions(+), 361 deletions(-) diff --git a/src/platform.ts b/src/platform.ts index e94262e903..fa61fb7231 100644 --- a/src/platform.ts +++ b/src/platform.ts @@ -8,7 +8,6 @@ import * as os from "os"; import * as path from "path"; import * as process from "process"; import * as Settings from "./settings"; -import { runInThisContext } from "vm"; export const System32PowerShellPath = getWindowsSystemPowerShellPath("System32"); export const SysnativePowerShellPath = getWindowsSystemPowerShellPath("Sysnative"); @@ -45,7 +44,11 @@ export interface IPlatformDetails { export interface IPowerShellExeDetails { readonly displayName: string; readonly exePath: string; - readonly version: string; +} + +export interface IPossiblePowerShellExe extends IPowerShellExeDetails { + readonly exists: boolean; + readonly is32Bit: boolean; } export function getPlatformDetails(): IPlatformDetails { @@ -77,232 +80,7 @@ export class PowerShellNotFoundError extends Error { } } -/** - * Gets the default instance of PowerShell for the specified platform, - * depending on the operating system and the bitness required. - * If a stable PowerShell Core installation can be found, this will be preferred. - * @param platformDetails Specifies information about the platform - primarily the operating system. - * @param use32Bit On Windows, this boolean determines whether the 32-bit version of Windows PowerShell is returned. - * @returns A string containing the path of the default version of PowerShell. - */ -export function getDefaultPowerShellPath( - platformDetails: IPlatformDetails, - use32Bit: boolean = false): string | null { - - let pwshPath: string; - for (pwshPath of enumeratePowerShellInstallationPaths(platformDetails, use32Bit)) { - // TODO: check if the file is executable (complicated stat logic...) - if (fs.existsSync(pwshPath)) { - return pwshPath; - } - } - - throw new PowerShellNotFoundError(pwshPath); -} - -export function getWindowsSystemPowerShellPath(systemFolderName: string) { - return `${process.env.windir}\\${systemFolderName}\\WindowsPowerShell\\v1.0\\powershell.exe`; -} - -export function fixWindowsPowerShellPath(powerShellExePath: string, platformDetails: IPlatformDetails): string { - const lowerCasedPath = powerShellExePath.toLocaleLowerCase(); - - if ((platformDetails.isProcess64Bit && (lowerCasedPath === WinPS64BitPathOn32Bit)) || - (!platformDetails.isProcess64Bit && (lowerCasedPath === WinPS32BitPathOn64Bit))) { - return System32PowerShellPath; - } - - // If the path doesn't need to be fixed, return the original - return powerShellExePath; -} - -/** - * Gets a list of all available PowerShell instance on the specified platform. - * @param platformDetails Specifies information about the platform - primarily the operating system. - * @param sessionSettings Specifies the user/workspace settings. Additional PowerShell exe paths loaded from settings. - * @returns An array of IPowerShellExeDetails objects with the PowerShell name & exe path for each instance found. - */ -export function getAvailablePowerShellExes( - platformDetails: IPlatformDetails, - sessionSettings: Settings.ISettings | undefined): IPowerShellExeDetails[] { - - let paths: IPowerShellExeDetails[] = []; - - if (platformDetails.operatingSystem === OperatingSystem.Windows) { - if (platformDetails.isProcess64Bit) { - paths.push({ - displayName: WindowsPowerShell64BitLabel, - exePath: System32PowerShellPath, - }); - - paths.push({ - displayName: WindowsPowerShell32BitLabel, - exePath: SysWow64PowerShellPath, - }); - } else { - if (platformDetails.isOS64Bit) { - paths.push({ - displayName: WindowsPowerShell64BitLabel, - exePath: SysnativePowerShellPath, - }); - } - - paths.push({ - displayName: WindowsPowerShell32BitLabel, - exePath: System32PowerShellPath, - }); - } - - const psCoreInstallPath = - (!platformDetails.isProcess64Bit ? process.env.ProgramW6432 : process.env.ProgramFiles) + "\\PowerShell"; - - if (fs.existsSync(psCoreInstallPath)) { - const arch = platformDetails.isProcess64Bit ? "(x64)" : "(x86)"; - const psCorePaths = - fs.readdirSync(psCoreInstallPath) - .map((item) => path.join(psCoreInstallPath, item)) - .filter((item) => { - const exePath = path.join(item, "pwsh.exe"); - return fs.lstatSync(item).isDirectory() && fs.existsSync(exePath); - }) - .map((item) => ({ - versionName: `PowerShell ${path.parse(item).base} ${arch}`, - exePath: path.join(item, "pwsh.exe"), - })); - - if (psCorePaths) { - paths = paths.concat(psCorePaths); - } - } - } else { - // Handle Linux and macOS case - let exePaths: string[]; - - if (platformDetails.operatingSystem === OperatingSystem.Linux) { - exePaths = [ LinuxExePath, SnapExePath, LinuxPreviewExePath, SnapPreviewExePath ]; - } else { - exePaths = [ MacOSExePath, MacOSPreviewExePath ]; - } - - exePaths.forEach((exePath) => { - if (fs.existsSync(exePath)) { - paths.push({ - displayName: "PowerShell" + (/-preview/.test(exePath) ? " Preview" : ""), - exePath, - }); - } - }); - } - - // When unit testing, we don't have session settings available to test, so skip reading this setting - if (sessionSettings) { - // Add additional PowerShell paths as configured in settings - for (const additionalPowerShellExePath of sessionSettings.powerShellAdditionalExePaths) { - paths.push({ - displayName: additionalPowerShellExePath.versionName, - exePath: additionalPowerShellExePath.exePath, - }); - } - } - - return paths; -} - -interface IPossiblePowerShellExe extends IPowerShellExeDetails { - readonly exists: boolean; - readonly is32Bit: boolean; -} - -abstract class PossiblePowerShellExe implements IPossiblePowerShellExe { - private readonly pathToExe: string; - private readonly installationName: string; - private readonly is32BitExe: boolean; - - private knownToExist: boolean = undefined; - - constructor( - pathToExe: string, - installationName: string, - options?: { knownToExist?: boolean, is32Bit?: boolean }) { - - this.pathToExe = pathToExe; - this.installationName = installationName; - - if (options) { - - if (options.knownToExist) { - this.knownToExist = options.knownToExist; - } - - options.is32Bit = !!options.is32Bit; - } - } - - abstract get version(): string; - - get is32Bit(): boolean { - return this.is32BitExe; - } - - get exePath(): string { - return this.pathToExe; - } - - get exists(): boolean { - if (this.knownToExist === undefined) { - this.knownToExist = fs.existsSync(this.exePath); - } - return this.knownToExist; - } - - get displayName(): string { - return this.installationName; - } -} - -class WinPSExe extends PossiblePowerShellExe { - private psVersion: string; - - get version(): string { - if (!this.psVersion) { - this.psVersion = child_process.execFileSync(this.exePath, ["-c", "$PSVersionTable.PSVersion.ToString()"]); - } - return this.psVersion; - } -} - -class PSCoreExe extends PossiblePowerShellExe { - private psVersion: string; - - get version(): string { - if (!this.psVersion) { - this.psVersion = child_process.execFileSync(this.exePath, ["-v"]); - } - return this.psVersion; - } -} - -class Lazy { - private readonly factory: () => T; - private constructed: boolean; - private underlyingValue: T; - - constructor(factory: () => T) { - this.constructed = false; - this.underlyingValue = undefined; - this.factory = factory; - } - - public get value() { - if (!this.constructed) { - this.constructed = true; - this.underlyingValue = this.factory(); - } - return this.underlyingValue; - } -} - -class PowerShellExeFinder { +export class PowerShellExeFinder { private readonly platformDetails: IPlatformDetails; // PowerShell 6+ installation @@ -328,23 +106,43 @@ class PowerShellExeFinder { private sysWow64WinPSExeVal: Lazy; private sysNativeWinPSExeVal: Lazy; - constructor(platformDetails: IPlatformDetails) { + private additionalPSExeSettings: Iterable; + + constructor( + platformDetails: IPlatformDetails, + additionalPowerShellExes?: Iterable) { + this.platformDetails = platformDetails; + this.additionalPSExeSettings = additionalPowerShellExes || []; + + this.stablePwshExeVal = new Lazy(() => this.findPSCoreStable()); + this.stable32BitPwshExeVal = new Lazy(() => this.findPSCore32BitStable()); + this.preview32BitPwshExeVal = new Lazy(() => this.findPSCore32BitPreview()); + this.previewPwshExeVal = new Lazy(() => this.findPSCorePreview()); + this.dotnetGlobalToolExeVal = new Lazy(() => this.findPSCoreDotnetGlobalTool()); + this.msixExeVal = new Lazy(() => this.findPSCoreMsix()); + this.stableSnapExeVal = new Lazy(() => this.findPSCoreStableSnap()); + this.previewSnapExeVal = new Lazy(() => this.findPSCorePreviewSnap()); + this.sys32WinPSExeVal = new Lazy(() => this.findSys32WinPS()); + this.sysWow64WinPSExeVal = new Lazy(() => this.findSysWow64WinPS()); + this.sysNativeWinPSExeVal = new Lazy(() => this.findSysNativeWinPS()); + } + + public *enumeratePowerShellInstallations(): Iterable { + for (const defaultPwsh of this.enumerateDefaultPowerShellInstallations()) { + if (defaultPwsh && defaultPwsh.exists) { + yield defaultPwsh; + } + } - this.stablePwshExeVal = new Lazy(this.findPSCoreStable); - this.stable32BitPwshExeVal = new Lazy(this.findPSCore32BitStable); - this.preview32BitPwshExeVal = new Lazy(this.findPSCore32BitPreview); - this.previewPwshExeVal = new Lazy(this.findPSCorePreview); - this.dotnetGlobalToolExeVal = new Lazy(this.findPSCoreDotnetGlobalTool); - this.msixExeVal = new Lazy(this.findPSCoreMsix); - this.stableSnapExeVal = new Lazy(this.findPSCoreStableSnap); - this.previewSnapExeVal = new Lazy(this.findPSCorePreviewSnap); - this.sys32WinPSExeVal = new Lazy(this.findSys32WinPS); - this.sysWow64WinPSExeVal = new Lazy(this.findSysWow64WinPS); - this.sysNativeWinPSExeVal = new Lazy(this.findSysNativeWinPS); + for (const additionalPwsh of this.enumerateAdditionalPowerShellInstallations()) { + if (additionalPwsh && additionalPwsh.exists) { + yield additionalPwsh; + } + } } - public *enumeratePowerShellInstallations(): Iterable { + public *enumerateDefaultPowerShellInstallations(): Iterable { // Find PSCore stable first if (this.pwshStable) { yield this.pwshStable; @@ -401,57 +199,69 @@ class PowerShellExeFinder { } } - private get pwshStable(): IPossiblePowerShellExe { + public *enumerateAdditionalPowerShellInstallations(): Iterable { + for (const additionalPwshSetting of this.additionalPSExeSettings) { + yield this.findAdditionalPwshExe(additionalPwshSetting); + } + } + + public get pwshStable(): IPossiblePowerShellExe { return this.stablePwshExeVal.value; } - private get pwshPreview(): IPossiblePowerShellExe { + public get pwshPreview(): IPossiblePowerShellExe { return this.previewPwshExeVal.value; } - private get pwsh32Stable(): IPossiblePowerShellExe { + public get pwsh32Stable(): IPossiblePowerShellExe { return this.stable32BitPwshExeVal.value; } - private get pwsh32Preview(): IPossiblePowerShellExe { + public get pwsh32Preview(): IPossiblePowerShellExe { return this.preview32BitPwshExeVal.value; } - private get pwshMsix(): IPossiblePowerShellExe { + public get pwshMsix(): IPossiblePowerShellExe { return this.msixExeVal.value; } - private get pwshSnapStable(): IPossiblePowerShellExe { + public get pwshSnapStable(): IPossiblePowerShellExe { return this.stableSnapExeVal.value; } - private get pwshSnapPreview(): IPossiblePowerShellExe { + public get pwshSnapPreview(): IPossiblePowerShellExe { return this.previewSnapExeVal.value; } - private get pwshDotnetGlobalTool(): IPossiblePowerShellExe { + public get pwshDotnetGlobalTool(): IPossiblePowerShellExe { return this.dotnetGlobalToolExeVal.value; } - private get sys32WinPS(): IPossiblePowerShellExe { + public get sys32WinPS(): IPossiblePowerShellExe { return this.sys32WinPSExeVal.value; } - private get sysWoW64WinPS(): IPossiblePowerShellExe { + public get sysWoW64WinPS(): IPossiblePowerShellExe { return this.sysWow64WinPSExeVal.value; } - private get sysnativeWinPS(): IPossiblePowerShellExe { + public get sysnativeWinPS(): IPossiblePowerShellExe { return this.sysNativeWinPSExeVal.value; } + private findAdditionalPwshExe(additionalPwshSetting: Settings.IPowerShellAdditionalExePathSettings) { + return new PSCoreExe( + additionalPwshSetting.exePath, + additionalPwshSetting.versionName); + } + private findPSCoreStable(): IPossiblePowerShellExe { switch (this.platformDetails.operatingSystem) { case OperatingSystem.Linux: - return new PSCoreExe(LinuxExePath, "PowerShell"); + return new PSCoreExe(LinuxExePath, "PowerShell (x64)"); case OperatingSystem.MacOS: - return new PSCoreExe(MacOSExePath, "PowerShell"); + return new PSCoreExe(MacOSExePath, "PowerShell (x64)"); case OperatingSystem.Windows: return this.findPSCoreWindows(); @@ -461,10 +271,10 @@ class PowerShellExeFinder { private findPSCorePreview(): IPossiblePowerShellExe { switch (this.platformDetails.operatingSystem) { case OperatingSystem.Linux: - return new PSCoreExe(LinuxPreviewExePath, "PowerShell Preview"); + return new PSCoreExe(LinuxPreviewExePath, "PowerShell Preview (x64)"); case OperatingSystem.MacOS: - return new PSCoreExe(MacOSPreviewExePath, "PowerShell Preview"); + return new PSCoreExe(MacOSPreviewExePath, "PowerShell Preview (x64)"); case OperatingSystem.Windows: return this.findPSCoreWindows({ findPreview: true }); @@ -511,8 +321,12 @@ class PowerShellExeFinder { } } + const pwshName = use32Bit + ? "PowerShell (x86)" + : "PowerShell (x64)"; + const exePath = path.join(psCoreInstallDirPath, item, "pwsh.exe"); - return new PSCoreExe(exePath, "PowerShell", { is32Bit: use32Bit }); + return new PSCoreExe(exePath, pwshName, { is32Bit: use32Bit }); } // This method may not find any installation @@ -533,7 +347,7 @@ class PowerShellExeFinder { private findPSCoreMsix(): IPossiblePowerShellExe { const winPSPath: string = this.findSys32WinPS().exePath; const msixDir: string = child_process.execFileSync(winPSPath, ["-c", "(Get-AppxPackage -Name Microsoft.PowerShell).InstallLocation"]) - .toString("utf8") + .toString() .trim(); const msixExePath = path.join(msixDir, "pwsh.exe"); @@ -570,3 +384,108 @@ class PowerShellExeFinder { { knownToExist: true, is32Bit: true }); } } + +export function getWindowsSystemPowerShellPath(systemFolderName: string) { + return `${process.env.windir}\\${systemFolderName}\\WindowsPowerShell\\v1.0\\powershell.exe`; +} + +export function fixWindowsPowerShellPath(powerShellExePath: string, platformDetails: IPlatformDetails): string { + const lowerCasedPath = powerShellExePath.toLocaleLowerCase(); + + if ((platformDetails.isProcess64Bit && (lowerCasedPath === WinPS64BitPathOn32Bit)) || + (!platformDetails.isProcess64Bit && (lowerCasedPath === WinPS32BitPathOn64Bit))) { + return System32PowerShellPath; + } + + // If the path doesn't need to be fixed, return the original + return powerShellExePath; +} + +abstract class PossiblePowerShellExe implements IPossiblePowerShellExe { + private readonly pathToExe: string; + private readonly installationName: string; + private readonly is32BitExe: boolean; + + private knownToExist: boolean = undefined; + + constructor( + pathToExe: string, + installationName: string, + options?: { knownToExist?: boolean, is32Bit?: boolean }) { + + this.pathToExe = pathToExe; + this.installationName = installationName; + + if (options) { + + if (options.knownToExist) { + this.knownToExist = options.knownToExist; + } + + options.is32Bit = !!options.is32Bit; + } + } + + abstract get version(): string; + + get is32Bit(): boolean { + return this.is32BitExe; + } + + get exePath(): string { + return this.pathToExe; + } + + get exists(): boolean { + if (this.knownToExist === undefined) { + this.knownToExist = fs.existsSync(this.exePath); + } + return this.knownToExist; + } + + get displayName(): string { + return this.installationName; + } +} + +class WinPSExe extends PossiblePowerShellExe { + private psVersion: string; + + get version(): string { + if (!this.psVersion) { + this.psVersion = child_process.execFileSync(this.exePath, ["-c", "$PSVersionTable.PSVersion.ToString()"]); + } + return this.psVersion; + } +} + +class PSCoreExe extends PossiblePowerShellExe { + private psVersion: string; + + get version(): string { + if (!this.psVersion) { + this.psVersion = child_process.execFileSync(this.exePath, ["-v"]); + } + return this.psVersion; + } +} + +class Lazy { + private readonly factory: () => T; + private constructed: boolean; + private underlyingValue: T; + + constructor(factory: () => T) { + this.constructed = false; + this.underlyingValue = undefined; + this.factory = factory; + } + + public get value() { + if (!this.constructed) { + this.constructed = true; + this.underlyingValue = this.factory(); + } + return this.underlyingValue; + } +} diff --git a/src/session.ts b/src/session.ts index 94c27c9ead..9ee6519eab 100644 --- a/src/session.ts +++ b/src/session.ts @@ -24,8 +24,8 @@ import { import { GitHubReleaseInformation, InvokePowerShellUpdateCheck } from "./features/UpdatePowerShell"; import { - fixWindowsPowerShellPath, getAvailablePowerShellExes, getDefaultPowerShellPath, - getPlatformDetails, IPlatformDetails, OperatingSystem } from "./platform"; + fixWindowsPowerShellPath, getPlatformDetails, IPlatformDetails, + OperatingSystem, PowerShellExeFinder } from "./platform"; export enum SessionStatus { NeverStarted, @@ -57,6 +57,9 @@ export class SessionManager implements Middleware { private bundledModulesPath: string; private telemetryReporter: TelemetryReporter; + // Initialized by the start() method, since this requires settings + private powershellExeFinder: PowerShellExeFinder; + // When in development mode, VS Code's session ID is a fake // value of "someValue.machineId". Use that to detect dev // mode for now until Microsoft/vscode#10272 gets implemented. @@ -71,6 +74,7 @@ export class SessionManager implements Middleware { private reporter: TelemetryReporter) { this.platformDetails = getPlatformDetails(); + this.HostVersion = version; this.telemetryReporter = reporter; @@ -104,8 +108,14 @@ export class SessionManager implements Middleware { public start() { this.sessionSettings = Settings.load(); + this.log.startNewLog(this.sessionSettings.developer.editorServicesLogLevel); + // Create the PowerShell executable finder now + this.powershellExeFinder = new PowerShellExeFinder( + this.platformDetails, + this.sessionSettings.powerShellAdditionalExePaths); + this.focusConsoleOnExecute = this.sessionSettings.integratedConsole.focusConsoleOnExecute; this.createStatusBarItem(); @@ -221,62 +231,16 @@ export class SessionManager implements Middleware { } public getPowerShellExePath(): string { - let powerShellExePath: string; - if (!this.sessionSettings.powerShellExePath && this.sessionSettings.developer.powerShellExePath) { // Show deprecation message with fix action. // We don't need to wait on this to complete // because we can finish gathering the configured // PowerShell path without the fix - vscode - .window - .showWarningMessage( - "The 'powershell.developer.powerShellExePath' setting is deprecated, use " + - "'powershell.powerShellExePath' instead.", - "Fix Automatically") - .then((choice) => { - if (choice) { - this.suppressRestartPrompt = true; - Settings - .change( - "powerShellExePath", - this.sessionSettings.developer.powerShellExePath, - true) - .then(() => { - return Settings.change( - "developer.powerShellExePath", - undefined, - true); - }) - .then(() => { - this.suppressRestartPrompt = false; - }); - } - }); - } - - // If powershell.powerShellDefaultVersion specified, attempt to find the PowerShell exe path - // of the version specified by the setting. - if ((this.sessionStatus === SessionStatus.NeverStarted) && this.sessionSettings.powerShellDefaultVersion) { - const powerShellExePaths = getAvailablePowerShellExes(this.platformDetails, this.sessionSettings); - const powerShellDefaultVersion = - powerShellExePaths.find((item) => item.displayName === this.sessionSettings.powerShellDefaultVersion); - - if (powerShellDefaultVersion) { - powerShellExePath = powerShellDefaultVersion.exePath; - } else { - this.log.writeWarning( - `Could not find powerShellDefaultVersion: '${this.sessionSettings.powerShellDefaultVersion}'`); - } + this.showExePathSettingDeprecationWarning(); } - // Is there a setting override for the PowerShell path? - powerShellExePath = - (powerShellExePath || - this.sessionSettings.powerShellExePath || - this.sessionSettings.developer.powerShellExePath || - "").trim(); + let powerShellExePath: string = this.getConfiguredPowerShellExePath().trim(); // New versions of PS Core uninstall the previous version // so make sure the path stored in the settings exists. @@ -287,54 +251,33 @@ export class SessionManager implements Middleware { powerShellExePath = ""; } - if (this.platformDetails.operatingSystem === OperatingSystem.Windows && - powerShellExePath.length > 0) { - - // Check the path bitness - const fixedPath = - fixWindowsPowerShellPath( + if (powerShellExePath) { + if (this.platformDetails.operatingSystem === OperatingSystem.Windows) { + // Check the path bitness + const fixedPath = fixWindowsPowerShellPath( powerShellExePath, this.platformDetails); - if (fixedPath !== powerShellExePath) { - const bitness = this.platformDetails.isOS64Bit ? 64 : 32; - // Show deprecation message with fix action. - // We don't need to wait on this to complete - // because we can finish gathering the configured - // PowerShell path without the fix - vscode - .window - .showWarningMessage( - `The specified PowerShell path is incorrect for ${bitness}-bit VS Code, using '${fixedPath}' ` + - "instead.", - "Fix Setting Automatically") - .then((choice) => { - if (choice) { - this.suppressRestartPrompt = true; - Settings - .change( - "powerShellExePath", - this.sessionSettings.developer.powerShellExePath, - true) - .then(() => { - return Settings.change( - "developer.powerShellExePath", - undefined, - true); - }) - .then(() => { - this.suppressRestartPrompt = false; - }); - } - }); - - powerShellExePath = fixedPath; + if (fixedPath !== powerShellExePath) { + // Show deprecation message with fix action. + // We don't need to wait on this to complete + // because we can finish gathering the configured + // PowerShell path without the fix + this.showBitnessPathFixWarning(fixedPath); + powerShellExePath = fixedPath; + } } + + return this.resolvePowerShellPath(powerShellExePath); } - return powerShellExePath.length > 0 - ? this.resolvePowerShellPath(powerShellExePath) - : getDefaultPowerShellPath(this.platformDetails, this.sessionSettings.useX86Host); + for (const powerShellExe of this.powershellExeFinder.enumeratePowerShellInstallations()) { + // No need to resolve these paths, since the enumeration checks for existence + return powerShellExe.exePath; + } + + this.setSessionFailure("Unable to find PowerShell installation, see logs for more details"); + return null; } // ----- LanguageClient middleware methods ----- @@ -382,6 +325,66 @@ export class SessionManager implements Middleware { return resolvedCodeLens; } + private async showExePathSettingDeprecationWarning(): Promise { + const choice: string = await vscode.window.showWarningMessage( + "The 'powershell.developer.powerShellExePath' setting is deprecated, use " + + "'powershell.powerShellExePath' instead.", + "Fix Automatically"); + + if (!choice) { + return; + } + + this.suppressRestartPrompt = true; + await Settings.change("powerShellExePath", this.sessionSettings.developer.powerShellExePath, true); + await Settings.change("developer,powerShellExePath", undefined, true); + this.suppressRestartPrompt = false; + } + + private async showBitnessPathFixWarning(fixedPath: string): Promise { + const bitness = this.platformDetails.isOS64Bit ? 64 : 32; + + const choice = await vscode.window.showWarningMessage( + `The specified PowerShell path is incorrect for ${bitness}-bit VS Code, using '${fixedPath}' ` + + "instead.", + "Fix Setting Automatically"); + + if (!choice) { + return; + } + + this.suppressRestartPrompt = true; + await Settings.change("powerShellExePath", this.sessionSettings.developer.powerShellExePath, true); + await Settings.change("developer.powerShellExePath", undefined, true); + this.suppressRestartPrompt = false; + } + + private getConfiguredPowerShellExePath(): string { + // If powershell.powerShellDefaultVersion specified, attempt to find the PowerShell exe path + // of the version specified by the setting. + if (this.sessionSettings.powerShellDefaultVersion && this.sessionStatus === SessionStatus.NeverStarted) { + for (const pwshExe of this.powershellExeFinder.enumeratePowerShellInstallations()) { + if (pwshExe.displayName === this.sessionSettings.powerShellDefaultVersion) { + return pwshExe.exePath; + } + } + + // Default PowerShell version was configured but we didn't find it + this.log.writeWarning(`Could not find powerShellDefaultVersion: '${this.sessionSettings.powerShellDefaultVersion}'`); + } + + if (this.sessionSettings.powerShellExePath) { + return this.sessionSettings.powerShellExePath; + } + + if (this.sessionSettings.developer.powerShellExePath) { + this.showExePathSettingDeprecationWarning(); + return this.sessionSettings.developer.powerShellExePath; + } + + return ""; + } + private onConfigurationUpdated() { const settings = Settings.load(); @@ -691,8 +694,10 @@ export class SessionManager implements Middleware { // If the path does not exist, show an error if (!utils.checkIfFileExists(resolvedPath)) { - this.setSessionFailure( - "powershell.exe cannot be found or is not accessible at path " + resolvedPath); + const pwshPath = resolvedPath || powerShellExePath; + const pwshExeName = path.basename(pwshPath) || "PowerShell executable"; + + this.setSessionFailure(`${pwshExeName} cannot be found or is not accessible at path '${pwshPath}'`); return null; } @@ -734,8 +739,7 @@ export class SessionManager implements Middleware { private showSessionMenu() { const currentExePath = (this.powerShellExePath || "").toLowerCase(); - const availablePowerShellExes = - getAvailablePowerShellExes(this.platformDetails, this.sessionSettings); + const availablePowerShellExes = Array.from(this.powershellExeFinder.enumeratePowerShellInstallations()); let sessionText: string; @@ -772,7 +776,7 @@ export class SessionManager implements Middleware { .filter((item) => item.exePath.toLowerCase() !== currentExePath) .map((item) => { return new SessionMenuItem( - `Switch to: ${item.versionName}`, + `Switch to: ${item.displayName}`, () => { this.changePowerShellExePath(item.exePath); }); }); diff --git a/test/platform.test.ts b/test/platform.test.ts index dcd5128f00..51e0900998 100644 --- a/test/platform.test.ts +++ b/test/platform.test.ts @@ -6,23 +6,31 @@ import * as assert from "assert"; import * as platform from "../src/platform"; function checkDefaultPowerShellPath(platformDetails, expectedPath) { + const powerShellExeFinder = new platform.PowerShellExeFinder(platformDetails); test("returns correct default path", () => { - assert.equal( - platform.getDefaultPowerShellPath(platformDetails), - expectedPath); + let defaultPath: string; + for (const pwshExe of powerShellExeFinder.enumeratePowerShellInstallations()) { + defaultPath = pwshExe.exePath; + break; + } + assert.equal(defaultPath, expectedPath); }); } function checkAvailableWindowsPowerShellPaths( platformDetails: platform.IPlatformDetails, expectedPaths: platform.IPowerShellExeDetails[]) { + + const pwshExeFinder = new platform.PowerShellExeFinder(platformDetails); + test("correctly enumerates available Windows PowerShell paths", () => { // The system may return PowerShell Core paths so only // enumerate the first list items. - const enumeratedPaths = platform.getAvailablePowerShellExes(platformDetails, undefined); - for (let i; i < expectedPaths.length; i++) { - assert.equal(enumeratedPaths[i], expectedPaths[i]); + let i = 0; + for (const pwshExe of pwshExeFinder.enumeratePowerShellInstallations()) { + assert.equal(pwshExe.exePath, expectedPaths[i]); + i++; } }); } From 66961d4ba1028e4764736af1c53ad5f865ad463b Mon Sep 17 00:00:00 2001 From: Rob Holt Date: Wed, 9 Oct 2019 17:10:40 -0700 Subject: [PATCH 04/46] Add documentation, fix style --- src/platform.ts | 206 +++++++++++++++++++++++++++++++----------------- 1 file changed, 132 insertions(+), 74 deletions(-) diff --git a/src/platform.ts b/src/platform.ts index fa61fb7231..c16443ba33 100644 --- a/src/platform.ts +++ b/src/platform.ts @@ -48,7 +48,6 @@ export interface IPowerShellExeDetails { export interface IPossiblePowerShellExe extends IPowerShellExeDetails { readonly exists: boolean; - readonly is32Bit: boolean; } export function getPlatformDetails(): IPlatformDetails { @@ -80,6 +79,11 @@ export class PowerShellNotFoundError extends Error { } } +/** + * Class to lazily find installed PowerShell executables on a machine. + * When given a list of additional PowerShell executables, + * this will also surface those at the end of the list. + */ export class PowerShellExeFinder { private readonly platformDetails: IPlatformDetails; @@ -108,6 +112,11 @@ export class PowerShellExeFinder { private additionalPSExeSettings: Iterable; + /** + * Create a new PowerShellFinder object to discover PowerShell installations. + * @param platformDetails Information about the machine we are running on. + * @param additionalPowerShellExes Additional PowerShell installations as configured in the settings. + */ constructor( platformDetails: IPlatformDetails, additionalPowerShellExes?: Iterable) { @@ -128,13 +137,106 @@ export class PowerShellExeFinder { this.sysNativeWinPSExeVal = new Lazy(() => this.findSysNativeWinPS()); } + /** + * The stable PowerShell 6+ installation. + * May be undefined if the installation directory is not present. + */ + private get pwshStable(): IPossiblePowerShellExe { + return this.stablePwshExeVal.value; + } + + /** + * The preview PowerShell 6+ installation. + * May be undefined if the installation directory is not present. + */ + private get pwshPreview(): IPossiblePowerShellExe { + return this.previewPwshExeVal.value; + } + + /** + * The stable 32-bit PowerShell 6+ installation. + * May be undefined if the installation directory is not present + */ + private get pwsh32Stable(): IPossiblePowerShellExe { + return this.stable32BitPwshExeVal.value; + } + + /** + * The preview 32-bit PowerShell 6+ installation. + * May be undefined if the installation directory is not present. + */ + private get pwsh32Preview(): IPossiblePowerShellExe { + return this.preview32BitPwshExeVal.value; + } + + /** + * PowerShell 6+ installation from an MSIX (through the Windows store). + * May be undefined if the AppX package is not found. + */ + private get pwshMsix(): IPossiblePowerShellExe { + return this.msixExeVal.value; + } + + /** + * PowerShell 6+ stable Snap installation on Linux. + */ + private get pwshSnapStable(): IPossiblePowerShellExe { + return this.stableSnapExeVal.value; + } + + /** + * PowerShell 6+ preview Snap installation on Linux. + */ + private get pwshSnapPreview(): IPossiblePowerShellExe { + return this.previewSnapExeVal.value; + } + + /** + * PowerShell 6+ .NET Core global tool installation. + */ + private get pwshDotnetGlobalTool(): IPossiblePowerShellExe { + return this.dotnetGlobalToolExeVal.value; + } + + /** + * The Windows PowerShell installation under the %windir%\System32 folder. + * This always exists. + */ + private get sys32WinPS(): IPossiblePowerShellExe { + return this.sys32WinPSExeVal.value; + } + + /** + * The 32-bit Windows PowerShell installation when running in a 64-bit process. + */ + private get sysWoW64WinPS(): IPossiblePowerShellExe { + return this.sysWow64WinPSExeVal.value; + } + + /** + * The 64-bit Windows PowerShell installation when running + * in a 32-bit process on a 64-bit Windows. + */ + private get sysnativeWinPS(): IPossiblePowerShellExe { + return this.sysNativeWinPSExeVal.value; + } + + /** + * Iterates through PowerShell installations on the machine according + * to configuration passed in through the constructor. + * PowerShell items returned by this object are verified + * to exist on the filesystem. + */ public *enumeratePowerShellInstallations(): Iterable { + // Get the default PowerShell installations first for (const defaultPwsh of this.enumerateDefaultPowerShellInstallations()) { if (defaultPwsh && defaultPwsh.exists) { yield defaultPwsh; } } + // Also show any additionally configured PowerShells + // These may be duplicates of the default installations, but given a different name. for (const additionalPwsh of this.enumerateAdditionalPowerShellInstallations()) { if (additionalPwsh && additionalPwsh.exists) { yield additionalPwsh; @@ -142,7 +244,12 @@ export class PowerShellExeFinder { } } - public *enumerateDefaultPowerShellInstallations(): Iterable { + /** + * Iterates through all the possible well-known PowerShell installations on a machine. + * Returned values may not exist, but come with an .exists property + * which will check whether the executable exists. + */ + private *enumerateDefaultPowerShellInstallations(): Iterable { // Find PSCore stable first if (this.pwshStable) { yield this.pwshStable; @@ -155,7 +262,9 @@ export class PowerShellExeFinder { yield this.pwsh32Stable; } // Also look for the MSIX/UWP installation - yield this.pwshMsix; + if (this.pwshMsix) { + yield this.pwshMsix; + } break; case OperatingSystem.Linux: @@ -199,56 +308,16 @@ export class PowerShellExeFinder { } } - public *enumerateAdditionalPowerShellInstallations(): Iterable { + /** + * Iterates through the configured additonal PowerShell executable locations, + * without checking for their existence. + */ + private *enumerateAdditionalPowerShellInstallations(): Iterable { for (const additionalPwshSetting of this.additionalPSExeSettings) { yield this.findAdditionalPwshExe(additionalPwshSetting); } } - public get pwshStable(): IPossiblePowerShellExe { - return this.stablePwshExeVal.value; - } - - public get pwshPreview(): IPossiblePowerShellExe { - return this.previewPwshExeVal.value; - } - - public get pwsh32Stable(): IPossiblePowerShellExe { - return this.stable32BitPwshExeVal.value; - } - - public get pwsh32Preview(): IPossiblePowerShellExe { - return this.preview32BitPwshExeVal.value; - } - - public get pwshMsix(): IPossiblePowerShellExe { - return this.msixExeVal.value; - } - - public get pwshSnapStable(): IPossiblePowerShellExe { - return this.stableSnapExeVal.value; - } - - public get pwshSnapPreview(): IPossiblePowerShellExe { - return this.previewSnapExeVal.value; - } - - public get pwshDotnetGlobalTool(): IPossiblePowerShellExe { - return this.dotnetGlobalToolExeVal.value; - } - - public get sys32WinPS(): IPossiblePowerShellExe { - return this.sys32WinPSExeVal.value; - } - - public get sysWoW64WinPS(): IPossiblePowerShellExe { - return this.sysWow64WinPSExeVal.value; - } - - public get sysnativeWinPS(): IPossiblePowerShellExe { - return this.sysNativeWinPSExeVal.value; - } - private findAdditionalPwshExe(additionalPwshSetting: Settings.IPowerShellAdditionalExePathSettings) { return new PSCoreExe( additionalPwshSetting.exePath, @@ -286,7 +355,7 @@ export class PowerShellExeFinder { } private findPSCore32BitPreview(): IPossiblePowerShellExe { - return this.findPSCoreWindows({ use32Bit: true }); + return this.findPSCoreWindows({ findPreview: true, use32Bit: true }); } // Search for PS 6/7 under "%ProgramFiles%\PowerShell\[-preview]\pwsh.exe" @@ -326,7 +395,7 @@ export class PowerShellExeFinder { : "PowerShell (x64)"; const exePath = path.join(psCoreInstallDirPath, item, "pwsh.exe"); - return new PSCoreExe(exePath, pwshName, { is32Bit: use32Bit }); + return new PSCoreExe(exePath, pwshName); } // This method may not find any installation @@ -350,6 +419,10 @@ export class PowerShellExeFinder { .toString() .trim(); + if (!msixDir) { + return undefined; + } + const msixExePath = path.join(msixDir, "pwsh.exe"); return new PSCoreExe(msixExePath, "PowerShell MSIX"); } @@ -363,25 +436,25 @@ export class PowerShellExeFinder { } private findSys32WinPS(): IPossiblePowerShellExe { - const displayName: string = this.platformDetails.isOS64Bit + const displayName: string = this.platformDetails.isProcess64Bit ? WindowsPowerShell64BitLabel : WindowsPowerShell32BitLabel; return new WinPSExe( System32PowerShellPath, displayName, - { knownToExist: true, is32Bit: !this.platformDetails.isOS64Bit }); + { knownToExist: true }); } private findSysWow64WinPS(): IPossiblePowerShellExe { - return new WinPSExe(WinPS32BitPathOn64Bit, WindowsPowerShell64BitLabel); + return new WinPSExe(WinPS32BitPathOn64Bit, WindowsPowerShell32BitLabel); } private findSysNativeWinPS(): IPossiblePowerShellExe { return new WinPSExe( WinPS64BitPathOn32Bit, - WindowsPowerShell32BitLabel, - { knownToExist: true, is32Bit: true }); + WindowsPowerShell64BitLabel, + { knownToExist: true }); } } @@ -404,43 +477,28 @@ export function fixWindowsPowerShellPath(powerShellExePath: string, platformDeta abstract class PossiblePowerShellExe implements IPossiblePowerShellExe { private readonly pathToExe: string; private readonly installationName: string; - private readonly is32BitExe: boolean; - private knownToExist: boolean = undefined; + private readonly existsCheck: Lazy = undefined; constructor( pathToExe: string, installationName: string, - options?: { knownToExist?: boolean, is32Bit?: boolean }) { + options?: { knownToExist?: boolean }) { this.pathToExe = pathToExe; this.installationName = installationName; - if (options) { - - if (options.knownToExist) { - this.knownToExist = options.knownToExist; - } - - options.is32Bit = !!options.is32Bit; - } + this.existsCheck = new Lazy(() => (options && options.knownToExist) || fs.existsSync(this.exePath)); } abstract get version(): string; - get is32Bit(): boolean { - return this.is32BitExe; - } - get exePath(): string { return this.pathToExe; } get exists(): boolean { - if (this.knownToExist === undefined) { - this.knownToExist = fs.existsSync(this.exePath); - } - return this.knownToExist; + return this.existsCheck.value; } get displayName(): string { From 3457dee4d11ce4cfe46ae89c17406c389f35e12d Mon Sep 17 00:00:00 2001 From: Rob Holt Date: Wed, 9 Oct 2019 17:32:43 -0700 Subject: [PATCH 05/46] Style fixes --- src/platform.ts | 18 ++++++------ src/session.ts | 73 ++++++++++++++++++++++++------------------------- 2 files changed, 44 insertions(+), 47 deletions(-) diff --git a/src/platform.ts b/src/platform.ts index c16443ba33..424a644f20 100644 --- a/src/platform.ts +++ b/src/platform.ts @@ -124,17 +124,17 @@ export class PowerShellExeFinder { this.platformDetails = platformDetails; this.additionalPSExeSettings = additionalPowerShellExes || []; - this.stablePwshExeVal = new Lazy(() => this.findPSCoreStable()); - this.stable32BitPwshExeVal = new Lazy(() => this.findPSCore32BitStable()); + this.stablePwshExeVal = new Lazy(() => this.findPSCoreStable()); + this.stable32BitPwshExeVal = new Lazy(() => this.findPSCore32BitStable()); this.preview32BitPwshExeVal = new Lazy(() => this.findPSCore32BitPreview()); - this.previewPwshExeVal = new Lazy(() => this.findPSCorePreview()); + this.previewPwshExeVal = new Lazy(() => this.findPSCorePreview()); this.dotnetGlobalToolExeVal = new Lazy(() => this.findPSCoreDotnetGlobalTool()); - this.msixExeVal = new Lazy(() => this.findPSCoreMsix()); - this.stableSnapExeVal = new Lazy(() => this.findPSCoreStableSnap()); - this.previewSnapExeVal = new Lazy(() => this.findPSCorePreviewSnap()); - this.sys32WinPSExeVal = new Lazy(() => this.findSys32WinPS()); - this.sysWow64WinPSExeVal = new Lazy(() => this.findSysWow64WinPS()); - this.sysNativeWinPSExeVal = new Lazy(() => this.findSysNativeWinPS()); + this.msixExeVal = new Lazy(() => this.findPSCoreMsix()); + this.stableSnapExeVal = new Lazy(() => this.findPSCoreStableSnap()); + this.previewSnapExeVal = new Lazy(() => this.findPSCorePreviewSnap()); + this.sys32WinPSExeVal = new Lazy(() => this.findSys32WinPS()); + this.sysWow64WinPSExeVal = new Lazy(() => this.findSysWow64WinPS()); + this.sysNativeWinPSExeVal = new Lazy(() => this.findSysNativeWinPS()); } /** diff --git a/src/session.ts b/src/session.ts index 9ee6519eab..38464a275b 100644 --- a/src/session.ts +++ b/src/session.ts @@ -5,7 +5,6 @@ import cp = require("child_process"); import fs = require("fs"); import net = require("net"); -import os = require("os"); import path = require("path"); import * as semver from "semver"; import vscode = require("vscode"); @@ -129,45 +128,44 @@ export class SessionManager implements Middleware { this.suppressRestartPrompt = false; - if (this.powerShellExePath) { - - this.bundledModulesPath = path.resolve(__dirname, this.sessionSettings.bundledModulesPath); - - if (this.inDevelopmentMode) { - const devBundledModulesPath = - path.resolve( - __dirname, - this.sessionSettings.developer.bundledModulesPath); + if (!this.powerShellExePath) { + this.setSessionFailure("PowerShell could not be started, click 'Show Logs' for more details."); + } - // Make sure the module's bin path exists - if (fs.existsSync(path.join(devBundledModulesPath, "PowerShellEditorServices/bin"))) { - this.bundledModulesPath = devBundledModulesPath; - } else { - this.log.write( - "\nWARNING: In development mode but PowerShellEditorServices dev module path cannot be " + - `found (or has not been built yet): ${devBundledModulesPath}\n`); - } + this.bundledModulesPath = path.resolve(__dirname, this.sessionSettings.bundledModulesPath); + + if (this.inDevelopmentMode) { + const devBundledModulesPath = + path.resolve( + __dirname, + this.sessionSettings.developer.bundledModulesPath); + + // Make sure the module's bin path exists + if (fs.existsSync(path.join(devBundledModulesPath, "PowerShellEditorServices/bin"))) { + this.bundledModulesPath = devBundledModulesPath; + } else { + this.log.write( + "\nWARNING: In development mode but PowerShellEditorServices dev module path cannot be " + + `found (or has not been built yet): ${devBundledModulesPath}\n`); } + } - this.editorServicesArgs = - `-HostName 'Visual Studio Code Host' ` + - `-HostProfileId 'Microsoft.VSCode' ` + - `-HostVersion '${this.HostVersion}' ` + - `-AdditionalModules @('PowerShellEditorServices.VSCode') ` + - `-BundledModulesPath '${PowerShellProcess.escapeSingleQuotes(this.bundledModulesPath)}' ` + - `-EnableConsoleRepl `; - - if (this.sessionSettings.developer.editorServicesWaitForDebugger) { - this.editorServicesArgs += "-WaitForDebugger "; - } - if (this.sessionSettings.developer.editorServicesLogLevel) { - this.editorServicesArgs += `-LogLevel '${this.sessionSettings.developer.editorServicesLogLevel}' `; - } + this.editorServicesArgs = + `-HostName 'Visual Studio Code Host' ` + + `-HostProfileId 'Microsoft.VSCode' ` + + `-HostVersion '${this.HostVersion}' ` + + `-AdditionalModules @('PowerShellEditorServices.VSCode') ` + + `-BundledModulesPath '${PowerShellProcess.escapeSingleQuotes(this.bundledModulesPath)}' ` + + `-EnableConsoleRepl `; - this.startPowerShell(); - } else { - this.setSessionFailure("PowerShell could not be started, click 'Show Logs' for more details."); + if (this.sessionSettings.developer.editorServicesWaitForDebugger) { + this.editorServicesArgs += "-WaitForDebugger "; + } + if (this.sessionSettings.developer.editorServicesLogLevel) { + this.editorServicesArgs += `-LogLevel '${this.sessionSettings.developer.editorServicesLogLevel}' `; } + + this.startPowerShell(); } public stop() { @@ -342,11 +340,10 @@ export class SessionManager implements Middleware { } private async showBitnessPathFixWarning(fixedPath: string): Promise { - const bitness = this.platformDetails.isOS64Bit ? 64 : 32; + const bitness = this.platformDetails.isOS64Bit ? "64" : "32"; const choice = await vscode.window.showWarningMessage( - `The specified PowerShell path is incorrect for ${bitness}-bit VS Code, using '${fixedPath}' ` + - "instead.", + `The specified PowerShell path is incorrect for ${bitness}-bit VS Code, using '${fixedPath}' instead.`, "Fix Setting Automatically"); if (!choice) { From e84c8e835a21a7fe44f58017ebad14a6787e9721 Mon Sep 17 00:00:00 2001 From: Rob Holt Date: Thu, 10 Oct 2019 09:23:04 -0700 Subject: [PATCH 06/46] Disable .NET global tool discovery --- src/platform.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/platform.ts b/src/platform.ts index 424a644f20..5ae62366a1 100644 --- a/src/platform.ts +++ b/src/platform.ts @@ -273,8 +273,13 @@ export class PowerShellExeFinder { break; } + // TODO: + // Enable this when the global tool has been updated + // to support proper argument passing. + // Currently it cannot take startup arguments to start PSES with. + // // Look for the .NET global tool - yield this.pwshDotnetGlobalTool; + // yield this.pwshDotnetGlobalTool; // Look for PSCore preview if (this.pwshPreview) { From ff872e599d1841865d4243e79001373efe11b3ed Mon Sep 17 00:00:00 2001 From: Rob Holt Date: Thu, 10 Oct 2019 11:33:06 -0700 Subject: [PATCH 07/46] Fix bug with finding ps core --- src/platform.ts | 229 +++++++++++++++++++++++++++++------------------- 1 file changed, 138 insertions(+), 91 deletions(-) diff --git a/src/platform.ts b/src/platform.ts index 5ae62366a1..abcd3d41d4 100644 --- a/src/platform.ts +++ b/src/platform.ts @@ -46,10 +46,6 @@ export interface IPowerShellExeDetails { readonly exePath: string; } -export interface IPossiblePowerShellExe extends IPowerShellExeDetails { - readonly exists: boolean; -} - export function getPlatformDetails(): IPlatformDetails { let operatingSystem = OperatingSystem.Unknown; @@ -85,32 +81,41 @@ export class PowerShellNotFoundError extends Error { * this will also surface those at the end of the list. */ export class PowerShellExeFinder { + // This is required, since parseInt("7-preview") will return 7. + private static IntRegex: RegExp = /^\d+$/; + private readonly platformDetails: IPlatformDetails; + // PSCore version table + private readonly pwshWindowsInstallationsVal: + Lazy<{ stable: IPossiblePowerShellExe, preview: IPossiblePowerShellExe }>; + private readonly pwsh32WindowsInstallationsVal: + Lazy<{ stable: IPossiblePowerShellExe, preview: IPossiblePowerShellExe }>; + // PowerShell 6+ installation - private stablePwshExeVal: Lazy; - private previewPwshExeVal: Lazy; + private readonly stablePwshExeVal: Lazy; + private readonly previewPwshExeVal: Lazy; // 32-bit PowerShell 6+ installation - private stable32BitPwshExeVal: Lazy; - private preview32BitPwshExeVal: Lazy; + private readonly stable32BitPwshExeVal: Lazy; + private readonly preview32BitPwshExeVal: Lazy; // .NET Global Tool pwsh installation - private dotnetGlobalToolExeVal: Lazy; + private readonly dotnetGlobalToolExeVal: Lazy; // MSIX/UWP installation - private msixExeVal: Lazy; + private readonly msixExeVal: Lazy; // Snap pwsh installations on Linux - private stableSnapExeVal: Lazy; - private previewSnapExeVal: Lazy; + private readonly stableSnapExeVal: Lazy; + private readonly previewSnapExeVal: Lazy; // Windows PowerShell installations - private sys32WinPSExeVal: Lazy; - private sysWow64WinPSExeVal: Lazy; - private sysNativeWinPSExeVal: Lazy; + private readonly sys32WinPSExeVal: Lazy; + private readonly sysWow64WinPSExeVal: Lazy; + private readonly sysNativeWinPSExeVal: Lazy; - private additionalPSExeSettings: Iterable; + private readonly additionalPSExeSettings: Iterable; /** * Create a new PowerShellFinder object to discover PowerShell installations. @@ -124,6 +129,10 @@ export class PowerShellExeFinder { this.platformDetails = platformDetails; this.additionalPSExeSettings = additionalPowerShellExes || []; + this.pwshWindowsInstallationsVal = new Lazy(() => this.findPSCoreWindowsInstallations()); + this.pwsh32WindowsInstallationsVal = new Lazy( + () => this.findPSCoreWindowsInstallations({ find32Bit: true })); + this.stablePwshExeVal = new Lazy(() => this.findPSCoreStable()); this.stable32BitPwshExeVal = new Lazy(() => this.findPSCore32BitStable()); this.preview32BitPwshExeVal = new Lazy(() => this.findPSCore32BitPreview()); @@ -139,7 +148,7 @@ export class PowerShellExeFinder { /** * The stable PowerShell 6+ installation. - * May be undefined if the installation directory is not present. + * May be null if the installation directory is not present. */ private get pwshStable(): IPossiblePowerShellExe { return this.stablePwshExeVal.value; @@ -147,7 +156,7 @@ export class PowerShellExeFinder { /** * The preview PowerShell 6+ installation. - * May be undefined if the installation directory is not present. + * May be null if the installation directory is not present. */ private get pwshPreview(): IPossiblePowerShellExe { return this.previewPwshExeVal.value; @@ -155,7 +164,7 @@ export class PowerShellExeFinder { /** * The stable 32-bit PowerShell 6+ installation. - * May be undefined if the installation directory is not present + * May be null if the installation directory is not present */ private get pwsh32Stable(): IPossiblePowerShellExe { return this.stable32BitPwshExeVal.value; @@ -163,7 +172,7 @@ export class PowerShellExeFinder { /** * The preview 32-bit PowerShell 6+ installation. - * May be undefined if the installation directory is not present. + * May be null if the installation directory is not present. */ private get pwsh32Preview(): IPossiblePowerShellExe { return this.preview32BitPwshExeVal.value; @@ -171,7 +180,7 @@ export class PowerShellExeFinder { /** * PowerShell 6+ installation from an MSIX (through the Windows store). - * May be undefined if the AppX package is not found. + * May be null if the AppX package is not found. */ private get pwshMsix(): IPossiblePowerShellExe { return this.msixExeVal.value; @@ -323,6 +332,88 @@ export class PowerShellExeFinder { } } + private findPSCoreWindowsInstallations(options?: { find32Bit: boolean }): + { stable: IPossiblePowerShellExe | null, preview: IPossiblePowerShellExe | null } | null { + + const find32Bit: boolean = options && options.find32Bit; + + const programFilesPath: string = find32Bit + ? process.env["ProgramFiles(x86)"] + : process.env.ProgramFiles; + + const powerShellInstallBaseDir = path.join(programFilesPath, "PowerShell"); + + // Ensure the base directory exists + if (!(fs.existsSync(powerShellInstallBaseDir) && fs.lstatSync(powerShellInstallBaseDir).isDirectory())) { + return null; + } + + let highestSeenStableNumber: number = -1; + let stablePath: string = null; + let highestSeenPreviewNumber: number = -1; + let previewPath: string = null; + for (const item of fs.readdirSync(powerShellInstallBaseDir)) { + + // Search for a directory like "6" or "7" first + if (item.match(PowerShellExeFinder.IntRegex)) { + const currentStable = parseInt(item, 10); + + // We may have already picked up a higher version + if (currentStable <= highestSeenStableNumber) { + continue; + } + + // If the directory exists, but not pwsh.exe, just keep looking through dirs + const stableExePath = path.join(powerShellInstallBaseDir, item, "pwsh.exe"); + if (!fs.existsSync(stableExePath)) { + continue; + } + + stablePath = stableExePath; + highestSeenStableNumber = currentStable; + continue; + } + + // Now look for something like "7-preview" + + // Preview dirs all have dashes in them + const dashIndex = item.indexOf("-"); + if (dashIndex < 0) { + continue; + } + + // Verify that the part before the dash is an integer + const intPart: string = item.substring(0, dashIndex); + if (!intPart.match(PowerShellExeFinder.IntRegex)) { + continue; + } + + // Weed out non preview dirs or versions lower than the one we've already seen + const currentPreview = parseInt(intPart, 10); + if (currentPreview <= highestSeenPreviewNumber || item.substring(dashIndex + 1) !== "preview") { + continue; + } + + // Now look for the file + const previewExePath = path.join(powerShellInstallBaseDir, item, "pwsh.exe"); + if (!fs.existsSync(previewExePath)) { + continue; + } + + previewPath = previewExePath; + highestSeenPreviewNumber = currentPreview; + } + + const bitness: string = find32Bit + ? "(x86)" + : "(x64)"; + + return { + stable: stablePath && new PSCoreExe(stablePath, `PowerShell ${bitness}`, { knownToExist: true }), + preview: previewPath && new PSCoreExe(previewPath, `PowerShell Preview ${bitness}`, { knownToExist: true }), + }; + } + private findAdditionalPwshExe(additionalPwshSetting: Settings.IPowerShellAdditionalExePathSettings) { return new PSCoreExe( additionalPwshSetting.exePath, @@ -338,7 +429,7 @@ export class PowerShellExeFinder { return new PSCoreExe(MacOSExePath, "PowerShell (x64)"); case OperatingSystem.Windows: - return this.findPSCoreWindows(); + return this.pwshWindowsInstallationsVal.value.stable; } } @@ -351,61 +442,16 @@ export class PowerShellExeFinder { return new PSCoreExe(MacOSPreviewExePath, "PowerShell Preview (x64)"); case OperatingSystem.Windows: - return this.findPSCoreWindows({ findPreview: true }); + return this.pwshWindowsInstallationsVal.value.preview; } } private findPSCore32BitStable(): IPossiblePowerShellExe { - return this.findPSCoreWindows({ use32Bit: true }); + return this.pwsh32WindowsInstallationsVal.value.stable; } private findPSCore32BitPreview(): IPossiblePowerShellExe { - return this.findPSCoreWindows({ findPreview: true, use32Bit: true }); - } - - // Search for PS 6/7 under "%ProgramFiles%\PowerShell\[-preview]\pwsh.exe" - private findPSCoreWindows(options?: { use32Bit?: boolean, findPreview?: boolean }): IPossiblePowerShellExe { - - const use32Bit: boolean = options && options.use32Bit; - - const programFilesPath: string = use32Bit - ? process.env["ProgramFiles(x86)"] - : process.env.ProgramFiles; - - // Path to "%ProgramFiles%\PowerShell" - const psCoreInstallDirPath: string = path.join(programFilesPath, "PowerShell"); - - // Make sure it exists and is a directory - if (!(fs.existsSync(psCoreInstallDirPath) && fs.lstatSync(psCoreInstallDirPath).isDirectory())) { - return undefined; - } - - // Look for folders that match the number[-preview] scheme - for (const item of fs.readdirSync(psCoreInstallDirPath)) { - if (options && options.findPreview) { - // Look for something like "7-preview" - const dashIndex = item.indexOf("-"); - if (dashIndex <= 0 || !parseInt(item.substring(0, dashIndex), 10) || item.substring(dashIndex + 1) !== "preview") { - continue; - } - } else { - // Look for something like "6" - if (!parseInt(item, 10)) { - continue; - } - } - - const pwshName = use32Bit - ? "PowerShell (x86)" - : "PowerShell (x64)"; - - const exePath = path.join(psCoreInstallDirPath, item, "pwsh.exe"); - return new PSCoreExe(exePath, pwshName); - } - - // This method may not find any installation - // Callers should be aware of this - return undefined; + return this.pwsh32WindowsInstallationsVal.value.preview; } private findPSCoreDotnetGlobalTool(): IPossiblePowerShellExe { @@ -425,7 +471,7 @@ export class PowerShellExeFinder { .trim(); if (!msixDir) { - return undefined; + return null; } const msixExePath = path.join(msixDir, "pwsh.exe"); @@ -458,8 +504,7 @@ export class PowerShellExeFinder { private findSysNativeWinPS(): IPossiblePowerShellExe { return new WinPSExe( WinPS64BitPathOn32Bit, - WindowsPowerShell64BitLabel, - { knownToExist: true }); + WindowsPowerShell64BitLabel); } } @@ -479,11 +524,16 @@ export function fixWindowsPowerShellPath(powerShellExePath: string, platformDeta return powerShellExePath; } +interface IPossiblePowerShellExe extends IPowerShellExeDetails { + readonly exists: boolean; +} + abstract class PossiblePowerShellExe implements IPossiblePowerShellExe { + protected readonly lazyVersion: Lazy; + private readonly pathToExe: string; private readonly installationName: string; - - private readonly existsCheck: Lazy = undefined; + private readonly existsCheck: Lazy = null; constructor( pathToExe: string, @@ -494,9 +544,12 @@ abstract class PossiblePowerShellExe implements IPossiblePowerShellExe { this.installationName = installationName; this.existsCheck = new Lazy(() => (options && options.knownToExist) || fs.existsSync(this.exePath)); + this.lazyVersion = new Lazy(() => this.findVersion()); } - abstract get version(): string; + get version(): string { + return this.lazyVersion.value; + } get exePath(): string { return this.pathToExe; @@ -509,27 +562,21 @@ abstract class PossiblePowerShellExe implements IPossiblePowerShellExe { get displayName(): string { return this.installationName; } + + protected abstract findVersion(): string; } class WinPSExe extends PossiblePowerShellExe { - private psVersion: string; - - get version(): string { - if (!this.psVersion) { - this.psVersion = child_process.execFileSync(this.exePath, ["-c", "$PSVersionTable.PSVersion.ToString()"]); - } - return this.psVersion; + protected findVersion(): string { + return child_process.execFileSync(this.exePath, ["-c", "$PSVersionTable.PSVersion.ToString()"]); } } class PSCoreExe extends PossiblePowerShellExe { - private psVersion: string; - - get version(): string { - if (!this.psVersion) { - this.psVersion = child_process.execFileSync(this.exePath, ["-v"]); - } - return this.psVersion; + protected findVersion(): string { + const versionLine = child_process.execFileSync(this.exePath, ["-v"]); + const spaceIndex = versionLine.indexOf(" "); + return versionLine.substring(spaceIndex + 1); } } @@ -540,7 +587,7 @@ class Lazy { constructor(factory: () => T) { this.constructed = false; - this.underlyingValue = undefined; + this.underlyingValue = null; this.factory = factory; } From 71c86e87b7c44ec895e746d3f5ca06beb597a373 Mon Sep 17 00:00:00 2001 From: Rob Holt Date: Thu, 10 Oct 2019 13:08:57 -0700 Subject: [PATCH 08/46] Fix issue when PS core dir doesn't exist --- src/platform.ts | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/platform.ts b/src/platform.ts index abcd3d41d4..603d7dba1e 100644 --- a/src/platform.ts +++ b/src/platform.ts @@ -429,7 +429,8 @@ export class PowerShellExeFinder { return new PSCoreExe(MacOSExePath, "PowerShell (x64)"); case OperatingSystem.Windows: - return this.pwshWindowsInstallationsVal.value.stable; + return this.pwshWindowsInstallationsVal.value + && this.pwshWindowsInstallationsVal.value.stable; } } @@ -442,16 +443,19 @@ export class PowerShellExeFinder { return new PSCoreExe(MacOSPreviewExePath, "PowerShell Preview (x64)"); case OperatingSystem.Windows: - return this.pwshWindowsInstallationsVal.value.preview; + return this.pwshWindowsInstallationsVal.value.preview + && this.pwshWindowsInstallationsVal.value.preview; } } private findPSCore32BitStable(): IPossiblePowerShellExe { - return this.pwsh32WindowsInstallationsVal.value.stable; + return this.pwsh32WindowsInstallationsVal.value + && this.pwsh32WindowsInstallationsVal.value.stable; } private findPSCore32BitPreview(): IPossiblePowerShellExe { - return this.pwsh32WindowsInstallationsVal.value.preview; + return this.pwsh32WindowsInstallationsVal.value + && this.pwsh32WindowsInstallationsVal.value.preview; } private findPSCoreDotnetGlobalTool(): IPossiblePowerShellExe { From 518579c9c009886366570fa949fa5f26683d211a Mon Sep 17 00:00:00 2001 From: Rob Holt Date: Thu, 10 Oct 2019 13:33:26 -0700 Subject: [PATCH 09/46] Actually fix --- src/platform.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/platform.ts b/src/platform.ts index 603d7dba1e..e2c21c248a 100644 --- a/src/platform.ts +++ b/src/platform.ts @@ -443,7 +443,7 @@ export class PowerShellExeFinder { return new PSCoreExe(MacOSPreviewExePath, "PowerShell Preview (x64)"); case OperatingSystem.Windows: - return this.pwshWindowsInstallationsVal.value.preview + return this.pwshWindowsInstallationsVal.value && this.pwshWindowsInstallationsVal.value.preview; } } From 02632098d75ef950eed02a13b35f3633a3ecf59e Mon Sep 17 00:00:00 2001 From: Rob Holt Date: Thu, 10 Oct 2019 16:10:46 -0700 Subject: [PATCH 10/46] Fix tests --- test/platform.test.ts | 49 +++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 47 insertions(+), 2 deletions(-) diff --git a/test/platform.test.ts b/test/platform.test.ts index 51e0900998..9bcb0803dd 100644 --- a/test/platform.test.ts +++ b/test/platform.test.ts @@ -5,7 +5,9 @@ import * as assert from "assert"; import * as platform from "../src/platform"; -function checkDefaultPowerShellPath(platformDetails, expectedPath) { +function checkDefaultPowerShellPath( + platformDetails: platform.IPlatformDetails, + expectedPath: string) { const powerShellExeFinder = new platform.PowerShellExeFinder(platformDetails); test("returns correct default path", () => { let defaultPath: string; @@ -35,7 +37,10 @@ function checkAvailableWindowsPowerShellPaths( }); } -function checkFixedWindowsPowerShellpath(platformDetails, inputPath, expectedPath) { +function checkFixedWindowsPowerShellpath( + platformDetails: platform.IPlatformDetails, + inputPath: string, + expectedPath: string) { test("fixes incorrect Windows PowerShell Sys* path", () => { assert.equal( platform.fixWindowsPowerShellPath(inputPath, platformDetails), @@ -59,6 +64,10 @@ suite("Platform module", () => { checkAvailableWindowsPowerShellPaths( platformDetails, [ + { + displayName: "PowerShell (x64)", + exePath: "C:\\Program Files\\PowerShell\\6\\pwsh.exe", + }, { displayName: platform.WindowsPowerShell64BitLabel, exePath: platform.System32PowerShellPath, @@ -89,6 +98,10 @@ suite("Platform module", () => { checkAvailableWindowsPowerShellPaths( platformDetails, [ + { + displayName: "PowerShell (x64)", + exePath: "C:\\Program Files\\PowerShell\\6\\pwsh.exe", + }, { displayName: platform.WindowsPowerShell64BitLabel, exePath: platform.SysnativePowerShellPath, @@ -119,6 +132,10 @@ suite("Platform module", () => { checkAvailableWindowsPowerShellPaths( platformDetails, [ + { + displayName: "PowerShell (x86)", + exePath: "C:\\Program Files (x86)\\PowerShell\\6\\pwsh.exe", + }, { displayName: platform.WindowsPowerShell32BitLabel, exePath: platform.System32PowerShellPath, @@ -126,4 +143,32 @@ suite("Platform module", () => { ]); }); } + + if (process.platform === "darwin") { + suite("macOS", () => { + const platformDetails: platform.IPlatformDetails = { + operatingSystem: platform.OperatingSystem.MacOS, + isOS64Bit: true, + isProcess64Bit: true, + }; + + checkDefaultPowerShellPath( + platformDetails, + "/usr/local/bin/pwsh"); + }); + } + + if (process.platform === "linux") { + suite("linux", () => { + const platformDetails: platform.IPlatformDetails = { + operatingSystem: platform.OperatingSystem.Linux, + isOS64Bit: true, + isProcess64Bit: true, + }; + + checkDefaultPowerShellPath( + platformDetails, + "/usr/bin/pwsh"); + }); + } }); From 835fc833b5ef5a7b82600a76db7e541a4ceafeda Mon Sep 17 00:00:00 2001 From: Rob Holt Date: Mon, 14 Oct 2019 16:19:27 -0700 Subject: [PATCH 11/46] Add default platform --- src/platform.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/platform.ts b/src/platform.ts index e2c21c248a..7b7def32a7 100644 --- a/src/platform.ts +++ b/src/platform.ts @@ -123,10 +123,10 @@ export class PowerShellExeFinder { * @param additionalPowerShellExes Additional PowerShell installations as configured in the settings. */ constructor( - platformDetails: IPlatformDetails, + platformDetails?: IPlatformDetails, additionalPowerShellExes?: Iterable) { - this.platformDetails = platformDetails; + this.platformDetails = platformDetails || getPlatformDetails(); this.additionalPSExeSettings = additionalPowerShellExes || []; this.pwshWindowsInstallationsVal = new Lazy(() => this.findPSCoreWindowsInstallations()); From fcde1949ad1d7a47b4f87d6790b57448596bfc97 Mon Sep 17 00:00:00 2001 From: Rob Holt Date: Mon, 14 Oct 2019 16:28:18 -0700 Subject: [PATCH 12/46] Add new APIs and use them in places --- src/platform.ts | 16 ++++++++++++++++ src/session.ts | 9 +++++---- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/src/platform.ts b/src/platform.ts index 7b7def32a7..c39f44ad35 100644 --- a/src/platform.ts +++ b/src/platform.ts @@ -230,6 +230,22 @@ export class PowerShellExeFinder { return this.sysNativeWinPSExeVal.value; } + /** + * Returns the first available PowerShell executable found in the search order. + */ + public getFirstAvailablePowerShellInstallation(): IPowerShellExeDetails { + for (const pwsh of this.enumeratePowerShellInstallations()) { + return pwsh; + } + } + + /** + * Get an array of all PowerShell executables found when searching for PowerShell installations. + */ + public getAllAvailablePowerShellInstallations(): IPowerShellExeDetails[] { + return Array.from(this.enumeratePowerShellInstallations()); + } + /** * Iterates through PowerShell installations on the machine according * to configuration passed in through the constructor. diff --git a/src/session.ts b/src/session.ts index 38464a275b..00be38eceb 100644 --- a/src/session.ts +++ b/src/session.ts @@ -269,9 +269,10 @@ export class SessionManager implements Middleware { return this.resolvePowerShellPath(powerShellExePath); } - for (const powerShellExe of this.powershellExeFinder.enumeratePowerShellInstallations()) { - // No need to resolve these paths, since the enumeration checks for existence - return powerShellExe.exePath; + const firstAvailablePwsh = this.powershellExeFinder.getFirstAvailablePowerShellInstallation(); + if (firstAvailablePwsh) { + // No need to resolve this path, since the finder guarantees its existence + return firstAvailablePwsh.exePath; } this.setSessionFailure("Unable to find PowerShell installation, see logs for more details"); @@ -736,7 +737,7 @@ export class SessionManager implements Middleware { private showSessionMenu() { const currentExePath = (this.powerShellExePath || "").toLowerCase(); - const availablePowerShellExes = Array.from(this.powershellExeFinder.enumeratePowerShellInstallations()); + const availablePowerShellExes = this.powershellExeFinder.getAllAvailablePowerShellInstallations(); let sessionText: string; From e78102107da9a0d0d588277fa9e533bc479ae6db Mon Sep 17 00:00:00 2001 From: Rob Holt Date: Mon, 14 Oct 2019 16:37:22 -0700 Subject: [PATCH 13/46] Use new apis in tests --- test/platform.test.ts | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/test/platform.test.ts b/test/platform.test.ts index 9bcb0803dd..6982148f0c 100644 --- a/test/platform.test.ts +++ b/test/platform.test.ts @@ -10,11 +10,7 @@ function checkDefaultPowerShellPath( expectedPath: string) { const powerShellExeFinder = new platform.PowerShellExeFinder(platformDetails); test("returns correct default path", () => { - let defaultPath: string; - for (const pwshExe of powerShellExeFinder.enumeratePowerShellInstallations()) { - defaultPath = pwshExe.exePath; - break; - } + const defaultPath = powerShellExeFinder.getFirstAvailablePowerShellInstallation(); assert.equal(defaultPath, expectedPath); }); } @@ -64,10 +60,6 @@ suite("Platform module", () => { checkAvailableWindowsPowerShellPaths( platformDetails, [ - { - displayName: "PowerShell (x64)", - exePath: "C:\\Program Files\\PowerShell\\6\\pwsh.exe", - }, { displayName: platform.WindowsPowerShell64BitLabel, exePath: platform.System32PowerShellPath, From 4745b0008a6f5abb172f828f015b4a7b02755ca4 Mon Sep 17 00:00:00 2001 From: Rob Holt Date: Mon, 14 Oct 2019 17:08:40 -0700 Subject: [PATCH 14/46] Fix text issue --- test/platform.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/platform.test.ts b/test/platform.test.ts index 6982148f0c..e0d56fd6d0 100644 --- a/test/platform.test.ts +++ b/test/platform.test.ts @@ -10,7 +10,7 @@ function checkDefaultPowerShellPath( expectedPath: string) { const powerShellExeFinder = new platform.PowerShellExeFinder(platformDetails); test("returns correct default path", () => { - const defaultPath = powerShellExeFinder.getFirstAvailablePowerShellInstallation(); + const defaultPath = powerShellExeFinder.getFirstAvailablePowerShellInstallation().exePath; assert.equal(defaultPath, expectedPath); }); } From bd7ba7f61e802df39005797f8cbb8cec6d405421 Mon Sep 17 00:00:00 2001 From: Rob Holt Date: Mon, 14 Oct 2019 17:47:03 -0700 Subject: [PATCH 15/46] Remove pwsh refs --- test/platform.test.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/test/platform.test.ts b/test/platform.test.ts index e0d56fd6d0..7f0d2d47f3 100644 --- a/test/platform.test.ts +++ b/test/platform.test.ts @@ -85,15 +85,11 @@ suite("Platform module", () => { checkDefaultPowerShellPath( platformDetails, - "C:\\Program Files\\PowerShell\\6\\pwsh.exe"); + platform.System32PowerShellPath); checkAvailableWindowsPowerShellPaths( platformDetails, [ - { - displayName: "PowerShell (x64)", - exePath: "C:\\Program Files\\PowerShell\\6\\pwsh.exe", - }, { displayName: platform.WindowsPowerShell64BitLabel, exePath: platform.SysnativePowerShellPath, From ea02656fae25801781d17e8ccf232995351812c3 Mon Sep 17 00:00:00 2001 From: Rob Holt Date: Tue, 15 Oct 2019 10:31:26 -0700 Subject: [PATCH 16/46] More tests --- test/platform.test.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/test/platform.test.ts b/test/platform.test.ts index 7f0d2d47f3..5dae8ab7fc 100644 --- a/test/platform.test.ts +++ b/test/platform.test.ts @@ -27,7 +27,7 @@ function checkAvailableWindowsPowerShellPaths( // enumerate the first list items. let i = 0; for (const pwshExe of pwshExeFinder.enumeratePowerShellInstallations()) { - assert.equal(pwshExe.exePath, expectedPaths[i]); + assert.equal(pwshExe, expectedPaths[i]); i++; } }); @@ -120,10 +120,6 @@ suite("Platform module", () => { checkAvailableWindowsPowerShellPaths( platformDetails, [ - { - displayName: "PowerShell (x86)", - exePath: "C:\\Program Files (x86)\\PowerShell\\6\\pwsh.exe", - }, { displayName: platform.WindowsPowerShell32BitLabel, exePath: platform.System32PowerShellPath, From fa9797afef51f068f13dfe5c0f29acac0c8fb343 Mon Sep 17 00:00:00 2001 From: Rob Holt Date: Tue, 15 Oct 2019 10:42:52 -0700 Subject: [PATCH 17/46] tests --- test/platform.test.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/platform.test.ts b/test/platform.test.ts index 5dae8ab7fc..d55533702d 100644 --- a/test/platform.test.ts +++ b/test/platform.test.ts @@ -27,7 +27,8 @@ function checkAvailableWindowsPowerShellPaths( // enumerate the first list items. let i = 0; for (const pwshExe of pwshExeFinder.enumeratePowerShellInstallations()) { - assert.equal(pwshExe, expectedPaths[i]); + assert.equal(pwshExe.displayName, expectedPaths[i].displayName); + assert.equal(pwshExe.exePath, expectedPaths[i].exePath); i++; } }); From ab6eba3a2ae02ab30eb34bc491f6fbfc00d5acd7 Mon Sep 17 00:00:00 2001 From: Rob Holt Date: Tue, 15 Oct 2019 11:01:11 -0700 Subject: [PATCH 18/46] tests --- test/platform.test.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/test/platform.test.ts b/test/platform.test.ts index d55533702d..782fb36cfb 100644 --- a/test/platform.test.ts +++ b/test/platform.test.ts @@ -28,7 +28,7 @@ function checkAvailableWindowsPowerShellPaths( let i = 0; for (const pwshExe of pwshExeFinder.enumeratePowerShellInstallations()) { assert.equal(pwshExe.displayName, expectedPaths[i].displayName); - assert.equal(pwshExe.exePath, expectedPaths[i].exePath); + assert.equal(pwshExe.exePath.toLowerCase(), expectedPaths[i].exePath.toLowerCase()) i++; } }); @@ -75,7 +75,7 @@ suite("Platform module", () => { platformDetails, platform.SysnativePowerShellPath, platform.System32PowerShellPath); - }); + }).timeout(5000); suite("64-bit Windows, 32-bit VS Code", () => { const platformDetails: platform.IPlatformDetails = { @@ -91,14 +91,14 @@ suite("Platform module", () => { checkAvailableWindowsPowerShellPaths( platformDetails, [ - { - displayName: platform.WindowsPowerShell64BitLabel, - exePath: platform.SysnativePowerShellPath, - }, { displayName: platform.WindowsPowerShell32BitLabel, exePath: platform.System32PowerShellPath, }, + { + displayName: platform.WindowsPowerShell64BitLabel, + exePath: platform.SysnativePowerShellPath, + }, ]); checkFixedWindowsPowerShellpath( @@ -116,7 +116,7 @@ suite("Platform module", () => { checkDefaultPowerShellPath( platformDetails, - "C:\\Program Files\\PowerShell\\6\\pwsh.exe"); + platform.System32PowerShellPath); checkAvailableWindowsPowerShellPaths( platformDetails, From 0d24866b5578132d0f4386f0bde33737e8185d48 Mon Sep 17 00:00:00 2001 From: Rob Holt Date: Tue, 15 Oct 2019 11:11:22 -0700 Subject: [PATCH 19/46] tests --- test/platform.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/platform.test.ts b/test/platform.test.ts index 782fb36cfb..32fec700f6 100644 --- a/test/platform.test.ts +++ b/test/platform.test.ts @@ -28,7 +28,7 @@ function checkAvailableWindowsPowerShellPaths( let i = 0; for (const pwshExe of pwshExeFinder.enumeratePowerShellInstallations()) { assert.equal(pwshExe.displayName, expectedPaths[i].displayName); - assert.equal(pwshExe.exePath.toLowerCase(), expectedPaths[i].exePath.toLowerCase()) + assert.equal(pwshExe.exePath.toLowerCase(), expectedPaths[i].exePath.toLowerCase()); i++; } }); From ad49daf5701810c4b15dc34ddf407568871742ac Mon Sep 17 00:00:00 2001 From: Rob Holt Date: Tue, 22 Oct 2019 17:37:24 -0700 Subject: [PATCH 20/46] Testing and bitness refactor --- package-lock.json | 145 +++++++++++++++++++++++++++++++ package.json | 4 + src/platform.ts | 194 ++++++++++++++++++++++++------------------ test/platform.test.ts | 117 ++++++++++++++++++++++++- 4 files changed, 375 insertions(+), 85 deletions(-) diff --git a/package-lock.json b/package-lock.json index f0cb0c489c..219d85c6fa 100644 --- a/package-lock.json +++ b/package-lock.json @@ -32,12 +32,65 @@ } } }, + "@sinonjs/commons": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.6.0.tgz", + "integrity": "sha512-w4/WHG7C4WWFyE5geCieFJF6MZkbW4VAriol5KlmQXpAQdxvV0p26sqNZOW6Qyw6Y0l9K4g+cHvvczR2sEEpqg==", + "dev": true, + "requires": { + "type-detect": "4.0.8" + } + }, + "@sinonjs/formatio": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@sinonjs/formatio/-/formatio-3.2.2.tgz", + "integrity": "sha512-B8SEsgd8gArBLMD6zpRw3juQ2FVSsmdd7qlevyDqzS9WTCtvF55/gAL+h6gue8ZvPYcdiPdvueM/qm//9XzyTQ==", + "dev": true, + "requires": { + "@sinonjs/commons": "^1", + "@sinonjs/samsam": "^3.1.0" + } + }, + "@sinonjs/samsam": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-3.3.3.tgz", + "integrity": "sha512-bKCMKZvWIjYD0BLGnNrxVuw4dkWCYsLqFOUWw8VgKF/+5Y+mE7LfHWPIYoDXowH+3a9LsWDMo0uAP8YDosPvHQ==", + "dev": true, + "requires": { + "@sinonjs/commons": "^1.3.0", + "array-from": "^2.1.1", + "lodash": "^4.17.15" + }, + "dependencies": { + "lodash": { + "version": "4.17.15", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", + "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", + "dev": true + } + } + }, + "@sinonjs/text-encoding": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.1.tgz", + "integrity": "sha512-+iTbntw2IZPb/anVDbypzfQa+ay64MW0Zo8aJ8gZPWMMK6/OubMVb6lUPMagqjOPnmtauXnFCACVl3O7ogjeqQ==", + "dev": true + }, "@types/mocha": { "version": "5.2.7", "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-5.2.7.tgz", "integrity": "sha512-NYrtPht0wGzhwe9+/idPaBB+TqkY9AhTvOLMkThm0IoEfLaiVQZwBwyJ5puCkO3AUCWrmcoePjp2mbFocKy4SQ==", "dev": true }, + "@types/mock-fs": { + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/@types/mock-fs/-/mock-fs-4.10.0.tgz", + "integrity": "sha512-FQ5alSzmHMmliqcL36JqIA4Yyn9jyJKvRSGV3mvPh108VFatX7naJDzSG4fnFQNZFq9dIx0Dzoe6ddflMB2Xkg==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, "@types/node": { "version": "10.11.7", "resolved": "https://registry.npmjs.org/@types/node/-/node-10.11.7.tgz", @@ -65,6 +118,12 @@ "integrity": "sha512-G1Ggy7/9Nsa1Jt2yiBR2riEuyK2DFNnqow6R7cromXPMNynackRY1vqFTLz/gwnef1LHokbXThcPhqMRjUbkpQ==", "dev": true }, + "@types/sinon": { + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@types/sinon/-/sinon-7.5.0.tgz", + "integrity": "sha512-NyzhuSBy97B/zE58cDw4NyGvByQbAHNP9069KVSgnXt/sc0T6MFRh0InKAeBVHJWdSXG1S3+PxgVIgKo9mTHbw==", + "dev": true + }, "acorn": { "version": "5.7.3", "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.3.tgz", @@ -153,6 +212,12 @@ "sprintf-js": "~1.0.2" } }, + "array-from": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/array-from/-/array-from-2.1.1.tgz", + "integrity": "sha1-z+nYwmYoudxa7MYqn12PHzUsEZU=", + "dev": true + }, "asn1": { "version": "0.2.4", "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", @@ -1253,6 +1318,12 @@ "verror": "1.10.0" } }, + "just-extend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-4.0.2.tgz", + "integrity": "sha512-FrLwOgm+iXrPV+5zDU6Jqu4gCRXbWEQg2O3SKONsWE4w7AXFRkryS53bpWdaL9cNol+AmR3AEYz6kn+o0fCPnw==", + "dev": true + }, "levn": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", @@ -1278,6 +1349,12 @@ "integrity": "sha512-mmKYbW3GLuJeX+iGP+Y7Gp1AiGHGbXHCOh/jZmrawMmsE7MS4znI3RL2FsjbqOyMayHInjOeykW7PEajUk1/xw==", "dev": true }, + "lolex": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lolex/-/lolex-4.2.0.tgz", + "integrity": "sha512-gKO5uExCXvSm6zbF562EvM+rd1kQDnB9AZBbiQVzf1ZmdDpxUSvpnAaVOP83N/31mRK8Ml8/VE8DMvsAZQ+7wg==", + "dev": true + }, "lru-cache": { "version": "4.1.5", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", @@ -1422,6 +1499,12 @@ "lodash": "^4.16.4" } }, + "mock-fs": { + "version": "4.10.2", + "resolved": "https://registry.npmjs.org/mock-fs/-/mock-fs-4.10.2.tgz", + "integrity": "sha512-ewPQ83O4U8/Gd8I15WoB6vgTTmq5khxBskUWCRvswUqjCfOOTREmxllztQOm+PXMWUxATry+VBWXQJloAyxtbQ==", + "dev": true + }, "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", @@ -1440,6 +1523,19 @@ "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", "dev": true }, + "nise": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/nise/-/nise-1.5.2.tgz", + "integrity": "sha512-/6RhOUlicRCbE9s+94qCUsyE+pKlVJ5AhIv+jEE7ESKwnbXqulKZ1FYU+XAtHHWE9TinYvAxDUJAb912PwPoWA==", + "dev": true, + "requires": { + "@sinonjs/formatio": "^3.2.1", + "@sinonjs/text-encoding": "^0.7.1", + "just-extend": "^4.0.2", + "lolex": "^4.1.0", + "path-to-regexp": "^1.7.0" + } + }, "node-fetch": { "version": "2.6.0", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.0.tgz", @@ -1570,6 +1666,23 @@ "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", "dev": true }, + "path-to-regexp": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.7.0.tgz", + "integrity": "sha1-Wf3g9DW62suhA6hOnTvGTpa5k30=", + "dev": true, + "requires": { + "isarray": "0.0.1" + }, + "dependencies": { + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + } + } + }, "pend": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", @@ -1836,6 +1949,32 @@ "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", "dev": true }, + "sinon": { + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-7.5.0.tgz", + "integrity": "sha512-AoD0oJWerp0/rY9czP/D6hDTTUYGpObhZjMpd7Cl/A6+j0xBE+ayL/ldfggkBXUs0IkvIiM1ljM8+WkOc5k78Q==", + "dev": true, + "requires": { + "@sinonjs/commons": "^1.4.0", + "@sinonjs/formatio": "^3.2.1", + "@sinonjs/samsam": "^3.3.3", + "diff": "^3.5.0", + "lolex": "^4.2.0", + "nise": "^1.5.2", + "supports-color": "^5.5.0" + }, + "dependencies": { + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, "slice-ansi": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-1.0.0.tgz", @@ -2065,6 +2204,12 @@ "prelude-ls": "~1.1.2" } }, + "type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true + }, "typed-rest-client": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/typed-rest-client/-/typed-rest-client-1.2.0.tgz", diff --git a/package.json b/package.json index 08189d7f86..08d918264c 100644 --- a/package.json +++ b/package.json @@ -48,14 +48,18 @@ }, "devDependencies": { "@types/mocha": "~5.2.7", + "@types/mock-fs": "^4.10.0", "@types/node": "~10.11.0", "@types/node-fetch": "^2.5.0", "@types/rewire": "^2.5.28", "@types/semver": "^6.0.2", + "@types/sinon": "^7.5.0", "mocha": "~5.2.0", "mocha-junit-reporter": "~1.23.1", "mocha-multi-reporters": "~1.1.7", + "mock-fs": "^4.10.2", "rewire": "~4.0.1", + "sinon": "^7.5.0", "tslint": "~5.20.0", "typescript": "~3.5.3", "vsce": "~1.66.0", diff --git a/src/platform.ts b/src/platform.ts index c39f44ad35..aff3bdc742 100644 --- a/src/platform.ts +++ b/src/platform.ts @@ -89,7 +89,7 @@ export class PowerShellExeFinder { // PSCore version table private readonly pwshWindowsInstallationsVal: Lazy<{ stable: IPossiblePowerShellExe, preview: IPossiblePowerShellExe }>; - private readonly pwsh32WindowsInstallationsVal: + private readonly pwshAlternateBitnessWindowsInstallationsVal: Lazy<{ stable: IPossiblePowerShellExe, preview: IPossiblePowerShellExe }>; // PowerShell 6+ installation @@ -97,8 +97,8 @@ export class PowerShellExeFinder { private readonly previewPwshExeVal: Lazy; // 32-bit PowerShell 6+ installation - private readonly stable32BitPwshExeVal: Lazy; - private readonly preview32BitPwshExeVal: Lazy; + private readonly stableAlternateBitnessPwshExeVal: Lazy; + private readonly previewAlternateBitnessPwshExeVal: Lazy; // .NET Global Tool pwsh installation private readonly dotnetGlobalToolExeVal: Lazy; @@ -111,10 +111,10 @@ export class PowerShellExeFinder { private readonly previewSnapExeVal: Lazy; // Windows PowerShell installations - private readonly sys32WinPSExeVal: Lazy; - private readonly sysWow64WinPSExeVal: Lazy; - private readonly sysNativeWinPSExeVal: Lazy; + private readonly winPSExeVal: Lazy; + private readonly alternateBitnessWinPSExeVal: Lazy; + // Additional configured PowerShells private readonly additionalPSExeSettings: Iterable; /** @@ -130,20 +130,19 @@ export class PowerShellExeFinder { this.additionalPSExeSettings = additionalPowerShellExes || []; this.pwshWindowsInstallationsVal = new Lazy(() => this.findPSCoreWindowsInstallations()); - this.pwsh32WindowsInstallationsVal = new Lazy( - () => this.findPSCoreWindowsInstallations({ find32Bit: true })); - - this.stablePwshExeVal = new Lazy(() => this.findPSCoreStable()); - this.stable32BitPwshExeVal = new Lazy(() => this.findPSCore32BitStable()); - this.preview32BitPwshExeVal = new Lazy(() => this.findPSCore32BitPreview()); - this.previewPwshExeVal = new Lazy(() => this.findPSCorePreview()); - this.dotnetGlobalToolExeVal = new Lazy(() => this.findPSCoreDotnetGlobalTool()); - this.msixExeVal = new Lazy(() => this.findPSCoreMsix()); - this.stableSnapExeVal = new Lazy(() => this.findPSCoreStableSnap()); - this.previewSnapExeVal = new Lazy(() => this.findPSCorePreviewSnap()); - this.sys32WinPSExeVal = new Lazy(() => this.findSys32WinPS()); - this.sysWow64WinPSExeVal = new Lazy(() => this.findSysWow64WinPS()); - this.sysNativeWinPSExeVal = new Lazy(() => this.findSysNativeWinPS()); + this.pwshAlternateBitnessWindowsInstallationsVal = new Lazy( + () => this.findPSCoreWindowsInstallations({ findNonNativeBitness: true })); + + this.stablePwshExeVal = new Lazy(() => this.findPSCoreStable()); + this.stableAlternateBitnessPwshExeVal = new Lazy(() => this.findPSCoreAlternateBitnessStable()); + this.previewAlternateBitnessPwshExeVal = new Lazy(() => this.findPSCoreAlternateBitnessPreview()); + this.previewPwshExeVal = new Lazy(() => this.findPSCorePreview()); + this.dotnetGlobalToolExeVal = new Lazy(() => this.findPSCoreDotnetGlobalTool()); + this.msixExeVal = new Lazy(() => this.findPSCoreMsix()); + this.stableSnapExeVal = new Lazy(() => this.findPSCoreStableSnap()); + this.previewSnapExeVal = new Lazy(() => this.findPSCorePreviewSnap()); + this.winPSExeVal = new Lazy(() => this.findWinPS()); + this.alternateBitnessWinPSExeVal = new Lazy(() => this.findWinPS({ findNonNativeBitness: true })); } /** @@ -163,19 +162,21 @@ export class PowerShellExeFinder { } /** - * The stable 32-bit PowerShell 6+ installation. + * The stable non-process-native bitness PowerShell 6+ installation. + * This means 32-bit in a 64-bit process, and vice versa. * May be null if the installation directory is not present */ - private get pwsh32Stable(): IPossiblePowerShellExe { - return this.stable32BitPwshExeVal.value; + private get pwshAlternateBitnessStable(): IPossiblePowerShellExe { + return this.stableAlternateBitnessPwshExeVal.value; } /** - * The preview 32-bit PowerShell 6+ installation. + * The preview non-process-native bitness PowerShell 6+ installation. + * This means 32-bit in a 64-bit process, and vice versa. * May be null if the installation directory is not present. */ - private get pwsh32Preview(): IPossiblePowerShellExe { - return this.preview32BitPwshExeVal.value; + private get pwshAlternateBitnessPreview(): IPossiblePowerShellExe { + return this.previewAlternateBitnessPwshExeVal.value; } /** @@ -211,23 +212,16 @@ export class PowerShellExeFinder { * The Windows PowerShell installation under the %windir%\System32 folder. * This always exists. */ - private get sys32WinPS(): IPossiblePowerShellExe { - return this.sys32WinPSExeVal.value; - } - - /** - * The 32-bit Windows PowerShell installation when running in a 64-bit process. - */ - private get sysWoW64WinPS(): IPossiblePowerShellExe { - return this.sysWow64WinPSExeVal.value; + private get winPS(): IPossiblePowerShellExe { + return this.winPSExeVal.value; } /** - * The 64-bit Windows PowerShell installation when running - * in a 32-bit process on a 64-bit Windows. + * On 64-bit Windows, refers to the Windows PowerShell installation + * not native to the current process' bitness. */ - private get sysnativeWinPS(): IPossiblePowerShellExe { - return this.sysNativeWinPSExeVal.value; + private get alternateBitnessWinPS(): IPossiblePowerShellExe { + return this.alternateBitnessWinPSExeVal.value; } /** @@ -283,8 +277,8 @@ export class PowerShellExeFinder { switch (this.platformDetails.operatingSystem) { case OperatingSystem.Windows: // Windows may have a 32-bit pwsh.exe - if (this.pwsh32Stable) { - yield this.pwsh32Stable; + if (this.pwshAlternateBitnessStable) { + yield this.pwshAlternateBitnessStable; } // Also look for the MSIX/UWP installation if (this.pwshMsix) { @@ -317,23 +311,21 @@ export class PowerShellExeFinder { yield this.pwshSnapPreview; break; - // Finally, on Windows, get Windows PowerShell case OperatingSystem.Windows: - if (this.pwsh32Preview) { - yield this.pwsh32Preview; + // Look for pwsh-preview with the opposite bitness + if (this.pwshAlternateBitnessPreview) { + yield this.pwshAlternateBitnessPreview; } + + // Finally, get Windows PowerShell + // Get the natural Windows PowerShell for the process bitness - yield this.sys32WinPS; - - if (this.platformDetails.isProcess64Bit) { - // If this is a 64-bit process (which must be on a 64-bit OS), - // look for a 32-bit in SysWoW WinPS as well - yield this.sysWoW64WinPS; - } else if (this.platformDetails.isOS64Bit) { - // If this is a 32-bit process on a 64-bit operating system, - // look for the the system-native 64-bit WinPS too - yield this.sysnativeWinPS; + yield this.winPS; + + if (this.alternateBitnessWinPS) { + yield this.alternateBitnessWinPS; } + break; } } @@ -348,14 +340,11 @@ export class PowerShellExeFinder { } } - private findPSCoreWindowsInstallations(options?: { find32Bit: boolean }): + private findPSCoreWindowsInstallations(options?: { findNonNativeBitness?: boolean }): { stable: IPossiblePowerShellExe | null, preview: IPossiblePowerShellExe | null } | null { - const find32Bit: boolean = options && options.find32Bit; - - const programFilesPath: string = find32Bit - ? process.env["ProgramFiles(x86)"] - : process.env.ProgramFiles; + const programFilesPath: string = this.getProgramFilesPath( + { useAlternateBitness: options && options.findNonNativeBitness }); const powerShellInstallBaseDir = path.join(programFilesPath, "PowerShell"); @@ -420,7 +409,7 @@ export class PowerShellExeFinder { highestSeenPreviewNumber = currentPreview; } - const bitness: string = find32Bit + const bitness: string = programFilesPath.includes("x84") ? "(x86)" : "(x64)"; @@ -430,6 +419,42 @@ export class PowerShellExeFinder { }; } + private getProgramFilesPath(options?: { useAlternateBitness?: boolean }): string | null { + if (!options || !options.useAlternateBitness) { + return process.env.ProgramFiles; + } + + if (this.platformDetails.isProcess64Bit) { + return process.env["ProgramFiles(x86)"]; + } + + if (this.platformDetails.isOS64Bit) { + return process.env.ProgramW6432; + } + + // We're a 32-bit process on 32-bit Windows, there is no other Program Files dir + return null; + } + + private getSystem32Path(options?: { useAlternateBitness?: boolean }): string | null { + const windir: string = process.env.windir; + + if (!options || !options.useAlternateBitness) { + return path.join(windir, "System32"); + } + + if (this.platformDetails.isProcess64Bit) { + return path.join(windir, "SysWOW64"); + } + + if (this.platformDetails.isOS64Bit) { + return path.join(windir, "Sysnative"); + } + + // We're on a 32-bit Windows, so no alternate bitness + return null; + } + private findAdditionalPwshExe(additionalPwshSetting: Settings.IPowerShellAdditionalExePathSettings) { return new PSCoreExe( additionalPwshSetting.exePath, @@ -464,14 +489,14 @@ export class PowerShellExeFinder { } } - private findPSCore32BitStable(): IPossiblePowerShellExe { - return this.pwsh32WindowsInstallationsVal.value - && this.pwsh32WindowsInstallationsVal.value.stable; + private findPSCoreAlternateBitnessStable(): IPossiblePowerShellExe { + return this.pwshAlternateBitnessWindowsInstallationsVal.value + && this.pwshAlternateBitnessWindowsInstallationsVal.value.stable; } - private findPSCore32BitPreview(): IPossiblePowerShellExe { - return this.pwsh32WindowsInstallationsVal.value - && this.pwsh32WindowsInstallationsVal.value.preview; + private findPSCoreAlternateBitnessPreview(): IPossiblePowerShellExe { + return this.pwshAlternateBitnessWindowsInstallationsVal.value + && this.pwshAlternateBitnessWindowsInstallationsVal.value.preview; } private findPSCoreDotnetGlobalTool(): IPossiblePowerShellExe { @@ -485,7 +510,7 @@ export class PowerShellExeFinder { } private findPSCoreMsix(): IPossiblePowerShellExe { - const winPSPath: string = this.findSys32WinPS().exePath; + const winPSPath: string = this.findWinPS().exePath; const msixDir: string = child_process.execFileSync(winPSPath, ["-c", "(Get-AppxPackage -Name Microsoft.PowerShell).InstallLocation"]) .toString() .trim(); @@ -506,25 +531,26 @@ export class PowerShellExeFinder { return new PSCoreExe(SnapExePath, "PowerShell Preview Snap"); } - private findSys32WinPS(): IPossiblePowerShellExe { - const displayName: string = this.platformDetails.isProcess64Bit - ? WindowsPowerShell64BitLabel - : WindowsPowerShell32BitLabel; + private findWinPS(options?: { findNonNativeBitness: boolean }): IPossiblePowerShellExe { + const useAlternateBitness: boolean = options && options.findNonNativeBitness; - return new WinPSExe( - System32PowerShellPath, - displayName, - { knownToExist: true }); - } + const systemFolderPath: string = this.getSystem32Path({ useAlternateBitness }); + const winPSPath = path.join(systemFolderPath, "WindowsPowerShell", "v1.0", "powershell.exe"); - private findSysWow64WinPS(): IPossiblePowerShellExe { - return new WinPSExe(WinPS32BitPathOn64Bit, WindowsPowerShell32BitLabel); - } + let displayName: string; + if (this.platformDetails.isProcess64Bit) { + displayName = useAlternateBitness + ? WindowsPowerShell32BitLabel + : WindowsPowerShell64BitLabel; + } else if (this.platformDetails.isOS64Bit) { + displayName = useAlternateBitness + ? WindowsPowerShell64BitLabel + : WindowsPowerShell32BitLabel; + } else { + displayName = WindowsPowerShell32BitLabel; + } - private findSysNativeWinPS(): IPossiblePowerShellExe { - return new WinPSExe( - WinPS64BitPathOn32Bit, - WindowsPowerShell64BitLabel); + return new WinPSExe(winPSPath, displayName, { knownToExist: !useAlternateBitness }); } } diff --git a/test/platform.test.ts b/test/platform.test.ts index 32fec700f6..887febe27c 100644 --- a/test/platform.test.ts +++ b/test/platform.test.ts @@ -3,6 +3,11 @@ *--------------------------------------------------------*/ import * as assert from "assert"; +import * as child_process from "child_process"; +import * as fs from "fs"; +import mockFS = require("mock-fs"); +import FileSystem = require("mock-fs/lib/filesystem"); +import * as sinon from "sinon"; import * as platform from "../src/platform"; function checkDefaultPowerShellPath( @@ -45,7 +50,116 @@ function checkFixedWindowsPowerShellpath( }); } +interface ITestPlatform { + name: string; + platformDetails: platform.IPlatformDetails; + expectedPowerShellSequence: platform.IPowerShellExeDetails[]; + filesystem: FileSystem.DirectoryItems; +} + +const testPlatforms: ITestPlatform[] = [ + { + name: "Linux (all installations)", + platformDetails: { + operatingSystem: platform.OperatingSystem.Linux, + isOS64Bit: true, + isProcess64Bit: true, + }, + expectedPowerShellSequence: [ + { exePath: "/usr/bin/pwsh", displayName: "PowerShell (x64)" }, + { exePath: "/snap/bin/pwsh", displayName: "PowerShell Snap" }, + { exePath: "/usr/bin/pwsh-preview", displayName: "PowerShell Preview (x64)" }, + { exePath: "/snap/bin/pwsh-preview", displayName: "PowerShell Snap Preview" }, + ], + filesystem: { + "/usr/bin": { + "pwsh": "", + "pwsh-preview": "", + }, + "/snap/bin": { + "pwsh": "", + "pwsh-preview": "", + }, + }, + }, +]; + suite("Platform module", () => { + suite("PlatformDetails", () => { + const platformDetails: platform.IPlatformDetails = platform.getPlatformDetails(); + switch (process.platform) { + case "darwin": + assert.equal(platformDetails.operatingSystem, platform.OperatingSystem.MacOS); + assert.equal(platformDetails.isProcess64Bit, true); + assert.equal(platformDetails.isOS64Bit, true); + + case "linux": + assert.equal(platformDetails.operatingSystem, platform.OperatingSystem.Linux); + assert.equal(platformDetails.isProcess64Bit, true); + assert.equal(platformDetails.isOS64Bit, true); + return; + + case "win32": + assert.equal(platformDetails.operatingSystem, platform.OperatingSystem.Windows); + assert.equal(platformDetails.isProcess64Bit, process.arch === "x64"); + assert.equal(platformDetails.isOS64Bit, !!(platformDetails.isProcess64Bit || process.env.ProgramW6432)); + return; + + default: + assert.fail("Tests run on unsupported platform"); + } + }); + + suite("Default PowerShell installation", () => { + teardown(() => { + sinon.restore(); + mockFS.restore(); + }); + + for (const testPlatform of testPlatforms) { + test(`Default PowerShell path on ${testPlatform.name}`, () => { + mockFS(testPlatform.filesystem); + + const powerShellExeFinder = new platform.PowerShellExeFinder(testPlatform.platformDetails); + + const defaultPowerShell = powerShellExeFinder.getFirstAvailablePowerShellInstallation(); + const expectedPowerShell = testPlatform.expectedPowerShellSequence[0]; + + assert.strictEqual(defaultPowerShell.exePath, expectedPowerShell.exePath); + assert.strictEqual(defaultPowerShell.displayName, expectedPowerShell.displayName); + }); + } + }); + + suite("Expected PowerShell installation list", () => { + teardown(() => { + sinon.restore(); + mockFS.restore(); + }); + + for (const testPlatform of testPlatforms) { + test(`PowerShell installation list on ${testPlatform.name}`, () => { + mockFS(testPlatform.filesystem); + + const powerShellExeFinder = new platform.PowerShellExeFinder(testPlatform.platformDetails); + + const foundPowerShells = powerShellExeFinder.getAllAvailablePowerShellInstallations(); + + assert.strictEqual(foundPowerShells.length, testPlatform.expectedPowerShellSequence.length); + + for (let i = 0; i < foundPowerShells.length; i++) { + const foundPowerShell = foundPowerShells[i]; + const expectedPowerShell = testPlatform.expectedPowerShellSequence[i]; + + assert.strictEqual(foundPowerShell.exePath, expectedPowerShell.exePath); + assert.strictEqual(foundPowerShell.displayName, expectedPowerShell.displayName); + } + }); + } + }); +}); + +/* if (process.platform === "win32") { suite("64-bit Windows, 64-bit VS Code", () => { const platformDetails: platform.IPlatformDetails = { @@ -75,7 +189,7 @@ suite("Platform module", () => { platformDetails, platform.SysnativePowerShellPath, platform.System32PowerShellPath); - }).timeout(5000); + }); suite("64-bit Windows, 32-bit VS Code", () => { const platformDetails: platform.IPlatformDetails = { @@ -157,3 +271,4 @@ suite("Platform module", () => { }); } }); +*/ From e07adeb12609b0bd051995938f756c8fad58e613 Mon Sep 17 00:00:00 2001 From: Rob Holt Date: Tue, 22 Oct 2019 21:53:07 -0700 Subject: [PATCH 21/46] Add more tests --- src/platform.ts | 4 +- test/platform.test.ts | 127 ++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 123 insertions(+), 8 deletions(-) diff --git a/src/platform.ts b/src/platform.ts index aff3bdc742..87853f8ee0 100644 --- a/src/platform.ts +++ b/src/platform.ts @@ -409,7 +409,7 @@ export class PowerShellExeFinder { highestSeenPreviewNumber = currentPreview; } - const bitness: string = programFilesPath.includes("x84") + const bitness: string = programFilesPath.includes("x86") ? "(x86)" : "(x64)"; @@ -528,7 +528,7 @@ export class PowerShellExeFinder { } private findPSCorePreviewSnap(): IPossiblePowerShellExe { - return new PSCoreExe(SnapExePath, "PowerShell Preview Snap"); + return new PSCoreExe(SnapPreviewExePath, "PowerShell Preview Snap"); } private findWinPS(options?: { findNonNativeBitness: boolean }): IPossiblePowerShellExe { diff --git a/test/platform.test.ts b/test/platform.test.ts index 887febe27c..023d5c83e0 100644 --- a/test/platform.test.ts +++ b/test/platform.test.ts @@ -55,6 +55,7 @@ interface ITestPlatform { platformDetails: platform.IPlatformDetails; expectedPowerShellSequence: platform.IPowerShellExeDetails[]; filesystem: FileSystem.DirectoryItems; + environmentVars?: Record; } const testPlatforms: ITestPlatform[] = [ @@ -69,7 +70,7 @@ const testPlatforms: ITestPlatform[] = [ { exePath: "/usr/bin/pwsh", displayName: "PowerShell (x64)" }, { exePath: "/snap/bin/pwsh", displayName: "PowerShell Snap" }, { exePath: "/usr/bin/pwsh-preview", displayName: "PowerShell Preview (x64)" }, - { exePath: "/snap/bin/pwsh-preview", displayName: "PowerShell Snap Preview" }, + { exePath: "/snap/bin/pwsh-preview", displayName: "PowerShell Preview Snap" }, ], filesystem: { "/usr/bin": { @@ -82,9 +83,81 @@ const testPlatforms: ITestPlatform[] = [ }, }, }, + { + name: "MacOS (all installations)", + platformDetails: { + operatingSystem: platform.OperatingSystem.MacOS, + isOS64Bit: true, + isProcess64Bit: true, + }, + expectedPowerShellSequence: [ + { exePath: "/usr/local/bin/pwsh", displayName: "PowerShell (x64)" }, + { exePath: "/usr/local/bin/pwsh-preview", displayName: "PowerShell Preview (x64)" }, + ], + filesystem: { + "/usr/local/bin": { + "pwsh": "", + "pwsh-preview": "", + }, + }, + }, + { + name: "Windows 64-bit, 64-bit VSCode (all installations)", + platformDetails: { + operatingSystem: platform.OperatingSystem.Windows, + isOS64Bit: true, + isProcess64Bit: true, + }, + environmentVars: { + "ProgramFiles": "C:\\Program Files", + "ProgramFiles(x86)": "C:\\Program Files (x86)", + }, + expectedPowerShellSequence: [ + { exePath: "C:\\Program Files\\PowerShell\\6\\pwsh.exe", displayName: "PowerShell (x64)" }, + { exePath: "C:\\Program Files (x86)\\PowerShell\\6\\pwsh.exe", displayName: "PowerShell (x86)" }, + { + exePath: + "C:\\Program Files\\WindowsApps\\Microsoft.PowerShell_7.0.0.4_neutral__8wekyb3d8bbwe\\pwsh.exe", + displayName: "PowerShell MSIX", + }, + { exePath: "C:\\Program Files\\PowerShell\\7-preview\\pwsh.exe", displayName: "PowerShell Preview (x64)" }, + { exePath: "C:\\Program Files (x86)\\PowerShell\\7-preview\\pwsh.exe", displayName: "PowerShell Preview (x86)" }, + { exePath: "C:\\WINDOWS\\System32\\WindowsPowerShell\\v1.0\\powershell.exe", displayName: "Windows PowerShell (x64)" }, + { exePath: "C:\\WINDOWS\\SysWOW64\\WindowsPowerShell\\v1.0\\powershell.exe", displayName: "Windows PowerShell (x86)" }, + ], + filesystem: { + "C:\\Program Files\\PowerShell": { + "6": { + "pwsh.exe": "", + }, + "7-preview": { + "pwsh.exe": "", + }, + }, + "C:\\Program Files (x86)\\PowerShell": { + "6": { + "pwsh.exe": "", + }, + "7-preview": { + "pwsh.exe": "", + }, + }, + "C:\\Program Files\\WindowsApps\\Microsoft.PowerShell_7.0.0.4_neutral__8wekyb3d8bbwe": { + "pwsh.exe": "", + }, + "C:\\WINDOWS\\System32\\WindowsPowerShell\\v1.0": { + "powershell.exe": "", + }, + "C:\\WINDOWS\\SysWOW64\\WindowsPowerShell\\v1.0": { + "powershell.exe": "", + }, + }, + }, ]; suite("Platform module", () => { + let tempEnv: NodeJS.ProcessEnv; + suite("PlatformDetails", () => { const platformDetails: platform.IPlatformDetails = platform.getPlatformDetails(); switch (process.platform) { @@ -111,7 +184,12 @@ suite("Platform module", () => { }); suite("Default PowerShell installation", () => { + setup(() => { + tempEnv = Object.assign({}, process.env); + }); + teardown(() => { + process.env = tempEnv; sinon.restore(); mockFS.restore(); }); @@ -120,6 +198,22 @@ suite("Platform module", () => { test(`Default PowerShell path on ${testPlatform.name}`, () => { mockFS(testPlatform.filesystem); + // The type inference here is wrong, so we need typescript to ignore it + // @ts-ignore + sinon.stub(child_process, "execFileSync").callsFake((procName, args?, options?) => { + if (!procName.includes("powershell")) { + return child_process.execFileSync(procName, args, options); + } + + return "C:\\Program Files\\WindowsApps\\Microsoft.PowerShell_7.0.0.4_neutral__8wekyb3d8bbwe"; + }); + + if (testPlatform.environmentVars) { + for (const envVar of Object.keys(testPlatform.environmentVars)) { + process.env[envVar] = testPlatform.environmentVars[envVar]; + } + } + const powerShellExeFinder = new platform.PowerShellExeFinder(testPlatform.platformDetails); const defaultPowerShell = powerShellExeFinder.getFirstAvailablePowerShellInstallation(); @@ -132,7 +226,12 @@ suite("Platform module", () => { }); suite("Expected PowerShell installation list", () => { + setup(() => { + tempEnv = Object.assign({}, process.env); + }); + teardown(() => { + process.env = tempEnv; sinon.restore(); mockFS.restore(); }); @@ -141,19 +240,35 @@ suite("Platform module", () => { test(`PowerShell installation list on ${testPlatform.name}`, () => { mockFS(testPlatform.filesystem); + // The type inference here is wrong, so we need typescript to ignore it + // @ts-ignore + sinon.stub(child_process, "execFileSync").callsFake((procName, args?, options?) => { + if (!procName.includes("powershell")) { + return child_process.execFileSync(procName, args, options); + } + + return "C:\\Program Files\\WindowsApps\\Microsoft.PowerShell_7.0.0.4_neutral__8wekyb3d8bbwe"; + }); + + if (testPlatform.environmentVars) { + for (const envVar of Object.keys(testPlatform.environmentVars)) { + process.env[envVar] = testPlatform.environmentVars[envVar]; + } + } + const powerShellExeFinder = new platform.PowerShellExeFinder(testPlatform.platformDetails); const foundPowerShells = powerShellExeFinder.getAllAvailablePowerShellInstallations(); - assert.strictEqual(foundPowerShells.length, testPlatform.expectedPowerShellSequence.length); - - for (let i = 0; i < foundPowerShells.length; i++) { + for (let i = 0; i < testPlatform.expectedPowerShellSequence.length; i++) { const foundPowerShell = foundPowerShells[i]; const expectedPowerShell = testPlatform.expectedPowerShellSequence[i]; - assert.strictEqual(foundPowerShell.exePath, expectedPowerShell.exePath); - assert.strictEqual(foundPowerShell.displayName, expectedPowerShell.displayName); + assert.strictEqual(foundPowerShell && foundPowerShell.exePath, expectedPowerShell.exePath); + assert.strictEqual(foundPowerShell && foundPowerShell.displayName, expectedPowerShell.displayName); } + + assert.strictEqual(foundPowerShells.length, testPlatform.expectedPowerShellSequence.length); }); } }); From 79a0ceca67e3518e8e80a3d7bf4c3ad28546712e Mon Sep 17 00:00:00 2001 From: Rob Holt Date: Tue, 22 Oct 2019 22:15:16 -0700 Subject: [PATCH 22/46] Add tests for other platforms --- test/platform.test.ts | 233 ++++++++++++++---------------------------- 1 file changed, 79 insertions(+), 154 deletions(-) diff --git a/test/platform.test.ts b/test/platform.test.ts index 023d5c83e0..af42de7bc4 100644 --- a/test/platform.test.ts +++ b/test/platform.test.ts @@ -10,46 +10,6 @@ import FileSystem = require("mock-fs/lib/filesystem"); import * as sinon from "sinon"; import * as platform from "../src/platform"; -function checkDefaultPowerShellPath( - platformDetails: platform.IPlatformDetails, - expectedPath: string) { - const powerShellExeFinder = new platform.PowerShellExeFinder(platformDetails); - test("returns correct default path", () => { - const defaultPath = powerShellExeFinder.getFirstAvailablePowerShellInstallation().exePath; - assert.equal(defaultPath, expectedPath); - }); -} - -function checkAvailableWindowsPowerShellPaths( - platformDetails: platform.IPlatformDetails, - expectedPaths: platform.IPowerShellExeDetails[]) { - - const pwshExeFinder = new platform.PowerShellExeFinder(platformDetails); - - test("correctly enumerates available Windows PowerShell paths", () => { - - // The system may return PowerShell Core paths so only - // enumerate the first list items. - let i = 0; - for (const pwshExe of pwshExeFinder.enumeratePowerShellInstallations()) { - assert.equal(pwshExe.displayName, expectedPaths[i].displayName); - assert.equal(pwshExe.exePath.toLowerCase(), expectedPaths[i].exePath.toLowerCase()); - i++; - } - }); -} - -function checkFixedWindowsPowerShellpath( - platformDetails: platform.IPlatformDetails, - inputPath: string, - expectedPath: string) { - test("fixes incorrect Windows PowerShell Sys* path", () => { - assert.equal( - platform.fixWindowsPowerShellPath(inputPath, platformDetails), - expectedPath); - }); -} - interface ITestPlatform { name: string; platformDetails: platform.IPlatformDetails; @@ -111,6 +71,7 @@ const testPlatforms: ITestPlatform[] = [ environmentVars: { "ProgramFiles": "C:\\Program Files", "ProgramFiles(x86)": "C:\\Program Files (x86)", + "winddir": "C:\\WINDOWS", }, expectedPowerShellSequence: [ { exePath: "C:\\Program Files\\PowerShell\\6\\pwsh.exe", displayName: "PowerShell (x64)" }, @@ -153,6 +114,84 @@ const testPlatforms: ITestPlatform[] = [ }, }, }, + { + name: "Windows 64-bit, 64-bit VSCode (only Windows PowerShell)", + platformDetails: { + operatingSystem: platform.OperatingSystem.Windows, + isOS64Bit: true, + isProcess64Bit: true, + }, + environmentVars: { + "ProgramFiles": "C:\\Program Files", + "ProgramFiles(x86)": "C:\\Program Files (x86)", + "winddir": "C:\\WINDOWS", + }, + expectedPowerShellSequence: [ + { exePath: "C:\\WINDOWS\\System32\\WindowsPowerShell\\v1.0\\powershell.exe", displayName: "Windows PowerShell (x64)" }, + { exePath: "C:\\WINDOWS\\SysWOW64\\WindowsPowerShell\\v1.0\\powershell.exe", displayName: "Windows PowerShell (x86)" }, + ], + filesystem: { + "C:\\WINDOWS\\System32\\WindowsPowerShell\\v1.0": { + "powershell.exe": "", + }, + "C:\\WINDOWS\\SysWOW64\\WindowsPowerShell\\v1.0": { + "powershell.exe": "", + }, + }, + }, + { + name: "Windows 64-bit, 32-bit VSCode (all installations)", + platformDetails: { + operatingSystem: platform.OperatingSystem.Windows, + isOS64Bit: true, + isProcess64Bit: false, + }, + environmentVars: { + "ProgramFiles": "C:\\Program Files", + "ProgramFiles(x86)": "C:\\Program Files (x86)", + "winddir": "C:\\WINDOWS", + }, + expectedPowerShellSequence: [ + { exePath: "C:\\Program Files (x86)\\PowerShell\\6\\pwsh.exe", displayName: "PowerShell (x86)" }, + { exePath: "C:\\Program Files\\PowerShell\\6\\pwsh.exe", displayName: "PowerShell (x64)" }, + { + exePath: + "C:\\Program Files\\WindowsApps\\Microsoft.PowerShell_7.0.0.4_neutral__8wekyb3d8bbwe\\pwsh.exe", + displayName: "PowerShell MSIX", + }, + { exePath: "C:\\Program Files (x86)\\PowerShell\\7-preview\\pwsh.exe", displayName: "PowerShell Preview (x86)" }, + { exePath: "C:\\Program Files\\PowerShell\\7-preview\\pwsh.exe", displayName: "PowerShell Preview (x64)" }, + { exePath: "C:\\WINDOWS\\Sysnative\\WindowsPowerShell\\v1.0\\powershell.exe", displayName: "Windows PowerShell (x86)" }, + { exePath: "C:\\WINDOWS\\System32\\WindowsPowerShell\\v1.0\\powershell.exe", displayName: "Windows PowerShell (x64)" }, + ], + filesystem: { + "C:\\Program Files\\PowerShell": { + "6": { + "pwsh.exe": "", + }, + "7-preview": { + "pwsh.exe": "", + }, + }, + "C:\\Program Files (x86)\\PowerShell": { + "6": { + "pwsh.exe": "", + }, + "7-preview": { + "pwsh.exe": "", + }, + }, + "C:\\Program Files\\WindowsApps\\Microsoft.PowerShell_7.0.0.4_neutral__8wekyb3d8bbwe": { + "pwsh.exe": "", + }, + "C:\\WINDOWS\\System32\\WindowsPowerShell\\v1.0": { + "powershell.exe": "", + }, + "C:\\WINDOWS\\Sysnative\\WindowsPowerShell\\v1.0": { + "powershell.exe": "", + }, + }, + }, ]; suite("Platform module", () => { @@ -273,117 +312,3 @@ suite("Platform module", () => { } }); }); - -/* - if (process.platform === "win32") { - suite("64-bit Windows, 64-bit VS Code", () => { - const platformDetails: platform.IPlatformDetails = { - operatingSystem: platform.OperatingSystem.Windows, - isOS64Bit: true, - isProcess64Bit: true, - }; - - checkDefaultPowerShellPath( - platformDetails, - platform.System32PowerShellPath); - - checkAvailableWindowsPowerShellPaths( - platformDetails, - [ - { - displayName: platform.WindowsPowerShell64BitLabel, - exePath: platform.System32PowerShellPath, - }, - { - displayName: platform.WindowsPowerShell32BitLabel, - exePath: platform.SysWow64PowerShellPath, - }, - ]); - - checkFixedWindowsPowerShellpath( - platformDetails, - platform.SysnativePowerShellPath, - platform.System32PowerShellPath); - }); - - suite("64-bit Windows, 32-bit VS Code", () => { - const platformDetails: platform.IPlatformDetails = { - operatingSystem: platform.OperatingSystem.Windows, - isOS64Bit: true, - isProcess64Bit: false, - }; - - checkDefaultPowerShellPath( - platformDetails, - platform.System32PowerShellPath); - - checkAvailableWindowsPowerShellPaths( - platformDetails, - [ - { - displayName: platform.WindowsPowerShell32BitLabel, - exePath: platform.System32PowerShellPath, - }, - { - displayName: platform.WindowsPowerShell64BitLabel, - exePath: platform.SysnativePowerShellPath, - }, - ]); - - checkFixedWindowsPowerShellpath( - platformDetails, - platform.SysWow64PowerShellPath, - platform.System32PowerShellPath); - }); - - suite("32-bit Windows, 32-bit VS Code", () => { - const platformDetails: platform.IPlatformDetails = { - operatingSystem: platform.OperatingSystem.Windows, - isOS64Bit: false, - isProcess64Bit: false, - }; - - checkDefaultPowerShellPath( - platformDetails, - platform.System32PowerShellPath); - - checkAvailableWindowsPowerShellPaths( - platformDetails, - [ - { - displayName: platform.WindowsPowerShell32BitLabel, - exePath: platform.System32PowerShellPath, - }, - ]); - }); - } - - if (process.platform === "darwin") { - suite("macOS", () => { - const platformDetails: platform.IPlatformDetails = { - operatingSystem: platform.OperatingSystem.MacOS, - isOS64Bit: true, - isProcess64Bit: true, - }; - - checkDefaultPowerShellPath( - platformDetails, - "/usr/local/bin/pwsh"); - }); - } - - if (process.platform === "linux") { - suite("linux", () => { - const platformDetails: platform.IPlatformDetails = { - operatingSystem: platform.OperatingSystem.Linux, - isOS64Bit: true, - isProcess64Bit: true, - }; - - checkDefaultPowerShellPath( - platformDetails, - "/usr/bin/pwsh"); - }); - } -}); -*/ From 86c2e166710c23eff0c7bf21c55aa04c7d35720b Mon Sep 17 00:00:00 2001 From: Rob Holt Date: Wed, 23 Oct 2019 09:21:55 -0700 Subject: [PATCH 23/46] Add more platform tests --- src/platform.ts | 8 +-- test/platform.test.ts | 140 +++++++++++++++++++++++++++++++++++++++--- 2 files changed, 136 insertions(+), 12 deletions(-) diff --git a/src/platform.ts b/src/platform.ts index 87853f8ee0..d85817c271 100644 --- a/src/platform.ts +++ b/src/platform.ts @@ -464,10 +464,10 @@ export class PowerShellExeFinder { private findPSCoreStable(): IPossiblePowerShellExe { switch (this.platformDetails.operatingSystem) { case OperatingSystem.Linux: - return new PSCoreExe(LinuxExePath, "PowerShell (x64)"); + return new PSCoreExe(LinuxExePath, "PowerShell"); case OperatingSystem.MacOS: - return new PSCoreExe(MacOSExePath, "PowerShell (x64)"); + return new PSCoreExe(MacOSExePath, "PowerShell"); case OperatingSystem.Windows: return this.pwshWindowsInstallationsVal.value @@ -478,10 +478,10 @@ export class PowerShellExeFinder { private findPSCorePreview(): IPossiblePowerShellExe { switch (this.platformDetails.operatingSystem) { case OperatingSystem.Linux: - return new PSCoreExe(LinuxPreviewExePath, "PowerShell Preview (x64)"); + return new PSCoreExe(LinuxPreviewExePath, "PowerShell Preview"); case OperatingSystem.MacOS: - return new PSCoreExe(MacOSPreviewExePath, "PowerShell Preview (x64)"); + return new PSCoreExe(MacOSPreviewExePath, "PowerShell Preview"); case OperatingSystem.Windows: return this.pwshWindowsInstallationsVal.value diff --git a/test/platform.test.ts b/test/platform.test.ts index af42de7bc4..28ff1b5bf9 100644 --- a/test/platform.test.ts +++ b/test/platform.test.ts @@ -4,7 +4,6 @@ import * as assert from "assert"; import * as child_process from "child_process"; -import * as fs from "fs"; import mockFS = require("mock-fs"); import FileSystem = require("mock-fs/lib/filesystem"); import * as sinon from "sinon"; @@ -27,9 +26,9 @@ const testPlatforms: ITestPlatform[] = [ isProcess64Bit: true, }, expectedPowerShellSequence: [ - { exePath: "/usr/bin/pwsh", displayName: "PowerShell (x64)" }, + { exePath: "/usr/bin/pwsh", displayName: "PowerShell" }, { exePath: "/snap/bin/pwsh", displayName: "PowerShell Snap" }, - { exePath: "/usr/bin/pwsh-preview", displayName: "PowerShell Preview (x64)" }, + { exePath: "/usr/bin/pwsh-preview", displayName: "PowerShell Preview" }, { exePath: "/snap/bin/pwsh-preview", displayName: "PowerShell Preview Snap" }, ], filesystem: { @@ -51,8 +50,8 @@ const testPlatforms: ITestPlatform[] = [ isProcess64Bit: true, }, expectedPowerShellSequence: [ - { exePath: "/usr/local/bin/pwsh", displayName: "PowerShell (x64)" }, - { exePath: "/usr/local/bin/pwsh-preview", displayName: "PowerShell Preview (x64)" }, + { exePath: "/usr/local/bin/pwsh", displayName: "PowerShell" }, + { exePath: "/usr/local/bin/pwsh-preview", displayName: "PowerShell Preview" }, ], filesystem: { "/usr/local/bin": { @@ -147,7 +146,7 @@ const testPlatforms: ITestPlatform[] = [ isProcess64Bit: false, }, environmentVars: { - "ProgramFiles": "C:\\Program Files", + "ProgramFiles": "C:\\Program Files (x86)", "ProgramFiles(x86)": "C:\\Program Files (x86)", "winddir": "C:\\WINDOWS", }, @@ -161,8 +160,8 @@ const testPlatforms: ITestPlatform[] = [ }, { exePath: "C:\\Program Files (x86)\\PowerShell\\7-preview\\pwsh.exe", displayName: "PowerShell Preview (x86)" }, { exePath: "C:\\Program Files\\PowerShell\\7-preview\\pwsh.exe", displayName: "PowerShell Preview (x64)" }, - { exePath: "C:\\WINDOWS\\Sysnative\\WindowsPowerShell\\v1.0\\powershell.exe", displayName: "Windows PowerShell (x86)" }, - { exePath: "C:\\WINDOWS\\System32\\WindowsPowerShell\\v1.0\\powershell.exe", displayName: "Windows PowerShell (x64)" }, + { exePath: "C:\\WINDOWS\\System32\\WindowsPowerShell\\v1.0\\powershell.exe", displayName: "Windows PowerShell (x86)" }, + { exePath: "C:\\WINDOWS\\Sysnative\\WindowsPowerShell\\v1.0\\powershell.exe", displayName: "Windows PowerShell (x64)" }, ], filesystem: { "C:\\Program Files\\PowerShell": { @@ -192,6 +191,131 @@ const testPlatforms: ITestPlatform[] = [ }, }, }, + { + name: "Windows 64-bit, 32-bit VSCode (Windows PowerShell only)", + platformDetails: { + operatingSystem: platform.OperatingSystem.Windows, + isOS64Bit: true, + isProcess64Bit: false, + }, + environmentVars: { + "ProgramFiles": "C:\\Program Files (x86)", + "ProgramFiles(x86)": "C:\\Program Files (x86)", + "winddir": "C:\\WINDOWS", + }, + expectedPowerShellSequence: [ + { exePath: "C:\\WINDOWS\\System32\\WindowsPowerShell\\v1.0\\powershell.exe", displayName: "Windows PowerShell (x86)" }, + { exePath: "C:\\WINDOWS\\Sysnative\\WindowsPowerShell\\v1.0\\powershell.exe", displayName: "Windows PowerShell (x64)" }, + ], + filesystem: { + "C:\\WINDOWS\\System32\\WindowsPowerShell\\v1.0": { + "powershell.exe": "", + }, + "C:\\WINDOWS\\Sysnative\\WindowsPowerShell\\v1.0": { + "powershell.exe": "", + }, + }, + }, + { + name: "Windows 32-bit, 32-bit VSCode (all installations)", + platformDetails: { + operatingSystem: platform.OperatingSystem.Windows, + isOS64Bit: true, + isProcess64Bit: false, + }, + environmentVars: { + "ProgramFiles": "C:\\Program Files (x86)", + "ProgramFiles(x86)": "C:\\Program Files (x86)", + "winddir": "C:\\WINDOWS", + }, + expectedPowerShellSequence: [ + { exePath: "C:\\Program Files (x86)\\PowerShell\\6\\pwsh.exe", displayName: "PowerShell (x86)" }, + { exePath: "C:\\Program Files (x86)\\PowerShell\\7-preview\\pwsh.exe", displayName: "PowerShell Preview (x86)" }, + { exePath: "C:\\WINDOWS\\System32\\WindowsPowerShell\\v1.0\\powershell.exe", displayName: "Windows PowerShell (x86)" }, + ], + filesystem: { + "C:\\Program Files (x86)\\PowerShell": { + "6": { + "pwsh.exe": "", + }, + "7-preview": { + "pwsh.exe": "", + }, + }, + "C:\\WINDOWS\\System32\\WindowsPowerShell\\v1.0": { + "powershell.exe": "", + }, + }, + }, + { + name: "Windows 32-bit, 32-bit VSCode (Windows PowerShell only)", + platformDetails: { + operatingSystem: platform.OperatingSystem.Windows, + isOS64Bit: true, + isProcess64Bit: false, + }, + environmentVars: { + "ProgramFiles": "C:\\Program Files (x86)", + "ProgramFiles(x86)": "C:\\Program Files (x86)", + "winddir": "C:\\WINDOWS", + }, + expectedPowerShellSequence: [ + { exePath: "C:\\WINDOWS\\System32\\WindowsPowerShell\\v1.0\\powershell.exe", displayName: "Windows PowerShell (x86)" }, + ], + filesystem: { + "C:\\WINDOWS\\System32\\WindowsPowerShell\\v1.0": { + "powershell.exe": "", + }, + }, + }, + { + name: "Linux (stable only)", + platformDetails: { + operatingSystem: platform.OperatingSystem.Linux, + isOS64Bit: true, + isProcess64Bit: true, + }, + expectedPowerShellSequence: [ + { exePath: "/usr/bin/pwsh", displayName: "PowerShell" }, + ], + filesystem: { + "/usr/bin": { + pwsh: "", + }, + }, + }, + { + name: "Linux (stable snap only)", + platformDetails: { + operatingSystem: platform.OperatingSystem.Linux, + isOS64Bit: true, + isProcess64Bit: true, + }, + expectedPowerShellSequence: [ + { exePath: "/snap/bin/pwsh", displayName: "PowerShell Snap" }, + ], + filesystem: { + "/snap/bin": { + pwsh: "", + }, + }, + }, + { + name: "MacOS (stable only)", + platformDetails: { + operatingSystem: platform.OperatingSystem.MacOS, + isOS64Bit: true, + isProcess64Bit: true, + }, + expectedPowerShellSequence: [ + { exePath: "/usr/local/bin/pwsh", displayName: "PowerShell" }, + ], + filesystem: { + "/usr/local/bin": { + pwsh: "", + }, + }, + }, ]; suite("Platform module", () => { From 41f8b18007f8da47c55010a0b034fb1f5a20737d Mon Sep 17 00:00:00 2001 From: Rob Holt Date: Wed, 23 Oct 2019 09:59:01 -0700 Subject: [PATCH 24/46] Fix 'winddir' --- test/platform.test.ts | 88 ++++++++++++++++++++++++++++++++++++++----- 1 file changed, 78 insertions(+), 10 deletions(-) diff --git a/test/platform.test.ts b/test/platform.test.ts index 28ff1b5bf9..feae9a1691 100644 --- a/test/platform.test.ts +++ b/test/platform.test.ts @@ -9,15 +9,28 @@ import FileSystem = require("mock-fs/lib/filesystem"); import * as sinon from "sinon"; import * as platform from "../src/platform"; +/** + * Describes a platform on which the PowerShell extension should work, + * including the test conditions (filesystem, environment variables), + * and the expected PowerShell installations the extension will resolve. + * The default PowerShell is the first installation. + */ interface ITestPlatform { name: string; platformDetails: platform.IPlatformDetails; - expectedPowerShellSequence: platform.IPowerShellExeDetails[]; filesystem: FileSystem.DirectoryItems; environmentVars?: Record; } -const testPlatforms: ITestPlatform[] = [ +interface ITestPlatformSuccessCase extends ITestPlatform { + expectedPowerShellSequence: platform.IPowerShellExeDetails[]; +} + +interface ITestPlatformFailureCase extends ITestPlatform { + error: string; +} + +const successTestCases: ITestPlatformSuccessCase[] = [ { name: "Linux (all installations)", platformDetails: { @@ -70,7 +83,7 @@ const testPlatforms: ITestPlatform[] = [ environmentVars: { "ProgramFiles": "C:\\Program Files", "ProgramFiles(x86)": "C:\\Program Files (x86)", - "winddir": "C:\\WINDOWS", + "windir": "C:\\WINDOWS", }, expectedPowerShellSequence: [ { exePath: "C:\\Program Files\\PowerShell\\6\\pwsh.exe", displayName: "PowerShell (x64)" }, @@ -123,7 +136,7 @@ const testPlatforms: ITestPlatform[] = [ environmentVars: { "ProgramFiles": "C:\\Program Files", "ProgramFiles(x86)": "C:\\Program Files (x86)", - "winddir": "C:\\WINDOWS", + "windir": "C:\\WINDOWS", }, expectedPowerShellSequence: [ { exePath: "C:\\WINDOWS\\System32\\WindowsPowerShell\\v1.0\\powershell.exe", displayName: "Windows PowerShell (x64)" }, @@ -148,7 +161,7 @@ const testPlatforms: ITestPlatform[] = [ environmentVars: { "ProgramFiles": "C:\\Program Files (x86)", "ProgramFiles(x86)": "C:\\Program Files (x86)", - "winddir": "C:\\WINDOWS", + "windir": "C:\\WINDOWS", }, expectedPowerShellSequence: [ { exePath: "C:\\Program Files (x86)\\PowerShell\\6\\pwsh.exe", displayName: "PowerShell (x86)" }, @@ -201,7 +214,7 @@ const testPlatforms: ITestPlatform[] = [ environmentVars: { "ProgramFiles": "C:\\Program Files (x86)", "ProgramFiles(x86)": "C:\\Program Files (x86)", - "winddir": "C:\\WINDOWS", + "windir": "C:\\WINDOWS", }, expectedPowerShellSequence: [ { exePath: "C:\\WINDOWS\\System32\\WindowsPowerShell\\v1.0\\powershell.exe", displayName: "Windows PowerShell (x86)" }, @@ -226,7 +239,7 @@ const testPlatforms: ITestPlatform[] = [ environmentVars: { "ProgramFiles": "C:\\Program Files (x86)", "ProgramFiles(x86)": "C:\\Program Files (x86)", - "winddir": "C:\\WINDOWS", + "windir": "C:\\WINDOWS", }, expectedPowerShellSequence: [ { exePath: "C:\\Program Files (x86)\\PowerShell\\6\\pwsh.exe", displayName: "PowerShell (x86)" }, @@ -257,7 +270,7 @@ const testPlatforms: ITestPlatform[] = [ environmentVars: { "ProgramFiles": "C:\\Program Files (x86)", "ProgramFiles(x86)": "C:\\Program Files (x86)", - "winddir": "C:\\WINDOWS", + "windir": "C:\\WINDOWS", }, expectedPowerShellSequence: [ { exePath: "C:\\WINDOWS\\System32\\WindowsPowerShell\\v1.0\\powershell.exe", displayName: "Windows PowerShell (x86)" }, @@ -318,6 +331,29 @@ const testPlatforms: ITestPlatform[] = [ }, ]; +const errorTestCases: ITestPlatformFailureCase[] = [ + { + name: "Linux (no PowerShell)", + platformDetails: { + operatingSystem: platform.OperatingSystem.Linux, + isOS64Bit: true, + isProcess64Bit: true, + }, + filesystem: {}, + error: "Bad", + }, + { + name: "MacOS (no PowerShell)", + platformDetails: { + operatingSystem: platform.OperatingSystem.MacOS, + isOS64Bit: true, + isProcess64Bit: true, + }, + filesystem: {}, + error: "Bad", + }, +]; + suite("Platform module", () => { let tempEnv: NodeJS.ProcessEnv; @@ -357,7 +393,7 @@ suite("Platform module", () => { mockFS.restore(); }); - for (const testPlatform of testPlatforms) { + for (const testPlatform of successTestCases) { test(`Default PowerShell path on ${testPlatform.name}`, () => { mockFS(testPlatform.filesystem); @@ -386,6 +422,38 @@ suite("Platform module", () => { assert.strictEqual(defaultPowerShell.displayName, expectedPowerShell.displayName); }); } + + for (const testPlatform of errorTestCases) { + test(`Extension startup fails gracefully on ${testPlatform.name}`, () => { + mockFS(testPlatform.filesystem); + + // The type inference here is wrong, so we need typescript to ignore it + // @ts-ignore + sinon.stub(child_process, "execFileSync").callsFake((procName, args?, options?) => { + if (!procName.includes("powershell")) { + return child_process.execFileSync(procName, args, options); + } + + return "C:\\Program Files\\WindowsApps\\Microsoft.PowerShell_7.0.0.4_neutral__8wekyb3d8bbwe"; + }); + + if (testPlatform.environmentVars) { + for (const envVar of Object.keys(testPlatform.environmentVars)) { + process.env[envVar] = testPlatform.environmentVars[envVar]; + } + } + + const powerShellExeFinder = new platform.PowerShellExeFinder(testPlatform.platformDetails); + + let error; + try { + const defaultPowerShell = powerShellExeFinder.getFirstAvailablePowerShellInstallation(); + error = defaultPowerShell; + } catch (e) { + error = e; + } + }); + } }); suite("Expected PowerShell installation list", () => { @@ -399,7 +467,7 @@ suite("Platform module", () => { mockFS.restore(); }); - for (const testPlatform of testPlatforms) { + for (const testPlatform of successTestCases) { test(`PowerShell installation list on ${testPlatform.name}`, () => { mockFS(testPlatform.filesystem); From 852c85cafa08564ea7a3468ae1316fcbcfe7a4bd Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Wed, 23 Oct 2019 12:52:38 -0700 Subject: [PATCH 25/46] Attempt to bring tests cross-plat --- .vscode/launch.json | 8 +- src/platform.ts | 7 +- test/platform.test.ts | 205 ++++++++++++++++++++++++------------------ 3 files changed, 133 insertions(+), 87 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index f53cedf458..6f9b811cd1 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -32,7 +32,13 @@ "stopOnEntry": false, "sourceMaps": true, "outFiles": ["${workspaceRoot}/out/test/**/*.js"], - "preLaunchTask": "Build" + "preLaunchTask": "Build", + "skipFiles": [ + "${workspaceFolder}/node_modules/**/*.js", + "${workspaceFolder}/lib/**/*.js", + "/private/var/folders/**/*.js", + "/**/*.js" + ] }, { "name": "Attach", diff --git a/src/platform.ts b/src/platform.ts index d85817c271..f2402de081 100644 --- a/src/platform.ts +++ b/src/platform.ts @@ -555,7 +555,12 @@ export class PowerShellExeFinder { } export function getWindowsSystemPowerShellPath(systemFolderName: string) { - return `${process.env.windir}\\${systemFolderName}\\WindowsPowerShell\\v1.0\\powershell.exe`; + return path.join( + process.env.windir, + systemFolderName, + "WindowsPowerShell", + "v1.0", + "powershell.exe"); } export function fixWindowsPowerShellPath(powerShellExePath: string, platformDetails: IPlatformDetails): string { diff --git a/test/platform.test.ts b/test/platform.test.ts index feae9a1691..08bf6ad182 100644 --- a/test/platform.test.ts +++ b/test/platform.test.ts @@ -11,9 +11,7 @@ import * as platform from "../src/platform"; /** * Describes a platform on which the PowerShell extension should work, - * including the test conditions (filesystem, environment variables), - * and the expected PowerShell installations the extension will resolve. - * The default PowerShell is the first installation. + * including the test conditions (filesystem, environment variables). */ interface ITestPlatform { name: string; @@ -22,14 +20,16 @@ interface ITestPlatform { environmentVars?: Record; } +/** + * A platform where the extension should find a PowerShell, + * including the sequence of PowerShell installations that should be found. + * The expected default PowerShell is the first installation. + */ interface ITestPlatformSuccessCase extends ITestPlatform { expectedPowerShellSequence: platform.IPowerShellExeDetails[]; } -interface ITestPlatformFailureCase extends ITestPlatform { - error: string; -} - +// Platform configurations where we expect to find a set of PowerShells const successTestCases: ITestPlatformSuccessCase[] = [ { name: "Linux (all installations)", @@ -81,25 +81,28 @@ const successTestCases: ITestPlatformSuccessCase[] = [ isProcess64Bit: true, }, environmentVars: { - "ProgramFiles": "C:\\Program Files", - "ProgramFiles(x86)": "C:\\Program Files (x86)", - "windir": "C:\\WINDOWS", + "ProgramFiles": "C:/Program Files", + "ProgramFiles(x86)": "C:/Program Files (x86)", + "windir": "C:/WINDOWS", }, expectedPowerShellSequence: [ - { exePath: "C:\\Program Files\\PowerShell\\6\\pwsh.exe", displayName: "PowerShell (x64)" }, - { exePath: "C:\\Program Files (x86)\\PowerShell\\6\\pwsh.exe", displayName: "PowerShell (x86)" }, + { exePath: "C:/Program Files/PowerShell/6/pwsh.exe", displayName: "PowerShell (x64)" }, + { exePath: "C:/Program Files (x86)/PowerShell/6/pwsh.exe", displayName: "PowerShell (x86)" }, { exePath: - "C:\\Program Files\\WindowsApps\\Microsoft.PowerShell_7.0.0.4_neutral__8wekyb3d8bbwe\\pwsh.exe", + "C:/Program Files/WindowsApps/Microsoft.PowerShell_7.0.0.4_neutral__8wekyb3d8bbwe/pwsh.exe", displayName: "PowerShell MSIX", }, - { exePath: "C:\\Program Files\\PowerShell\\7-preview\\pwsh.exe", displayName: "PowerShell Preview (x64)" }, - { exePath: "C:\\Program Files (x86)\\PowerShell\\7-preview\\pwsh.exe", displayName: "PowerShell Preview (x86)" }, - { exePath: "C:\\WINDOWS\\System32\\WindowsPowerShell\\v1.0\\powershell.exe", displayName: "Windows PowerShell (x64)" }, - { exePath: "C:\\WINDOWS\\SysWOW64\\WindowsPowerShell\\v1.0\\powershell.exe", displayName: "Windows PowerShell (x86)" }, + { exePath: "C:/Program Files/PowerShell/7-preview/pwsh.exe", displayName: "PowerShell Preview (x64)" }, + { + exePath: "C:/Program Files (x86)/PowerShell/7-preview/pwsh.exe", + displayName: "PowerShell Preview (x86)", + }, + { exePath: "C:/WINDOWS/System32/WindowsPowerShell/v1.0/powershell.exe", displayName: "Windows PowerShell (x64)" }, + { exePath: "C:/WINDOWS/SysWOW64/WindowsPowerShell/v1.0/powershell.exe", displayName: "Windows PowerShell (x86)" }, ], filesystem: { - "C:\\Program Files\\PowerShell": { + "C:/Program Files/PowerShell": { "6": { "pwsh.exe": "", }, @@ -107,7 +110,7 @@ const successTestCases: ITestPlatformSuccessCase[] = [ "pwsh.exe": "", }, }, - "C:\\Program Files (x86)\\PowerShell": { + "C:/Program Files (x86)/PowerShell": { "6": { "pwsh.exe": "", }, @@ -115,13 +118,13 @@ const successTestCases: ITestPlatformSuccessCase[] = [ "pwsh.exe": "", }, }, - "C:\\Program Files\\WindowsApps\\Microsoft.PowerShell_7.0.0.4_neutral__8wekyb3d8bbwe": { + "C:/Program Files/WindowsApps/Microsoft.PowerShell_7.0.0.4_neutral__8wekyb3d8bbwe": { "pwsh.exe": "", }, - "C:\\WINDOWS\\System32\\WindowsPowerShell\\v1.0": { + "C:/WINDOWS/System32/WindowsPowerShell/v1.0": { "powershell.exe": "", }, - "C:\\WINDOWS\\SysWOW64\\WindowsPowerShell\\v1.0": { + "C:/WINDOWS/SysWOW64/WindowsPowerShell/v1.0": { "powershell.exe": "", }, }, @@ -134,19 +137,19 @@ const successTestCases: ITestPlatformSuccessCase[] = [ isProcess64Bit: true, }, environmentVars: { - "ProgramFiles": "C:\\Program Files", - "ProgramFiles(x86)": "C:\\Program Files (x86)", - "windir": "C:\\WINDOWS", + "ProgramFiles": "C:/Program Files", + "ProgramFiles(x86)": "C:/Program Files (x86)", + "windir": "C:/WINDOWS", }, expectedPowerShellSequence: [ - { exePath: "C:\\WINDOWS\\System32\\WindowsPowerShell\\v1.0\\powershell.exe", displayName: "Windows PowerShell (x64)" }, - { exePath: "C:\\WINDOWS\\SysWOW64\\WindowsPowerShell\\v1.0\\powershell.exe", displayName: "Windows PowerShell (x86)" }, + { exePath: "C:/WINDOWS/System32/WindowsPowerShell/v1.0/powershell.exe", displayName: "Windows PowerShell (x64)" }, + { exePath: "C:/WINDOWS/SysWOW64/WindowsPowerShell/v1.0/powershell.exe", displayName: "Windows PowerShell (x86)" }, ], filesystem: { - "C:\\WINDOWS\\System32\\WindowsPowerShell\\v1.0": { + "C:/WINDOWS/System32/WindowsPowerShell/v1.0": { "powershell.exe": "", }, - "C:\\WINDOWS\\SysWOW64\\WindowsPowerShell\\v1.0": { + "C:/WINDOWS/SysWOW64/WindowsPowerShell/v1.0": { "powershell.exe": "", }, }, @@ -159,25 +162,27 @@ const successTestCases: ITestPlatformSuccessCase[] = [ isProcess64Bit: false, }, environmentVars: { - "ProgramFiles": "C:\\Program Files (x86)", - "ProgramFiles(x86)": "C:\\Program Files (x86)", - "windir": "C:\\WINDOWS", + "ProgramFiles": "C:/Program Files (x86)", + "ProgramFiles(x86)": "C:/Program Files (x86)", + "windir": "C:/WINDOWS", }, expectedPowerShellSequence: [ - { exePath: "C:\\Program Files (x86)\\PowerShell\\6\\pwsh.exe", displayName: "PowerShell (x86)" }, - { exePath: "C:\\Program Files\\PowerShell\\6\\pwsh.exe", displayName: "PowerShell (x64)" }, + { exePath: "C:/Program Files (x86)/PowerShell/6/pwsh.exe", displayName: "PowerShell (x86)" }, + { exePath: "C:/Program Files/PowerShell/6/pwsh.exe", displayName: "PowerShell (x64)" }, { - exePath: - "C:\\Program Files\\WindowsApps\\Microsoft.PowerShell_7.0.0.4_neutral__8wekyb3d8bbwe\\pwsh.exe", + exePath: "C:/Program Files/WindowsApps/Microsoft.PowerShell_7.0.0.4_neutral__8wekyb3d8bbwe/pwsh.exe", displayName: "PowerShell MSIX", }, - { exePath: "C:\\Program Files (x86)\\PowerShell\\7-preview\\pwsh.exe", displayName: "PowerShell Preview (x86)" }, - { exePath: "C:\\Program Files\\PowerShell\\7-preview\\pwsh.exe", displayName: "PowerShell Preview (x64)" }, - { exePath: "C:\\WINDOWS\\System32\\WindowsPowerShell\\v1.0\\powershell.exe", displayName: "Windows PowerShell (x86)" }, - { exePath: "C:\\WINDOWS\\Sysnative\\WindowsPowerShell\\v1.0\\powershell.exe", displayName: "Windows PowerShell (x64)" }, + { + exePath: "C:/Program Files (x86)/PowerShell/7-preview/pwsh.exe", + displayName: "PowerShell Preview (x86)", + }, + { exePath: "C:/Program Files/PowerShell/7-preview/pwsh.exe", displayName: "PowerShell Preview (x64)" }, + { exePath: "C:/WINDOWS/System32/WindowsPowerShell/v1.0/powershell.exe", displayName: "Windows PowerShell (x86)" }, + { exePath: "C:/WINDOWS/Sysnative/WindowsPowerShell/v1.0/powershell.exe", displayName: "Windows PowerShell (x64)" }, ], filesystem: { - "C:\\Program Files\\PowerShell": { + "C:/Program Files/PowerShell": { "6": { "pwsh.exe": "", }, @@ -185,7 +190,7 @@ const successTestCases: ITestPlatformSuccessCase[] = [ "pwsh.exe": "", }, }, - "C:\\Program Files (x86)\\PowerShell": { + "C:/Program Files (x86)/PowerShell": { "6": { "pwsh.exe": "", }, @@ -193,13 +198,13 @@ const successTestCases: ITestPlatformSuccessCase[] = [ "pwsh.exe": "", }, }, - "C:\\Program Files\\WindowsApps\\Microsoft.PowerShell_7.0.0.4_neutral__8wekyb3d8bbwe": { + "C:/Program Files/WindowsApps/Microsoft.PowerShell_7.0.0.4_neutral__8wekyb3d8bbwe": { "pwsh.exe": "", }, - "C:\\WINDOWS\\System32\\WindowsPowerShell\\v1.0": { + "C:/WINDOWS/System32/WindowsPowerShell/v1.0": { "powershell.exe": "", }, - "C:\\WINDOWS\\Sysnative\\WindowsPowerShell\\v1.0": { + "C:/WINDOWS/Sysnative/WindowsPowerShell/v1.0": { "powershell.exe": "", }, }, @@ -212,19 +217,19 @@ const successTestCases: ITestPlatformSuccessCase[] = [ isProcess64Bit: false, }, environmentVars: { - "ProgramFiles": "C:\\Program Files (x86)", - "ProgramFiles(x86)": "C:\\Program Files (x86)", - "windir": "C:\\WINDOWS", + "ProgramFiles": "C:/Program Files (x86)", + "ProgramFiles(x86)": "C:/Program Files (x86)", + "windir": "C:/WINDOWS", }, expectedPowerShellSequence: [ - { exePath: "C:\\WINDOWS\\System32\\WindowsPowerShell\\v1.0\\powershell.exe", displayName: "Windows PowerShell (x86)" }, - { exePath: "C:\\WINDOWS\\Sysnative\\WindowsPowerShell\\v1.0\\powershell.exe", displayName: "Windows PowerShell (x64)" }, + { exePath: "C:/WINDOWS/System32/WindowsPowerShell/v1.0/powershell.exe", displayName: "Windows PowerShell (x86)" }, + { exePath: "C:/WINDOWS/Sysnative/WindowsPowerShell/v1.0/powershell.exe", displayName: "Windows PowerShell (x64)" }, ], filesystem: { - "C:\\WINDOWS\\System32\\WindowsPowerShell\\v1.0": { + "C:/WINDOWS/System32/WindowsPowerShell/v1.0": { "powershell.exe": "", }, - "C:\\WINDOWS\\Sysnative\\WindowsPowerShell\\v1.0": { + "C:/WINDOWS/Sysnative/WindowsPowerShell/v1.0": { "powershell.exe": "", }, }, @@ -237,17 +242,23 @@ const successTestCases: ITestPlatformSuccessCase[] = [ isProcess64Bit: false, }, environmentVars: { - "ProgramFiles": "C:\\Program Files (x86)", - "ProgramFiles(x86)": "C:\\Program Files (x86)", - "windir": "C:\\WINDOWS", + "ProgramFiles": "C:/Program Files (x86)", + "ProgramFiles(x86)": "C:/Program Files (x86)", + "windir": "C:/WINDOWS", }, expectedPowerShellSequence: [ - { exePath: "C:\\Program Files (x86)\\PowerShell\\6\\pwsh.exe", displayName: "PowerShell (x86)" }, - { exePath: "C:\\Program Files (x86)\\PowerShell\\7-preview\\pwsh.exe", displayName: "PowerShell Preview (x86)" }, - { exePath: "C:\\WINDOWS\\System32\\WindowsPowerShell\\v1.0\\powershell.exe", displayName: "Windows PowerShell (x86)" }, + { exePath: "C:/Program Files (x86)/PowerShell/6/pwsh.exe", displayName: "PowerShell (x86)" }, + { + exePath: "C:/Program Files (x86)/PowerShell/7-preview/pwsh.exe", + displayName: "PowerShell Preview (x86)", + }, + { + exePath: "C:/WINDOWS/System32/WindowsPowerShell/v1.0/powershell.exe", + displayName: "Windows PowerShell (x86)", + }, ], filesystem: { - "C:\\Program Files (x86)\\PowerShell": { + "C:/Program Files (x86)/PowerShell": { "6": { "pwsh.exe": "", }, @@ -255,7 +266,7 @@ const successTestCases: ITestPlatformSuccessCase[] = [ "pwsh.exe": "", }, }, - "C:\\WINDOWS\\System32\\WindowsPowerShell\\v1.0": { + "C:/WINDOWS/System32/WindowsPowerShell/v1.0": { "powershell.exe": "", }, }, @@ -268,15 +279,15 @@ const successTestCases: ITestPlatformSuccessCase[] = [ isProcess64Bit: false, }, environmentVars: { - "ProgramFiles": "C:\\Program Files (x86)", - "ProgramFiles(x86)": "C:\\Program Files (x86)", - "windir": "C:\\WINDOWS", + "ProgramFiles": "C:/Program Files (x86)", + "ProgramFiles(x86)": "C:/Program Files (x86)", + "windir": "C:/WINDOWS", }, expectedPowerShellSequence: [ - { exePath: "C:\\WINDOWS\\System32\\WindowsPowerShell\\v1.0\\powershell.exe", displayName: "Windows PowerShell (x86)" }, + { exePath: "C:/WINDOWS/System32/WindowsPowerShell/v1.0/powershell.exe", displayName: "Windows PowerShell (x86)" }, ], filesystem: { - "C:\\WINDOWS\\System32\\WindowsPowerShell\\v1.0": { + "C:/WINDOWS/System32/WindowsPowerShell/v1.0": { "powershell.exe": "", }, }, @@ -331,7 +342,7 @@ const successTestCases: ITestPlatformSuccessCase[] = [ }, ]; -const errorTestCases: ITestPlatformFailureCase[] = [ +const errorTestCases: ITestPlatform[] = [ { name: "Linux (no PowerShell)", platformDetails: { @@ -340,7 +351,6 @@ const errorTestCases: ITestPlatformFailureCase[] = [ isProcess64Bit: true, }, filesystem: {}, - error: "Bad", }, { name: "MacOS (no PowerShell)", @@ -350,7 +360,6 @@ const errorTestCases: ITestPlatformFailureCase[] = [ isProcess64Bit: true, }, filesystem: {}, - error: "Bad", }, ]; @@ -361,20 +370,48 @@ suite("Platform module", () => { const platformDetails: platform.IPlatformDetails = platform.getPlatformDetails(); switch (process.platform) { case "darwin": - assert.equal(platformDetails.operatingSystem, platform.OperatingSystem.MacOS); - assert.equal(platformDetails.isProcess64Bit, true); - assert.equal(platformDetails.isOS64Bit, true); + assert.strictEqual( + platformDetails.operatingSystem, + platform.OperatingSystem.MacOS, + "Platform details operating system should be MacOS"); + assert.strictEqual( + platformDetails.isProcess64Bit, + true, + "VSCode on darwin should be 64-bit"); + assert.strictEqual( + platformDetails.isOS64Bit, + true, + "Darwin is 64-bit only"); + break; case "linux": - assert.equal(platformDetails.operatingSystem, platform.OperatingSystem.Linux); - assert.equal(platformDetails.isProcess64Bit, true); - assert.equal(platformDetails.isOS64Bit, true); + assert.strictEqual( + platformDetails.operatingSystem, + platform.OperatingSystem.Linux, + "Platform details operating system should be Linux"); + assert.strictEqual( + platformDetails.isProcess64Bit, + true, + "Only 64-bit VSCode supported on Linux"); + assert.strictEqual( + platformDetails.isOS64Bit, + true, + "Only 64-bit Linux supported by PowerShell"); return; case "win32": - assert.equal(platformDetails.operatingSystem, platform.OperatingSystem.Windows); - assert.equal(platformDetails.isProcess64Bit, process.arch === "x64"); - assert.equal(platformDetails.isOS64Bit, !!(platformDetails.isProcess64Bit || process.env.ProgramW6432)); + assert.strictEqual( + platformDetails.operatingSystem, + platform.OperatingSystem.Windows, + "Platform details operating system should be Windows"); + assert.strictEqual( + platformDetails.isProcess64Bit, + process.arch === "x64", + "Windows process bitness should match process arch"); + assert.strictEqual( + platformDetails.isOS64Bit, + !!(platformDetails.isProcess64Bit || process.env.ProgramW6432), + "Windows OS arch should match process bitness unless 64-bit env var set"); return; default: @@ -445,13 +482,8 @@ suite("Platform module", () => { const powerShellExeFinder = new platform.PowerShellExeFinder(testPlatform.platformDetails); - let error; - try { - const defaultPowerShell = powerShellExeFinder.getFirstAvailablePowerShellInstallation(); - error = defaultPowerShell; - } catch (e) { - error = e; - } + const defaultPowerShell = powerShellExeFinder.getFirstAvailablePowerShellInstallation(); + assert.strictEqual(defaultPowerShell, undefined); }); } }); @@ -499,7 +531,10 @@ suite("Platform module", () => { assert.strictEqual(foundPowerShell && foundPowerShell.displayName, expectedPowerShell.displayName); } - assert.strictEqual(foundPowerShells.length, testPlatform.expectedPowerShellSequence.length); + assert.strictEqual( + foundPowerShells.length, + testPlatform.expectedPowerShellSequence.length, + "Number of expected PowerShells found does not match"); }); } }); From 3a06286479bf1211c14b539c6f1c6c1935fc6c7c Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Wed, 23 Oct 2019 13:14:34 -0700 Subject: [PATCH 26/46] Fix path issue --- .vscode/launch.json | 8 ++++---- package-lock.json | 6 +++--- package.json | 2 +- src/platform.ts | 38 +++++++++++++++++--------------------- src/session.ts | 7 +++---- 5 files changed, 28 insertions(+), 33 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index 6f9b811cd1..155db9a086 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -34,10 +34,10 @@ "outFiles": ["${workspaceRoot}/out/test/**/*.js"], "preLaunchTask": "Build", "skipFiles": [ - "${workspaceFolder}/node_modules/**/*.js", - "${workspaceFolder}/lib/**/*.js", - "/private/var/folders/**/*.js", - "/**/*.js" + "${workspaceFolder}/node_modules/**/*", + "${workspaceFolder}/lib/**/*", + "/private/var/folders/**/*", + "/**/*" ] }, { diff --git a/package-lock.json b/package-lock.json index 219d85c6fa..9b979b6a2b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -98,9 +98,9 @@ "dev": true }, "@types/node-fetch": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.5.0.tgz", - "integrity": "sha512-TLFRywthBgL68auWj+ziWu+vnmmcHCDFC/sqCOQf1xTz4hRq8cu79z8CtHU9lncExGBsB8fXA4TiLDLt6xvMzw==", + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.5.2.tgz", + "integrity": "sha512-djYYKmdNRSBtL1x4CiE9UJb9yZhwtI1VC+UxZD0psNznrUj80ywsxKlEGAE+QL1qvLjPbfb24VosjkYM6W4RSQ==", "dev": true, "requires": { "@types/node": "*" diff --git a/package.json b/package.json index 08d918264c..72b4633ec8 100644 --- a/package.json +++ b/package.json @@ -50,7 +50,7 @@ "@types/mocha": "~5.2.7", "@types/mock-fs": "^4.10.0", "@types/node": "~10.11.0", - "@types/node-fetch": "^2.5.0", + "@types/node-fetch": "^2.5.2", "@types/rewire": "^2.5.28", "@types/semver": "^6.0.2", "@types/sinon": "^7.5.0", diff --git a/src/platform.ts b/src/platform.ts index f2402de081..cf845f8092 100644 --- a/src/platform.ts +++ b/src/platform.ts @@ -9,15 +9,8 @@ import * as path from "path"; import * as process from "process"; import * as Settings from "./settings"; -export const System32PowerShellPath = getWindowsSystemPowerShellPath("System32"); -export const SysnativePowerShellPath = getWindowsSystemPowerShellPath("Sysnative"); -export const SysWow64PowerShellPath = getWindowsSystemPowerShellPath("SysWow64"); - -export const WindowsPowerShell64BitLabel = "Windows PowerShell (x64)"; -export const WindowsPowerShell32BitLabel = "Windows PowerShell (x86)"; - -const WinPS64BitPathOn32Bit = SysnativePowerShellPath.toLocaleLowerCase(); -const WinPS32BitPathOn64Bit = SysWow64PowerShellPath.toLocaleLowerCase(); +const WindowsPowerShell64BitLabel = "Windows PowerShell (x64)"; +const WindowsPowerShell32BitLabel = "Windows PowerShell (x86)"; const LinuxExePath = "/usr/bin/pwsh"; const LinuxPreviewExePath = "/usr/bin/pwsh-preview"; @@ -240,6 +233,21 @@ export class PowerShellExeFinder { return Array.from(this.enumeratePowerShellInstallations()); } + /** + * Fixes PowerShell paths when Windows PowerShell is set to the non-native bitness. + * @param configuredPowerShellPath the PowerShell path configured by the user. + */ + public fixWindowsPowerShellPath(configuredPowerShellPath: string): string { + const lowerConfiguredPath = configuredPowerShellPath.toLocaleLowerCase(); + const lowerAltWinPSPath = this.alternateBitnessWinPS.exePath.toLocaleLowerCase(); + + if (lowerConfiguredPath === lowerAltWinPSPath) { + return this.winPS.exePath; + } + + return configuredPowerShellPath; + } + /** * Iterates through PowerShell installations on the machine according * to configuration passed in through the constructor. @@ -563,18 +571,6 @@ export function getWindowsSystemPowerShellPath(systemFolderName: string) { "powershell.exe"); } -export function fixWindowsPowerShellPath(powerShellExePath: string, platformDetails: IPlatformDetails): string { - const lowerCasedPath = powerShellExePath.toLocaleLowerCase(); - - if ((platformDetails.isProcess64Bit && (lowerCasedPath === WinPS64BitPathOn32Bit)) || - (!platformDetails.isProcess64Bit && (lowerCasedPath === WinPS32BitPathOn64Bit))) { - return System32PowerShellPath; - } - - // If the path doesn't need to be fixed, return the original - return powerShellExePath; -} - interface IPossiblePowerShellExe extends IPowerShellExeDetails { readonly exists: boolean; } diff --git a/src/session.ts b/src/session.ts index 00be38eceb..e4205db9f2 100644 --- a/src/session.ts +++ b/src/session.ts @@ -23,7 +23,7 @@ import { import { GitHubReleaseInformation, InvokePowerShellUpdateCheck } from "./features/UpdatePowerShell"; import { - fixWindowsPowerShellPath, getPlatformDetails, IPlatformDetails, + getPlatformDetails, IPlatformDetails, OperatingSystem, PowerShellExeFinder } from "./platform"; export enum SessionStatus { @@ -252,9 +252,8 @@ export class SessionManager implements Middleware { if (powerShellExePath) { if (this.platformDetails.operatingSystem === OperatingSystem.Windows) { // Check the path bitness - const fixedPath = fixWindowsPowerShellPath( - powerShellExePath, - this.platformDetails); + const fixedPath = this.powershellExeFinder.fixWindowsPowerShellPath( + powerShellExePath); if (fixedPath !== powerShellExePath) { // Show deprecation message with fix action. From 06a16d21b4d7e7ddb9bc9da60c05779f14afed5c Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Wed, 23 Oct 2019 13:24:45 -0700 Subject: [PATCH 27/46] Make path-based tests version-specific --- test/platform.test.ts | 591 +++++++++++++++++++++++------------------- 1 file changed, 323 insertions(+), 268 deletions(-) diff --git a/test/platform.test.ts b/test/platform.test.ts index 08bf6ad182..a7d85dfd03 100644 --- a/test/platform.test.ts +++ b/test/platform.test.ts @@ -30,317 +30,372 @@ interface ITestPlatformSuccessCase extends ITestPlatform { } // Platform configurations where we expect to find a set of PowerShells -const successTestCases: ITestPlatformSuccessCase[] = [ - { - name: "Linux (all installations)", - platformDetails: { - operatingSystem: platform.OperatingSystem.Linux, - isOS64Bit: true, - isProcess64Bit: true, - }, - expectedPowerShellSequence: [ - { exePath: "/usr/bin/pwsh", displayName: "PowerShell" }, - { exePath: "/snap/bin/pwsh", displayName: "PowerShell Snap" }, - { exePath: "/usr/bin/pwsh-preview", displayName: "PowerShell Preview" }, - { exePath: "/snap/bin/pwsh-preview", displayName: "PowerShell Preview Snap" }, - ], - filesystem: { - "/usr/bin": { - "pwsh": "", - "pwsh-preview": "", +let successTestCases: ITestPlatformSuccessCase[]; + +if (process.platform === "win32") { + successTestCases = [ + { + name: "Windows 64-bit, 64-bit VSCode (all installations)", + platformDetails: { + operatingSystem: platform.OperatingSystem.Windows, + isOS64Bit: true, + isProcess64Bit: true, }, - "/snap/bin": { - "pwsh": "", - "pwsh-preview": "", + environmentVars: { + "ProgramFiles": "C:\\Program Files", + "ProgramFiles(x86)": "C:\\Program Files (x86)", + "windir": "C:\\WINDOWS", }, - }, - }, - { - name: "MacOS (all installations)", - platformDetails: { - operatingSystem: platform.OperatingSystem.MacOS, - isOS64Bit: true, - isProcess64Bit: true, - }, - expectedPowerShellSequence: [ - { exePath: "/usr/local/bin/pwsh", displayName: "PowerShell" }, - { exePath: "/usr/local/bin/pwsh-preview", displayName: "PowerShell Preview" }, - ], - filesystem: { - "/usr/local/bin": { - "pwsh": "", - "pwsh-preview": "", + expectedPowerShellSequence: [ + { + exePath: "C:\\Program Files\\PowerShell\\6\\pwsh.exe", + displayName: "PowerShell (x64)", + }, + { + exePath: "C:\\Program Files (x86)\\PowerShell\\6\\pwsh.exe", + displayName: "PowerShell (x86)", + }, + { + exePath: + "C:\\Program Files\\WindowsApps\\Microsoft.PowerShell_7.0.0.4_neutral__8wekyb3d8bbwe\\pwsh.exe", + displayName: "PowerShell MSIX", + }, + { + exePath: "C:\\Program Files\\PowerShell\\7-preview\\pwsh.exe", + displayName: "PowerShell Preview (x64)", + }, + { + exePath: "C:\\Program Files (x86)\\PowerShell\\7-preview\\pwsh.exe", + displayName: "PowerShell Preview (x86)", + }, + { + exePath: "C:\\WINDOWS\\System32\\WindowsPowerShell\\v1.0\\powershell.exe", + displayName: "Windows PowerShell (x64)", + }, + { + exePath: "C:\\WINDOWS\\SysWOW64\\WindowsPowerShell\\v1.0\\powershell.exe", + displayName: "Windows PowerShell (x86)", + }, + ], + filesystem: { + "C:\\Program Files\\PowerShell": { + "6": { + "pwsh.exe": "", + }, + "7-preview": { + "pwsh.exe": "", + }, + }, + "C:\\Program Files (x86)\\PowerShell": { + "6": { + "pwsh.exe": "", + }, + "7-preview": { + "pwsh.exe": "", + }, + }, + "C:\\Program Files\\WindowsApps\\Microsoft.PowerShell_7.0.0.4_neutral__8wekyb3d8bbwe": { + "pwsh.exe": "", + }, + "C:\\WINDOWS\\System32\\WindowsPowerShell\\v1.0": { + "powershell.exe": "", + }, + "C:\\WINDOWS\\SysWOW64\\WindowsPowerShell\\v1.0": { + "powershell.exe": "", + }, }, }, - }, - { - name: "Windows 64-bit, 64-bit VSCode (all installations)", - platformDetails: { - operatingSystem: platform.OperatingSystem.Windows, - isOS64Bit: true, - isProcess64Bit: true, - }, - environmentVars: { - "ProgramFiles": "C:/Program Files", - "ProgramFiles(x86)": "C:/Program Files (x86)", - "windir": "C:/WINDOWS", - }, - expectedPowerShellSequence: [ - { exePath: "C:/Program Files/PowerShell/6/pwsh.exe", displayName: "PowerShell (x64)" }, - { exePath: "C:/Program Files (x86)/PowerShell/6/pwsh.exe", displayName: "PowerShell (x86)" }, - { - exePath: - "C:/Program Files/WindowsApps/Microsoft.PowerShell_7.0.0.4_neutral__8wekyb3d8bbwe/pwsh.exe", - displayName: "PowerShell MSIX", + { + name: "Windows 64-bit, 64-bit VSCode (only Windows PowerShell)", + platformDetails: { + operatingSystem: platform.OperatingSystem.Windows, + isOS64Bit: true, + isProcess64Bit: true, }, - { exePath: "C:/Program Files/PowerShell/7-preview/pwsh.exe", displayName: "PowerShell Preview (x64)" }, - { - exePath: "C:/Program Files (x86)/PowerShell/7-preview/pwsh.exe", - displayName: "PowerShell Preview (x86)", + environmentVars: { + "ProgramFiles": "C:\\Program Files", + "ProgramFiles(x86)": "C:\\Program Files (x86)", + "windir": "C:\\WINDOWS", }, - { exePath: "C:/WINDOWS/System32/WindowsPowerShell/v1.0/powershell.exe", displayName: "Windows PowerShell (x64)" }, - { exePath: "C:/WINDOWS/SysWOW64/WindowsPowerShell/v1.0/powershell.exe", displayName: "Windows PowerShell (x86)" }, - ], - filesystem: { - "C:/Program Files/PowerShell": { - "6": { - "pwsh.exe": "", + expectedPowerShellSequence: [ + { + exePath: "C:\\WINDOWS\\System32\\WindowsPowerShell\\v1.0\\powershell.exe", + displayName: "Windows PowerShell (x64)", }, - "7-preview": { - "pwsh.exe": "", + { + exePath: "C:\\WINDOWS\\SysWOW64\\WindowsPowerShell\\v1.0\\powershell.exe", + displayName: "Windows PowerShell (x86)", }, - }, - "C:/Program Files (x86)/PowerShell": { - "6": { - "pwsh.exe": "", + ], + filesystem: { + "C:\\WINDOWS\\System32\\WindowsPowerShell\\v1.0": { + "powershell.exe": "", }, - "7-preview": { - "pwsh.exe": "", + "C:\\WINDOWS\\SysWOW64\\WindowsPowerShell\\v1.0": { + "powershell.exe": "", }, }, - "C:/Program Files/WindowsApps/Microsoft.PowerShell_7.0.0.4_neutral__8wekyb3d8bbwe": { - "pwsh.exe": "", + }, + { + name: "Windows 64-bit, 32-bit VSCode (all installations)", + platformDetails: { + operatingSystem: platform.OperatingSystem.Windows, + isOS64Bit: true, + isProcess64Bit: false, }, - "C:/WINDOWS/System32/WindowsPowerShell/v1.0": { - "powershell.exe": "", + environmentVars: { + "ProgramFiles": "C:\\Program Files (x86)", + "ProgramFiles(x86)": "C:\\Program Files (x86)", + "windir": "C:\\WINDOWS", }, - "C:/WINDOWS/SysWOW64/WindowsPowerShell/v1.0": { - "powershell.exe": "", + expectedPowerShellSequence: [ + { + exePath: "C:\\Program Files (x86)\\PowerShell\\6\\pwsh.exe", + displayName: "PowerShell (x86)", + }, + { + exePath: "C:\\Program Files\\PowerShell\\6\\pwsh.exe", + displayName: "PowerShell (x64)", + }, + { + exePath: "C:\\Program Files\\WindowsApps\\Microsoft.PowerShell_7.0.0.4_neutral__8wekyb3d8bbwe\\pwsh.exe", + displayName: "PowerShell MSIX", + }, + { + exePath: "C:\\Program Files (x86)\\PowerShell\\7-preview\\pwsh.exe", + displayName: "PowerShell Preview (x86)", + }, + { + exePath: "C:\\Program Files\\PowerShell\\7-preview\\pwsh.exe", + displayName: "PowerShell Preview (x64)", + }, + { + exePath: "C:\\WINDOWS\\System32\\WindowsPowerShell\\v1.0\\powershell.exe", + displayName: "Windows PowerShell (x86)", + }, + { + exePath: "C:\\WINDOWS\\Sysnative\\WindowsPowerShell\\v1.0\\powershell.exe", + displayName: "Windows PowerShell (x64)", + }, + ], + filesystem: { + "C:\\Program Files\\PowerShell": { + "6": { + "pwsh.exe": "", + }, + "7-preview": { + "pwsh.exe": "", + }, + }, + "C:\\Program Files (x86)\\PowerShell": { + "6": { + "pwsh.exe": "", + }, + "7-preview": { + "pwsh.exe": "", + }, + }, + "C:\\Program Files\\WindowsApps\\Microsoft.PowerShell_7.0.0.4_neutral__8wekyb3d8bbwe": { + "pwsh.exe": "", + }, + "C:\\WINDOWS\\System32\\WindowsPowerShell\\v1.0": { + "powershell.exe": "", + }, + "C:\\WINDOWS\\Sysnative\\WindowsPowerShell\\v1.0": { + "powershell.exe": "", + }, }, }, - }, - { - name: "Windows 64-bit, 64-bit VSCode (only Windows PowerShell)", - platformDetails: { - operatingSystem: platform.OperatingSystem.Windows, - isOS64Bit: true, - isProcess64Bit: true, - }, - environmentVars: { - "ProgramFiles": "C:/Program Files", - "ProgramFiles(x86)": "C:/Program Files (x86)", - "windir": "C:/WINDOWS", - }, - expectedPowerShellSequence: [ - { exePath: "C:/WINDOWS/System32/WindowsPowerShell/v1.0/powershell.exe", displayName: "Windows PowerShell (x64)" }, - { exePath: "C:/WINDOWS/SysWOW64/WindowsPowerShell/v1.0/powershell.exe", displayName: "Windows PowerShell (x86)" }, - ], - filesystem: { - "C:/WINDOWS/System32/WindowsPowerShell/v1.0": { - "powershell.exe": "", + { + name: "Windows 64-bit, 32-bit VSCode (Windows PowerShell only)", + platformDetails: { + operatingSystem: platform.OperatingSystem.Windows, + isOS64Bit: true, + isProcess64Bit: false, }, - "C:/WINDOWS/SysWOW64/WindowsPowerShell/v1.0": { - "powershell.exe": "", + environmentVars: { + "ProgramFiles": "C:\\Program Files (x86)", + "ProgramFiles(x86)": "C:\\Program Files (x86)", + "windir": "C:\\WINDOWS", + }, + expectedPowerShellSequence: [ + { + exePath: "C:\\WINDOWS\\System32\\WindowsPowerShell\\v1.0\\powershell.exe", + displayName: "Windows PowerShell (x86)", + }, + { + exePath: "C:\\WINDOWS\\Sysnative\\WindowsPowerShell\\v1.0\\powershell.exe", + displayName: "Windows PowerShell (x64)", + }, + ], + filesystem: { + "C:\\WINDOWS\\System32\\WindowsPowerShell\\v1.0": { + "powershell.exe": "", + }, + "C:\\WINDOWS\\Sysnative\\WindowsPowerShell\\v1.0": { + "powershell.exe": "", + }, }, }, - }, - { - name: "Windows 64-bit, 32-bit VSCode (all installations)", - platformDetails: { - operatingSystem: platform.OperatingSystem.Windows, - isOS64Bit: true, - isProcess64Bit: false, - }, - environmentVars: { - "ProgramFiles": "C:/Program Files (x86)", - "ProgramFiles(x86)": "C:/Program Files (x86)", - "windir": "C:/WINDOWS", - }, - expectedPowerShellSequence: [ - { exePath: "C:/Program Files (x86)/PowerShell/6/pwsh.exe", displayName: "PowerShell (x86)" }, - { exePath: "C:/Program Files/PowerShell/6/pwsh.exe", displayName: "PowerShell (x64)" }, - { - exePath: "C:/Program Files/WindowsApps/Microsoft.PowerShell_7.0.0.4_neutral__8wekyb3d8bbwe/pwsh.exe", - displayName: "PowerShell MSIX", + { + name: "Windows 32-bit, 32-bit VSCode (all installations)", + platformDetails: { + operatingSystem: platform.OperatingSystem.Windows, + isOS64Bit: true, + isProcess64Bit: false, }, - { - exePath: "C:/Program Files (x86)/PowerShell/7-preview/pwsh.exe", - displayName: "PowerShell Preview (x86)", + environmentVars: { + "ProgramFiles": "C:\\Program Files (x86)", + "ProgramFiles(x86)": "C:\\Program Files (x86)", + "windir": "C:\\WINDOWS", }, - { exePath: "C:/Program Files/PowerShell/7-preview/pwsh.exe", displayName: "PowerShell Preview (x64)" }, - { exePath: "C:/WINDOWS/System32/WindowsPowerShell/v1.0/powershell.exe", displayName: "Windows PowerShell (x86)" }, - { exePath: "C:/WINDOWS/Sysnative/WindowsPowerShell/v1.0/powershell.exe", displayName: "Windows PowerShell (x64)" }, - ], - filesystem: { - "C:/Program Files/PowerShell": { - "6": { - "pwsh.exe": "", + expectedPowerShellSequence: [ + { + exePath: "C:\\Program Files (x86)\\PowerShell\\6\\pwsh.exe", + displayName: "PowerShell (x86)", }, - "7-preview": { - "pwsh.exe": "", + { + exePath: "C:\\Program Files (x86)\\PowerShell\\7-preview\\pwsh.exe", + displayName: "PowerShell Preview (x86)", }, - }, - "C:/Program Files (x86)/PowerShell": { - "6": { - "pwsh.exe": "", + { + exePath: "C:\\WINDOWS\\System32\\WindowsPowerShell\\v1.0\\powershell.exe", + displayName: "Windows PowerShell (x86)", }, - "7-preview": { - "pwsh.exe": "", + ], + filesystem: { + "C:\\Program Files (x86)\\PowerShell": { + "6": { + "pwsh.exe": "", + }, + "7-preview": { + "pwsh.exe": "", + }, + }, + "C:\\WINDOWS\\System32\\WindowsPowerShell\\v1.0": { + "powershell.exe": "", }, }, - "C:/Program Files/WindowsApps/Microsoft.PowerShell_7.0.0.4_neutral__8wekyb3d8bbwe": { - "pwsh.exe": "", + }, + { + name: "Windows 32-bit, 32-bit VSCode (Windows PowerShell only)", + platformDetails: { + operatingSystem: platform.OperatingSystem.Windows, + isOS64Bit: true, + isProcess64Bit: false, }, - "C:/WINDOWS/System32/WindowsPowerShell/v1.0": { - "powershell.exe": "", + environmentVars: { + "ProgramFiles": "C:\\Program Files (x86)", + "ProgramFiles(x86)": "C:\\Program Files (x86)", + "windir": "C:\\WINDOWS", }, - "C:/WINDOWS/Sysnative/WindowsPowerShell/v1.0": { - "powershell.exe": "", + expectedPowerShellSequence: [ + { + exePath: "C:\\WINDOWS\\System32\\WindowsPowerShell\\v1.0\\powershell.exe", + displayName: "Windows PowerShell (x86)", + }, + ], + filesystem: { + "C:\\WINDOWS\\System32\\WindowsPowerShell\\v1.0": { + "powershell.exe": "", + }, }, }, - }, - { - name: "Windows 64-bit, 32-bit VSCode (Windows PowerShell only)", - platformDetails: { - operatingSystem: platform.OperatingSystem.Windows, - isOS64Bit: true, - isProcess64Bit: false, - }, - environmentVars: { - "ProgramFiles": "C:/Program Files (x86)", - "ProgramFiles(x86)": "C:/Program Files (x86)", - "windir": "C:/WINDOWS", - }, - expectedPowerShellSequence: [ - { exePath: "C:/WINDOWS/System32/WindowsPowerShell/v1.0/powershell.exe", displayName: "Windows PowerShell (x86)" }, - { exePath: "C:/WINDOWS/Sysnative/WindowsPowerShell/v1.0/powershell.exe", displayName: "Windows PowerShell (x64)" }, - ], - filesystem: { - "C:/WINDOWS/System32/WindowsPowerShell/v1.0": { - "powershell.exe": "", + ]; +} else { + successTestCases = [ + { + name: "Linux (all installations)", + platformDetails: { + operatingSystem: platform.OperatingSystem.Linux, + isOS64Bit: true, + isProcess64Bit: true, }, - "C:/WINDOWS/Sysnative/WindowsPowerShell/v1.0": { - "powershell.exe": "", + expectedPowerShellSequence: [ + { exePath: "/usr/bin/pwsh", displayName: "PowerShell" }, + { exePath: "/snap/bin/pwsh", displayName: "PowerShell Snap" }, + { exePath: "/usr/bin/pwsh-preview", displayName: "PowerShell Preview" }, + { exePath: "/snap/bin/pwsh-preview", displayName: "PowerShell Preview Snap" }, + ], + filesystem: { + "/usr/bin": { + "pwsh": "", + "pwsh-preview": "", + }, + "/snap/bin": { + "pwsh": "", + "pwsh-preview": "", + }, }, }, - }, - { - name: "Windows 32-bit, 32-bit VSCode (all installations)", - platformDetails: { - operatingSystem: platform.OperatingSystem.Windows, - isOS64Bit: true, - isProcess64Bit: false, - }, - environmentVars: { - "ProgramFiles": "C:/Program Files (x86)", - "ProgramFiles(x86)": "C:/Program Files (x86)", - "windir": "C:/WINDOWS", - }, - expectedPowerShellSequence: [ - { exePath: "C:/Program Files (x86)/PowerShell/6/pwsh.exe", displayName: "PowerShell (x86)" }, - { - exePath: "C:/Program Files (x86)/PowerShell/7-preview/pwsh.exe", - displayName: "PowerShell Preview (x86)", + { + name: "MacOS (all installations)", + platformDetails: { + operatingSystem: platform.OperatingSystem.MacOS, + isOS64Bit: true, + isProcess64Bit: true, }, - { - exePath: "C:/WINDOWS/System32/WindowsPowerShell/v1.0/powershell.exe", - displayName: "Windows PowerShell (x86)", - }, - ], - filesystem: { - "C:/Program Files (x86)/PowerShell": { - "6": { - "pwsh.exe": "", - }, - "7-preview": { - "pwsh.exe": "", + expectedPowerShellSequence: [ + { exePath: "/usr/local/bin/pwsh", displayName: "PowerShell" }, + { exePath: "/usr/local/bin/pwsh-preview", displayName: "PowerShell Preview" }, + ], + filesystem: { + "/usr/local/bin": { + "pwsh": "", + "pwsh-preview": "", }, }, - "C:/WINDOWS/System32/WindowsPowerShell/v1.0": { - "powershell.exe": "", - }, }, - }, - { - name: "Windows 32-bit, 32-bit VSCode (Windows PowerShell only)", - platformDetails: { - operatingSystem: platform.OperatingSystem.Windows, - isOS64Bit: true, - isProcess64Bit: false, - }, - environmentVars: { - "ProgramFiles": "C:/Program Files (x86)", - "ProgramFiles(x86)": "C:/Program Files (x86)", - "windir": "C:/WINDOWS", - }, - expectedPowerShellSequence: [ - { exePath: "C:/WINDOWS/System32/WindowsPowerShell/v1.0/powershell.exe", displayName: "Windows PowerShell (x86)" }, - ], - filesystem: { - "C:/WINDOWS/System32/WindowsPowerShell/v1.0": { - "powershell.exe": "", + { + name: "Linux (stable only)", + platformDetails: { + operatingSystem: platform.OperatingSystem.Linux, + isOS64Bit: true, + isProcess64Bit: true, }, - }, - }, - { - name: "Linux (stable only)", - platformDetails: { - operatingSystem: platform.OperatingSystem.Linux, - isOS64Bit: true, - isProcess64Bit: true, - }, - expectedPowerShellSequence: [ - { exePath: "/usr/bin/pwsh", displayName: "PowerShell" }, - ], - filesystem: { - "/usr/bin": { - pwsh: "", + expectedPowerShellSequence: [ + { exePath: "/usr/bin/pwsh", displayName: "PowerShell" }, + ], + filesystem: { + "/usr/bin": { + pwsh: "", + }, }, }, - }, - { - name: "Linux (stable snap only)", - platformDetails: { - operatingSystem: platform.OperatingSystem.Linux, - isOS64Bit: true, - isProcess64Bit: true, - }, - expectedPowerShellSequence: [ - { exePath: "/snap/bin/pwsh", displayName: "PowerShell Snap" }, - ], - filesystem: { - "/snap/bin": { - pwsh: "", + { + name: "Linux (stable snap only)", + platformDetails: { + operatingSystem: platform.OperatingSystem.Linux, + isOS64Bit: true, + isProcess64Bit: true, + }, + expectedPowerShellSequence: [ + { exePath: "/snap/bin/pwsh", displayName: "PowerShell Snap" }, + ], + filesystem: { + "/snap/bin": { + pwsh: "", + }, }, }, - }, - { - name: "MacOS (stable only)", - platformDetails: { - operatingSystem: platform.OperatingSystem.MacOS, - isOS64Bit: true, - isProcess64Bit: true, - }, - expectedPowerShellSequence: [ - { exePath: "/usr/local/bin/pwsh", displayName: "PowerShell" }, - ], - filesystem: { - "/usr/local/bin": { - pwsh: "", + { + name: "MacOS (stable only)", + platformDetails: { + operatingSystem: platform.OperatingSystem.MacOS, + isOS64Bit: true, + isProcess64Bit: true, + }, + expectedPowerShellSequence: [ + { exePath: "/usr/local/bin/pwsh", displayName: "PowerShell" }, + ], + filesystem: { + "/usr/local/bin": { + pwsh: "", + }, }, }, - }, -]; + ]; +} const errorTestCases: ITestPlatform[] = [ { From 5fd7f8349c63be8477376b27028cbfc021d424e5 Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Wed, 23 Oct 2019 14:02:06 -0700 Subject: [PATCH 28/46] Fix path length issues --- src/platform.ts | 3 ++- src/session.ts | 6 ++++-- test/platform.test.ts | 18 ++++++++++-------- 3 files changed, 16 insertions(+), 11 deletions(-) diff --git a/src/platform.ts b/src/platform.ts index cf845f8092..1d9394aa19 100644 --- a/src/platform.ts +++ b/src/platform.ts @@ -519,7 +519,8 @@ export class PowerShellExeFinder { private findPSCoreMsix(): IPossiblePowerShellExe { const winPSPath: string = this.findWinPS().exePath; - const msixDir: string = child_process.execFileSync(winPSPath, ["-c", "(Get-AppxPackage -Name Microsoft.PowerShell).InstallLocation"]) + const winPSArgs = ["-c", "(Get-AppxPackage -Name Microsoft.PowerShell).InstallLocation"]; + const msixDir: string = child_process.execFileSync(winPSPath, winPSArgs) .toString() .trim(); diff --git a/src/session.ts b/src/session.ts index e4205db9f2..407040b189 100644 --- a/src/session.ts +++ b/src/session.ts @@ -122,7 +122,8 @@ export class SessionManager implements Middleware { try { this.powerShellExePath = this.getPowerShellExePath(); } catch (e) { - vscode.window.showErrorMessage("Unable to find PowerShell. Do you have PowerShell installed? See logs for more details."); + vscode.window.showErrorMessage( + "Unable to find PowerShell. Do you have PowerShell installed? See logs for more details."); this.log.writeError(`Unable to find PowerShell executable:\n${e}`); } @@ -367,7 +368,8 @@ export class SessionManager implements Middleware { } // Default PowerShell version was configured but we didn't find it - this.log.writeWarning(`Could not find powerShellDefaultVersion: '${this.sessionSettings.powerShellDefaultVersion}'`); + this.log.writeWarning( + `Could not find powerShellDefaultVersion: '${this.sessionSettings.powerShellDefaultVersion}'`); } if (this.sessionSettings.powerShellExePath) { diff --git a/test/platform.test.ts b/test/platform.test.ts index a7d85dfd03..2b4a7888f0 100644 --- a/test/platform.test.ts +++ b/test/platform.test.ts @@ -6,6 +6,7 @@ import * as assert from "assert"; import * as child_process from "child_process"; import mockFS = require("mock-fs"); import FileSystem = require("mock-fs/lib/filesystem"); +import * as path from "path"; import * as sinon from "sinon"; import * as platform from "../src/platform"; @@ -32,6 +33,8 @@ interface ITestPlatformSuccessCase extends ITestPlatform { // Platform configurations where we expect to find a set of PowerShells let successTestCases: ITestPlatformSuccessCase[]; +const pwshMsixDir = "C:\\Program Files\\WindowsApps\\Microsoft.PowerShell_7.0.0.4_neutral__8wekyb3d8bbwe"; +const pwshMsixPath = path.join(pwshMsixDir, "pwsh.exe"); if (process.platform === "win32") { successTestCases = [ { @@ -56,8 +59,7 @@ if (process.platform === "win32") { displayName: "PowerShell (x86)", }, { - exePath: - "C:\\Program Files\\WindowsApps\\Microsoft.PowerShell_7.0.0.4_neutral__8wekyb3d8bbwe\\pwsh.exe", + exePath: pwshMsixPath, displayName: "PowerShell MSIX", }, { @@ -94,7 +96,7 @@ if (process.platform === "win32") { "pwsh.exe": "", }, }, - "C:\\Program Files\\WindowsApps\\Microsoft.PowerShell_7.0.0.4_neutral__8wekyb3d8bbwe": { + [pwshMsixDir]: { "pwsh.exe": "", }, "C:\\WINDOWS\\System32\\WindowsPowerShell\\v1.0": { @@ -158,7 +160,7 @@ if (process.platform === "win32") { displayName: "PowerShell (x64)", }, { - exePath: "C:\\Program Files\\WindowsApps\\Microsoft.PowerShell_7.0.0.4_neutral__8wekyb3d8bbwe\\pwsh.exe", + exePath: pwshMsixPath, displayName: "PowerShell MSIX", }, { @@ -195,7 +197,7 @@ if (process.platform === "win32") { "pwsh.exe": "", }, }, - "C:\\Program Files\\WindowsApps\\Microsoft.PowerShell_7.0.0.4_neutral__8wekyb3d8bbwe": { + [pwshMsixDir]: { "pwsh.exe": "", }, "C:\\WINDOWS\\System32\\WindowsPowerShell\\v1.0": { @@ -496,7 +498,7 @@ suite("Platform module", () => { return child_process.execFileSync(procName, args, options); } - return "C:\\Program Files\\WindowsApps\\Microsoft.PowerShell_7.0.0.4_neutral__8wekyb3d8bbwe"; + return pwshMsixDir; }); if (testPlatform.environmentVars) { @@ -526,7 +528,7 @@ suite("Platform module", () => { return child_process.execFileSync(procName, args, options); } - return "C:\\Program Files\\WindowsApps\\Microsoft.PowerShell_7.0.0.4_neutral__8wekyb3d8bbwe"; + return pwshMsixDir; }); if (testPlatform.environmentVars) { @@ -565,7 +567,7 @@ suite("Platform module", () => { return child_process.execFileSync(procName, args, options); } - return "C:\\Program Files\\WindowsApps\\Microsoft.PowerShell_7.0.0.4_neutral__8wekyb3d8bbwe"; + return pwshMsixDir; }); if (testPlatform.environmentVars) { From 66ddfdb2248d19b323bf5173c5ed61a7c7fd5465 Mon Sep 17 00:00:00 2001 From: Rob Holt Date: Thu, 24 Oct 2019 09:41:31 -0700 Subject: [PATCH 29/46] Add new bits --- package.json | 14 +++++++------- src/platform.ts | 8 ++++---- src/session.ts | 10 ++++++---- 3 files changed, 17 insertions(+), 15 deletions(-) diff --git a/package.json b/package.json index 72b4633ec8..747eb5c6e2 100644 --- a/package.json +++ b/package.json @@ -48,18 +48,18 @@ }, "devDependencies": { "@types/mocha": "~5.2.7", - "@types/mock-fs": "^4.10.0", + "@types/mock-fs": "~4.10.0", "@types/node": "~10.11.0", - "@types/node-fetch": "^2.5.2", - "@types/rewire": "^2.5.28", - "@types/semver": "^6.0.2", - "@types/sinon": "^7.5.0", + "@types/node-fetch": "~2.5.0", + "@types/rewire": "~2.5.28", + "@types/semver": "~6.0.2", + "@types/sinon": "~7.5.0", "mocha": "~5.2.0", "mocha-junit-reporter": "~1.23.1", "mocha-multi-reporters": "~1.1.7", - "mock-fs": "^4.10.2", + "mock-fs": "~4.10.2", "rewire": "~4.0.1", - "sinon": "^7.5.0", + "sinon": "~7.5.0", "tslint": "~5.20.0", "typescript": "~3.5.3", "vsce": "~1.66.0", diff --git a/src/platform.ts b/src/platform.ts index 1d9394aa19..3008e3faba 100644 --- a/src/platform.ts +++ b/src/platform.ts @@ -7,7 +7,7 @@ import * as fs from "fs"; import * as os from "os"; import * as path from "path"; import * as process from "process"; -import * as Settings from "./settings"; +import { IPowerShellAdditionalExePathSettings } from "./settings"; const WindowsPowerShell64BitLabel = "Windows PowerShell (x64)"; const WindowsPowerShell32BitLabel = "Windows PowerShell (x86)"; @@ -108,7 +108,7 @@ export class PowerShellExeFinder { private readonly alternateBitnessWinPSExeVal: Lazy; // Additional configured PowerShells - private readonly additionalPSExeSettings: Iterable; + private readonly additionalPSExeSettings: Iterable; /** * Create a new PowerShellFinder object to discover PowerShell installations. @@ -117,7 +117,7 @@ export class PowerShellExeFinder { */ constructor( platformDetails?: IPlatformDetails, - additionalPowerShellExes?: Iterable) { + additionalPowerShellExes?: Iterable) { this.platformDetails = platformDetails || getPlatformDetails(); this.additionalPSExeSettings = additionalPowerShellExes || []; @@ -463,7 +463,7 @@ export class PowerShellExeFinder { return null; } - private findAdditionalPwshExe(additionalPwshSetting: Settings.IPowerShellAdditionalExePathSettings) { + private findAdditionalPwshExe(additionalPwshSetting: IPowerShellAdditionalExePathSettings) { return new PSCoreExe( additionalPwshSetting.exePath, additionalPwshSetting.versionName); diff --git a/src/session.ts b/src/session.ts index 407040b189..439a778277 100644 --- a/src/session.ts +++ b/src/session.ts @@ -122,15 +122,18 @@ export class SessionManager implements Middleware { try { this.powerShellExePath = this.getPowerShellExePath(); } catch (e) { - vscode.window.showErrorMessage( - "Unable to find PowerShell. Do you have PowerShell installed? See logs for more details."); this.log.writeError(`Unable to find PowerShell executable:\n${e}`); } this.suppressRestartPrompt = false; if (!this.powerShellExePath) { - this.setSessionFailure("PowerShell could not be started, click 'Show Logs' for more details."); + this.setSessionFailure( + "Unable to find PowerShell." + + " Do you have PowerShell installed?" + + " To get PowerShell, go to https://aka.ms/get-powershell." + + " Click 'Show Logs' for more details."); + return; } this.bundledModulesPath = path.resolve(__dirname, this.sessionSettings.bundledModulesPath); @@ -275,7 +278,6 @@ export class SessionManager implements Middleware { return firstAvailablePwsh.exePath; } - this.setSessionFailure("Unable to find PowerShell installation, see logs for more details"); return null; } From 0afeed06a054030896d7adaa61347502096be445 Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Thu, 24 Oct 2019 10:03:48 -0700 Subject: [PATCH 30/46] Simply PowerShell missing logic --- src/session.ts | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/session.ts b/src/session.ts index 439a778277..d4c372317f 100644 --- a/src/session.ts +++ b/src/session.ts @@ -272,13 +272,8 @@ export class SessionManager implements Middleware { return this.resolvePowerShellPath(powerShellExePath); } - const firstAvailablePwsh = this.powershellExeFinder.getFirstAvailablePowerShellInstallation(); - if (firstAvailablePwsh) { - // No need to resolve this path, since the finder guarantees its existence - return firstAvailablePwsh.exePath; - } - - return null; + // No need to resolve this path, since the finder guarantees its existence + return this.powershellExeFinder.getFirstAvailablePowerShellInstallation() || null; } // ----- LanguageClient middleware methods ----- From 15da0eea9b3f7df6aba5abda15b456f3deb6e70b Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Thu, 24 Oct 2019 10:05:05 -0700 Subject: [PATCH 31/46] Fix type issue --- src/session.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/session.ts b/src/session.ts index d4c372317f..510927615a 100644 --- a/src/session.ts +++ b/src/session.ts @@ -273,7 +273,8 @@ export class SessionManager implements Middleware { } // No need to resolve this path, since the finder guarantees its existence - return this.powershellExeFinder.getFirstAvailablePowerShellInstallation() || null; + const firstPowerShell = this.powershellExeFinder.getFirstAvailablePowerShellInstallation(); + return firstPowerShell && firstPowerShell.exePath || null; } // ----- LanguageClient middleware methods ----- From 90a76920756c28df7404372c01c494d1194864da Mon Sep 17 00:00:00 2001 From: Rob Holt Date: Thu, 24 Oct 2019 12:42:54 -0700 Subject: [PATCH 32/46] Remove unused exception class --- src/platform.ts | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/platform.ts b/src/platform.ts index 3008e3faba..614cb22237 100644 --- a/src/platform.ts +++ b/src/platform.ts @@ -59,15 +59,6 @@ export function getPlatformDetails(): IPlatformDetails { }; } -export class PowerShellNotFoundError extends Error { - public readonly powershellPath: string; - - constructor(powershellPath: string) { - super(`Unable to find PowerShell installation at path '${powershellPath}'`); - this.powershellPath = powershellPath; - } -} - /** * Class to lazily find installed PowerShell executables on a machine. * When given a list of additional PowerShell executables, From b68bbc9b559fe6b5ce949404d26b38db0111ecf7 Mon Sep 17 00:00:00 2001 From: Rob Holt Date: Thu, 24 Oct 2019 16:29:45 -0700 Subject: [PATCH 33/46] Use noprofile to find MSIX --- src/platform.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/platform.ts b/src/platform.ts index 614cb22237..f46c40bc66 100644 --- a/src/platform.ts +++ b/src/platform.ts @@ -510,7 +510,12 @@ export class PowerShellExeFinder { private findPSCoreMsix(): IPossiblePowerShellExe { const winPSPath: string = this.findWinPS().exePath; - const winPSArgs = ["-c", "(Get-AppxPackage -Name Microsoft.PowerShell).InstallLocation"]; + const winPSArgs = [ + "-nologo", + "-noprofile", + "-c", + "(Get-AppxPackage -Name Microsoft.PowerShell).InstallLocation", + ]; const msixDir: string = child_process.execFileSync(winPSPath, winPSArgs) .toString() .trim(); From ff081d4f0daa2537655e867a99c37e884f0ed023 Mon Sep 17 00:00:00 2001 From: Rob Holt Date: Thu, 24 Oct 2019 20:55:35 -0700 Subject: [PATCH 34/46] Add link to PowerShell installation instructions --- src/logging.ts | 23 +++++++++++++++++++++++ src/platform.ts | 12 +++++++++--- src/session.ts | 22 +++++++++++++++++----- 3 files changed, 49 insertions(+), 8 deletions(-) diff --git a/src/logging.ts b/src/logging.ts index 62d26a56a4..0716863eed 100644 --- a/src/logging.ts +++ b/src/logging.ts @@ -114,6 +114,29 @@ export class Logger implements ILogger { }); } + public async writeAndShowErrorWithActions( + message: string, + actions: Array<{ prompt: string; action: () => Promise }>) { + this.writeError(message); + + const fullActions = [ + ...actions, + { prompt: "Show Logs", action: async () => { this.showLogPanel(); } }, + ]; + + const actionKeys: string[] = fullActions.map((action) => action.prompt); + + const choice = await vscode.window.showErrorMessage(message, ...actionKeys); + if (choice !== undefined) { + for (const action of fullActions) { + if (choice === action.prompt) { + await action.action(); + return; + } + } + } + } + public startNewLog(minimumLogLevel: string = "Normal") { this.MinimumLogLevel = this.logLevelNameToValue(minimumLogLevel.trim()); diff --git a/src/platform.ts b/src/platform.ts index f46c40bc66..036988caed 100644 --- a/src/platform.ts +++ b/src/platform.ts @@ -516,9 +516,15 @@ export class PowerShellExeFinder { "-c", "(Get-AppxPackage -Name Microsoft.PowerShell).InstallLocation", ]; - const msixDir: string = child_process.execFileSync(winPSPath, winPSArgs) - .toString() - .trim(); + + let msixDir: string; + try { + msixDir = child_process.execFileSync(winPSPath, winPSArgs) + .toString() + .trim(); + } catch { + // Do nothing + } if (!msixDir) { return null; diff --git a/src/session.ts b/src/session.ts index 49298e59be..f3479c3a8e 100644 --- a/src/session.ts +++ b/src/session.ts @@ -122,17 +122,23 @@ export class SessionManager implements Middleware { try { this.powerShellExePath = this.getPowerShellExePath(); } catch (e) { - this.log.writeError(`Unable to find PowerShell executable:\n${e}`); + this.log.writeError(`Error occurred while searching for a PowerShell executable:\n${e}`); } this.suppressRestartPrompt = false; if (!this.powerShellExePath) { - this.setSessionFailure( - "Unable to find PowerShell." + const message = "Unable to find PowerShell." + " Do you have PowerShell installed?" - + " To get PowerShell, go to https://aka.ms/get-powershell." - + " Click 'Show Logs' for more details."); + + " You can also set the 'powershell.powerShellAdditionalExePaths' configuration."; + + this.log.writeAndShowErrorWithActions(message, [{ + prompt: "Get PowerShell", + action: async () => { + const getPSUri = vscode.Uri.parse("https://aka.ms/AA6dwxc"); + vscode.env.openExternal(getPSUri); + }, + }]); return; } @@ -671,6 +677,12 @@ export class SessionManager implements Middleware { this.statusBarItem.text = statusIconText + statusText; } + private async warnPowerShellNotFoundAndSetSessionFailure() { + const warningMessage = "Unable to find PowerShell." + + " Do you have PowerShell installed?" + + " Click 'Show Logs' for more details."; + } + private setSessionFailure(message: string, ...additionalMessages: string[]) { this.log.writeAndShowError(message, ...additionalMessages); From 731e65182f988f5972aaf7fc7bdce22e4592bbdf Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Thu, 24 Oct 2019 22:22:03 -0700 Subject: [PATCH 35/46] Finalise message --- src/session.ts | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/src/session.ts b/src/session.ts index f3479c3a8e..aecc68909f 100644 --- a/src/session.ts +++ b/src/session.ts @@ -130,15 +130,18 @@ export class SessionManager implements Middleware { if (!this.powerShellExePath) { const message = "Unable to find PowerShell." + " Do you have PowerShell installed?" - + " You can also set the 'powershell.powerShellAdditionalExePaths' configuration."; - - this.log.writeAndShowErrorWithActions(message, [{ - prompt: "Get PowerShell", - action: async () => { - const getPSUri = vscode.Uri.parse("https://aka.ms/AA6dwxc"); - vscode.env.openExternal(getPSUri); + + " You can also configure custom PowerShell installations" + + " with the 'powershell.powerShellAdditionalExePaths' setting."; + + this.log.writeAndShowErrorWithActions(message, [ + { + prompt: "Get PowerShell", + action: async () => { + const getPSUri = vscode.Uri.parse("https://aka.ms/get-powershell-vscode"); + vscode.env.openExternal(getPSUri); + }, }, - }]); + ]); return; } From fe7a21c0eac407c53c0abace15875679f38d68d0 Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Fri, 25 Oct 2019 08:33:21 -0700 Subject: [PATCH 36/46] Update package.json Co-Authored-By: Tyler James Leonhardt --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 244fa733fb..8936ffe665 100644 --- a/package.json +++ b/package.json @@ -50,7 +50,7 @@ "@types/mocha": "~5.2.7", "@types/mock-fs": "~4.10.0", "@types/node": "~10.11.0", - "@types/node-fetch": "^2.5.2", + "@types/node-fetch": "~2.5.2", "@types/rewire": "~2.5.28", "@types/semver": "~6.0.2", "@types/sinon": "~7.5.0", From b3c769683cbf164d3453897e432730c5cce9b182 Mon Sep 17 00:00:00 2001 From: Rob Holt Date: Mon, 28 Oct 2019 17:06:16 -0700 Subject: [PATCH 37/46] Remove laziness, add preview MSIX detection --- src/platform.ts | 549 +++++++++++++++--------------------------- test/platform.test.ts | 63 +++-- 2 files changed, 218 insertions(+), 394 deletions(-) diff --git a/src/platform.ts b/src/platform.ts index 036988caed..8748f63438 100644 --- a/src/platform.ts +++ b/src/platform.ts @@ -68,39 +68,20 @@ export class PowerShellExeFinder { // This is required, since parseInt("7-preview") will return 7. private static IntRegex: RegExp = /^\d+$/; - private readonly platformDetails: IPlatformDetails; - - // PSCore version table - private readonly pwshWindowsInstallationsVal: - Lazy<{ stable: IPossiblePowerShellExe, preview: IPossiblePowerShellExe }>; - private readonly pwshAlternateBitnessWindowsInstallationsVal: - Lazy<{ stable: IPossiblePowerShellExe, preview: IPossiblePowerShellExe }>; - - // PowerShell 6+ installation - private readonly stablePwshExeVal: Lazy; - private readonly previewPwshExeVal: Lazy; - - // 32-bit PowerShell 6+ installation - private readonly stableAlternateBitnessPwshExeVal: Lazy; - private readonly previewAlternateBitnessPwshExeVal: Lazy; + private static PwshMsixRegex: RegExp = /^Microsoft.PowerShell_.*/; - // .NET Global Tool pwsh installation - private readonly dotnetGlobalToolExeVal: Lazy; + private static PwshPreviewMsixRegex: RegExp = /^Microsoft.PowerShellPreview_.*/; - // MSIX/UWP installation - private readonly msixExeVal: Lazy; - - // Snap pwsh installations on Linux - private readonly stableSnapExeVal: Lazy; - private readonly previewSnapExeVal: Lazy; - - // Windows PowerShell installations - private readonly winPSExeVal: Lazy; - private readonly alternateBitnessWinPSExeVal: Lazy; + // The platform details descriptor for the platform we're on + private readonly platformDetails: IPlatformDetails; // Additional configured PowerShells private readonly additionalPSExeSettings: Iterable; + private winPS: IPossiblePowerShellExe; + + private alternateBitnessWinPS: IPossiblePowerShellExe; + /** * Create a new PowerShellFinder object to discover PowerShell installations. * @param platformDetails Information about the machine we are running on. @@ -112,100 +93,6 @@ export class PowerShellExeFinder { this.platformDetails = platformDetails || getPlatformDetails(); this.additionalPSExeSettings = additionalPowerShellExes || []; - - this.pwshWindowsInstallationsVal = new Lazy(() => this.findPSCoreWindowsInstallations()); - this.pwshAlternateBitnessWindowsInstallationsVal = new Lazy( - () => this.findPSCoreWindowsInstallations({ findNonNativeBitness: true })); - - this.stablePwshExeVal = new Lazy(() => this.findPSCoreStable()); - this.stableAlternateBitnessPwshExeVal = new Lazy(() => this.findPSCoreAlternateBitnessStable()); - this.previewAlternateBitnessPwshExeVal = new Lazy(() => this.findPSCoreAlternateBitnessPreview()); - this.previewPwshExeVal = new Lazy(() => this.findPSCorePreview()); - this.dotnetGlobalToolExeVal = new Lazy(() => this.findPSCoreDotnetGlobalTool()); - this.msixExeVal = new Lazy(() => this.findPSCoreMsix()); - this.stableSnapExeVal = new Lazy(() => this.findPSCoreStableSnap()); - this.previewSnapExeVal = new Lazy(() => this.findPSCorePreviewSnap()); - this.winPSExeVal = new Lazy(() => this.findWinPS()); - this.alternateBitnessWinPSExeVal = new Lazy(() => this.findWinPS({ findNonNativeBitness: true })); - } - - /** - * The stable PowerShell 6+ installation. - * May be null if the installation directory is not present. - */ - private get pwshStable(): IPossiblePowerShellExe { - return this.stablePwshExeVal.value; - } - - /** - * The preview PowerShell 6+ installation. - * May be null if the installation directory is not present. - */ - private get pwshPreview(): IPossiblePowerShellExe { - return this.previewPwshExeVal.value; - } - - /** - * The stable non-process-native bitness PowerShell 6+ installation. - * This means 32-bit in a 64-bit process, and vice versa. - * May be null if the installation directory is not present - */ - private get pwshAlternateBitnessStable(): IPossiblePowerShellExe { - return this.stableAlternateBitnessPwshExeVal.value; - } - - /** - * The preview non-process-native bitness PowerShell 6+ installation. - * This means 32-bit in a 64-bit process, and vice versa. - * May be null if the installation directory is not present. - */ - private get pwshAlternateBitnessPreview(): IPossiblePowerShellExe { - return this.previewAlternateBitnessPwshExeVal.value; - } - - /** - * PowerShell 6+ installation from an MSIX (through the Windows store). - * May be null if the AppX package is not found. - */ - private get pwshMsix(): IPossiblePowerShellExe { - return this.msixExeVal.value; - } - - /** - * PowerShell 6+ stable Snap installation on Linux. - */ - private get pwshSnapStable(): IPossiblePowerShellExe { - return this.stableSnapExeVal.value; - } - - /** - * PowerShell 6+ preview Snap installation on Linux. - */ - private get pwshSnapPreview(): IPossiblePowerShellExe { - return this.previewSnapExeVal.value; - } - - /** - * PowerShell 6+ .NET Core global tool installation. - */ - private get pwshDotnetGlobalTool(): IPossiblePowerShellExe { - return this.dotnetGlobalToolExeVal.value; - } - - /** - * The Windows PowerShell installation under the %windir%\System32 folder. - * This always exists. - */ - private get winPS(): IPossiblePowerShellExe { - return this.winPSExeVal.value; - } - - /** - * On 64-bit Windows, refers to the Windows PowerShell installation - * not native to the current process' bitness. - */ - private get alternateBitnessWinPS(): IPossiblePowerShellExe { - return this.alternateBitnessWinPSExeVal.value; } /** @@ -248,7 +135,7 @@ export class PowerShellExeFinder { public *enumeratePowerShellInstallations(): Iterable { // Get the default PowerShell installations first for (const defaultPwsh of this.enumerateDefaultPowerShellInstallations()) { - if (defaultPwsh && defaultPwsh.exists) { + if (defaultPwsh && defaultPwsh.exists()) { yield defaultPwsh; } } @@ -256,7 +143,7 @@ export class PowerShellExeFinder { // Also show any additionally configured PowerShells // These may be duplicates of the default installations, but given a different name. for (const additionalPwsh of this.enumerateAdditionalPowerShellInstallations()) { - if (additionalPwsh && additionalPwsh.exists) { + if (additionalPwsh && additionalPwsh.exists()) { yield additionalPwsh; } } @@ -269,25 +156,21 @@ export class PowerShellExeFinder { */ private *enumerateDefaultPowerShellInstallations(): Iterable { // Find PSCore stable first - if (this.pwshStable) { - yield this.pwshStable; - } + yield this.findPSCoreStable(); switch (this.platformDetails.operatingSystem) { + case OperatingSystem.Linux: + // On Linux, find the snap + yield this.findPSCoreStableSnap(); + break; + case OperatingSystem.Windows: // Windows may have a 32-bit pwsh.exe - if (this.pwshAlternateBitnessStable) { - yield this.pwshAlternateBitnessStable; - } + yield this.findPSCoreWindowsInstallation({ findNonNativeBitness: true }); + // Also look for the MSIX/UWP installation - if (this.pwshMsix) { - yield this.pwshMsix; - } - break; + yield this.findPSCoreMsix(); - case OperatingSystem.Linux: - // On Linux, find the snap - yield this.pwshSnapStable; break; } @@ -300,30 +183,28 @@ export class PowerShellExeFinder { // yield this.pwshDotnetGlobalTool; // Look for PSCore preview - if (this.pwshPreview) { - yield this.pwshPreview; - } + yield this.findPSCorePreview(); switch (this.platformDetails.operatingSystem) { // On Linux, there might be a preview snap case OperatingSystem.Linux: - yield this.pwshSnapPreview; + yield this.findPSCorePreviewSnap(); break; case OperatingSystem.Windows: + // Find a preview MSIX + yield this.findPSCoreMsix({ findPreview: true }); + // Look for pwsh-preview with the opposite bitness - if (this.pwshAlternateBitnessPreview) { - yield this.pwshAlternateBitnessPreview; - } + yield this.findPSCoreWindowsInstallation({ findNonNativeBitness: true, findPreview: true }); // Finally, get Windows PowerShell // Get the natural Windows PowerShell for the process bitness - yield this.winPS; + yield this.findWinPS(); - if (this.alternateBitnessWinPS) { - yield this.alternateBitnessWinPS; - } + // Get the alternate bitness Windows PowerShell + yield this.findWinPS({ findNonNativeBitness: true }); break; } @@ -335,12 +216,90 @@ export class PowerShellExeFinder { */ private *enumerateAdditionalPowerShellInstallations(): Iterable { for (const additionalPwshSetting of this.additionalPSExeSettings) { - yield this.findAdditionalPwshExe(additionalPwshSetting); + yield new PossiblePowerShellExe(additionalPwshSetting.exePath, additionalPwshSetting.versionName); + } + } + + private findPSCoreStable(): IPossiblePowerShellExe { + switch (this.platformDetails.operatingSystem) { + case OperatingSystem.Linux: + return new PossiblePowerShellExe(LinuxExePath, "PowerShell"); + + case OperatingSystem.MacOS: + return new PossiblePowerShellExe(MacOSExePath, "PowerShell"); + + case OperatingSystem.Windows: + return this.findPSCoreWindowsInstallation(); + } + } + + private findPSCorePreview(): IPossiblePowerShellExe { + switch (this.platformDetails.operatingSystem) { + case OperatingSystem.Linux: + return new PossiblePowerShellExe(LinuxPreviewExePath, "PowerShell Preview"); + + case OperatingSystem.MacOS: + return new PossiblePowerShellExe(MacOSPreviewExePath, "PowerShell Preview"); + + case OperatingSystem.Windows: + return this.findPSCoreWindowsInstallation({ findPreview: true }); } } - private findPSCoreWindowsInstallations(options?: { findNonNativeBitness?: boolean }): - { stable: IPossiblePowerShellExe | null, preview: IPossiblePowerShellExe | null } | null { + private findPSCoreDotnetGlobalTool(): IPossiblePowerShellExe { + const exeName: string = this.platformDetails.operatingSystem === OperatingSystem.Windows + ? "pwsh.exe" + : "pwsh"; + + const dotnetGlobalToolExePath: string = path.join(os.homedir(), ".dotnet", "tools", exeName); + + return new PossiblePowerShellExe(dotnetGlobalToolExePath, ".NET Core PowerShell Global Tool"); + } + + private findPSCoreMsix(options?: { findPreview: boolean }): IPossiblePowerShellExe { + // We can't proceed if there's no LOCALAPPDATA path + if (!process.env.LOCALAPPDATA) { + return null; + } + + // Find the base directory for MSIX application exe shortcuts + const msixAppDir = path.join(process.env.LOCALAPPDATA, "Microsoft", "WindowsApps"); + + // Define whether we're looking for the preview or the stable + let pwshMsixDirRegex: RegExp = null; + let pwshMsixName: string = null; + if (options && options.findPreview) { + pwshMsixDirRegex = PowerShellExeFinder.PwshPreviewMsixRegex; + pwshMsixName = "PowerShell Preview MSIX"; + } else { + pwshMsixDirRegex = PowerShellExeFinder.PwshMsixRegex; + pwshMsixName = "PowerShell MSIX"; + } + + // We should find only one such application, so return on the first one + for (const subdir of fs.readdirSync(msixAppDir)) { + if (pwshMsixDirRegex.test(subdir)) { + const pwshMsixPath = path.join(msixAppDir, subdir, "pwsh.exe"); + return new PossiblePowerShellExe(pwshMsixPath, pwshMsixName); + } + } + + // If we find nothing, return null + return null; + } + + private findPSCoreStableSnap(): IPossiblePowerShellExe { + return new PossiblePowerShellExe(SnapExePath, "PowerShell Snap"); + } + + private findPSCorePreviewSnap(): IPossiblePowerShellExe { + return new PossiblePowerShellExe(SnapPreviewExePath, "PowerShell Preview Snap"); + } + + private findPSCoreWindowsInstallation(options?: { + findNonNativeBitness?: boolean; findPreview?: boolean }): IPossiblePowerShellExe { + + const findPreview = options && options.findPreview || false; const programFilesPath: string = this.getProgramFilesPath( { useAlternateBitness: options && options.findNonNativeBitness }); @@ -352,70 +311,102 @@ export class PowerShellExeFinder { return null; } - let highestSeenStableNumber: number = -1; - let stablePath: string = null; - let highestSeenPreviewNumber: number = -1; - let previewPath: string = null; + let highestSeenVersion: number = -1; + let pwshExePath: string = null; for (const item of fs.readdirSync(powerShellInstallBaseDir)) { - // Search for a directory like "6" or "7" first - if (item.match(PowerShellExeFinder.IntRegex)) { - const currentStable = parseInt(item, 10); + let currentVersion: number = -1; + if (findPreview) { + // We are looking for something like "7-preview" - // We may have already picked up a higher version - if (currentStable <= highestSeenStableNumber) { + // Preview dirs all have dashes in them + const dashIndex = item.indexOf("-"); + if (dashIndex < 0) { continue; } - // If the directory exists, but not pwsh.exe, just keep looking through dirs - const stableExePath = path.join(powerShellInstallBaseDir, item, "pwsh.exe"); - if (!fs.existsSync(stableExePath)) { + // Verify that the part before the dash is an integer + const intPart: string = item.substring(0, dashIndex); + if (!PowerShellExeFinder.IntRegex.test(intPart)) { continue; } - stablePath = stableExePath; - highestSeenStableNumber = currentStable; - continue; - } + // Verify that the part after the dash is "preview" + if (item.substring(dashIndex + 1) !== "preview") { + continue; + } - // Now look for something like "7-preview" + currentVersion = parseInt(intPart, 10); + } else { + // Search for a directory like "6" or "7" + if (!PowerShellExeFinder.IntRegex.test(item)) { + continue; + } - // Preview dirs all have dashes in them - const dashIndex = item.indexOf("-"); - if (dashIndex < 0) { - continue; + currentVersion = parseInt(item, 10); } - // Verify that the part before the dash is an integer - const intPart: string = item.substring(0, dashIndex); - if (!intPart.match(PowerShellExeFinder.IntRegex)) { - continue; - } - - // Weed out non preview dirs or versions lower than the one we've already seen - const currentPreview = parseInt(intPart, 10); - if (currentPreview <= highestSeenPreviewNumber || item.substring(dashIndex + 1) !== "preview") { + // Ensure we haven't already seen a higher version + if (currentVersion <= highestSeenVersion) { continue; } // Now look for the file - const previewExePath = path.join(powerShellInstallBaseDir, item, "pwsh.exe"); - if (!fs.existsSync(previewExePath)) { + const exePath = path.join(powerShellInstallBaseDir, item, "pwsh.exe"); + if (!fs.existsSync(exePath)) { continue; } - previewPath = previewExePath; - highestSeenPreviewNumber = currentPreview; + pwshExePath = exePath; + highestSeenVersion = currentVersion; } const bitness: string = programFilesPath.includes("x86") ? "(x86)" : "(x64)"; - return { - stable: stablePath && new PSCoreExe(stablePath, `PowerShell ${bitness}`, { knownToExist: true }), - preview: previewPath && new PSCoreExe(previewPath, `PowerShell Preview ${bitness}`, { knownToExist: true }), - }; + const preview: string = findPreview ? " Preview" : ""; + + return new PossiblePowerShellExe(pwshExePath, `PowerShell${preview} ${bitness}`); + } + + private findWinPS(options?: { findNonNativeBitness: boolean }): IPossiblePowerShellExe { + const useAlternateBitness: boolean = options && options.findNonNativeBitness; + + // 32-bit OSes only have one WinPS on them + if (!this.platformDetails.isOS64Bit && useAlternateBitness) { + return null; + } + + let winPS = useAlternateBitness ? this.alternateBitnessWinPS : this.winPS; + if (winPS === undefined) { + const systemFolderPath: string = this.getSystem32Path({ useAlternateBitness }); + + const winPSPath = path.join(systemFolderPath, "WindowsPowerShell", "v1.0", "powershell.exe"); + + let displayName: string; + if (this.platformDetails.isProcess64Bit) { + displayName = useAlternateBitness + ? WindowsPowerShell32BitLabel + : WindowsPowerShell64BitLabel; + } else if (this.platformDetails.isOS64Bit) { + displayName = useAlternateBitness + ? WindowsPowerShell64BitLabel + : WindowsPowerShell32BitLabel; + } else { + displayName = WindowsPowerShell32BitLabel; + } + + winPS = new PossiblePowerShellExe(winPSPath, displayName, { knownToExist: true }); + + if (useAlternateBitness) { + this.alternateBitnessWinPS = winPS; + } else { + this.winPS = winPS; + } + } + + return winPS; } private getProgramFilesPath(options?: { useAlternateBitness?: boolean }): string | null { @@ -453,116 +444,6 @@ export class PowerShellExeFinder { // We're on a 32-bit Windows, so no alternate bitness return null; } - - private findAdditionalPwshExe(additionalPwshSetting: IPowerShellAdditionalExePathSettings) { - return new PSCoreExe( - additionalPwshSetting.exePath, - additionalPwshSetting.versionName); - } - - private findPSCoreStable(): IPossiblePowerShellExe { - switch (this.platformDetails.operatingSystem) { - case OperatingSystem.Linux: - return new PSCoreExe(LinuxExePath, "PowerShell"); - - case OperatingSystem.MacOS: - return new PSCoreExe(MacOSExePath, "PowerShell"); - - case OperatingSystem.Windows: - return this.pwshWindowsInstallationsVal.value - && this.pwshWindowsInstallationsVal.value.stable; - } - } - - private findPSCorePreview(): IPossiblePowerShellExe { - switch (this.platformDetails.operatingSystem) { - case OperatingSystem.Linux: - return new PSCoreExe(LinuxPreviewExePath, "PowerShell Preview"); - - case OperatingSystem.MacOS: - return new PSCoreExe(MacOSPreviewExePath, "PowerShell Preview"); - - case OperatingSystem.Windows: - return this.pwshWindowsInstallationsVal.value - && this.pwshWindowsInstallationsVal.value.preview; - } - } - - private findPSCoreAlternateBitnessStable(): IPossiblePowerShellExe { - return this.pwshAlternateBitnessWindowsInstallationsVal.value - && this.pwshAlternateBitnessWindowsInstallationsVal.value.stable; - } - - private findPSCoreAlternateBitnessPreview(): IPossiblePowerShellExe { - return this.pwshAlternateBitnessWindowsInstallationsVal.value - && this.pwshAlternateBitnessWindowsInstallationsVal.value.preview; - } - - private findPSCoreDotnetGlobalTool(): IPossiblePowerShellExe { - const exeName: string = this.platformDetails.operatingSystem === OperatingSystem.Windows - ? "pwsh.exe" - : "pwsh"; - - const dotnetGlobalToolExePath: string = path.join(os.homedir(), ".dotnet", "tools", exeName); - - return new PSCoreExe(dotnetGlobalToolExePath, ".NET Core PowerShell Global Tool"); - } - - private findPSCoreMsix(): IPossiblePowerShellExe { - const winPSPath: string = this.findWinPS().exePath; - const winPSArgs = [ - "-nologo", - "-noprofile", - "-c", - "(Get-AppxPackage -Name Microsoft.PowerShell).InstallLocation", - ]; - - let msixDir: string; - try { - msixDir = child_process.execFileSync(winPSPath, winPSArgs) - .toString() - .trim(); - } catch { - // Do nothing - } - - if (!msixDir) { - return null; - } - - const msixExePath = path.join(msixDir, "pwsh.exe"); - return new PSCoreExe(msixExePath, "PowerShell MSIX"); - } - - private findPSCoreStableSnap(): IPossiblePowerShellExe { - return new PSCoreExe(SnapExePath, "PowerShell Snap"); - } - - private findPSCorePreviewSnap(): IPossiblePowerShellExe { - return new PSCoreExe(SnapPreviewExePath, "PowerShell Preview Snap"); - } - - private findWinPS(options?: { findNonNativeBitness: boolean }): IPossiblePowerShellExe { - const useAlternateBitness: boolean = options && options.findNonNativeBitness; - - const systemFolderPath: string = this.getSystem32Path({ useAlternateBitness }); - const winPSPath = path.join(systemFolderPath, "WindowsPowerShell", "v1.0", "powershell.exe"); - - let displayName: string; - if (this.platformDetails.isProcess64Bit) { - displayName = useAlternateBitness - ? WindowsPowerShell32BitLabel - : WindowsPowerShell64BitLabel; - } else if (this.platformDetails.isOS64Bit) { - displayName = useAlternateBitness - ? WindowsPowerShell64BitLabel - : WindowsPowerShell32BitLabel; - } else { - displayName = WindowsPowerShell32BitLabel; - } - - return new WinPSExe(winPSPath, displayName, { knownToExist: !useAlternateBitness }); - } } export function getWindowsSystemPowerShellPath(systemFolderName: string) { @@ -575,77 +456,29 @@ export function getWindowsSystemPowerShellPath(systemFolderName: string) { } interface IPossiblePowerShellExe extends IPowerShellExeDetails { - readonly exists: boolean; + exists(): boolean; } -abstract class PossiblePowerShellExe implements IPossiblePowerShellExe { - protected readonly lazyVersion: Lazy; +class PossiblePowerShellExe implements IPossiblePowerShellExe { + public readonly exePath: string; + public readonly displayName: string; - private readonly pathToExe: string; - private readonly installationName: string; - private readonly existsCheck: Lazy = null; + private knownToExist: boolean; constructor( pathToExe: string, installationName: string, options?: { knownToExist?: boolean }) { - this.pathToExe = pathToExe; - this.installationName = installationName; - - this.existsCheck = new Lazy(() => (options && options.knownToExist) || fs.existsSync(this.exePath)); - this.lazyVersion = new Lazy(() => this.findVersion()); - } - - get version(): string { - return this.lazyVersion.value; - } - - get exePath(): string { - return this.pathToExe; - } - - get exists(): boolean { - return this.existsCheck.value; - } - - get displayName(): string { - return this.installationName; - } - - protected abstract findVersion(): string; -} - -class WinPSExe extends PossiblePowerShellExe { - protected findVersion(): string { - return child_process.execFileSync(this.exePath, ["-c", "$PSVersionTable.PSVersion.ToString()"]); - } -} - -class PSCoreExe extends PossiblePowerShellExe { - protected findVersion(): string { - const versionLine = child_process.execFileSync(this.exePath, ["-v"]); - const spaceIndex = versionLine.indexOf(" "); - return versionLine.substring(spaceIndex + 1); - } -} - -class Lazy { - private readonly factory: () => T; - private constructed: boolean; - private underlyingValue: T; - - constructor(factory: () => T) { - this.constructed = false; - this.underlyingValue = null; - this.factory = factory; + this.exePath = pathToExe; + this.displayName = installationName; + this.knownToExist = options && options.knownToExist || undefined; } - public get value() { - if (!this.constructed) { - this.constructed = true; - this.underlyingValue = this.factory(); + public exists(): boolean { + if (this.knownToExist === undefined) { + this.knownToExist = fs.existsSync(this.exePath); } - return this.underlyingValue; + return this.knownToExist; } } diff --git a/test/platform.test.ts b/test/platform.test.ts index 2b4a7888f0..afe079d136 100644 --- a/test/platform.test.ts +++ b/test/platform.test.ts @@ -33,8 +33,10 @@ interface ITestPlatformSuccessCase extends ITestPlatform { // Platform configurations where we expect to find a set of PowerShells let successTestCases: ITestPlatformSuccessCase[]; -const pwshMsixDir = "C:\\Program Files\\WindowsApps\\Microsoft.PowerShell_7.0.0.4_neutral__8wekyb3d8bbwe"; -const pwshMsixPath = path.join(pwshMsixDir, "pwsh.exe"); +const localAppDataDir = "C:\\Users\\Alex Briggs\\AppData\\Local"; +const msixAppDir = path.join(localAppDataDir, "Microsoft", "WindowsApps"); +const pwshMsixPath = path.join(msixAppDir, "Microsoft.PowerShell_8wekyb3d8bbwe", "pwsh.exe"); +const pwshPreviewMsixPath = path.join(msixAppDir, "Microsoft.PowerShellPreview_8wekyb3d8bbwe", "pwsh.exe"); if (process.platform === "win32") { successTestCases = [ { @@ -48,6 +50,7 @@ if (process.platform === "win32") { "ProgramFiles": "C:\\Program Files", "ProgramFiles(x86)": "C:\\Program Files (x86)", "windir": "C:\\WINDOWS", + "LOCALAPPDATA": localAppDataDir, }, expectedPowerShellSequence: [ { @@ -66,6 +69,10 @@ if (process.platform === "win32") { exePath: "C:\\Program Files\\PowerShell\\7-preview\\pwsh.exe", displayName: "PowerShell Preview (x64)", }, + { + exePath: pwshPreviewMsixPath, + displayName: "PowerShell Preview MSIX", + }, { exePath: "C:\\Program Files (x86)\\PowerShell\\7-preview\\pwsh.exe", displayName: "PowerShell Preview (x86)", @@ -96,8 +103,13 @@ if (process.platform === "win32") { "pwsh.exe": "", }, }, - [pwshMsixDir]: { - "pwsh.exe": "", + [msixAppDir]: { + "Microsoft.PowerShell_8wekyb3d8bbwe": { + "pwsh.exe": "", + }, + "Microsoft.PowerShellPreview_8wekyb3d8bbwe": { + "pwsh.exe": "", + }, }, "C:\\WINDOWS\\System32\\WindowsPowerShell\\v1.0": { "powershell.exe": "", @@ -167,6 +179,10 @@ if (process.platform === "win32") { exePath: "C:\\Program Files (x86)\\PowerShell\\7-preview\\pwsh.exe", displayName: "PowerShell Preview (x86)", }, + { + exePath: pwshPreviewMsixPath, + displayName: "PowerShell Preview MSIX", + }, { exePath: "C:\\Program Files\\PowerShell\\7-preview\\pwsh.exe", displayName: "PowerShell Preview (x64)", @@ -197,8 +213,13 @@ if (process.platform === "win32") { "pwsh.exe": "", }, }, - [pwshMsixDir]: { - "pwsh.exe": "", + [msixAppDir]: { + "Microsoft.PowerShell_8wekyb3d8bbwe": { + "pwsh.exe": "", + }, + "Microsoft.PowerShellPreview_8wekyb3d8bbwe": { + "pwsh.exe": "", + }, }, "C:\\WINDOWS\\System32\\WindowsPowerShell\\v1.0": { "powershell.exe": "", @@ -491,16 +512,6 @@ suite("Platform module", () => { test(`Default PowerShell path on ${testPlatform.name}`, () => { mockFS(testPlatform.filesystem); - // The type inference here is wrong, so we need typescript to ignore it - // @ts-ignore - sinon.stub(child_process, "execFileSync").callsFake((procName, args?, options?) => { - if (!procName.includes("powershell")) { - return child_process.execFileSync(procName, args, options); - } - - return pwshMsixDir; - }); - if (testPlatform.environmentVars) { for (const envVar of Object.keys(testPlatform.environmentVars)) { process.env[envVar] = testPlatform.environmentVars[envVar]; @@ -521,16 +532,6 @@ suite("Platform module", () => { test(`Extension startup fails gracefully on ${testPlatform.name}`, () => { mockFS(testPlatform.filesystem); - // The type inference here is wrong, so we need typescript to ignore it - // @ts-ignore - sinon.stub(child_process, "execFileSync").callsFake((procName, args?, options?) => { - if (!procName.includes("powershell")) { - return child_process.execFileSync(procName, args, options); - } - - return pwshMsixDir; - }); - if (testPlatform.environmentVars) { for (const envVar of Object.keys(testPlatform.environmentVars)) { process.env[envVar] = testPlatform.environmentVars[envVar]; @@ -560,16 +561,6 @@ suite("Platform module", () => { test(`PowerShell installation list on ${testPlatform.name}`, () => { mockFS(testPlatform.filesystem); - // The type inference here is wrong, so we need typescript to ignore it - // @ts-ignore - sinon.stub(child_process, "execFileSync").callsFake((procName, args?, options?) => { - if (!procName.includes("powershell")) { - return child_process.execFileSync(procName, args, options); - } - - return pwshMsixDir; - }); - if (testPlatform.environmentVars) { for (const envVar of Object.keys(testPlatform.environmentVars)) { process.env[envVar] = testPlatform.environmentVars[envVar]; From 24a3a4b61cc256c0d5890f7041887b3fe01b2def Mon Sep 17 00:00:00 2001 From: Rob Holt Date: Mon, 28 Oct 2019 17:08:29 -0700 Subject: [PATCH 38/46] Remove unused method --- src/session.ts | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/session.ts b/src/session.ts index aecc68909f..8a2a304d66 100644 --- a/src/session.ts +++ b/src/session.ts @@ -680,12 +680,6 @@ export class SessionManager implements Middleware { this.statusBarItem.text = statusIconText + statusText; } - private async warnPowerShellNotFoundAndSetSessionFailure() { - const warningMessage = "Unable to find PowerShell." + - " Do you have PowerShell installed?" + - " Click 'Show Logs' for more details."; - } - private setSessionFailure(message: string, ...additionalMessages: string[]) { this.log.writeAndShowError(message, ...additionalMessages); From aa91a24196adc883eb6d0ca129c51b576f5dd756 Mon Sep 17 00:00:00 2001 From: Rob Holt Date: Tue, 29 Oct 2019 10:55:17 -0700 Subject: [PATCH 39/46] Fix test issues --- src/platform.ts | 12 ++++++++++++ test/platform.test.ts | 43 ++++++++++++++++++++++--------------------- 2 files changed, 34 insertions(+), 21 deletions(-) diff --git a/src/platform.ts b/src/platform.ts index 8748f63438..2fab588f1d 100644 --- a/src/platform.ts +++ b/src/platform.ts @@ -265,6 +265,10 @@ export class PowerShellExeFinder { // Find the base directory for MSIX application exe shortcuts const msixAppDir = path.join(process.env.LOCALAPPDATA, "Microsoft", "WindowsApps"); + if (!fs.existsSync(msixAppDir)) { + return null; + } + // Define whether we're looking for the preview or the stable let pwshMsixDirRegex: RegExp = null; let pwshMsixName: string = null; @@ -304,6 +308,10 @@ export class PowerShellExeFinder { const programFilesPath: string = this.getProgramFilesPath( { useAlternateBitness: options && options.findNonNativeBitness }); + if (!programFilesPath) { + return null; + } + const powerShellInstallBaseDir = path.join(programFilesPath, "PowerShell"); // Ensure the base directory exists @@ -361,6 +369,10 @@ export class PowerShellExeFinder { highestSeenVersion = currentVersion; } + if (!pwshExePath) { + return null; + } + const bitness: string = programFilesPath.includes("x86") ? "(x86)" : "(x64)"; diff --git a/test/platform.test.ts b/test/platform.test.ts index afe079d136..ce861e4485 100644 --- a/test/platform.test.ts +++ b/test/platform.test.ts @@ -3,7 +3,6 @@ *--------------------------------------------------------*/ import * as assert from "assert"; -import * as child_process from "child_process"; import mockFS = require("mock-fs"); import FileSystem = require("mock-fs/lib/filesystem"); import * as path from "path"; @@ -33,8 +32,7 @@ interface ITestPlatformSuccessCase extends ITestPlatform { // Platform configurations where we expect to find a set of PowerShells let successTestCases: ITestPlatformSuccessCase[]; -const localAppDataDir = "C:\\Users\\Alex Briggs\\AppData\\Local"; -const msixAppDir = path.join(localAppDataDir, "Microsoft", "WindowsApps"); +const msixAppDir = path.join(process.env.LOCALAPPDATA, "Microsoft", "WindowsApps"); const pwshMsixPath = path.join(msixAppDir, "Microsoft.PowerShell_8wekyb3d8bbwe", "pwsh.exe"); const pwshPreviewMsixPath = path.join(msixAppDir, "Microsoft.PowerShellPreview_8wekyb3d8bbwe", "pwsh.exe"); if (process.platform === "win32") { @@ -50,7 +48,6 @@ if (process.platform === "win32") { "ProgramFiles": "C:\\Program Files", "ProgramFiles(x86)": "C:\\Program Files (x86)", "windir": "C:\\WINDOWS", - "LOCALAPPDATA": localAppDataDir, }, expectedPowerShellSequence: [ { @@ -264,7 +261,7 @@ if (process.platform === "win32") { name: "Windows 32-bit, 32-bit VSCode (all installations)", platformDetails: { operatingSystem: platform.OperatingSystem.Windows, - isOS64Bit: true, + isOS64Bit: false, isProcess64Bit: false, }, environmentVars: { @@ -277,10 +274,18 @@ if (process.platform === "win32") { exePath: "C:\\Program Files (x86)\\PowerShell\\6\\pwsh.exe", displayName: "PowerShell (x86)", }, + { + exePath: pwshMsixPath, + displayName: "PowerShell MSIX", + }, { exePath: "C:\\Program Files (x86)\\PowerShell\\7-preview\\pwsh.exe", displayName: "PowerShell Preview (x86)", }, + { + exePath: pwshPreviewMsixPath, + displayName: "PowerShell Preview MSIX", + }, { exePath: "C:\\WINDOWS\\System32\\WindowsPowerShell\\v1.0\\powershell.exe", displayName: "Windows PowerShell (x86)", @@ -295,6 +300,14 @@ if (process.platform === "win32") { "pwsh.exe": "", }, }, + [msixAppDir]: { + "Microsoft.PowerShell_8wekyb3d8bbwe": { + "pwsh.exe": "", + }, + "Microsoft.PowerShellPreview_8wekyb3d8bbwe": { + "pwsh.exe": "", + }, + }, "C:\\WINDOWS\\System32\\WindowsPowerShell\\v1.0": { "powershell.exe": "", }, @@ -304,7 +317,7 @@ if (process.platform === "win32") { name: "Windows 32-bit, 32-bit VSCode (Windows PowerShell only)", platformDetails: { operatingSystem: platform.OperatingSystem.Windows, - isOS64Bit: true, + isOS64Bit: false, isProcess64Bit: false, }, environmentVars: { @@ -442,8 +455,6 @@ const errorTestCases: ITestPlatform[] = [ ]; suite("Platform module", () => { - let tempEnv: NodeJS.ProcessEnv; - suite("PlatformDetails", () => { const platformDetails: platform.IPlatformDetails = platform.getPlatformDetails(); switch (process.platform) { @@ -498,12 +509,7 @@ suite("Platform module", () => { }); suite("Default PowerShell installation", () => { - setup(() => { - tempEnv = Object.assign({}, process.env); - }); - teardown(() => { - process.env = tempEnv; sinon.restore(); mockFS.restore(); }); @@ -514,7 +520,7 @@ suite("Platform module", () => { if (testPlatform.environmentVars) { for (const envVar of Object.keys(testPlatform.environmentVars)) { - process.env[envVar] = testPlatform.environmentVars[envVar]; + sinon.stub(process.env, envVar).value(testPlatform.environmentVars[envVar]); } } @@ -534,7 +540,7 @@ suite("Platform module", () => { if (testPlatform.environmentVars) { for (const envVar of Object.keys(testPlatform.environmentVars)) { - process.env[envVar] = testPlatform.environmentVars[envVar]; + sinon.stub(process.env, envVar).value(testPlatform.environmentVars[envVar]); } } @@ -547,12 +553,7 @@ suite("Platform module", () => { }); suite("Expected PowerShell installation list", () => { - setup(() => { - tempEnv = Object.assign({}, process.env); - }); - teardown(() => { - process.env = tempEnv; sinon.restore(); mockFS.restore(); }); @@ -563,7 +564,7 @@ suite("Platform module", () => { if (testPlatform.environmentVars) { for (const envVar of Object.keys(testPlatform.environmentVars)) { - process.env[envVar] = testPlatform.environmentVars[envVar]; + sinon.stub(process.env, envVar).value(testPlatform.environmentVars[envVar]); } } From 7a9c5a81856042ce09d7a661afb2e43768c9f8a6 Mon Sep 17 00:00:00 2001 From: Rob Holt Date: Tue, 29 Oct 2019 12:05:10 -0700 Subject: [PATCH 40/46] Fix test bug on *nix --- test/platform.test.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/test/platform.test.ts b/test/platform.test.ts index ce861e4485..62503e28fc 100644 --- a/test/platform.test.ts +++ b/test/platform.test.ts @@ -32,10 +32,14 @@ interface ITestPlatformSuccessCase extends ITestPlatform { // Platform configurations where we expect to find a set of PowerShells let successTestCases: ITestPlatformSuccessCase[]; -const msixAppDir = path.join(process.env.LOCALAPPDATA, "Microsoft", "WindowsApps"); -const pwshMsixPath = path.join(msixAppDir, "Microsoft.PowerShell_8wekyb3d8bbwe", "pwsh.exe"); -const pwshPreviewMsixPath = path.join(msixAppDir, "Microsoft.PowerShellPreview_8wekyb3d8bbwe", "pwsh.exe"); +let msixAppDir = null; +let pwshMsixPath = null; +let pwshPreviewMsixPath = null; if (process.platform === "win32") { + msixAppDir = path.join(process.env.LOCALAPPDATA, "Microsoft", "WindowsApps"); + pwshMsixPath = path.join(msixAppDir, "Microsoft.PowerShell_8wekyb3d8bbwe", "pwsh.exe"); + pwshPreviewMsixPath = path.join(msixAppDir, "Microsoft.PowerShellPreview_8wekyb3d8bbwe", "pwsh.exe"); + successTestCases = [ { name: "Windows 64-bit, 64-bit VSCode (all installations)", From d1f8933a669708ec9e9d48344519e2006d274ee4 Mon Sep 17 00:00:00 2001 From: Rob Holt Date: Tue, 29 Oct 2019 12:14:15 -0700 Subject: [PATCH 41/46] Fix code factor issue --- src/logging.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/logging.ts b/src/logging.ts index 0716863eed..5faf83df35 100644 --- a/src/logging.ts +++ b/src/logging.ts @@ -127,7 +127,7 @@ export class Logger implements ILogger { const actionKeys: string[] = fullActions.map((action) => action.prompt); const choice = await vscode.window.showErrorMessage(message, ...actionKeys); - if (choice !== undefined) { + if (choice) { for (const action of fullActions) { if (choice === action.prompt) { await action.action(); From 6b04fbec3be8536613142293c0b66030b4139cff Mon Sep 17 00:00:00 2001 From: Rob Holt Date: Tue, 29 Oct 2019 15:21:57 -0700 Subject: [PATCH 42/46] Better destructuring --- src/platform.ts | 47 ++++++++++++++++++++--------------------------- 1 file changed, 20 insertions(+), 27 deletions(-) diff --git a/src/platform.ts b/src/platform.ts index 2fab588f1d..d8cf38e0de 100644 --- a/src/platform.ts +++ b/src/platform.ts @@ -166,7 +166,7 @@ export class PowerShellExeFinder { case OperatingSystem.Windows: // Windows may have a 32-bit pwsh.exe - yield this.findPSCoreWindowsInstallation({ findNonNativeBitness: true }); + yield this.findPSCoreWindowsInstallation({ useAlternateBitness: true }); // Also look for the MSIX/UWP installation yield this.findPSCoreMsix(); @@ -196,7 +196,7 @@ export class PowerShellExeFinder { yield this.findPSCoreMsix({ findPreview: true }); // Look for pwsh-preview with the opposite bitness - yield this.findPSCoreWindowsInstallation({ findNonNativeBitness: true, findPreview: true }); + yield this.findPSCoreWindowsInstallation({ useAlternateBitness: true, findPreview: true }); // Finally, get Windows PowerShell @@ -204,7 +204,7 @@ export class PowerShellExeFinder { yield this.findWinPS(); // Get the alternate bitness Windows PowerShell - yield this.findWinPS({ findNonNativeBitness: true }); + yield this.findWinPS({ useAlternateBitness: true }); break; } @@ -256,7 +256,7 @@ export class PowerShellExeFinder { return new PossiblePowerShellExe(dotnetGlobalToolExePath, ".NET Core PowerShell Global Tool"); } - private findPSCoreMsix(options?: { findPreview: boolean }): IPossiblePowerShellExe { + private findPSCoreMsix({ findPreview }: { findPreview?: boolean } = {}): IPossiblePowerShellExe { // We can't proceed if there's no LOCALAPPDATA path if (!process.env.LOCALAPPDATA) { return null; @@ -270,15 +270,9 @@ export class PowerShellExeFinder { } // Define whether we're looking for the preview or the stable - let pwshMsixDirRegex: RegExp = null; - let pwshMsixName: string = null; - if (options && options.findPreview) { - pwshMsixDirRegex = PowerShellExeFinder.PwshPreviewMsixRegex; - pwshMsixName = "PowerShell Preview MSIX"; - } else { - pwshMsixDirRegex = PowerShellExeFinder.PwshMsixRegex; - pwshMsixName = "PowerShell MSIX"; - } + const { pwshMsixDirRegex, pwshMsixName } = findPreview + ? { pwshMsixDirRegex: PowerShellExeFinder.PwshPreviewMsixRegex, pwshMsixName: "PowerShell Preview MSIX" } + : { pwshMsixDirRegex: PowerShellExeFinder.PwshMsixRegex, pwshMsixName: "PowerShell MSIX" }; // We should find only one such application, so return on the first one for (const subdir of fs.readdirSync(msixAppDir)) { @@ -300,13 +294,11 @@ export class PowerShellExeFinder { return new PossiblePowerShellExe(SnapPreviewExePath, "PowerShell Preview Snap"); } - private findPSCoreWindowsInstallation(options?: { - findNonNativeBitness?: boolean; findPreview?: boolean }): IPossiblePowerShellExe { - - const findPreview = options && options.findPreview || false; + private findPSCoreWindowsInstallation( + { useAlternateBitness = false, findPreview = false }: + { useAlternateBitness?: boolean; findPreview?: boolean } = {}): IPossiblePowerShellExe { - const programFilesPath: string = this.getProgramFilesPath( - { useAlternateBitness: options && options.findNonNativeBitness }); + const programFilesPath: string = this.getProgramFilesPath({ useAlternateBitness }); if (!programFilesPath) { return null; @@ -382,8 +374,7 @@ export class PowerShellExeFinder { return new PossiblePowerShellExe(pwshExePath, `PowerShell${preview} ${bitness}`); } - private findWinPS(options?: { findNonNativeBitness: boolean }): IPossiblePowerShellExe { - const useAlternateBitness: boolean = options && options.findNonNativeBitness; + private findWinPS({ useAlternateBitness = false }: { useAlternateBitness?: boolean } = {}): IPossiblePowerShellExe { // 32-bit OSes only have one WinPS on them if (!this.platformDetails.isOS64Bit && useAlternateBitness) { @@ -421,8 +412,10 @@ export class PowerShellExeFinder { return winPS; } - private getProgramFilesPath(options?: { useAlternateBitness?: boolean }): string | null { - if (!options || !options.useAlternateBitness) { + private getProgramFilesPath( + { useAlternateBitness = false }: { useAlternateBitness?: boolean } = {}): string | null { + + if (!useAlternateBitness) { return process.env.ProgramFiles; } @@ -438,10 +431,10 @@ export class PowerShellExeFinder { return null; } - private getSystem32Path(options?: { useAlternateBitness?: boolean }): string | null { + private getSystem32Path({ useAlternateBitness = false }: { useAlternateBitness?: boolean } = {}): string | null { const windir: string = process.env.windir; - if (!options || !options.useAlternateBitness) { + if (!useAlternateBitness) { return path.join(windir, "System32"); } @@ -480,11 +473,11 @@ class PossiblePowerShellExe implements IPossiblePowerShellExe { constructor( pathToExe: string, installationName: string, - options?: { knownToExist?: boolean }) { + { knownToExist = false }: { knownToExist?: boolean } = {}) { this.exePath = pathToExe; this.displayName = installationName; - this.knownToExist = options && options.knownToExist || undefined; + this.knownToExist = knownToExist || undefined; } public exists(): boolean { From 89e8a8dbdda2d457e0cee8993bee52118fc3aafd Mon Sep 17 00:00:00 2001 From: Rob Holt Date: Tue, 29 Oct 2019 15:36:40 -0700 Subject: [PATCH 43/46] Improve comments --- src/platform.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/platform.ts b/src/platform.ts index d8cf38e0de..ad2823c2e0 100644 --- a/src/platform.ts +++ b/src/platform.ts @@ -416,13 +416,16 @@ export class PowerShellExeFinder { { useAlternateBitness = false }: { useAlternateBitness?: boolean } = {}): string | null { if (!useAlternateBitness) { + // Just use the native system bitness return process.env.ProgramFiles; } + // We might be a 64-bit process looking for 32-bit program files if (this.platformDetails.isProcess64Bit) { return process.env["ProgramFiles(x86)"]; } + // We might be a 32-bit process looking for 64-bit program files if (this.platformDetails.isOS64Bit) { return process.env.ProgramW6432; } @@ -435,13 +438,16 @@ export class PowerShellExeFinder { const windir: string = process.env.windir; if (!useAlternateBitness) { + // Just use the native system bitness return path.join(windir, "System32"); } + // We might be a 64-bit process looking for 32-bit system32 if (this.platformDetails.isProcess64Bit) { return path.join(windir, "SysWOW64"); } + // We might be a 32-bit process looking for 64-bit system32 if (this.platformDetails.isOS64Bit) { return path.join(windir, "Sysnative"); } From 04046ec0bddf8182453fa3be5d4e7f240bf0634a Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Tue, 29 Oct 2019 15:46:03 -0700 Subject: [PATCH 44/46] Update src/session.ts Co-Authored-By: Tyler James Leonhardt --- src/session.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/session.ts b/src/session.ts index 8a2a304d66..eae0df823a 100644 --- a/src/session.ts +++ b/src/session.ts @@ -343,7 +343,7 @@ export class SessionManager implements Middleware { this.suppressRestartPrompt = true; await Settings.change("powerShellExePath", this.sessionSettings.developer.powerShellExePath, true); - await Settings.change("developer,powerShellExePath", undefined, true); + await Settings.change("developer.powerShellExePath", undefined, true); this.suppressRestartPrompt = false; } From bb95c19a3b74977c3d40e921f23b123d074ac77c Mon Sep 17 00:00:00 2001 From: Rob Holt Date: Tue, 29 Oct 2019 15:51:01 -0700 Subject: [PATCH 45/46] Rename MSIX to (Store) --- src/platform.ts | 4 ++-- test/platform.test.ts | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/platform.ts b/src/platform.ts index ad2823c2e0..2ade340989 100644 --- a/src/platform.ts +++ b/src/platform.ts @@ -271,8 +271,8 @@ export class PowerShellExeFinder { // Define whether we're looking for the preview or the stable const { pwshMsixDirRegex, pwshMsixName } = findPreview - ? { pwshMsixDirRegex: PowerShellExeFinder.PwshPreviewMsixRegex, pwshMsixName: "PowerShell Preview MSIX" } - : { pwshMsixDirRegex: PowerShellExeFinder.PwshMsixRegex, pwshMsixName: "PowerShell MSIX" }; + ? { pwshMsixDirRegex: PowerShellExeFinder.PwshPreviewMsixRegex, pwshMsixName: "PowerShell Preview (Store)" } + : { pwshMsixDirRegex: PowerShellExeFinder.PwshMsixRegex, pwshMsixName: "PowerShell (Store)" }; // We should find only one such application, so return on the first one for (const subdir of fs.readdirSync(msixAppDir)) { diff --git a/test/platform.test.ts b/test/platform.test.ts index 62503e28fc..51ef786f96 100644 --- a/test/platform.test.ts +++ b/test/platform.test.ts @@ -64,7 +64,7 @@ if (process.platform === "win32") { }, { exePath: pwshMsixPath, - displayName: "PowerShell MSIX", + displayName: "PowerShell (Store)", }, { exePath: "C:\\Program Files\\PowerShell\\7-preview\\pwsh.exe", @@ -72,7 +72,7 @@ if (process.platform === "win32") { }, { exePath: pwshPreviewMsixPath, - displayName: "PowerShell Preview MSIX", + displayName: "PowerShell Preview (Store)", }, { exePath: "C:\\Program Files (x86)\\PowerShell\\7-preview\\pwsh.exe", @@ -174,7 +174,7 @@ if (process.platform === "win32") { }, { exePath: pwshMsixPath, - displayName: "PowerShell MSIX", + displayName: "PowerShell (Store)", }, { exePath: "C:\\Program Files (x86)\\PowerShell\\7-preview\\pwsh.exe", @@ -182,7 +182,7 @@ if (process.platform === "win32") { }, { exePath: pwshPreviewMsixPath, - displayName: "PowerShell Preview MSIX", + displayName: "PowerShell Preview (Store)", }, { exePath: "C:\\Program Files\\PowerShell\\7-preview\\pwsh.exe", @@ -280,7 +280,7 @@ if (process.platform === "win32") { }, { exePath: pwshMsixPath, - displayName: "PowerShell MSIX", + displayName: "PowerShell (Store)", }, { exePath: "C:\\Program Files (x86)\\PowerShell\\7-preview\\pwsh.exe", @@ -288,7 +288,7 @@ if (process.platform === "win32") { }, { exePath: pwshPreviewMsixPath, - displayName: "PowerShell Preview MSIX", + displayName: "PowerShell Preview (Store)", }, { exePath: "C:\\WINDOWS\\System32\\WindowsPowerShell\\v1.0\\powershell.exe", From 50caf85d731adb4752eaa1ea58f0124597d40187 Mon Sep 17 00:00:00 2001 From: Rob Holt Date: Tue, 29 Oct 2019 16:24:40 -0700 Subject: [PATCH 46/46] Factor out test platform --- test/platform.test.ts | 45 +++++++++++++++++++++++-------------------- 1 file changed, 24 insertions(+), 21 deletions(-) diff --git a/test/platform.test.ts b/test/platform.test.ts index 51ef786f96..e141795029 100644 --- a/test/platform.test.ts +++ b/test/platform.test.ts @@ -458,6 +458,16 @@ const errorTestCases: ITestPlatform[] = [ }, ]; +function setupTestEnvironment(testPlatform: ITestPlatform) { + mockFS(testPlatform.filesystem); + + if (testPlatform.environmentVars) { + for (const envVar of Object.keys(testPlatform.environmentVars)) { + sinon.stub(process.env, envVar).value(testPlatform.environmentVars[envVar]); + } + } +} + suite("Platform module", () => { suite("PlatformDetails", () => { const platformDetails: platform.IPlatformDetails = platform.getPlatformDetails(); @@ -520,13 +530,7 @@ suite("Platform module", () => { for (const testPlatform of successTestCases) { test(`Default PowerShell path on ${testPlatform.name}`, () => { - mockFS(testPlatform.filesystem); - - if (testPlatform.environmentVars) { - for (const envVar of Object.keys(testPlatform.environmentVars)) { - sinon.stub(process.env, envVar).value(testPlatform.environmentVars[envVar]); - } - } + setupTestEnvironment(testPlatform); const powerShellExeFinder = new platform.PowerShellExeFinder(testPlatform.platformDetails); @@ -540,13 +544,7 @@ suite("Platform module", () => { for (const testPlatform of errorTestCases) { test(`Extension startup fails gracefully on ${testPlatform.name}`, () => { - mockFS(testPlatform.filesystem); - - if (testPlatform.environmentVars) { - for (const envVar of Object.keys(testPlatform.environmentVars)) { - sinon.stub(process.env, envVar).value(testPlatform.environmentVars[envVar]); - } - } + setupTestEnvironment(testPlatform); const powerShellExeFinder = new platform.PowerShellExeFinder(testPlatform.platformDetails); @@ -564,13 +562,7 @@ suite("Platform module", () => { for (const testPlatform of successTestCases) { test(`PowerShell installation list on ${testPlatform.name}`, () => { - mockFS(testPlatform.filesystem); - - if (testPlatform.environmentVars) { - for (const envVar of Object.keys(testPlatform.environmentVars)) { - sinon.stub(process.env, envVar).value(testPlatform.environmentVars[envVar]); - } - } + setupTestEnvironment(testPlatform); const powerShellExeFinder = new platform.PowerShellExeFinder(testPlatform.platformDetails); @@ -590,5 +582,16 @@ suite("Platform module", () => { "Number of expected PowerShells found does not match"); }); } + + for (const testPlatform of errorTestCases) { + test(`Extension startup fails gracefully on ${testPlatform.name}`, () => { + setupTestEnvironment(testPlatform); + + const powerShellExeFinder = new platform.PowerShellExeFinder(testPlatform.platformDetails); + + const foundPowerShells = powerShellExeFinder.getAllAvailablePowerShellInstallations(); + assert.strictEqual(foundPowerShells.length, 0); + }); + } }); });