diff --git a/sample/yarn.lock b/sample/yarn.lock index fe17f37b95..70136c2307 100644 --- a/sample/yarn.lock +++ b/sample/yarn.lock @@ -799,7 +799,7 @@ "vscode-dotnet-runtime@file:../vscode-dotnet-runtime-extension": "resolved" "file:../vscode-dotnet-runtime-extension" - "version" "1.6.0" + "version" "1.7.0" dependencies: "chai" "4.3.4" "child_process" "^1.0.2" diff --git a/vscode-dotnet-runtime-extension/README.md b/vscode-dotnet-runtime-extension/README.md index cf352a4e5e..9a281fa13e 100644 --- a/vscode-dotnet-runtime-extension/README.md +++ b/vscode-dotnet-runtime-extension/README.md @@ -1,6 +1,6 @@ # .NET Install Tool for Extension Authors -[![Version](https://vsmarketplacebadge.apphb.com/version/ms-dotnettools.vscode-dotnet-runtime.svg)](https://marketplace.visualstudio.com/items?itemName=ms-dotnettools.vscode-dotnet-runtime) [![Installs](https://vsmarketplacebadge.apphb.com/installs-short/ms-dotnettools.vscode-dotnet-runtime.svg)](https://marketplace.visualstudio.com/items?itemName=ms-dotnettools.vscode-dotnet-runtime) + [![Installs](https://ms-dotnettools.gallerycdn.vsassets.io/extensions/ms-dotnettools/vscode-dotnet-runtime/1.6.0/1667237254317/Microsoft.VisualStudio.Services.Icons.Default)](https://marketplace.visualstudio.com/items?itemName=ms-dotnettools.vscode-dotnet-runtime) This extension allows acquisition of the .NET runtime specifically for Visual Studio Code extension authors. This tool is intended to be leveraged in extensions that are written in .NET and require .NET to boot pieces of the extension (e.g. a language server). The extension is not intended to be used directly by users to install .NET for development. diff --git a/vscode-dotnet-runtime-extension/package-lock.json b/vscode-dotnet-runtime-extension/package-lock.json index 4183a1b578..6e7bc3ab22 100644 --- a/vscode-dotnet-runtime-extension/package-lock.json +++ b/vscode-dotnet-runtime-extension/package-lock.json @@ -1,12 +1,12 @@ { "name": "vscode-dotnet-runtime", - "version": "1.6.0", + "version": "1.7.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "vscode-dotnet-runtime", - "version": "1.6.0", + "version": "1.7.0", "license": "MIT", "dependencies": { "chai": "4.3.4", diff --git a/vscode-dotnet-runtime-extension/package.json b/vscode-dotnet-runtime-extension/package.json index bf39df4380..2695710584 100644 --- a/vscode-dotnet-runtime-extension/package.json +++ b/vscode-dotnet-runtime-extension/package.json @@ -13,7 +13,7 @@ "description": "Allows acquisition of the .NET runtime specifically for VS Code extension authors.", "appInsightsKey": "02dc18e0-7494-43b2-b2a3-18ada5fcb522", "icon": "images/dotnetIcon.png", - "version": "1.6.0", + "version": "1.7.0", "publisher": "ms-dotnettools", "engines": { "vscode": "^1.72.0" @@ -60,7 +60,7 @@ }, "dotnetAcquisitionExtension.installTimeoutValue": { "type": "number", - "default": 120, + "default": 300, "description": "Timeout for installing .NET in seconds." }, "dotnetAcquisitionExtension.existingDotnetPath": { diff --git a/vscode-dotnet-runtime-extension/src/extension.ts b/vscode-dotnet-runtime-extension/src/extension.ts index 1fa951c796..a1b1188825 100644 --- a/vscode-dotnet-runtime-extension/src/extension.ts +++ b/vscode-dotnet-runtime-extension/src/extension.ts @@ -60,7 +60,7 @@ namespace commandKeys { const commandPrefix = 'dotnet'; const configPrefix = 'dotnetAcquisitionExtension'; const displayChannelName = '.NET Runtime'; -const defaultTimeoutValue = 120; +const defaultTimeoutValue = 300; const moreInfoUrl = 'https://github.com/dotnet/vscode-dotnet-runtime/blob/main/Documentation/troubleshooting-runtime.md'; export function activate(context: vscode.ExtensionContext, extensionContext?: IExtensionContext) { @@ -114,6 +114,7 @@ export function activate(context: vscode.ExtensionContext, extensionContext?: IE const dotnetPath = await callWithErrorHandling>(async () => { eventStream.post(new DotnetRuntimeAcquisitionStarted()); eventStream.post(new DotnetAcquisitionRequested(commandContext.version, commandContext.requestingExtensionId)); + acquisitionWorker.setAcquisitionContext(commandContext); if (!commandContext.version || commandContext.version === 'latest') { throw new Error(`Cannot acquire .NET version "${commandContext.version}". Please provide a valid version.`); diff --git a/vscode-dotnet-runtime-library/src/Acquisition/AcquisitionInvoker.ts b/vscode-dotnet-runtime-library/src/Acquisition/AcquisitionInvoker.ts index 3489477f63..c9d08f346a 100644 --- a/vscode-dotnet-runtime-library/src/Acquisition/AcquisitionInvoker.ts +++ b/vscode-dotnet-runtime-library/src/Acquisition/AcquisitionInvoker.ts @@ -25,6 +25,9 @@ import { InstallScriptAcquisitionWorker } from './InstallScriptAcquisitionWorker export class AcquisitionInvoker extends IAcquisitionInvoker { private readonly scriptWorker: IInstallScriptAcquisitionWorker; + private noPowershellError = `powershell.exe is not discoverable on your system. Is PowerShell added to your PATH and correctly installed? Please visit: https://learn.microsoft.com/powershell/scripting/install/installing-powershell-on-windows. +You will need to restart VS Code after these changes. If PowerShell is still not discoverable, try setting a custom existingDotnetPath following our instructions here: https://github.com/dotnet/vscode-dotnet-runtime/blob/main/Documentation/troubleshooting-runtime.md.` + constructor(extensionState: IExtensionState, eventStream: IEventStream) { super(eventStream); this.scriptWorker = new InstallScriptAcquisitionWorker(extensionState, eventStream); @@ -36,6 +39,11 @@ export class AcquisitionInvoker extends IAcquisitionInvoker { return new Promise((resolve, reject) => { try { const windowsFullCommand = `powershell.exe -NoProfile -ExecutionPolicy unrestricted -Command "& { [Net.ServicePointManager]::SecurityProtocol = [Net.ServicePointManager]::SecurityProtocol -bor [Net.SecurityProtocolType]::Tls12 ; & ${installCommand} }`; + if(winOS) + { + this.verifyPowershellCanRun(installContext); + } + cp.exec(winOS ? windowsFullCommand : installCommand, { cwd: process.cwd(), maxBuffer: 500 * 1024, timeout: 1000 * installContext.timeoutValue, killSignal: 'SIGKILL' }, async (error, stdout, stderr) => { @@ -99,4 +107,51 @@ export class AcquisitionInvoker extends IAcquisitionInvoker { return `"${path}"`; } } + + /** + * + * @remarks Some users have reported not having powershell.exe or having execution policy that fails property evaluation functions in powershell install scripts. + * We use this function to throw better errors if powershell is not configured correctly. + */ + private async verifyPowershellCanRun(installContext : IDotnetInstallationContext) + { + let knownError = false; + let error = null; + + try + { + // Check if PowerShell exists and is on the path. + const exeFoundOutput = cp.spawnSync(`powershell`); + if(exeFoundOutput.status !== 0) + { + knownError = true; + const err = Error(this.noPowershellError); + error = err; + } + + // Check Execution Policy + const execPolicyOutput = cp.spawnSync(`powershell`, [`-command`, `$ExecutionContext.SessionState.LanguageMode`]); + const languageMode = execPolicyOutput.stdout.toString().trim(); + if(languageMode === 'ConstrainedLanguage' || languageMode === 'NoLanguage') + { + knownError = true; + const err = Error(`Your machine policy disables PowerShell language features that may be needed to install .NET. Read more at: https://learn.microsoft.com/powershell/module/microsoft.powershell.core/about/about_language_modes?view=powershell-7.3. +If you cannot safely and confidently change the execution policy, try setting a custom existingDotnetPath following our instructions here: https://github.com/dotnet/vscode-dotnet-runtime/blob/main/Documentation/troubleshooting-runtime.md.`); + error = err; + } + } + catch(err) + { + if(!knownError) + { + error = new Error(`${this.noPowershellError} More details: ${(err as Error).message}`); + } + } + + if(error != null) + { + this.eventStream.post(new DotnetAcquisitionScriptError(error as Error, installContext.version)); + throw error; + } + } } diff --git a/vscode-dotnet-runtime-library/src/Acquisition/DotnetCoreAcquisitionWorker.ts b/vscode-dotnet-runtime-library/src/Acquisition/DotnetCoreAcquisitionWorker.ts index 384a9725fc..9ae2c1056f 100644 --- a/vscode-dotnet-runtime-library/src/Acquisition/DotnetCoreAcquisitionWorker.ts +++ b/vscode-dotnet-runtime-library/src/Acquisition/DotnetCoreAcquisitionWorker.ts @@ -23,6 +23,7 @@ import { IDotnetAcquireResult } from '../IDotnetAcquireResult'; import { IAcquisitionWorkerContext } from './IAcquisitionWorkerContext'; import { IDotnetCoreAcquisitionWorker } from './IDotnetCoreAcquisitionWorker'; import { IDotnetInstallationContext } from './IDotnetInstallationContext'; +import { IDotnetAcquireContext } from '..'; export class DotnetCoreAcquisitionWorker implements IDotnetCoreAcquisitionWorker { private readonly installingVersionsKey = 'installing'; @@ -92,7 +93,9 @@ export class DotnetCoreAcquisitionWorker implements IDotnetCoreAcquisitionWorker const existingAcquisitionPromise = this.acquisitionPromises[version]; if (existingAcquisitionPromise) { // This version of dotnet is already being acquired. Memoize the promise. - this.context.eventStream.post(new DotnetAcquisitionInProgress(version)); + this.context.eventStream.post(new DotnetAcquisitionInProgress(version, + (this.context.acquisitionContext && this.context.acquisitionContext.requestingExtensionId) + ? this.context.acquisitionContext!.requestingExtensionId : null)); return existingAcquisitionPromise.then((res) => ({ dotnetPath: res })); } else { // We're the only one acquiring this version of dotnet, start the acquisition process. @@ -132,7 +135,9 @@ export class DotnetCoreAcquisitionWorker implements IDotnetCoreAcquisitionWorker if (installedVersions.includes(version) && fs.existsSync(dotnetPath)) { // Version requested has already been installed. this.context.installationValidator.validateDotnetInstall(version, dotnetPath); - this.context.eventStream.post(new DotnetAcquisitionAlreadyInstalled(version)); + this.context.eventStream.post(new DotnetAcquisitionAlreadyInstalled(version, + (this.context.acquisitionContext && this.context.acquisitionContext.requestingExtensionId) + ? this.context.acquisitionContext!.requestingExtensionId : null)); return dotnetPath; } @@ -158,6 +163,11 @@ export class DotnetCoreAcquisitionWorker implements IDotnetCoreAcquisitionWorker return dotnetPath; } + public setAcquisitionContext(context : IDotnetAcquireContext) + { + this.context.acquisitionContext = context; + } + private async uninstallRuntime(version: string) { delete this.acquisitionPromises[version]; diff --git a/vscode-dotnet-runtime-library/src/Acquisition/IAcquisitionWorkerContext.ts b/vscode-dotnet-runtime-library/src/Acquisition/IAcquisitionWorkerContext.ts index abbe0c6ad9..13d23e277a 100644 --- a/vscode-dotnet-runtime-library/src/Acquisition/IAcquisitionWorkerContext.ts +++ b/vscode-dotnet-runtime-library/src/Acquisition/IAcquisitionWorkerContext.ts @@ -2,6 +2,7 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. * ------------------------------------------------------------------------------------------ */ +import { IDotnetAcquireContext } from '..'; import { IEventStream } from '../EventStream/EventStream'; import { IExtensionState } from '../IExtensionState'; import { IAcquisitionInvoker } from './IAcquisitionInvoker'; @@ -16,4 +17,5 @@ export interface IAcquisitionWorkerContext { installationValidator: IInstallationValidator; timeoutValue: number; installDirectoryProvider: IInstallationDirectoryProvider; + acquisitionContext? : IDotnetAcquireContext | null; } diff --git a/vscode-dotnet-runtime-library/src/EventStream/EventStreamEvents.ts b/vscode-dotnet-runtime-library/src/EventStream/EventStreamEvents.ts index c74a650b3a..a67f8f141c 100644 --- a/vscode-dotnet-runtime-library/src/EventStream/EventStreamEvents.ts +++ b/vscode-dotnet-runtime-library/src/EventStream/EventStreamEvents.ts @@ -269,21 +269,25 @@ export class DotnetAcquisitionPartialInstallation extends DotnetAcquisitionMessa } } -export class DotnetAcquisitionInProgress extends DotnetAcquisitionMessage { +export class DotnetAcquisitionInProgress extends IEvent { + public readonly type = EventType.DotnetAcquisitionInProgress; + public readonly eventName = 'DotnetAcquisitionInProgress'; - constructor(public readonly version: string) { super(); } + constructor(public readonly version: string, public readonly requestingExtensionId: string | null) { super(); } public getProperties() { - return {InProgressInstallationVersion : this.version}; + return {InProgressInstallationVersion : this.version, extensionId : this.requestingExtensionId != null ? this.requestingExtensionId : ''}; } } -export class DotnetAcquisitionAlreadyInstalled extends DotnetAcquisitionMessage { +export class DotnetAcquisitionAlreadyInstalled extends IEvent { public readonly eventName = 'DotnetAcquisitionAlreadyInstalled'; - constructor(public readonly version: string) { super(); } + public readonly type = EventType.DotnetAcquisitionAlreadyInstalled; + + constructor(public readonly version: string, public readonly requestingExtensionId: string | null) { super(); } public getProperties() { - return {AlreadyInstalledVersion : this.version}; + return {AlreadyInstalledVersion : this.version, extensionId : this.requestingExtensionId != null ? this.requestingExtensionId : ''}; } } diff --git a/vscode-dotnet-runtime-library/src/EventStream/EventType.ts b/vscode-dotnet-runtime-library/src/EventStream/EventType.ts index 2cc85194e1..7776d46bfd 100644 --- a/vscode-dotnet-runtime-library/src/EventStream/EventType.ts +++ b/vscode-dotnet-runtime-library/src/EventStream/EventType.ts @@ -12,4 +12,6 @@ export enum EventType { DotnetAcquisitionSuccessEvent, DotnetAcquisitionMessage, DotnetAcquisitionTest, + DotnetAcquisitionAlreadyInstalled, + DotnetAcquisitionInProgress } diff --git a/vscode-dotnet-runtime-library/src/EventStream/OutputChannelObserver.ts b/vscode-dotnet-runtime-library/src/EventStream/OutputChannelObserver.ts index f7a93d3e9a..f953bd3b74 100644 --- a/vscode-dotnet-runtime-library/src/EventStream/OutputChannelObserver.ts +++ b/vscode-dotnet-runtime-library/src/EventStream/OutputChannelObserver.ts @@ -4,8 +4,10 @@ *--------------------------------------------------------------------------------------------*/ import * as vscode from 'vscode'; import { + DotnetAcquisitionAlreadyInstalled, DotnetAcquisitionCompleted, DotnetAcquisitionError, + DotnetAcquisitionInProgress, DotnetAcquisitionStarted, DotnetAcquisitionVersionError, DotnetExistingPathResolutionCompleted, @@ -68,6 +70,26 @@ export class OutputChannelObserver implements IEventStreamObserver { this.outputChannel.append(`Using configured .NET path: ${ (event as DotnetExistingPathResolutionCompleted).resolvedPath }\n`); } break; + case EventType.DotnetAcquisitionAlreadyInstalled: + if(event instanceof DotnetAcquisitionAlreadyInstalled) + { + this.outputChannel.append(`${ + (event as DotnetAcquisitionAlreadyInstalled).requestingExtensionId + } wants to install .NET ${ + (event as DotnetAcquisitionAlreadyInstalled).version + } but it already exists. No downloads or changes were made.\n`); + } + break; + case EventType.DotnetAcquisitionInProgress: + if(event instanceof DotnetAcquisitionInProgress) + { + this.outputChannel.append(`${ + (event as DotnetAcquisitionInProgress).requestingExtensionId + } tried to install .NET ${ + (event as DotnetAcquisitionInProgress).version + } but that install had already been requested. No downloads or changes were made.\n`); + } + break; case EventType.DotnetAcquisitionError: const error = event as DotnetAcquisitionError; this.outputChannel.appendLine(' Error!'); diff --git a/vscode-dotnet-runtime-library/src/Utils/ErrorHandler.ts b/vscode-dotnet-runtime-library/src/Utils/ErrorHandler.ts index 3b877cdb68..8df63386c7 100644 --- a/vscode-dotnet-runtime-library/src/Utils/ErrorHandler.ts +++ b/vscode-dotnet-runtime-library/src/Utils/ErrorHandler.ts @@ -38,8 +38,8 @@ export namespace errorConstants { } export namespace timeoutConstants { - export const timeoutMessage = '.NET installation timed out.'; - export const moreInfoOption = 'More information'; + export const timeoutMessage = `.NET installation timed out. You may need to change the timeout time if you have a slow connection. Please see: https://github.com/dotnet/vscode-dotnet-runtime/blob/main/Documentation/troubleshooting-runtime.md#install-script-timeouts.`; + export const moreInfoOption = 'Change Timeout Value'; } let showMessage = true; @@ -53,7 +53,8 @@ export async function callWithErrorHandling(callback: () => T, context: IIssu const error = caughtError as Error; context.eventStream.post(new DotnetCommandFailed(error, context.commandName)); if (context.errorConfiguration === AcquireErrorConfiguration.DisplayAllErrorPopups) { - if ((error.message as string).includes(timeoutConstants.timeoutMessage)) { + if ((error.message as string).includes(timeoutConstants.timeoutMessage)) + { context.displayWorker.showErrorMessage(`${errorConstants.errorMessage}${ context.version ? ` (${context.version})` : '' }: ${ error.message }`, async (response: string | undefined) => { if (response === timeoutConstants.moreInfoOption) { diff --git a/vscode-dotnet-sdk-extension/README.md b/vscode-dotnet-sdk-extension/README.md index fad9b78a0b..4d8649f9a4 100644 --- a/vscode-dotnet-sdk-extension/README.md +++ b/vscode-dotnet-sdk-extension/README.md @@ -1,6 +1,6 @@ # .NET Education Bundle SDK Install Tool -[![Version](https://vsmarketplacebadge.apphb.com/version/ms-dotnettools.vscode-dotnet-sdk.svg)](https://marketplace.visualstudio.com/items?itemName=ms-dotnettools.vscode-dotnet-sdk) [![Installs](https://vsmarketplacebadge.apphb.com/installs-short/ms-dotnettools.vscode-dotnet-sdk.svg)](https://marketplace.visualstudio.com/items?itemName=ms-dotnettools.vscode-dotnet-sdk) +[![Version](https://ms-dotnettools.gallerycdn.vsassets.io/extensions/ms-dotnettools/vscode-dotnet-runtime/1.6.0/1667237254317/Microsoft.VisualStudio.Services.Icons.Default)](https://marketplace.visualstudio.com/items?itemName=ms-dotnettools.vscode-dotnet-sdk) [![Installs](https://ms-dotnettools.gallerycdn.vsassets.io/extensions/ms-dotnettools/vscode-dotnet-runtime/1.6.0/1667237254317/Microsoft.VisualStudio.Services.Icons.Default)](https://marketplace.visualstudio.com/items?itemName=ms-dotnettools.vscode-dotnet-sdk) **Note: This is a very early preview of this tool and is not intended for use outside the .NET education bundle. Using this in other scenarios will result in broken installs on user machines due to conflict with the normal SDK installer.** diff --git a/vscode-dotnet-sdk-extension/src/extension.ts b/vscode-dotnet-sdk-extension/src/extension.ts index dd73872271..82825ad291 100644 --- a/vscode-dotnet-sdk-extension/src/extension.ts +++ b/vscode-dotnet-sdk-extension/src/extension.ts @@ -118,6 +118,7 @@ export function activate(context: vscode.ExtensionContext, extensionContext?: IE installationValidator: new InstallationValidator(eventStream), timeoutValue: timeoutValue === undefined ? defaultTimeoutValue : timeoutValue, installDirectoryProvider: new SdkInstallationDirectoryProvider(storagePath), + acquisitionContext : null }); const versionResolver = new VersionResolver(context.globalState, eventStream); @@ -144,6 +145,7 @@ export function activate(context: vscode.ExtensionContext, extensionContext?: IE eventStream.post(new DotnetAcquisitionRequested(commandContext.version, commandContext.requestingExtensionId)); const resolvedVersion = await versionResolver.getFullSDKVersion(commandContext.version); + acquisitionWorker.setAcquisitionContext(commandContext); const dotnetPath = await acquisitionWorker.acquireSDK(resolvedVersion); const pathEnvVar = path.dirname(dotnetPath.dotnetPath); setPathEnvVar(pathEnvVar, displayWorker, context.environmentVariableCollection);