Skip to content

Commit 741314b

Browse files
authored
Merge pull request #1212 from nagilson/nagilson-no-ps-error
Verify that PowerShell Install Scripts can Execute
2 parents 2b3b740 + de4c9f1 commit 741314b

File tree

14 files changed

+118
-19
lines changed

14 files changed

+118
-19
lines changed

sample/yarn.lock

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -799,7 +799,7 @@
799799

800800
"vscode-dotnet-runtime@file:../vscode-dotnet-runtime-extension":
801801
"resolved" "file:../vscode-dotnet-runtime-extension"
802-
"version" "1.6.0"
802+
"version" "1.7.0"
803803
dependencies:
804804
"chai" "4.3.4"
805805
"child_process" "^1.0.2"

vscode-dotnet-runtime-extension/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# .NET Install Tool for Extension Authors
22

3-
[![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)
3+
[![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)
44

55
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.
66

vscode-dotnet-runtime-extension/package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

vscode-dotnet-runtime-extension/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
"description": "Allows acquisition of the .NET runtime specifically for VS Code extension authors.",
1414
"appInsightsKey": "02dc18e0-7494-43b2-b2a3-18ada5fcb522",
1515
"icon": "images/dotnetIcon.png",
16-
"version": "1.6.0",
16+
"version": "1.7.0",
1717
"publisher": "ms-dotnettools",
1818
"engines": {
1919
"vscode": "^1.72.0"
@@ -60,7 +60,7 @@
6060
},
6161
"dotnetAcquisitionExtension.installTimeoutValue": {
6262
"type": "number",
63-
"default": 120,
63+
"default": 300,
6464
"description": "Timeout for installing .NET in seconds."
6565
},
6666
"dotnetAcquisitionExtension.existingDotnetPath": {

vscode-dotnet-runtime-extension/src/extension.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ namespace commandKeys {
6060
const commandPrefix = 'dotnet';
6161
const configPrefix = 'dotnetAcquisitionExtension';
6262
const displayChannelName = '.NET Runtime';
63-
const defaultTimeoutValue = 120;
63+
const defaultTimeoutValue = 300;
6464
const moreInfoUrl = 'https://github.com/dotnet/vscode-dotnet-runtime/blob/main/Documentation/troubleshooting-runtime.md';
6565

6666
export function activate(context: vscode.ExtensionContext, extensionContext?: IExtensionContext) {
@@ -114,6 +114,7 @@ export function activate(context: vscode.ExtensionContext, extensionContext?: IE
114114
const dotnetPath = await callWithErrorHandling<Promise<IDotnetAcquireResult>>(async () => {
115115
eventStream.post(new DotnetRuntimeAcquisitionStarted());
116116
eventStream.post(new DotnetAcquisitionRequested(commandContext.version, commandContext.requestingExtensionId));
117+
acquisitionWorker.setAcquisitionContext(commandContext);
117118

118119
if (!commandContext.version || commandContext.version === 'latest') {
119120
throw new Error(`Cannot acquire .NET version "${commandContext.version}". Please provide a valid version.`);

vscode-dotnet-runtime-library/src/Acquisition/AcquisitionInvoker.ts

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,9 @@ import { InstallScriptAcquisitionWorker } from './InstallScriptAcquisitionWorker
2525
export class AcquisitionInvoker extends IAcquisitionInvoker {
2626
private readonly scriptWorker: IInstallScriptAcquisitionWorker;
2727

28+
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.
29+
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.`
30+
2831
constructor(extensionState: IExtensionState, eventStream: IEventStream) {
2932
super(eventStream);
3033
this.scriptWorker = new InstallScriptAcquisitionWorker(extensionState, eventStream);
@@ -36,6 +39,11 @@ export class AcquisitionInvoker extends IAcquisitionInvoker {
3639
return new Promise<void>((resolve, reject) => {
3740
try {
3841
const windowsFullCommand = `powershell.exe -NoProfile -ExecutionPolicy unrestricted -Command "& { [Net.ServicePointManager]::SecurityProtocol = [Net.ServicePointManager]::SecurityProtocol -bor [Net.SecurityProtocolType]::Tls12 ; & ${installCommand} }`;
42+
if(winOS)
43+
{
44+
this.verifyPowershellCanRun(installContext);
45+
}
46+
3947
cp.exec(winOS ? windowsFullCommand : installCommand,
4048
{ cwd: process.cwd(), maxBuffer: 500 * 1024, timeout: 1000 * installContext.timeoutValue, killSignal: 'SIGKILL' },
4149
async (error, stdout, stderr) => {
@@ -99,4 +107,51 @@ export class AcquisitionInvoker extends IAcquisitionInvoker {
99107
return `"${path}"`;
100108
}
101109
}
110+
111+
/**
112+
*
113+
* @remarks Some users have reported not having powershell.exe or having execution policy that fails property evaluation functions in powershell install scripts.
114+
* We use this function to throw better errors if powershell is not configured correctly.
115+
*/
116+
private async verifyPowershellCanRun(installContext : IDotnetInstallationContext)
117+
{
118+
let knownError = false;
119+
let error = null;
120+
121+
try
122+
{
123+
// Check if PowerShell exists and is on the path.
124+
const exeFoundOutput = cp.spawnSync(`powershell`);
125+
if(exeFoundOutput.status !== 0)
126+
{
127+
knownError = true;
128+
const err = Error(this.noPowershellError);
129+
error = err;
130+
}
131+
132+
// Check Execution Policy
133+
const execPolicyOutput = cp.spawnSync(`powershell`, [`-command`, `$ExecutionContext.SessionState.LanguageMode`]);
134+
const languageMode = execPolicyOutput.stdout.toString().trim();
135+
if(languageMode === 'ConstrainedLanguage' || languageMode === 'NoLanguage')
136+
{
137+
knownError = true;
138+
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.
139+
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.`);
140+
error = err;
141+
}
142+
}
143+
catch(err)
144+
{
145+
if(!knownError)
146+
{
147+
error = new Error(`${this.noPowershellError} More details: ${(err as Error).message}`);
148+
}
149+
}
150+
151+
if(error != null)
152+
{
153+
this.eventStream.post(new DotnetAcquisitionScriptError(error as Error, installContext.version));
154+
throw error;
155+
}
156+
}
102157
}

vscode-dotnet-runtime-library/src/Acquisition/DotnetCoreAcquisitionWorker.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import { IDotnetAcquireResult } from '../IDotnetAcquireResult';
2323
import { IAcquisitionWorkerContext } from './IAcquisitionWorkerContext';
2424
import { IDotnetCoreAcquisitionWorker } from './IDotnetCoreAcquisitionWorker';
2525
import { IDotnetInstallationContext } from './IDotnetInstallationContext';
26+
import { IDotnetAcquireContext } from '..';
2627

2728
export class DotnetCoreAcquisitionWorker implements IDotnetCoreAcquisitionWorker {
2829
private readonly installingVersionsKey = 'installing';
@@ -92,7 +93,9 @@ export class DotnetCoreAcquisitionWorker implements IDotnetCoreAcquisitionWorker
9293
const existingAcquisitionPromise = this.acquisitionPromises[version];
9394
if (existingAcquisitionPromise) {
9495
// This version of dotnet is already being acquired. Memoize the promise.
95-
this.context.eventStream.post(new DotnetAcquisitionInProgress(version));
96+
this.context.eventStream.post(new DotnetAcquisitionInProgress(version,
97+
(this.context.acquisitionContext && this.context.acquisitionContext.requestingExtensionId)
98+
? this.context.acquisitionContext!.requestingExtensionId : null));
9699
return existingAcquisitionPromise.then((res) => ({ dotnetPath: res }));
97100
} else {
98101
// We're the only one acquiring this version of dotnet, start the acquisition process.
@@ -132,7 +135,9 @@ export class DotnetCoreAcquisitionWorker implements IDotnetCoreAcquisitionWorker
132135
if (installedVersions.includes(version) && fs.existsSync(dotnetPath)) {
133136
// Version requested has already been installed.
134137
this.context.installationValidator.validateDotnetInstall(version, dotnetPath);
135-
this.context.eventStream.post(new DotnetAcquisitionAlreadyInstalled(version));
138+
this.context.eventStream.post(new DotnetAcquisitionAlreadyInstalled(version,
139+
(this.context.acquisitionContext && this.context.acquisitionContext.requestingExtensionId)
140+
? this.context.acquisitionContext!.requestingExtensionId : null));
136141
return dotnetPath;
137142
}
138143

@@ -158,6 +163,11 @@ export class DotnetCoreAcquisitionWorker implements IDotnetCoreAcquisitionWorker
158163
return dotnetPath;
159164
}
160165

166+
public setAcquisitionContext(context : IDotnetAcquireContext)
167+
{
168+
this.context.acquisitionContext = context;
169+
}
170+
161171
private async uninstallRuntime(version: string) {
162172
delete this.acquisitionPromises[version];
163173

vscode-dotnet-runtime-library/src/Acquisition/IAcquisitionWorkerContext.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
* Copyright (c) Microsoft Corporation. All rights reserved.
33
* Licensed under the MIT License. See License.txt in the project root for license information.
44
* ------------------------------------------------------------------------------------------ */
5+
import { IDotnetAcquireContext } from '..';
56
import { IEventStream } from '../EventStream/EventStream';
67
import { IExtensionState } from '../IExtensionState';
78
import { IAcquisitionInvoker } from './IAcquisitionInvoker';
@@ -16,4 +17,5 @@ export interface IAcquisitionWorkerContext {
1617
installationValidator: IInstallationValidator;
1718
timeoutValue: number;
1819
installDirectoryProvider: IInstallationDirectoryProvider;
20+
acquisitionContext? : IDotnetAcquireContext | null;
1921
}

vscode-dotnet-runtime-library/src/EventStream/EventStreamEvents.ts

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -269,21 +269,25 @@ export class DotnetAcquisitionPartialInstallation extends DotnetAcquisitionMessa
269269
}
270270
}
271271

272-
export class DotnetAcquisitionInProgress extends DotnetAcquisitionMessage {
272+
export class DotnetAcquisitionInProgress extends IEvent {
273+
public readonly type = EventType.DotnetAcquisitionInProgress;
274+
273275
public readonly eventName = 'DotnetAcquisitionInProgress';
274-
constructor(public readonly version: string) { super(); }
276+
constructor(public readonly version: string, public readonly requestingExtensionId: string | null) { super(); }
275277

276278
public getProperties() {
277-
return {InProgressInstallationVersion : this.version};
279+
return {InProgressInstallationVersion : this.version, extensionId : this.requestingExtensionId != null ? this.requestingExtensionId : ''};
278280
}
279281
}
280282

281-
export class DotnetAcquisitionAlreadyInstalled extends DotnetAcquisitionMessage {
283+
export class DotnetAcquisitionAlreadyInstalled extends IEvent {
282284
public readonly eventName = 'DotnetAcquisitionAlreadyInstalled';
283-
constructor(public readonly version: string) { super(); }
285+
public readonly type = EventType.DotnetAcquisitionAlreadyInstalled;
286+
287+
constructor(public readonly version: string, public readonly requestingExtensionId: string | null) { super(); }
284288

285289
public getProperties() {
286-
return {AlreadyInstalledVersion : this.version};
290+
return {AlreadyInstalledVersion : this.version, extensionId : this.requestingExtensionId != null ? this.requestingExtensionId : ''};
287291
}
288292
}
289293

vscode-dotnet-runtime-library/src/EventStream/EventType.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,6 @@ export enum EventType {
1212
DotnetAcquisitionSuccessEvent,
1313
DotnetAcquisitionMessage,
1414
DotnetAcquisitionTest,
15+
DotnetAcquisitionAlreadyInstalled,
16+
DotnetAcquisitionInProgress
1517
}

0 commit comments

Comments
 (0)