Skip to content

Commit

Permalink
Merge pull request #1212 from nagilson/nagilson-no-ps-error
Browse files Browse the repository at this point in the history
Verify that PowerShell Install Scripts can Execute
  • Loading branch information
nagilson authored Aug 14, 2023
2 parents 2b3b740 + de4c9f1 commit 741314b
Show file tree
Hide file tree
Showing 14 changed files with 118 additions and 19 deletions.
2 changes: 1 addition & 1 deletion sample/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
2 changes: 1 addition & 1 deletion vscode-dotnet-runtime-extension/README.md
Original file line number Diff line number Diff line change
@@ -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.

Expand Down
4 changes: 2 additions & 2 deletions vscode-dotnet-runtime-extension/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions vscode-dotnet-runtime-extension/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -60,7 +60,7 @@
},
"dotnetAcquisitionExtension.installTimeoutValue": {
"type": "number",
"default": 120,
"default": 300,
"description": "Timeout for installing .NET in seconds."
},
"dotnetAcquisitionExtension.existingDotnetPath": {
Expand Down
3 changes: 2 additions & 1 deletion vscode-dotnet-runtime-extension/src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -114,6 +114,7 @@ export function activate(context: vscode.ExtensionContext, extensionContext?: IE
const dotnetPath = await callWithErrorHandling<Promise<IDotnetAcquireResult>>(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.`);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -36,6 +39,11 @@ export class AcquisitionInvoker extends IAcquisitionInvoker {
return new Promise<void>((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) => {
Expand Down Expand Up @@ -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;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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;
}

Expand All @@ -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];

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -16,4 +17,5 @@ export interface IAcquisitionWorkerContext {
installationValidator: IInstallationValidator;
timeoutValue: number;
installDirectoryProvider: IInstallationDirectoryProvider;
acquisitionContext? : IDotnetAcquireContext | null;
}
16 changes: 10 additions & 6 deletions vscode-dotnet-runtime-library/src/EventStream/EventStreamEvents.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 : ''};
}
}

Expand Down
2 changes: 2 additions & 0 deletions vscode-dotnet-runtime-library/src/EventStream/EventType.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,6 @@ export enum EventType {
DotnetAcquisitionSuccessEvent,
DotnetAcquisitionMessage,
DotnetAcquisitionTest,
DotnetAcquisitionAlreadyInstalled,
DotnetAcquisitionInProgress
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@
*--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode';
import {
DotnetAcquisitionAlreadyInstalled,
DotnetAcquisitionCompleted,
DotnetAcquisitionError,
DotnetAcquisitionInProgress,
DotnetAcquisitionStarted,
DotnetAcquisitionVersionError,
DotnetExistingPathResolutionCompleted,
Expand Down Expand Up @@ -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!');
Expand Down
7 changes: 4 additions & 3 deletions vscode-dotnet-runtime-library/src/Utils/ErrorHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -53,7 +53,8 @@ export async function callWithErrorHandling<T>(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) {
Expand Down
2 changes: 1 addition & 1 deletion vscode-dotnet-sdk-extension/README.md
Original file line number Diff line number Diff line change
@@ -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.**

Expand Down
2 changes: 2 additions & 0 deletions vscode-dotnet-sdk-extension/src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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);
Expand Down

0 comments on commit 741314b

Please sign in to comment.