diff --git a/src/platform.ts b/src/platform.ts new file mode 100644 index 0000000000..a148877a7d --- /dev/null +++ b/src/platform.ts @@ -0,0 +1,163 @@ +/*--------------------------------------------------------- + * Copyright (C) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------*/ + +import fs = require('fs'); +import os = require('os'); +import path = require('path'); +import vscode = require('vscode'); +import process = require('process'); +import Settings = require('./settings'); + +export enum OperatingSystem { + Unknown, + Windows, + MacOS, + Linux +} + +export interface PlatformDetails { + operatingSystem: OperatingSystem + isOS64Bit: boolean + isProcess64Bit: boolean +} + +interface PowerShellExeDetails { + versionName: string; + exePath: string; +} + +export function getPlatformDetails(): PlatformDetails { + var operatingSystem = OperatingSystem.Unknown; + + if (process.platform === "win32") { + operatingSystem = OperatingSystem.Windows; + } + else if (process.platform === "darwin") { + operatingSystem = OperatingSystem.MacOS; + } + else if (process.platform === "linux") { + operatingSystem = OperatingSystem.Linux; + } + + let isProcess64Bit = process.arch === "x64"; + + return { + operatingSystem: operatingSystem, + isOS64Bit: isProcess64Bit || process.env.hasOwnProperty('PROCESSOR_ARCHITEW6432'), + isProcess64Bit: isProcess64Bit + } +} + +export function getDefaultPowerShellPath( + platformDetails: PlatformDetails, + use32Bit: boolean = false): string | null { + + var powerShellExePath = undefined; + + // Find the path to powershell.exe based on the current platform + // and the user's desire to run the x86 version of PowerShell + if (platformDetails.operatingSystem == OperatingSystem.Windows) { + powerShellExePath = + use32Bit || !process.env.hasOwnProperty('PROCESSOR_ARCHITEW6432') + ? System32PowerShellPath + : SysnativePowerShellPath + } + else if (platformDetails.operatingSystem == OperatingSystem.MacOS) { + powerShellExePath = "/usr/local/bin/powershell"; + } + else if (platformDetails.operatingSystem == OperatingSystem.Linux) { + powerShellExePath = "/usr/bin/powershell"; + } + + return powerShellExePath; +} + +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'); + +const powerShell64BitPathOn32Bit = SysnativePowerShellPath.toLocaleLowerCase(); +const powerShell32BitPathOn64Bit = SysWow64PowerShellPath.toLocaleLowerCase(); + +export function fixWindowsPowerShellPath(powerShellExePath: string, platformDetails: PlatformDetails): string { + let lowerCasedPath = powerShellExePath.toLocaleLowerCase(); + + if ((platformDetails.isProcess64Bit && (lowerCasedPath === powerShell64BitPathOn32Bit)) || + (!platformDetails.isProcess64Bit && (lowerCasedPath === powerShell32BitPathOn64Bit))) { + return System32PowerShellPath; + } + + // If the path doesn't need to be fixed, return the original + return powerShellExePath; +} + +export function getPowerShellExeItems(platformDetails: PlatformDetails): PowerShellExeDetails[] { + + var paths: PowerShellExeDetails[] = []; + + const windowsPowerShell64BitLabel = "Windows PowerShell (x64)"; + const windowsPowerShell32BitLabel = "Windows PowerShell (x86)"; + + if (platformDetails.operatingSystem === OperatingSystem.Windows) { + const psCoreInstallPath = + (!platformDetails.isProcess64Bit ? process.env.ProgramW6432 : process.env.ProgramFiles) + '\\PowerShell'; + + if (platformDetails.isProcess64Bit) { + paths.push({ + versionName: windowsPowerShell64BitLabel, + exePath: System32PowerShellPath + }) + + paths.push({ + versionName: windowsPowerShell32BitLabel, + exePath: SysWow64PowerShellPath + }) + } + else { + if (platformDetails.isOS64Bit) { + paths.push({ + versionName: windowsPowerShell64BitLabel, + exePath: SysnativePowerShellPath + }) + } + + paths.push({ + versionName: windowsPowerShell32BitLabel, + exePath: System32PowerShellPath + }) + } + + if (fs.existsSync(psCoreInstallPath)) { + var psCorePaths = + fs.readdirSync(psCoreInstallPath) + .map(item => path.join(psCoreInstallPath, item)) + .filter(item => fs.lstatSync(item).isDirectory()) + .map(item => { + return { + versionName: `PowerShell Core ${path.parse(item).base}`, + exePath: path.join(item, "powershell.exe") + }; + }); + + if (psCorePaths) { + paths = paths.concat(psCorePaths); + } + } + } + else { + paths.push({ + versionName: "PowerShell Core", + exePath: + os.platform() === "darwin" + ? "/usr/local/bin/powershell" + : "/usr/bin/powershell" + }); + } + + return paths; +} diff --git a/src/session.ts b/src/session.ts index df997bf4fc..e26f6e28a7 100644 --- a/src/session.ts +++ b/src/session.ts @@ -16,12 +16,18 @@ import { IFeature } from './feature'; import { Message } from 'vscode-jsonrpc'; import { PowerShellProcess } from './process'; import { StringDecoder } from 'string_decoder'; + import { LanguageClient, LanguageClientOptions, Executable, RequestType, RequestType0, NotificationType, StreamInfo, ErrorAction, CloseAction, RevealOutputChannelOn, Middleware, ResolveCodeLensSignature } from 'vscode-languageclient'; +import { + OperatingSystem, PlatformDetails, getDefaultPowerShellPath, + getPlatformDetails, fixWindowsPowerShellPath, + getPowerShellExeItems } from './platform'; + export enum SessionStatus { NotStarted, Initializing, @@ -35,12 +41,12 @@ export class SessionManager implements Middleware { private ShowSessionMenuCommandName = "PowerShell.ShowSessionMenu"; private hostVersion: string; - private isWindowsOS: boolean; private editorServicesArgs: string; private powerShellExePath: string = ""; private sessionStatus: SessionStatus; private suppressRestartPrompt: boolean; private focusConsoleOnExecute: boolean; + private platformDetails: PlatformDetails; private extensionFeatures: IFeature[] = []; private statusBarItem: vscode.StatusBarItem; private languageServerProcess: PowerShellProcess; @@ -61,7 +67,7 @@ export class SessionManager implements Middleware { private requiredEditorServicesVersion: string, private log: Logger) { - this.isWindowsOS = os.platform() == "win32"; + this.platformDetails = getPlatformDetails(); // Get the current version of this extension this.hostVersion = @@ -542,9 +548,53 @@ export class SessionManager implements Middleware { this.sessionSettings.developer.powerShellExePath || "").trim(); + if (this.platformDetails.operatingSystem === OperatingSystem.Windows && + powerShellExePath.length > 0) { + + // Check the path bitness + let fixedPath = + fixWindowsPowerShellPath( + powerShellExePath, + this.platformDetails); + + if (fixedPath !== powerShellExePath) { + let 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; + } + } + return powerShellExePath.length > 0 ? this.resolvePowerShellPath(powerShellExePath) - : this.getDefaultPowerShellPath(this.sessionSettings.useX86Host); + : getDefaultPowerShellPath(this.platformDetails, this.sessionSettings.useX86Host); } private changePowerShellExePath(exePath: string) { @@ -554,78 +604,6 @@ export class SessionManager implements Middleware { .then(() => this.restartSession()); } - private getPowerShellExeItems(): PowerShellExeDetails[] { - - var paths: PowerShellExeDetails[] = []; - - if (this.isWindowsOS) { - const is64Bit = process.env.hasOwnProperty('PROCESSOR_ARCHITEW6432'); - const rootInstallPath = (is64Bit ? process.env.ProgramW6432 : process.env.ProgramFiles) + '\\PowerShell'; - - if (fs.existsSync(rootInstallPath)) { - var psCorePaths = - fs.readdirSync(rootInstallPath) - .map(item => path.join(rootInstallPath, item)) - .filter(item => fs.lstatSync(item).isDirectory()) - .map(item => { - return { - versionName: `PowerShell Core ${path.parse(item).base}`, - exePath: path.join(item, "powershell.exe") - }; - }); - - if (psCorePaths) { - paths = paths.concat(psCorePaths); - } - } - - if (is64Bit) { - paths.push({ - versionName: "Windows PowerShell (x64)", - exePath: process.env.windir + '\\Sysnative\\WindowsPowerShell\\v1.0\\powershell.exe' - }) - } - - paths.push({ - versionName: "Windows PowerShell (x86)", - exePath: process.env.windir + '\\System32\\WindowsPowerShell\\v1.0\\powershell.exe' - }) - } - else { - paths.push({ - versionName: "PowerShell Core", - exePath: - os.platform() === "darwin" - ? "/usr/local/bin/powershell" - : "/usr/bin/powershell" - }); - } - - return paths; - } - - private getDefaultPowerShellPath(use32Bit: boolean): string | null { - - // Find the path to powershell.exe based on the current platform - // and the user's desire to run the x86 version of PowerShell - var powerShellExePath = undefined; - - if (this.isWindowsOS) { - powerShellExePath = - use32Bit || !process.env.hasOwnProperty('PROCESSOR_ARCHITEW6432') - ? process.env.windir + '\\System32\\WindowsPowerShell\\v1.0\\powershell.exe' - : process.env.windir + '\\Sysnative\\WindowsPowerShell\\v1.0\\powershell.exe'; - } - else if (os.platform() == "darwin") { - powerShellExePath = "/usr/local/bin/powershell"; - } - else { - powerShellExePath = "/usr/bin/powershell"; - } - - return this.resolvePowerShellPath(powerShellExePath); - } - private resolvePowerShellPath(powerShellExePath: string): string { var resolvedPath = path.resolve(__dirname, powerShellExePath); @@ -679,7 +657,7 @@ export class SessionManager implements Middleware { var currentExePath = this.powerShellExePath.toLowerCase(); var powerShellItems = - this.getPowerShellExeItems() + getPowerShellExeItems(this.platformDetails) .filter(item => item.exePath.toLowerCase() !== currentExePath) .map(item => { return new SessionMenuItem( @@ -748,11 +726,6 @@ export class SessionManager implements Middleware { } } -interface PowerShellExeDetails { - versionName: string; - exePath: string; -} - class SessionMenuItem implements vscode.QuickPickItem { public description: string;