From 68bc64b27f93fb4bdaff83ded0a3bdd49122e1a8 Mon Sep 17 00:00:00 2001 From: David Wilson Date: Fri, 25 Aug 2017 07:55:41 -0700 Subject: [PATCH 01/11] Update tasks.json files for 64-bit VS Code This change modifies the path used for PowerShell on Windows due to the fact that 64-bit VS Code has been released (and is now being promoted as the primary build). We're only pointing to the System32 path for now because VS Code doesn't provide a way to specify a different path in the 32-bit build. --- .vscode/tasks.json | 24 ++++++++++++++++++------ examples/.vscode/tasks.json | 2 +- 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 8bdc954f97..ae3f14998e 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -2,7 +2,7 @@ "version": "2.0.0", "windows": { - "command": "${env:windir}\\sysnative\\WindowsPowerShell\\v1.0\\powershell.exe", + "command": "${env:windir}/System32/WindowsPowerShell/v1.0/powershell.exe", "args": [ "-NoProfile", "-ExecutionPolicy", "Bypass" ] }, "linux": { @@ -20,28 +20,40 @@ { "taskName": "Install", "suppressTaskName": true, - "args": [ "Invoke-Build Restore" ] + "args": [ + "Invoke-Build Restore" + ] }, { "taskName": "CleanAll", "suppressTaskName": true, - "args": [ "Invoke-Build CleanAll" ] + "args": [ + "Invoke-Build CleanAll" + ] }, { "taskName": "Clean", "suppressTaskName": true, - "args": [ "Invoke-Build Clean" ] + "args": [ + "Invoke-Build Clean" + ] }, { "taskName": "BuildAll", "suppressTaskName": true, "isBuildCommand": true, - "args": [ "Invoke-Build BuildAll" ] + "args": [ + "Invoke-Build BuildAll" + ], + "problemMatcher": [] }, { "taskName": "Build", "suppressTaskName": true, - "args": [ "Invoke-Build Build" ] + "args": [ + "Invoke-Build Build" + ], + "problemMatcher": [] } ] } diff --git a/examples/.vscode/tasks.json b/examples/.vscode/tasks.json index 6452e9fd81..a9076f42b4 100644 --- a/examples/.vscode/tasks.json +++ b/examples/.vscode/tasks.json @@ -33,7 +33,7 @@ // Start PowerShell "windows": { - "command": "${env:windir}\\sysnative\\WindowsPowerShell\\v1.0\\powershell.exe", + "command": "${env:windir}/System32/WindowsPowerShell/v1.0/powershell.exe", "args": [ "-NoProfile", "-ExecutionPolicy", "Bypass" ] }, "linux": { From 88e8e909bc33dd9f428c9e3d4ef4a325403f1eec Mon Sep 17 00:00:00 2001 From: David Wilson Date: Fri, 25 Aug 2017 08:03:20 -0700 Subject: [PATCH 02/11] Fix #1009: Add tooltip to PS version status bar item --- src/session.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/session.ts b/src/session.ts index f6fde9de1f..d38d517a1c 100644 --- a/src/session.ts +++ b/src/session.ts @@ -460,6 +460,7 @@ export class SessionManager implements Middleware { 1); this.statusBarItem.command = this.ShowSessionMenuCommandName; + this.statusBarItem.tooltip = "Show PowerShell Session Menu"; this.statusBarItem.show(); vscode.window.onDidChangeActiveTextEditor(textEditor => { if (textEditor === undefined From 9aab76e7cd965d5e7733478c63c782e9d6a69bdf Mon Sep 17 00:00:00 2001 From: David Wilson Date: Fri, 25 Aug 2017 13:45:59 -0700 Subject: [PATCH 03/11] Add extension unit testing infrastructure This change enables Mocha-based unit tests for the PowerShell extension using VS Code's embedded test running mode. --- .gitignore | 1 + .vscode/launch.json | 15 +++++++++++++-- .vscodeignore | 3 +++ package.json | 11 +++++++---- src/debugAdapter.ts | 2 +- src/features/Examples.ts | 2 +- src/logging.ts | 2 +- src/main.ts | 2 +- src/process.ts | 4 ++-- src/session.ts | 4 ++-- src/utils.ts | 2 +- test/index.ts | 9 +++++++++ 12 files changed, 42 insertions(+), 15 deletions(-) create mode 100644 test/index.ts diff --git a/.gitignore b/.gitignore index 989ccd324e..0a35854e2e 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,4 @@ vscode-powershell.zip vscps-preview.zip *.vsix npm-debug.log +.vscode-test/ diff --git a/.vscode/launch.json b/.vscode/launch.json index 03bcd3cafb..f53cedf458 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -9,7 +9,7 @@ "args": [ "--extensionDevelopmentPath=${workspaceRoot}" ], "stopOnEntry": false, "sourceMaps": true, - "outFiles": ["${workspaceRoot}/out"], + "outFiles": ["${workspaceRoot}/out/src/**/*.js"], "preLaunchTask": "BuildAll" }, { @@ -20,7 +20,18 @@ "args": [ "--extensionDevelopmentPath=${workspaceRoot}" ], "stopOnEntry": false, "sourceMaps": true, - "outFiles": ["${workspaceRoot}/out"], + "outFiles": ["${workspaceRoot}/out/src/**/*.js"], + "preLaunchTask": "Build" + }, + { + "name": "Launch Extension Tests", + "type": "extensionHost", + "request": "launch", + "runtimeExecutable": "${execPath}", + "args": ["--extensionDevelopmentPath=${workspaceRoot}", "--extensionTestsPath=${workspaceRoot}/out/test" ], + "stopOnEntry": false, + "sourceMaps": true, + "outFiles": ["${workspaceRoot}/out/test/**/*.js"], "preLaunchTask": "Build" }, { diff --git a/.vscodeignore b/.vscodeignore index 76e2e6caf5..3decb35fad 100644 --- a/.vscodeignore +++ b/.vscodeignore @@ -1,4 +1,5 @@ .vscode/** +.vscode-test/** vscode-powershell.build.ps1 typings/** **/*.ts @@ -10,5 +11,7 @@ bin/DebugAdapter.log bin/*.vshost.* bin/PowerShell/** logs/** +out/test/** +test/** sessions/** scripts/Install-VSCode.ps1 diff --git a/package.json b/package.json index 64654b2812..4a4cb33002 100644 --- a/package.json +++ b/package.json @@ -24,7 +24,7 @@ "type": "git", "url": "https://github.com/PowerShell/vscode-powershell.git" }, - "main": "./out/main", + "main": "./out/src/main", "activationEvents": [ "onLanguage:powershell", "onCommand:PowerShell.NewProjectFromTemplate", @@ -41,7 +41,9 @@ "@types/node": "^6.0.40", "typescript": "^2.0.3", "vsce": "^1.18.0", - "vscode": "^1.1.0" + "vscode": "^1.1.0", + "mocha": "^2.3.3", + "@types/mocha": "^2.2.32" }, "extensionDependencies": [ "vscode.powershell" @@ -49,7 +51,8 @@ "scripts": { "compile": "tsc -p ./", "compile-watch": "tsc -watch -p ./", - "postinstall": "node ./node_modules/vscode/bin/install" + "postinstall": "node ./node_modules/vscode/bin/install", + "test": "node ./node_modules/vscode/bin/test" }, "contributes": { "keybindings": [ @@ -188,7 +191,7 @@ "powershell" ] }, - "program": "./out/debugAdapter.js", + "program": "./out/src/debugAdapter.js", "runtime": "node", "variables": { "PickPSHostProcess": "PowerShell.PickPSHostProcess", diff --git a/src/debugAdapter.ts b/src/debugAdapter.ts index 928973828d..10940dec24 100644 --- a/src/debugAdapter.ts +++ b/src/debugAdapter.ts @@ -14,7 +14,7 @@ import { Logger } from './logging'; // named pipes or a network protocol). It is purely a naive data // relay between the two transports. -var logBasePath = path.resolve(__dirname, "../logs"); +var logBasePath = path.resolve(__dirname, "../../logs"); var debugAdapterLogWriter = fs.createWriteStream( diff --git a/src/features/Examples.ts b/src/features/Examples.ts index ec436e0b10..77862b6777 100644 --- a/src/features/Examples.ts +++ b/src/features/Examples.ts @@ -12,7 +12,7 @@ export class ExamplesFeature implements IFeature { private examplesPath: string; constructor() { - this.examplesPath = path.resolve(__dirname, "../../examples"); + this.examplesPath = path.resolve(__dirname, "../../../examples"); this.command = vscode.commands.registerCommand('PowerShell.OpenExamplesFolder', () => { vscode.commands.executeCommand( "vscode.openFolder", diff --git a/src/logging.ts b/src/logging.ts index 784301dfca..22774b3563 100644 --- a/src/logging.ts +++ b/src/logging.ts @@ -29,7 +29,7 @@ export class Logger { constructor() { this.logChannel = vscode.window.createOutputChannel("PowerShell Extension Logs"); - this.logBasePath = path.resolve(__dirname, "../logs"); + this.logBasePath = path.resolve(__dirname, "../../logs"); utils.ensurePathExists(this.logBasePath); this.commands = [ diff --git a/src/main.ts b/src/main.ts index 8ccb72bcb4..e2d3aa2f00 100644 --- a/src/main.ts +++ b/src/main.ts @@ -161,7 +161,7 @@ function checkForUpdatedVersion(context: vscode.ExtensionContext) { if (choice === showReleaseNotes) { vscode.commands.executeCommand( 'markdown.showPreview', - vscode.Uri.file(path.resolve(__dirname, "../CHANGELOG.md"))); + vscode.Uri.file(path.resolve(__dirname, "../../CHANGELOG.md"))); } }); } diff --git a/src/process.ts b/src/process.ts index 99e4b94734..8530ca8344 100644 --- a/src/process.ts +++ b/src/process.ts @@ -40,7 +40,7 @@ export class PowerShellProcess { let startScriptPath = path.resolve( __dirname, - '../scripts/Start-EditorServices.ps1'); + '../../scripts/Start-EditorServices.ps1'); var editorServicesLogPath = this.log.getLogFilePath(logFileName); @@ -77,7 +77,7 @@ export class PowerShellProcess { // NOTE: This batch file approach is needed temporarily until VS Code's // createTerminal API gets an argument for setting environment variables // on the launched process. - var batScriptPath = path.resolve(__dirname, '../sessions/powershell.bat'); + var batScriptPath = path.resolve(__dirname, '../../sessions/powershell.bat'); fs.writeFileSync( batScriptPath, `@set DEVPATH=${path.dirname(powerShellExePath)}\r\n@${powerShellExePath} %*`); diff --git a/src/session.ts b/src/session.ts index d38d517a1c..df997bf4fc 100644 --- a/src/session.ts +++ b/src/session.ts @@ -121,7 +121,7 @@ export class SessionManager implements Middleware { if (this.powerShellExePath) { - var bundledModulesPath = path.resolve(__dirname, "../modules"); + var bundledModulesPath = path.resolve(__dirname, "../../modules"); if (this.inDevelopmentMode) { var devBundledModulesPath = @@ -129,7 +129,7 @@ export class SessionManager implements Middleware { path.resolve( __dirname, this.sessionSettings.developer.bundledModulesPath || - "../../PowerShellEditorServices/module"); + "../../../PowerShellEditorServices/module"); // Make sure the module's bin path exists if (fs.existsSync(path.join(devBundledModulesPath, "PowerShellEditorServices/bin"))) { diff --git a/src/utils.ts b/src/utils.ts index 1fa9e0f30a..edffeb1d90 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -52,7 +52,7 @@ export interface WaitForSessionFileCallback { (details: EditorServicesSessionDetails, error: string): void; } -let sessionsFolder = path.resolve(__dirname, "..", "sessions/"); +let sessionsFolder = path.resolve(__dirname, "..", "..", "sessions/"); let sessionFilePathPrefix = path.resolve(sessionsFolder, "PSES-VSCode-" + process.env.VSCODE_PID); // Create the sessions path if it doesn't exist already diff --git a/test/index.ts b/test/index.ts new file mode 100644 index 0000000000..1d6519d0d3 --- /dev/null +++ b/test/index.ts @@ -0,0 +1,9 @@ +var testRunner = require('vscode/lib/testrunner'); + +// See https://github.com/mochajs/mocha/wiki/Using-mocha-programmatically#set-options for options +testRunner.configure({ + ui: 'tdd', // the TDD UI is being used in extension.test.ts (suite, test, etc.) + useColors: true // colored output from test results +}); + +module.exports = testRunner; From d173c53ca5cdb3003cdbd47a4e4e361a4a731ca1 Mon Sep 17 00:00:00 2001 From: David Wilson Date: Fri, 1 Sep 2017 17:10:22 -0700 Subject: [PATCH 04/11] Fix issues with resolving correct PowerShell paths in 64-bit VS Code 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 --- src/platform.ts | 163 ++++++++++++++++++++++++++++++++++++++++++++++++ src/session.ts | 135 ++++++++++++++++----------------------- 2 files changed, 217 insertions(+), 81 deletions(-) create mode 100644 src/platform.ts 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; From 8f18848566ba7e0359f76ec8703d7b80766fc5d5 Mon Sep 17 00:00:00 2001 From: David Wilson Date: Tue, 5 Sep 2017 12:06:45 -0700 Subject: [PATCH 05/11] Add unit tests to verify platform module behavior --- .vscode/tasks.json | 12 ++++ src/platform.ts | 34 ++++++---- src/session.ts | 4 +- test/platform.test.ts | 121 ++++++++++++++++++++++++++++++++++++ vscode-powershell.build.ps1 | 7 ++- 5 files changed, 162 insertions(+), 16 deletions(-) create mode 100644 test/platform.test.ts diff --git a/.vscode/tasks.json b/.vscode/tasks.json index ae3f14998e..2bd076fbdc 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -54,6 +54,18 @@ "Invoke-Build Build" ], "problemMatcher": [] + }, + { + "taskName": "Test", + "suppressTaskName": true, + "args": [ + "Invoke-Build Test" + ], + "group": { + "kind": "test", + "isDefault": true + }, + "problemMatcher": [] } ] } diff --git a/src/platform.ts b/src/platform.ts index a148877a7d..adcaae1294 100644 --- a/src/platform.ts +++ b/src/platform.ts @@ -22,7 +22,7 @@ export interface PlatformDetails { isProcess64Bit: boolean } -interface PowerShellExeDetails { +export interface PowerShellExeDetails { versionName: string; exePath: string; } @@ -58,10 +58,18 @@ export function getDefaultPowerShellPath( // 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 + if (use32Bit) { + powerShellExePath = + platformDetails.isOS64Bit && platformDetails.isProcess64Bit + ? SysWow64PowerShellPath + : System32PowerShellPath + } + else { + powerShellExePath = + !platformDetails.isOS64Bit || platformDetails.isProcess64Bit + ? System32PowerShellPath + : SysnativePowerShellPath + } } else if (platformDetails.operatingSystem == OperatingSystem.MacOS) { powerShellExePath = "/usr/local/bin/powershell"; @@ -81,6 +89,9 @@ 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(); @@ -96,38 +107,35 @@ export function fixWindowsPowerShellPath(powerShellExePath: string, platformDeta return powerShellExePath; } -export function getPowerShellExeItems(platformDetails: PlatformDetails): PowerShellExeDetails[] { +export function getAvailablePowerShellExes(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, + versionName: WindowsPowerShell64BitLabel, exePath: System32PowerShellPath }) paths.push({ - versionName: windowsPowerShell32BitLabel, + versionName: WindowsPowerShell32BitLabel, exePath: SysWow64PowerShellPath }) } else { if (platformDetails.isOS64Bit) { paths.push({ - versionName: windowsPowerShell64BitLabel, + versionName: WindowsPowerShell64BitLabel, exePath: SysnativePowerShellPath }) } paths.push({ - versionName: windowsPowerShell32BitLabel, + versionName: WindowsPowerShell32BitLabel, exePath: System32PowerShellPath }) } diff --git a/src/session.ts b/src/session.ts index e26f6e28a7..bc31bddcb6 100644 --- a/src/session.ts +++ b/src/session.ts @@ -26,7 +26,7 @@ import { import { OperatingSystem, PlatformDetails, getDefaultPowerShellPath, getPlatformDetails, fixWindowsPowerShellPath, - getPowerShellExeItems } from './platform'; + getAvailablePowerShellExes } from './platform'; export enum SessionStatus { NotStarted, @@ -657,7 +657,7 @@ export class SessionManager implements Middleware { var currentExePath = this.powerShellExePath.toLowerCase(); var powerShellItems = - getPowerShellExeItems(this.platformDetails) + getAvailablePowerShellExes(this.platformDetails) .filter(item => item.exePath.toLowerCase() !== currentExePath) .map(item => { return new SessionMenuItem( diff --git a/test/platform.test.ts b/test/platform.test.ts new file mode 100644 index 0000000000..b115451e77 --- /dev/null +++ b/test/platform.test.ts @@ -0,0 +1,121 @@ +/*--------------------------------------------------------- + * Copyright (C) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------*/ + +import * as assert from 'assert'; +import * as vscode from 'vscode'; +import * as platform from '../src/platform'; + +function checkDefaultPowerShellPath(platformDetails, expectedPath) { + test("returns correct default path", () => { + assert.equal( + platform.getDefaultPowerShellPath(platformDetails), + expectedPath); + }); +} + +function checkAvailableWindowsPowerShellPaths( + platformDetails: platform.PlatformDetails, + expectedPaths: platform.PowerShellExeDetails[]) { + test("correctly enumerates available Windows PowerShell paths", () => { + + // The system may return PowerShell Core paths so only + // enumerate the first list items. + let enumeratedPaths = platform.getAvailablePowerShellExes(platformDetails); + for (var i; i < expectedPaths.length; i++) { + assert.equal(enumeratedPaths[i], expectedPaths[i]); + } + }); +} + +function checkFixedWindowsPowerShellpath(platformDetails, inputPath, expectedPath) { + test("fixes incorrect Windows PowerShell Sys* path", () => { + assert.equal( + platform.fixWindowsPowerShellPath(inputPath, platformDetails), + expectedPath); + }); +} + +suite("Platform module", () => { + + suite("64-bit Windows, 64-bit VS Code", () => { + let platformDetails: platform.PlatformDetails = { + operatingSystem: platform.OperatingSystem.Windows, + isOS64Bit: true, + isProcess64Bit: true + }; + + checkDefaultPowerShellPath( + platformDetails, + platform.System32PowerShellPath); + + checkAvailableWindowsPowerShellPaths( + platformDetails, + [ + { + versionName: platform.WindowsPowerShell64BitLabel, + exePath: platform.System32PowerShellPath + }, + { + versionName: platform.WindowsPowerShell32BitLabel, + exePath: platform.SysWow64PowerShellPath + } + ]); + + checkFixedWindowsPowerShellpath( + platformDetails, + platform.SysnativePowerShellPath, + platform.System32PowerShellPath); + }); + + suite("64-bit Windows, 32-bit VS Code", () => { + let platformDetails: platform.PlatformDetails = { + operatingSystem: platform.OperatingSystem.Windows, + isOS64Bit: true, + isProcess64Bit: false + }; + + checkDefaultPowerShellPath( + platformDetails, + platform.SysnativePowerShellPath); + + checkAvailableWindowsPowerShellPaths( + platformDetails, + [ + { + versionName: platform.WindowsPowerShell64BitLabel, + exePath: platform.SysnativePowerShellPath + }, + { + versionName: platform.WindowsPowerShell32BitLabel, + exePath: platform.System32PowerShellPath + } + ]); + + checkFixedWindowsPowerShellpath( + platformDetails, + platform.SysWow64PowerShellPath, + platform.System32PowerShellPath); + }); + + suite("32-bit Windows, 32-bit VS Code", () => { + let platformDetails: platform.PlatformDetails = { + operatingSystem: platform.OperatingSystem.Windows, + isOS64Bit: false, + isProcess64Bit: false + }; + + checkDefaultPowerShellPath( + platformDetails, + platform.System32PowerShellPath); + + checkAvailableWindowsPowerShellPaths( + platformDetails, + [ + { + versionName: platform.WindowsPowerShell32BitLabel, + exePath: platform.System32PowerShellPath + } + ]); + }); +}); diff --git a/vscode-powershell.build.ps1 b/vscode-powershell.build.ps1 index bd3163be30..4ba4e4481e 100644 --- a/vscode-powershell.build.ps1 +++ b/vscode-powershell.build.ps1 @@ -92,6 +92,11 @@ task BuildEditorServices { task BuildAll BuildEditorServices, Build -Before Package +task Test Build, { + Write-Host "`n### Running extension tests" -ForegroundColor Green + exec { & npm run test } +} + task Package { if ($script:psesBuildScriptPath) { @@ -112,4 +117,4 @@ task UploadArtifacts -If { $env:AppVeyor } { } # The default task is to run the entire CI build -task . GetExtensionVersion, CleanAll, BuildAll, Package, UploadArtifacts +task . GetExtensionVersion, CleanAll, BuildAll, Test, Package, UploadArtifacts From 53b9d8285780570b10c067c0f41b6441985a0734 Mon Sep 17 00:00:00 2001 From: David Wilson Date: Tue, 5 Sep 2017 12:47:21 -0700 Subject: [PATCH 06/11] PowerShell session commands should activate extension This change fixes #1020 which states that PowerShell session commands should activate the extension if it isn't already started. These commands are useful for activating a PowerShell session if the user doesn't currently have a PowerShell file open. --- package.json | 4 +++- src/session.ts | 10 +--------- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/package.json b/package.json index 4a4cb33002..6c2cd53bc5 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,9 @@ "onCommand:PowerShell.StartDebugSession", "onCommand:PowerShell.PickPSHostProcess", "onCommand:PowerShell.SpecifyScriptArgs", - "onCommand:PowerShell.ShowSessionConsole" + "onCommand:PowerShell.ShowSessionConsole", + "onCommand:PowerShell.ShowSessionMenu", + "onCommand:PowerShell.RestartSession" ], "dependencies": { "vscode-languageclient": "3.3.0-alpha.6" diff --git a/src/session.ts b/src/session.ts index bc31bddcb6..cff60f1336 100644 --- a/src/session.ts +++ b/src/session.ts @@ -628,14 +628,6 @@ export class SessionManager implements Middleware { private showSessionMenu() { var menuItems: SessionMenuItem[] = []; - if (this.sessionStatus === SessionStatus.Initializing || - this.sessionStatus === SessionStatus.NotStarted || - this.sessionStatus === SessionStatus.Stopping) { - - // Don't show a menu for these states - return; - } - if (this.sessionStatus === SessionStatus.Running) { menuItems = [ new SessionMenuItem( @@ -655,7 +647,7 @@ export class SessionManager implements Middleware { ]; } - var currentExePath = this.powerShellExePath.toLowerCase(); + var currentExePath = (this.powerShellExePath || "").toLowerCase(); var powerShellItems = getAvailablePowerShellExes(this.platformDetails) .filter(item => item.exePath.toLowerCase() !== currentExePath) From 9f6e02fd54ce151b1ecf3b1bbf50ac41c794197d Mon Sep 17 00:00:00 2001 From: David Wilson Date: Tue, 5 Sep 2017 16:21:54 -0700 Subject: [PATCH 07/11] Log VS Code version and process architecture information at startup --- src/session.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/session.ts b/src/session.ts index cff60f1336..fca1ed453a 100644 --- a/src/session.ts +++ b/src/session.ts @@ -77,6 +77,14 @@ export class SessionManager implements Middleware { .packageJSON .version; + let osBitness = this.platformDetails.isOS64Bit ? "64-bit" : "32-bit"; + let procBitness = this.platformDetails.isProcess64Bit ? "64-bit" : "32-bit"; + + this.log.write( + `Visual Studio Code v${vscode.version} ${procBitness}`, + `PowerShell Extension v${this.hostVersion}`, + `Operating System: ${OperatingSystem[this.platformDetails.operatingSystem]} ${osBitness}\n`); + // Fix the host version so that PowerShell can consume it. // This is needed when the extension uses a prerelease // version string like 0.9.1-insiders-1234. From ad3566fab9927dfc137b22a36253b845b518a177 Mon Sep 17 00:00:00 2001 From: David Wilson Date: Tue, 5 Sep 2017 16:22:24 -0700 Subject: [PATCH 08/11] Add logging for document formatting operations This change adds additional logging for document formatting operations to help diagnose user reported issues with this feature. --- src/features/DocumentFormatter.ts | 82 +++++++++++++++++++++---------- src/main.ts | 2 +- 2 files changed, 58 insertions(+), 26 deletions(-) diff --git a/src/features/DocumentFormatter.ts b/src/features/DocumentFormatter.ts index 9bacc9b44f..5a1d299161 100644 --- a/src/features/DocumentFormatter.ts +++ b/src/features/DocumentFormatter.ts @@ -26,6 +26,7 @@ import { } from 'vscode-languageclient'; import { TextDocumentIdentifier } from "vscode-languageserver-types"; import Window = vscode.window; +import { Logger } from '../logging'; import { IFeature } from '../feature'; import * as Settings from '../settings'; import * as Utils from '../utils'; @@ -130,15 +131,17 @@ class PSDocumentFormattingEditProvider implements return Promise.resolve(TextEdit[0]); } - constructor() { + constructor(private logger: Logger) { } provideDocumentFormattingEdits( document: TextDocument, options: FormattingOptions, token: CancellationToken): TextEdit[] | Thenable { - return this.provideDocumentRangeFormattingEdits(document, null, options, token); - } + + this.logger.writeVerbose(`Formatting entire document - ${document.uri}...`) + return this.sendDocumentFormatRequest(document, null, options, token); + } provideDocumentRangeFormattingEdits( document: TextDocument, @@ -146,6 +149,42 @@ class PSDocumentFormattingEditProvider implements options: FormattingOptions, token: CancellationToken): TextEdit[] | Thenable { + this.logger.writeVerbose(`Formatting document range ${JSON.stringify(range)} - ${document.uri}...`) + return this.sendDocumentFormatRequest(document, range, options, token); + } + + provideOnTypeFormattingEdits( + document: TextDocument, + position: Position, + ch: string, + options: FormattingOptions, + token: CancellationToken): TextEdit[] | Thenable { + + this.logger.writeVerbose(`Formatting on type at position ${JSON.stringify(position)} - ${document.uri}...`) + + return this.getScriptRegion(document, position, ch).then(scriptRegion => { + if (scriptRegion === null) { + this.logger.writeVerbose("No formattable range returned."); + return this.emptyPromise; + } + + return this.sendDocumentFormatRequest( + document, + toRange(scriptRegion), + options, + token); + }, + (err) => { + this.logger.writeVerbose(`Error while requesting script region for formatting: ${err}`) + }); + } + + private sendDocumentFormatRequest( + document: TextDocument, + range: Range, + options: FormattingOptions, + token: CancellationToken): TextEdit[] | Thenable { + let editor: TextEditor = this.getEditor(document); if (editor === undefined) { return this.emptyPromise; @@ -157,7 +196,6 @@ class PSDocumentFormattingEditProvider implements return this.emptyPromise; } - // somehow range object gets serialized to an array of Position objects, // so we need to use the object literal syntax to initialize it. let rangeParam = null; @@ -180,31 +218,25 @@ class PSDocumentFormattingEditProvider implements options: this.getEditorSettings() }; + let formattingStartTime = new Date().valueOf(); + function getFormattingDuration() { + return ((new Date().valueOf()) - formattingStartTime) / 1000; + } + let textEdits = this.languageClient.sendRequest( DocumentRangeFormattingRequest.type, requestParams); this.lockDocument(document, textEdits); PSDocumentFormattingEditProvider.showStatusBar(document, textEdits); - return textEdits; - } - - provideOnTypeFormattingEdits( - document: TextDocument, - position: Position, - ch: string, - options: FormattingOptions, - token: CancellationToken): TextEdit[] | Thenable { - return this.getScriptRegion(document, position, ch).then(scriptRegion => { - if (scriptRegion === null) { - return this.emptyPromise; - } - return this.provideDocumentRangeFormattingEdits( - document, - toRange(scriptRegion), - options, - token); - }); + return textEdits.then( + (edits) => { + this.logger.writeVerbose(`Document formatting finished in ${getFormattingDuration()}s`); + return edits; + }, + (err) => { + this.logger.writeVerbose(`Document formatting failed in ${getFormattingDuration()}: ${err}`); + }); } setLanguageClient(languageClient: LanguageClient): void { @@ -284,8 +316,8 @@ export class DocumentFormatterFeature implements IFeature { private languageClient: LanguageClient; private documentFormattingEditProvider: PSDocumentFormattingEditProvider; - constructor() { - this.documentFormattingEditProvider = new PSDocumentFormattingEditProvider(); + constructor(private logger: Logger) { + this.documentFormattingEditProvider = new PSDocumentFormattingEditProvider(logger); this.formattingEditProvider = vscode.languages.registerDocumentFormattingEditProvider( "powershell", this.documentFormattingEditProvider); diff --git a/src/main.ts b/src/main.ts index e2d3aa2f00..8204017626 100644 --- a/src/main.ts +++ b/src/main.ts @@ -117,7 +117,7 @@ export function activate(context: vscode.ExtensionContext): void { new SelectPSSARulesFeature(), new CodeActionsFeature(), new NewFileOrProjectFeature(), - new DocumentFormatterFeature(), + new DocumentFormatterFeature(logger), new RemoteFilesFeature(), new DebugSessionFeature(sessionManager), new PickPSHostProcessFeature(), From 5ea19c872ba0c0b7288cd61102e26aca69f9c7cb Mon Sep 17 00:00:00 2001 From: David Wilson Date: Tue, 5 Sep 2017 16:55:09 -0700 Subject: [PATCH 09/11] Temporarily disable extension unit tests on non-Windows platforms The VS Code extension test runner is failing consistently on Linux and MacOS so we are disabling it for now until we can diagnose the issue. Tracked by issue #1024. --- vscode-powershell.build.ps1 | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/vscode-powershell.build.ps1 b/vscode-powershell.build.ps1 index 4ba4e4481e..43b4c20693 100644 --- a/vscode-powershell.build.ps1 +++ b/vscode-powershell.build.ps1 @@ -93,8 +93,13 @@ task BuildEditorServices { task BuildAll BuildEditorServices, Build -Before Package task Test Build, { - Write-Host "`n### Running extension tests" -ForegroundColor Green - exec { & npm run test } + if (!$global:IsLinux -and !$global:IsOSX) { + Write-Host "`n### Running extension tests" -ForegroundColor Green + exec { & npm run test } + } + else { + Write-Host "`n### Skipping extension tests on non-Windows platform" -ForegroundColor Yellow + } } task Package { From 948ede2b7970ff10965c5882798307413c9954fa Mon Sep 17 00:00:00 2001 From: David Wilson Date: Tue, 5 Sep 2017 17:38:54 -0700 Subject: [PATCH 10/11] Add timestamps and log levels to all extension-side log messages --- src/logging.ts | 14 ++++++++------ src/process.ts | 2 +- src/session.ts | 4 ++-- 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/src/logging.ts b/src/logging.ts index 22774b3563..7691621ea7 100644 --- a/src/logging.ts +++ b/src/logging.ts @@ -49,10 +49,10 @@ export class Logger { public writeAtLevel(logLevel: LogLevel, message: string, ...additionalMessages: string[]) { if (logLevel >= this.MinimumLogLevel) { - this.writeLine(message) + this.writeLine(message, logLevel) additionalMessages.forEach((line) => { - this.writeLine(line); + this.writeLine(line, logLevel); }); } } @@ -136,11 +136,13 @@ export class Logger { } } - private writeLine(message: string) { - // TODO: Add timestamp - this.logChannel.appendLine(message); + private writeLine(message: string, level: LogLevel = LogLevel.Normal) { + let now = new Date(); + let timestampedMessage = `${now.toLocaleDateString()} ${now.toLocaleTimeString()} [${LogLevel[level].toUpperCase()}] - ${message}` + + this.logChannel.appendLine(timestampedMessage); if (this.logFilePath) { - fs.appendFile(this.logFilePath, message + os.EOL); + fs.appendFile(this.logFilePath, timestampedMessage + os.EOL); } } } diff --git a/src/process.ts b/src/process.ts index 8530ca8344..f8d3237328 100644 --- a/src/process.ts +++ b/src/process.ts @@ -85,7 +85,7 @@ export class PowerShellProcess { powerShellExePath = batScriptPath; } - this.log.write(`${utils.getTimestampString()} Language server starting...`); + this.log.write("Language server starting..."); // Make sure no old session file exists utils.deleteSessionFile(this.sessionFilePath); diff --git a/src/session.ts b/src/session.ts index fca1ed453a..417d0aaf83 100644 --- a/src/session.ts +++ b/src/session.ts @@ -329,7 +329,7 @@ export class SessionManager implements Middleware { this.sessionDetails = sessionDetails; if (sessionDetails.status === "started") { - this.log.write(`${utils.getTimestampString()} Language server started.`); + this.log.write("Language server started."); // Start the language service client this.startLanguageClient(sessionDetails); @@ -352,7 +352,7 @@ export class SessionManager implements Middleware { } }, error => { - this.log.write(`${utils.getTimestampString()} Language server startup failed.`); + this.log.write("Language server startup failed."); this.setSessionFailure("The language service could not be started: ", error); } ); From afd2026d91494b92852bf9733d057e2202d83524 Mon Sep 17 00:00:00 2001 From: David Wilson Date: Tue, 5 Sep 2017 16:36:35 -0700 Subject: [PATCH 11/11] Bump version to 1.4.2, update CHANGELOG.md --- CHANGELOG.md | 14 ++++++++++++++ appveyor.yml | 2 +- package.json | 2 +- 3 files changed, 16 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1d35531630..6a1524d469 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,19 @@ # vscode-powershell Release History +## 1.4.2 +### Tuesday, September 5, 2017 + +- [#993](https://github.com/PowerShell/vscode-powershell/issues/993) - + `powershell.powerShellExePath` using Sysnative path should be automatically + corrected when using 64-bit Visual Studio Code +- [#1008](https://github.com/PowerShell/vscode-powershell/issues/1008) - + Windows PowerShell versions (x64 and x86) are not enumerated correctly + when using 64-bit Visual Studio Code +- [#1009](https://github.com/PowerShell/vscode-powershell/issues/1009) - + PowerShell version indicator in status bar is missing tooltip +- [#1020](https://github.com/PowerShell/vscode-powershell/issues/1020) - + "Show Session Menu", "Show Integrated Console", and "Restart Current Session" + commands should cause PowerShell extension to be activated ## 1.4.1 ### Thursday, June 22, 2017 diff --git a/appveyor.yml b/appveyor.yml index 601c5a6c3e..0f5b13b620 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,4 +1,4 @@ -version: '1.4.1-insiders-{build}' +version: '1.4.2-insiders-{build}' image: Visual Studio 2017 clone_depth: 10 skip_tags: true diff --git a/package.json b/package.json index 6c2cd53bc5..f3f4fd1b57 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "PowerShell", "displayName": "PowerShell", - "version": "1.4.1", + "version": "1.4.2", "publisher": "ms-vscode", "description": "Develop PowerShell scripts in Visual Studio Code!", "engines": {