From 43413c9b9c165f18ac2af65fe20615d998d8edbb Mon Sep 17 00:00:00 2001 From: David Wilson Date: Wed, 17 Aug 2016 17:12:53 -0700 Subject: [PATCH 1/2] Integrate Linux conversion work --- README.md | 2 +- examples/StopTest.ps1 | 2 +- package.json | 5 + scripts/Start-EditorServices.ps1 | 169 +++++++++++++++++++++++---- src/debugAdapter.ts | 30 ++++- src/logging.ts | 14 --- src/main.ts | 194 ++++++++++++++++++------------- src/utils.ts | 82 +++++++++++++ 8 files changed, 372 insertions(+), 126 deletions(-) create mode 100644 src/utils.ts diff --git a/README.md b/README.md index efd9a11808..bc7ecaea08 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ how to use them. This folder can be found at the following path: ``` -$env:USERPROFILE\.vscode\extensions\ms-vscode.PowerShell-\examples +c:\Users\\.vscode\extensions\ms-vscode.PowerShell-\examples ``` To open/view the extension's examples Visual Studio Code, run the following from your PowerShell command prompt: ``` diff --git a/examples/StopTest.ps1 b/examples/StopTest.ps1 index 4e0291a978..cf633a81fa 100644 --- a/examples/StopTest.ps1 +++ b/examples/StopTest.ps1 @@ -1,4 +1,4 @@ -. .\Stop-Process2.ps1 +. ./Stop-Process2.ps1 notepad.exe notepad.exe diff --git a/package.json b/package.json index 0657f516ea..6ab085aed6 100644 --- a/package.json +++ b/package.json @@ -182,6 +182,11 @@ "default": "", "description": "Specifies the path to a PowerShell Script Analyzer settings file. Use either an absolute path (to override the default settings for all projects) or use a path relative to your workspace." }, + "powershell.developer.powerShellExePath": { + "type": "string", + "default": "", + "description": "Specifies the full path to a PowerShell executable. Used to change the installation of PowerShell used for language and debugging services." + }, "powershell.developer.bundledModulesPath": { "type": "string", "default": "../modules/", diff --git a/scripts/Start-EditorServices.ps1 b/scripts/Start-EditorServices.ps1 index 0b2ee40fa6..4b343135b5 100644 --- a/scripts/Start-EditorServices.ps1 +++ b/scripts/Start-EditorServices.ps1 @@ -1,3 +1,21 @@ +# PowerShell Editor Services Bootstrapper Script +# ---------------------------------------------- +# This script contains startup logic for the PowerShell Editor Services +# module when launched by an editor. It handles the following tasks: +# +# - Verifying the existence of dependencies like PowerShellGet +# - Verifying that the expected version of the PowerShellEditorServices module is installed +# - Installing the PowerShellEditorServices module if confirmed by the user +# - Finding unused TCP port numbers for the language and debug services to use +# - Starting the language and debug services from the PowerShellEditorServices module +# +# NOTE: If editor integration authors make modifications to this +# script, please consider contributing changes back to the +# canonical version of this script at the PowerShell Editor +# Services GitHub repository: +# +# https://github.com/PowerShell/PowerShellEditorServices/blob/master/module/Start-EditorServices.ps1 + param( [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] @@ -19,16 +37,6 @@ param( [string] $HostVersion, - [Parameter(Mandatory=$true)] - [ValidateNotNullOrEmpty()] - [string] - $LanguageServicePipeName, - - [Parameter(Mandatory=$true)] - [ValidateNotNullOrEmpty()] - [string] - $DebugServicePipeName, - [ValidateNotNullOrEmpty()] [string] $BundledModulesPath, @@ -40,28 +48,141 @@ param( $LogLevel, [switch] - $WaitForCompletion, + $WaitForDebugger, [switch] - $WaitForDebugger + $ConfirmInstall ) +# This variable will be assigned later to contain information about +# what happened while attempting to launch the PowerShell Editor +# Services host +$resultDetails = $null; + +function Test-ModuleAvailable($ModuleName, $ModuleVersion) { + $modules = Get-Module -ListAvailable $moduleName + if ($modules -ne $null) { + if ($ModuleVersion -ne $null) { + foreach ($module in $modules) { + if ($module.Version.Equals($moduleVersion)) { + return $true; + } + } + } + else { + return $true; + } + } + + return $false; +} + +function Test-PortAvailability($PortNumber) { + $portAvailable = $true; + + try { + $ipAddress = [System.Net.Dns]::GetHostEntryAsync("localhost").Result.AddressList[0]; + $tcpListener = [System.Net.Sockets.TcpListener]::new($ipAddress, $portNumber); + $tcpListener.Start(); + $tcpListener.Stop(); + + } + catch [System.Net.Sockets.SocketException] { + # Check the SocketErrorCode to see if it's the expected exception + if ($error[0].Exception.InnerException.SocketErrorCode -eq [System.Net.Sockets.SocketError]::AddressAlreadyInUse) { + $portAvailable = $false; + } + else { + Write-Output ("Error code: " + $error[0].SocketErrorCode) + } + } + + return $portAvailable; +} + +$rand = [System.Random]::new() +function Get-AvailablePort { + $triesRemaining = 10; + + while ($triesRemaining -gt 0) { + $port = $rand.Next(10000, 30000) + if ((Test-PortAvailability -PortAvailability $port) -eq $true) { + return $port + } + + $triesRemaining--; + } + + return $null +} + # Add BundledModulesPath to $env:PSModulePath if ($BundledModulesPath) { - $env:PSModulePath = $BundledModulesPath + ";" + $env:PSModulePath + $env:PSMODULEPATH = $BundledModulesPath + [System.IO.Path]::PathSeparator + $env:PSMODULEPATH } +# Check if PowerShellGet module is available +if ((Test-ModuleAvailable "PowerShellGet") -eq $false) { + # TODO: WRITE ERROR +} + +# Check if the expected version of the PowerShell Editor Services +# module is installed $parsedVersion = [System.Version]::new($EditorServicesVersion) +if ((Test-ModuleAvailable "PowerShellEditorServices" -RequiredVersion $parsedVersion) -eq $false) { + if ($ConfirmInstall) { + # TODO: Check for error and return failure if necessary + Install-Module "PowerShellEditorServices" -RequiredVersion $parsedVersion -Confirm + } + else { + # Indicate to the client that the PowerShellEditorServices module + # needs to be installed + Write-Output "needs_install" + } +} + Import-Module PowerShellEditorServices -RequiredVersion $parsedVersion -ErrorAction Stop -Start-EditorServicesHost ` - -HostName $HostName ` - -HostProfileId $HostProfileId ` - -HostVersion $HostVersion ` - -LogPath $LogPath ` - -LogLevel $LogLevel ` - -LanguageServicePipeName $LanguageServicePipeName ` - -DebugServicePipeName $DebugServicePipeName ` - -BundledModulesPath $BundledModulesPath ` - -WaitForCompletion:$WaitForCompletion.IsPresent ` - -WaitForDebugger:$WaitForDebugger.IsPresent +# Locate available port numbers for services +$languageServicePort = Get-AvailablePort +$debugServicePort = Get-AvailablePort + +$editorServicesHost = + Start-EditorServicesHost ` + -HostName $HostName ` + -HostProfileId $HostProfileId ` + -HostVersion $HostVersion ` + -LogPath $LogPath ` + -LogLevel $LogLevel ` + -LanguageServicePort $languageServicePort ` + -DebugServicePort $debugServicePort ` + -BundledModulesPath $BundledModulesPath ` + -WaitForDebugger:$WaitForDebugger.IsPresent + +# TODO: Verify that the service is started + +$resultDetails = @{ + "status" = "started"; + "channel" = "tcp"; + "languageServicePort" = $languageServicePort; + "debugServicePort" = $debugServicePort; +}; + +# Notify the client that the services have started +Write-Output (ConvertTo-Json -InputObject $resultDetails -Compress) + +try { + # Wait for the host to complete execution before exiting + $editorServicesHost.WaitForCompletion() +} +catch [System.Exception] { + $e = $_.Exception; #.InnerException; + $errorString = "" + + while ($e -ne $null) { + $errorString = $errorString + ($e.Message + "`r`n" + $e.StackTrace + "`r`n") + $e = $e.InnerException; + } + + Write-Error ("`r`nCaught error while waiting for EditorServicesHost to complete:`r`n" + $errorString) +} \ No newline at end of file diff --git a/src/debugAdapter.ts b/src/debugAdapter.ts index c1ede3ec46..6a9484cd9d 100644 --- a/src/debugAdapter.ts +++ b/src/debugAdapter.ts @@ -1,6 +1,7 @@ import fs = require('fs'); import path = require('path'); import net = require('net'); +import utils = require('./utils'); import logging = require('./logging'); // NOTE: The purpose of this file is to serve as a bridge between @@ -10,7 +11,7 @@ import logging = require('./logging'); // relay between the two transports. var logBasePath = path.resolve(__dirname, "../logs"); -logging.ensurePathExists(logBasePath); +utils.ensurePathExists(logBasePath); var debugAdapterLogWriter = fs.createWriteStream( @@ -22,15 +23,18 @@ var debugAdapterLogWriter = // debug server process.stdin.pause(); +// Read the details of the current session to learn +// the connection details for the debug service +let sessionDetails = utils.readSessionFile(); + // Establish connection before setting up the session -let pipeName = "\\\\.\\pipe\\PSES-VSCode-DebugService-" + process.env.VSCODE_PID; -debugAdapterLogWriter.write("Connecting to named pipe: " + pipeName + "\r\n"); -let debugServiceSocket = net.connect(pipeName); +debugAdapterLogWriter.write("Connecting to port: " + sessionDetails.debugServicePort + "\r\n"); +let debugServiceSocket = net.connect(sessionDetails.debugServicePort); // Write any errors to the log file debugServiceSocket.on( 'error', - (e) => debugAdapterLogWriter.write("Named pipe ERROR: " + e + "\r\n")); + (e) => debugAdapterLogWriter.write("Socket connect ERROR: " + e + "\r\n")); // Route any output from the socket through stdout debugServiceSocket.on( @@ -41,7 +45,7 @@ debugServiceSocket.on( debugServiceSocket.on( 'connect', () => { - debugAdapterLogWriter.write("Connected to named pipe: " + pipeName + "\r\n"); + debugAdapterLogWriter.write("Connected to socket!\r\n\r\n"); // When data comes on stdin, route it through the socket process.stdin.on( @@ -51,3 +55,17 @@ debugServiceSocket.on( // Resume the stdin stream process.stdin.resume(); }); + +// When the socket closes, end the session +debugServiceSocket.on( + 'close', + () => { + debugAdapterLogWriter.write("Socket closed, shutting down."); + + // Close after a short delay to give the client time + // to finish up + setTimeout(() => { + process.exit(0); + }, 1000); + } +) \ No newline at end of file diff --git a/src/logging.ts b/src/logging.ts index ee929bd758..4e6d38e4af 100644 --- a/src/logging.ts +++ b/src/logging.ts @@ -1,19 +1,5 @@ import fs = require('fs'); -export function ensurePathExists(targetPath: string) { - // Ensure that the path exists - try { - fs.mkdirSync(targetPath); - } - catch (e) { - // If the exception isn't to indicate that the folder - // exists already, rethrow it. - if (e.code != 'EEXIST') { - throw e; - } - } -} - export function getLogName(baseName: string): string { return Math.floor(Date.now() / 1000) + '-' + baseName + '.log'; } \ No newline at end of file diff --git a/src/main.ts b/src/main.ts index 0d483306c1..0f2a9bbe66 100644 --- a/src/main.ts +++ b/src/main.ts @@ -8,12 +8,12 @@ import os = require('os'); import fs = require('fs'); import cp = require('child_process'); import path = require('path'); +import utils = require('./utils'); import vscode = require('vscode'); import logging = require('./logging'); import settingsManager = require('./settings'); import { StringDecoder } from 'string_decoder'; import { LanguageClient, LanguageClientOptions, Executable, RequestType, NotificationType, StreamInfo } from 'vscode-languageclient'; - import { registerExpandAliasCommand } from './features/ExpandAlias'; import { registerShowHelpCommand } from './features/ShowOnlineHelp'; import { registerOpenInISECommand } from './features/OpenInISE'; @@ -30,6 +30,7 @@ var requiredEditorServicesVersion = "0.7.0"; var powerShellProcess: cp.ChildProcess = undefined; var languageServerClient: LanguageClient = undefined; var PowerShellLanguageId = 'powershell'; +var powerShellLogWriter: fs.WriteStream = undefined; export function activate(context: vscode.ExtensionContext): void { @@ -72,107 +73,117 @@ export function activate(context: vscode.ExtensionContext): void { } }); - // The language server is only available on Windows - if (os.platform() == "win32") - { - // Get the current version of this extension - var hostVersion = - vscode - .extensions - .getExtension("ms-vscode.PowerShell") - .packageJSON - .version; - - var bundledModulesPath = settings.developer.bundledModulesPath; - if (!path.isAbsolute(bundledModulesPath)) { - bundledModulesPath = path.resolve(__dirname, bundledModulesPath); - } + // Get the current version of this extension + var hostVersion = + vscode + .extensions + .getExtension("ms-vscode.PowerShell") + .packageJSON + .version; + + var bundledModulesPath = settings.developer.bundledModulesPath; + if (!path.isAbsolute(bundledModulesPath)) { + bundledModulesPath = path.resolve(__dirname, bundledModulesPath); + } - var startArgs = - '-EditorServicesVersion "' + requiredEditorServicesVersion + '"' + - '-HostName "Visual Studio Code Host" ' + - '-HostProfileId "Microsoft.VSCode" ' + - '-HostVersion "' + hostVersion + '" ' + - '-BundledModulesPath "' + bundledModulesPath + '" ' + - '-WaitForCompletion '; + var startArgs = + '-EditorServicesVersion "' + requiredEditorServicesVersion + '" ' + + '-HostName "Visual Studio Code Host" ' + + '-HostProfileId "Microsoft.VSCode" ' + + '-HostVersion "' + hostVersion + '" ' + + '-BundledModulesPath "' + bundledModulesPath + '" '; - if (settings.developer.editorServicesWaitForDebugger) { - startArgs += '-WaitForDebugger '; - } - if (settings.developer.editorServicesLogLevel) { - startArgs += '-LogLevel "' + settings.developer.editorServicesLogLevel + '" ' - } + if (settings.developer.editorServicesWaitForDebugger) { + startArgs += '-WaitForDebugger '; + } + if (settings.developer.editorServicesLogLevel) { + startArgs += '-LogLevel "' + settings.developer.editorServicesLogLevel + '" ' + } + + // 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; - // 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 = + if (os.platform() == "win32") { + powerShellExePath = settings.useX86Host || !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"; + } - // Is there a setting override for the PowerShell path? - if (settings.developer.powerShellExePath) { - powerShellExePath = settings.developer.powerShellExePath; - - // If the path does not exist, show an error - fs.access( - powerShellExePath, fs.X_OK, - (err) => { - if (err) { - vscode.window.showErrorMessage( - "powershell.exe cannot be found or is not accessible at path " + powerShellExePath); - } - else { - startPowerShell( - powerShellExePath, - bundledModulesPath, - startArgs); - } - }); - } - else { - startPowerShell( - powerShellExePath, - bundledModulesPath, - startArgs); - } + // Is there a setting override for the PowerShell path? + if (settings.developer.powerShellExePath && + settings.developer.powerShellExePath.trim().length > 0) { + + powerShellExePath = settings.developer.powerShellExePath; + + // If the path does not exist, show an error + fs.access( + powerShellExePath, fs.X_OK, + (err) => { + if (err) { + vscode.window.showErrorMessage( + "powershell.exe cannot be found or is not accessible at path " + powerShellExePath); + } + else { + startPowerShell( + powerShellExePath, + bundledModulesPath, + startArgs); + } + }); + } + else { + startPowerShell( + powerShellExePath, + bundledModulesPath, + startArgs); } } function startPowerShell(powerShellExePath: string, bundledModulesPath: string, startArgs: string) { try { - let languageServicePipeName = "PSES-VSCode-LanguageService-" + process.env.VSCODE_PID; - let debugServicePipeName = "PSES-VSCode-DebugService-" + process.env.VSCODE_PID; - let startScriptPath = path.resolve( __dirname, '../scripts/Start-EditorServices.ps1'); var logBasePath = path.resolve(__dirname, "../logs"); - logging.ensurePathExists(logBasePath); + utils.ensurePathExists(logBasePath); var editorServicesLogName = logging.getLogName("EditorServices"); var powerShellLogName = logging.getLogName("PowerShell"); startArgs += - '-LogPath "' + path.resolve(logBasePath, editorServicesLogName) + '" ' + - '-LanguageServicePipeName "' + languageServicePipeName + '" ' + - '-DebugServicePipeName "' + debugServicePipeName + '" '; + '-LogPath "' + path.resolve(logBasePath, editorServicesLogName) + '" '; let args = [ '-NoProfile', - '-NonInteractive', - '-ExecutionPolicy', 'Unrestricted', - '-Command', startScriptPath + ' ' + startArgs + '-NonInteractive' ] + // Only add ExecutionPolicy param on Windows + if (os.platform() == "win32") { + args.push('-ExecutionPolicy'); + args.push('Unrestricted'); + } + + // Add the Start-EditorServices.ps1 invocation arguments + args.push('-Command') + args.push(startScriptPath + ' ' + startArgs) + // Launch PowerShell as child process powerShellProcess = cp.spawn(powerShellExePath, args); // Open a log file to be used for PowerShell.exe output - var powerShellLogWriter = + powerShellLogWriter = fs.createWriteStream( path.resolve(logBasePath, powerShellLogName)) @@ -181,8 +192,19 @@ function startPowerShell(powerShellExePath: string, bundledModulesPath: string, 'data', (data: Buffer) => { powerShellLogWriter.write("OUTPUT: " + data); - if (decoder.write(data).trim() == "PowerShell Editor Services host has started.") { - startLanguageClient(languageServicePipeName); + var response = JSON.parse(decoder.write(data).trim()); + + if (response["status"] === "started") { + let sessionDetails: utils.EditorServicesSessionDetails = response; + + // Write out the session configuration file + utils.writeSessionFile(sessionDetails); + + // Start the language service client + startLanguageClient(sessionDetails.languageServicePort, powerShellLogWriter); + } + else { + // TODO: Handle other response cases } }); @@ -196,9 +218,12 @@ function startPowerShell(powerShellExePath: string, bundledModulesPath: string, powerShellProcess.on( 'close', (exitCode) => { - languageServerClient.stop(); console.log("powershell.exe terminated with exit code: " + exitCode); powerShellLogWriter.write("\r\npowershell.exe terminated with exit code: " + exitCode + "\r\n"); + + if (languageServerClient != undefined) { + languageServerClient.stop(); + } }); console.log("powershell.exe started, pid: " + powerShellProcess.pid + ", exe: " + powerShellExePath); @@ -206,7 +231,8 @@ function startPowerShell(powerShellExePath: string, bundledModulesPath: string, "powershell.exe started --" + "\r\n pid: " + powerShellProcess.pid + "\r\n exe: " + powerShellExePath + - "\r\n bundledModulesPath: " + bundledModulesPath + "\r\n\r\n"); + "\r\n bundledModulesPath: " + bundledModulesPath + + "\r\n args: " + startScriptPath + ' ' + startArgs + "\r\n\r\n"); // TODO: Set timeout for response from powershell.exe } @@ -217,17 +243,20 @@ function startPowerShell(powerShellExePath: string, bundledModulesPath: string, } } -function startLanguageClient(pipeName: string) { +function startLanguageClient(port: number, logWriter: fs.WriteStream) { + + logWriter.write("Connecting to port: " + port + "\r\n"); + try { let connectFunc = () => { return new Promise( (resolve, reject) => { - var socket = net.connect("\\\\.\\pipe\\" + pipeName); + var socket = net.connect(port); socket.on( 'connect', function() { - console.log("Pipe connected!"); + console.log("Socket connected!"); resolve({writer: socket, reader: socket}) }); }); @@ -271,13 +300,18 @@ function registerFeatures() { } export function deactivate(): void { + powerShellLogWriter.write("\r\n\r\nShutting down language client..."); + + // Close the language server client if (languageServerClient) { - // Close the language server client languageServerClient.stop(); languageServerClient = undefined; } - // Kill the child process after some time just in case - setTimeout(() => {}, 3000); -} + // Clean up the session file + utils.deleteSessionFile(); + // Kill the PowerShell process we spawned + powerShellLogWriter.write("\r\nTerminating PowerShell process..."); + powerShellProcess.kill(); +} diff --git a/src/utils.ts b/src/utils.ts new file mode 100644 index 0000000000..6ab779ce46 --- /dev/null +++ b/src/utils.ts @@ -0,0 +1,82 @@ +import fs = require('fs'); +import os = require('os'); +import path = require('path'); + +export function ensurePathExists(targetPath: string) { + // Ensure that the path exists + try { + fs.mkdirSync(targetPath); + } + catch (e) { + // If the exception isn't to indicate that the folder + // exists already, rethrow it. + if (e.code != 'EEXIST') { + throw e; + } + } +} + +export function getUniqueSessionId() { + // We need to uniquely identify the current VS Code session + // using some string so that we get a reliable pipe server name + // for both the language and debug servers. + + if (os.platform() == "linux") { + // Electron running on Linux uses an additional layer of + // separation between parent and child processes which + // prevents environment variables from being inherited + // easily. This causes VSCODE_PID to not be available + // (for now) so use a different variable to get a + // unique session. + return process.env.VSCODE_PID; + } + else { + // VSCODE_PID is available on Windows and OSX + return process.env.VSCODE_PID; + } +} + +export function getPipePath(pipeName: string) { + if (os.platform() == "win32") { + return "\\\\.\\pipe\\" + pipeName; + } + else { + // On UNIX platforms the pipe will live under the temp path + // For details on how this path is computed, see the corefx + // source for System.IO.Pipes.PipeStream: + // https://github.com/dotnet/corefx/blob/d0dc5fc099946adc1035b34a8b1f6042eddb0c75/src/System.IO.Pipes/src/System/IO/Pipes/PipeStream.Unix.cs#L340 + return path.resolve( + os.tmpdir(), + ".dotnet", "corefx", "pipe", + pipeName); + } +} + +export interface EditorServicesSessionDetails { + channel: string; + languageServicePort: number; + debugServicePort: number; +} +export interface ReadSessionFileCallback { + (details: EditorServicesSessionDetails): void; +} + +let sessionsFolder = path.resolve(__dirname, "sessions/"); +let sessionFilePath = path.resolve(sessionsFolder, "PSES-VSCode-" + process.env.VSCODE_PID); + +export function writeSessionFile(sessionDetails: EditorServicesSessionDetails) { + ensurePathExists(sessionsFolder); + + var writeStream = fs.createWriteStream(sessionFilePath); + writeStream.write(JSON.stringify(sessionDetails)); + writeStream.close(); +} + +export function readSessionFile(): EditorServicesSessionDetails { + let fileContents = fs.readFileSync(sessionFilePath, "utf-8"); + return JSON.parse(fileContents) +} + +export function deleteSessionFile() { + fs.unlinkSync(sessionFilePath); +} \ No newline at end of file From 4c915c41deef60482a291717b373c92f513e8c13 Mon Sep 17 00:00:00 2001 From: David Wilson Date: Thu, 18 Aug 2016 10:41:34 -0700 Subject: [PATCH 2/2] Bump version to 0.7.0, update CHANGELOG.md --- CHANGELOG.md | 9 +++++++++ package.json | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f03c675a8d..cea1bc9247 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,14 @@ # vscode-powershell Release History +## 0.7.0 +### Thursday, August 18, 2016 + +#### Introducing support for Linux and macOS! + +This release marks the beginning of our support for Linux and macOS via +the new [cross-platform release of PowerShell](https://github.com/PowerShell/PowerShell). +You can find installation and usage instructions at the [PowerShell GitHub repository](https://github.com/PowerShell/PowerShell). + ## 0.6.2 ### Friday, August 12, 2016 diff --git a/package.json b/package.json index 6ab085aed6..c9aa04bca4 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "PowerShell", "displayName": "PowerShell", - "version": "0.6.2", + "version": "0.7.0", "publisher": "ms-vscode", "description": "Develop PowerShell scripts in Visual Studio Code!", "engines": {