-
Notifications
You must be signed in to change notification settings - Fork 281
/
WinMacGlobalInstaller.ts
565 lines (514 loc) · 28.4 KB
/
WinMacGlobalInstaller.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
/* --------------------------------------------------------------------------------------------
* Licensed to the .NET Foundation under one or more agreements.
* The .NET Foundation licenses this file to you under the MIT license.
* Licensed under the MIT License. See License.txt in the project root for license information.
* ------------------------------------------------------------------------------------------ */
import * as fs from 'fs';
import * as os from 'os';
import * as path from 'path';
import * as versionUtils from './VersionUtilities'
import { FileUtilities } from '../Utils/FileUtilities';
import { VersionResolver } from './VersionResolver';
import { WebRequestWorker } from '../Utils/WebRequestWorker';
import { getInstallFromContext } from '../Utils/InstallIdUtilities';
import { CommandExecutor } from '../Utils/CommandExecutor';
import {
DotnetAcquisitionAlreadyInstalled,
DotnetConflictingGlobalWindowsInstallError,
DotnetFileIntegrityCheckEvent,
DotnetFileIntegrityFailureEvent,
DotnetInstallCancelledByUserError,
DotnetNoInstallerResponseError,
DotnetUnexpectedInstallerOSError,
EventBasedError,
EventCancellationError,
NetInstallerBeginExecutionEvent,
NetInstallerEndExecutionEvent,
OSXOpenNotAvailableError,
SuppressedAcquisitionError,
} from '../EventStream/EventStreamEvents';
import { IGlobalInstaller } from './IGlobalInstaller';
import { ICommandExecutor } from '../Utils/ICommandExecutor';
import { IFileUtilities } from '../Utils/IFileUtilities';
import { IUtilityContext } from '../Utils/IUtilityContext';
import { IAcquisitionWorkerContext } from './IAcquisitionWorkerContext';
import { DotnetInstall } from './DotnetInstall';
import { CommandExecutorResult } from '../Utils/CommandExecutorResult';
import { getOSArch } from '../Utils/TypescriptUtilities';
namespace validationPromptConstants
{
export const noSignatureMessage = `The .NET Installer file could not be validated. It may be insecure or too new to verify. Would you like to continue installing .NET and accept the risks?`;
export const cancelOption = 'Cancel Install';
export const allowOption = 'Install Anyways';
}
/**
* @remarks
* This class manages global .NET SDK installations for windows and mac.
* Both of these OS's have official installers that we can download and run on the machine.
* Since Linux does not, it is delegated into its own set of classes.
*/
export class WinMacGlobalInstaller extends IGlobalInstaller {
private installerUrl : string;
private installingVersion : string;
private installerHash : string;
protected commandRunner : ICommandExecutor;
public cleanupInstallFiles = true;
private completedInstall = false;
protected versionResolver : VersionResolver;
public file : IFileUtilities;
protected webWorker : WebRequestWorker;
private invalidIntegrityError = `The integrity of the .NET install file is invalid, or there was no integrity to check and you denied the request to continue with those risks.
We cannot verify our .NET file host at this time. Please try again later or install the SDK manually.`;
constructor(context : IAcquisitionWorkerContext, utilContext : IUtilityContext, installingVersion : string, installerUrl : string,
installerHash : string, executor : ICommandExecutor | null = null)
{
super(context, utilContext);
this.installerUrl = installerUrl;
this.installingVersion = installingVersion;
this.installerHash = installerHash;
this.commandRunner = executor ?? new CommandExecutor(context, utilContext);
this.versionResolver = new VersionResolver(context);
this.file = new FileUtilities();
this.webWorker = new WebRequestWorker(context, installerUrl);
}
public static InterpretExitCode(code : string) : string
{
const reportLogMessage = `Please provide your .NET Installer log (note our privacy notice), which can be found at %temp%.
The file has a name like 'Microsoft_.NET_SDK*.log and should appear in recent files.
This report should be made at https://github.com/dotnet/vscode-dotnet-runtime/issues.`
switch(code)
{
case '1':
return `The .NET SDK installer has failed with a generic failure. ${reportLogMessage}`;
case '5':
return `Insufficient permissions are available to install .NET. Please run the installer as an administrator.`;
case '67':
return `The network name cannot be found. ${reportLogMessage}`;
case '112':
return `The disk is full. Please free up space and try again.`;
case '255':
return `The .NET Installer was terminated by another process unexpectedly. Please try again.`;
case '1260':
return `The .NET SDK is blocked by group policy. Can you please report this at https://github.com/dotnet/vscode-dotnet-runtime/issues`
case '1460':
return `The .NET SDK had a timeout error. ${reportLogMessage}`;
case '1603':
return `Fatal error during .NET SDK installation. ${reportLogMessage}`;
case '1618':
return `Another installation is already in progress. Complete that installation before proceeding with this install.`;
case '000751':
return `Page fault was satisfied by reading from a secondary storage device. ${reportLogMessage}`;
case '2147500037':
return `An unspecified error occurred. ${reportLogMessage}`;
case '2147942405':
return `Insufficient permissions are available to install .NET. Please try again as an administrator.`;
}
return '';
}
public async installSDK(install : DotnetInstall): Promise<string>
{
// Check for conflicting windows installs
if(os.platform() === 'win32')
{
const conflictingVersion = await this.GlobalWindowsInstallWithConflictingVersionAlreadyExists(this.installingVersion);
if(conflictingVersion !== '')
{
if(conflictingVersion === this.installingVersion)
{
// The install already exists, we can just exit with Ok.
this.acquisitionContext.eventStream.post(new DotnetAcquisitionAlreadyInstalled(install,
(this.acquisitionContext.acquisitionContext && this.acquisitionContext.acquisitionContext.requestingExtensionId)
? this.acquisitionContext.acquisitionContext.requestingExtensionId : null));
return '0';
}
const err = new DotnetConflictingGlobalWindowsInstallError(new EventCancellationError(
'DotnetConflictingGlobalWindowsInstallError',
`A global install is already on the machine: version ${conflictingVersion}, that conflicts with the requested version.
Please uninstall this version first if you would like to continue.
If Visual Studio is installed, you may need to use the VS Setup Window to uninstall the SDK component.`), install);
this.acquisitionContext.eventStream.post(err);
throw err.error;
}
}
const installerFile : string = await this.downloadInstaller(this.installerUrl);
const canContinue = await this.installerFileHasValidIntegrity(installerFile);
if(!canContinue)
{
const err = new DotnetConflictingGlobalWindowsInstallError(new EventCancellationError('DotnetConflictingGlobalWindowsInstallError',
this.invalidIntegrityError), install);
this.acquisitionContext.eventStream.post(err);
throw err.error;
}
const installerResult : string = await this.executeInstall(installerFile);
return this.handleStatus(installerResult, installerFile, install);
}
private async handleStatus(installerResult : string, installerFile : string, install : DotnetInstall, allowRetry = true) : Promise<string>
{
const validInstallerStatusCodes = ['0', '1641', '3010']; // Ok, Pending Reboot, + Reboot Starting Now
const noPermissionStatusCodes = ['1', '5', '1260', '2147942405'];
if(validInstallerStatusCodes.includes(installerResult))
{
if(this.cleanupInstallFiles)
{
this.file.wipeDirectory(path.dirname(installerFile), this.acquisitionContext.eventStream);
}
return '0'; // These statuses are a success, we don't want to throw.
}
else if(installerResult === '1602')
{
// Special code for when user cancels the install
const err = new DotnetInstallCancelledByUserError(new EventCancellationError('DotnetInstallCancelledByUserError',
`The install of .NET was cancelled by the user. Aborting.`), install);
this.acquisitionContext.eventStream.post(err);
throw err.error;
}
else if(noPermissionStatusCodes.includes(installerResult) && allowRetry)
{
const retryWithElevationResult = await this.executeInstall(installerFile, true);
return this.handleStatus(retryWithElevationResult, installerFile, install, false);
}
else
{
return installerResult;
}
}
public async uninstallSDK(install : DotnetInstall): Promise<string>
{
if(os.platform() === 'win32')
{
const installerFile : string = await this.downloadInstaller(this.installerUrl);
const canContinue = await this.installerFileHasValidIntegrity(installerFile);
if(!canContinue)
{
const err = new DotnetConflictingGlobalWindowsInstallError(new EventCancellationError('DotnetConflictingGlobalWindowsInstallError',
this.invalidIntegrityError), install);
this.acquisitionContext.eventStream.post(err);
throw err.error;
}
const command = `${path.resolve(installerFile)}`;
const uninstallArgs = ['/uninstall', '/passive', '/norestart'];
const commandResult = await this.commandRunner.execute(CommandExecutor.makeCommand(command, uninstallArgs), {timeout : this.acquisitionContext.timeoutSeconds * 1000});
this.handleTimeout(commandResult);
return commandResult.status;
}
else
{
const macPath = await this.getMacPath();
const command = CommandExecutor.makeCommand(`rm`, [`-rf`, `${path.join(path.dirname(macPath), 'sdk', install.version)}`, `&&`,
`rm`, `-rf`, `${path.join(path.dirname(macPath), 'sdk-manifests', install.version)}`], true);
const commandResult = await this.commandRunner.execute(command, {timeout : this.acquisitionContext.timeoutSeconds * 1000});
this.handleTimeout(commandResult);
return commandResult.status;
}
}
/**
*
* @param installerUrl the url of the installer to download.
* @returns the path to the installer which was downloaded into a directory managed by us.
*/
private async downloadInstaller(installerUrl : string) : Promise<string>
{
const ourInstallerDownloadFolder = IGlobalInstaller.getDownloadedInstallFilesFolder(installerUrl);
this.file.wipeDirectory(ourInstallerDownloadFolder, this.acquisitionContext.eventStream);
const installerPath = path.join(ourInstallerDownloadFolder, `${installerUrl.split('/').slice(-1)}`);
const installerDir = path.dirname(installerPath);
if (!fs.existsSync(installerDir)){
fs.mkdirSync(installerDir, {recursive: true});
}
await this.webWorker.downloadFile(installerUrl, installerPath);
try
{
if(os.platform() === 'win32') // Windows does not have chmod +x ability with nodejs.
{
const permissionsCommand = CommandExecutor.makeCommand('icacls', [`"${installerPath}"`, '/grant:r', `"%username%":F`, '/t', '/c']);
const commandRes = await this.commandRunner.execute(permissionsCommand, {}, false);
if(commandRes.stderr !== '')
{
const error = new EventBasedError('FailedToSetInstallerPermissions', `Failed to set icacls permissions on the installer file ${installerPath}. ${commandRes.stderr}`);
this.acquisitionContext.eventStream.post(new SuppressedAcquisitionError(error, error.message));
}
}
else
{
fs.chmodSync(installerPath, 0o744);
}
}
catch(error : any)
{
this.acquisitionContext.eventStream.post(new SuppressedAcquisitionError(error, `Failed to chmod +x on ${installerPath}.`));
}
return installerPath;
}
private async userChoosesToContinueWithInvalidHash() : Promise<boolean>
{
const yes = validationPromptConstants.allowOption;
const no = validationPromptConstants.cancelOption;
const message = validationPromptConstants.noSignatureMessage;
const pick = await this.utilityContext.ui.getModalWarningResponse(message, no, yes);
const userConsentsToContinue = pick === yes;
this.acquisitionContext.eventStream.post(new DotnetFileIntegrityCheckEvent(`The valid hash could not be found. The user chose to continue? ${userConsentsToContinue}`));
return userConsentsToContinue;
}
private async installerFileHasValidIntegrity(installerFile : string) : Promise<boolean>
{
try
{
const realFileHash = await this.file.getFileHash(installerFile);
this.acquisitionContext.eventStream.post(new DotnetFileIntegrityCheckEvent(`The hash of the installer file we downloaded is ${realFileHash}`));
const expectedFileHash = this.installerHash;
this.acquisitionContext.eventStream.post(new DotnetFileIntegrityCheckEvent(`The valid and expected hash of the installer file is ${expectedFileHash}`));
if(expectedFileHash === null)
{
return await this.userChoosesToContinueWithInvalidHash();
}
if(realFileHash !== expectedFileHash)
{
this.acquisitionContext.eventStream.post(new DotnetFileIntegrityCheckEvent(`The hashes DO NOT match.`));
return false;
}
else
{
this.acquisitionContext.eventStream.post(new DotnetFileIntegrityCheckEvent(`This file is valid.`));
return true;
}
}
catch(error : any)
{
// Remove this when https://github.com/typescript-eslint/typescript-eslint/issues/2728 is done
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
if(error?.message?.includes('ENOENT'))
{
this.acquisitionContext.eventStream.post(new DotnetFileIntegrityFailureEvent(`The file ${installerFile} was not found, so we couldn't verify it.
Please try again, or download the .NET Installer file yourself. You may also report your issue at https://github.com/dotnet/vscode-dotnet-runtime/issues.`));
}
// Remove this when https://github.com/typescript-eslint/typescript-eslint/issues/2728 is done
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
else if(error?.message?.includes('EPERM'))
{
this.acquisitionContext.eventStream.post(new DotnetFileIntegrityFailureEvent(`The file ${installerFile} did not have the correct permissions scope to be assessed.
Permissions: ${JSON.stringify(await this.commandRunner.execute(CommandExecutor.makeCommand('icacls', [`"${installerFile}"`])))}`));
}
return this.userChoosesToContinueWithInvalidHash();
}
}
// async is needed to match the interface even if we don't use await.
// eslint-disable-next-line @typescript-eslint/require-await
public async getExpectedGlobalSDKPath(specificSDKVersionInstalled : string, installedArch : string, macPathShouldExist = true) : Promise<string>
{
if(os.platform() === 'win32')
{
// The program files should always be set, but in the off chance they are wiped, we can try to use the default as backup.
// Both ia32 and x64 machines will use 'Program Files'
// We don't anticipate a user would need to install the x86 SDK, and we don't have any routes that support that yet.
return process.env.programfiles ? path.resolve(path.join(process.env.programfiles, 'dotnet', 'dotnet.exe')) : path.resolve(`C:\\Program Files\\dotnet\\dotnet.exe`);
}
else if(os.platform() === 'darwin')
{
const sdkPath = await this.getMacPath(macPathShouldExist);
return sdkPath;
}
const err = new DotnetUnexpectedInstallerOSError(new EventBasedError('DotnetUnexpectedInstallerOSError',
`The operating system ${os.platform()} is unsupported.`), getInstallFromContext(this.acquisitionContext));
this.acquisitionContext.eventStream.post(err);
throw err.error;
}
private handleTimeout(commandResult : CommandExecutorResult)
{
if(commandResult.status === 'SIGTERM')
{
const noResponseError = new DotnetNoInstallerResponseError(new EventBasedError('DotnetNoInstallerResponseError',
`The .NET Installer did not complete after ${this.acquisitionContext.timeoutSeconds} seconds.
If you would like to install .NET, please proceed to interact with the .NET Installer pop-up.
If you were waiting for the install to succeed, please extend the timeout setting of the .NET Install Tool extension.`), getInstallFromContext(this.acquisitionContext));
this.acquisitionContext.eventStream.post(noResponseError);
throw noResponseError.error;
}
}
private async getMacPath(macPathShouldExist = true) : Promise<string>
{
const standardHostPath = path.resolve(`/usr/local/share/dotnet/dotnet`);
const arm64EmulationHostPath = path.resolve(`/usr/local/share/dotnet/x64/dotnet`);
if((os.arch() === 'x64' || os.arch() === 'ia32') && (await getOSArch(this.commandRunner)).includes('arm') && (fs.existsSync(arm64EmulationHostPath) || !macPathShouldExist))
{
// VS Code runs on an emulated version of node which will return x64 or use x86 emulation for ARM devices.
// os.arch() returns the architecture of the node binary, not the system architecture, so it will not report arm on an arm device.
return arm64EmulationHostPath;
}
if(!macPathShouldExist || fs.existsSync(standardHostPath) || !fs.existsSync(arm64EmulationHostPath))
{
return standardHostPath;
}
return arm64EmulationHostPath;
}
/**
*
* @param installerPath The path to the installer file to run.
* @returns The exit result from running the global install.
*/
public async executeInstall(installerPath : string, elevateVsCode = false) : Promise<string>
{
if(os.platform() === 'darwin')
{
// For Mac:
// We don't rely on the installer because it doesn't allow us to run without sudo, and we don't want to handle the user password.
// The -W flag makes it so we wait for the installer .pkg to exit, though we are unable to get the exit code.
const possibleCommands =
[
CommandExecutor.makeCommand(`command`, [`-v`, `open`]),
CommandExecutor.makeCommand(`/usr/bin/open`, [])
];
let workingCommand = await this.commandRunner.tryFindWorkingCommand(possibleCommands);
if(!workingCommand)
{
const error = new EventBasedError('OSXOpenNotAvailableError',
`The 'open' command on OSX was not detected. This is likely due to the PATH environment variable on your system being clobbered by another program.
Please correct your PATH variable or make sure the 'open' utility is installed so .NET can properly execute.`);
this.acquisitionContext.eventStream.post(new OSXOpenNotAvailableError(error, getInstallFromContext(this.acquisitionContext)));
throw error;
}
else if(workingCommand.commandRoot === 'command')
{
workingCommand = CommandExecutor.makeCommand(`open`, [`-W`, `"${path.resolve(installerPath)}"`]);
}
this.acquisitionContext.eventStream.post(new NetInstallerBeginExecutionEvent(`The OS X .NET Installer has been launched.`));
const commandResult = await this.commandRunner.execute(workingCommand, {timeout : this.acquisitionContext.timeoutSeconds * 1000});
this.acquisitionContext.eventStream.post(new NetInstallerEndExecutionEvent(`The OS X .NET Installer has closed.`));
this.handleTimeout(commandResult);
return commandResult.status;
}
else
{
const command = `"${path.resolve(installerPath)}"`;
let commandOptions : string[] = [];
if(this.file.isElevated(this.acquisitionContext.eventStream))
{
commandOptions = [`/quiet`, `/install`, `/norestart`];
}
else
{
commandOptions = [`/passive`, `/install`, `/norestart`]
}
this.acquisitionContext.eventStream.post(new NetInstallerBeginExecutionEvent(`The Windows .NET Installer has been launched.`));
try
{
const commandResult = await this.commandRunner.execute(CommandExecutor.makeCommand(command, commandOptions, elevateVsCode), {timeout : this.acquisitionContext.timeoutSeconds * 1000});
this.handleTimeout(commandResult);
this.acquisitionContext.eventStream.post(new NetInstallerEndExecutionEvent(`The Windows .NET Installer has closed.`));
return commandResult.status;
}
catch(error : any)
{
// Remove this when https://github.com/typescript-eslint/typescript-eslint/issues/2728 is done
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
if((error?.message as string)?.includes('EPERM'))
{
// Remove this when https://github.com/typescript-eslint/typescript-eslint/issues/2728 is done
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
error.message = `The installer does not have permission to execute. Please try running as an administrator. ${error?.message}.
Permissions: ${JSON.stringify(await this.commandRunner.execute(CommandExecutor.makeCommand('icacls', [`"${installerPath}"`])))}`;
}
// Remove this when https://github.com/typescript-eslint/typescript-eslint/issues/2728 is done
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
else if((error?.message as string)?.includes('ENOENT'))
{
// Remove this when https://github.com/typescript-eslint/typescript-eslint/issues/2728 is done
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
error.message = `The .NET Installation files were not found. Please try again. ${error?.message}`;
}
throw error;
}
}
}
/**
*
* @param registryQueryResult the raw output of a registry query converted into a string
* @returns
*/
private extractVersionsOutOfRegistryKeyStrings(registryQueryResult : string) : string[]
{
if(registryQueryResult === '')
{
return [];
}
else
{
return registryQueryResult.split(' ')
.filter
(
function(value : string, i : number) { return value !== '' && i !== 0; } // Filter out the whitespace & query as the query return value starts with the query.
)
.filter
(
function(value : string, i : number) { return i % 3 === 0; } // Every 0th, 4th, etc item will be a value name AKA the SDK version. The rest will be REGTYPE and REGHEXVALUE.
);
}
}
/**
*
* @returns Returns '' if no conflicting version was found on the machine.
* Returns the existing version if a global install with the requested version already exists.
* OR: If a global install exists for the same band with a higher version.
* For non-windows cases: In Mac the installer is always shown so that will show users this. For Linux, it's handled by the distro specific code.
*/
public async GlobalWindowsInstallWithConflictingVersionAlreadyExists(requestedVersion : string) : Promise<string>
{
// Note that we could be more intelligent here and consider only if the SDKs conflict within an architecture, but for now we won't do this.
const sdks : Array<string> = await this.getGlobalSdkVersionsInstalledOnMachine();
for (const sdk of sdks)
{
if
( // Side by side installs of the same major.minor and band can cause issues in some cases. So we decided to just not allow it unless upgrading to a newer patch version.
// The installer can catch this but we can avoid unnecessary work this way,
// and for windows the installer may never appear to the user. With this approach, we don't need to handle installer error codes.
Number(versionUtils.getMajorMinor(requestedVersion, this.acquisitionContext.eventStream, this.acquisitionContext)) ===
Number(versionUtils.getMajorMinor(sdk, this.acquisitionContext.eventStream, this.acquisitionContext)) &&
Number(versionUtils.getFeatureBandFromVersion(requestedVersion, this.acquisitionContext.eventStream, this.acquisitionContext)) ===
Number(versionUtils.getFeatureBandFromVersion(sdk, this.acquisitionContext.eventStream, this.acquisitionContext)) &&
Number(versionUtils.getFeatureBandPatchVersion(requestedVersion, this.acquisitionContext.eventStream, this.acquisitionContext)) <=
Number(versionUtils.getFeatureBandPatchVersion(sdk, this.acquisitionContext.eventStream, this.acquisitionContext))
)
{
return sdk;
}
}
return '';
}
/**
*
* @returns an array containing fully specified / specific versions of all globally installed sdks on the machine in windows for 32 and 64 bit sdks.
*/
public async getGlobalSdkVersionsInstalledOnMachine() : Promise<Array<string>>
{
let sdks: string[] = [];
if (os.platform() === 'win32')
{
const sdkInstallRecords64Bit = 'HKEY_LOCAL_MACHINE\\SOFTWARE\\dotnet\\Setup\\InstalledVersions\\x64\\sdk';
const sdkInstallRecords32Bit = sdkInstallRecords64Bit.replace('x64', 'x86');
const sdkInstallRecordsArm64 = sdkInstallRecords64Bit.replace('x64', 'arm64');
const queries = [sdkInstallRecords32Bit, sdkInstallRecords64Bit, sdkInstallRecordsArm64];
for ( const query of queries )
{
try
{
const registryQueryCommand = path.join(`${process.env.SystemRoot}`, `System32\\reg.exe`);
// /reg:32 is added because all keys on 64 bit machines are all put into the WOW node. They won't be on the WOW node on a 32 bit machine.
const command = CommandExecutor.makeCommand(registryQueryCommand, [`query`, `${query}`, `\/reg:32`]);
let installRecordKeysOfXBit = '';
const registryLookup = (await this.commandRunner.execute(command));
if(registryLookup.status === '0')
{
installRecordKeysOfXBit = registryLookup.stdout;
}
const installedSdks = this.extractVersionsOutOfRegistryKeyStrings(installRecordKeysOfXBit);
// Append any newly found sdk versions
sdks = sdks.concat(installedSdks.filter((item) => sdks.indexOf(item) < 0));
}
catch(e)
{
// There are no "X" bit sdks on the machine.
}
}
}
return sdks;
}
}