Skip to content

Commit

Permalink
Fix issues with resolving correct PowerShell paths in 64-bit VS Code
Browse files Browse the repository at this point in the history
This change introduces a new module that contains all the logic for
determining the current OS platform, process architecture and OS
architecture for use in determining available Windows PowerShell EXE
paths.  This module helps resolve problems that were introduced by the
new 64-bit distribution of Visual Studio Code.

Fixes #1008
Fixes #1007
Fixes #1006
Fixes #993
  • Loading branch information
daviwil committed Sep 5, 2017
1 parent 9aab76e commit d173c53
Show file tree
Hide file tree
Showing 2 changed files with 217 additions and 81 deletions.
163 changes: 163 additions & 0 deletions src/platform.ts
Original file line number Diff line number Diff line change
@@ -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;
}
135 changes: 54 additions & 81 deletions src/session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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;
Expand All @@ -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 =
Expand Down Expand Up @@ -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) {
Expand All @@ -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);

Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -748,11 +726,6 @@ export class SessionManager implements Middleware {
}
}

interface PowerShellExeDetails {
versionName: string;
exePath: string;
}

class SessionMenuItem implements vscode.QuickPickItem {
public description: string;

Expand Down

0 comments on commit d173c53

Please sign in to comment.