Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/base/node/powershell.ts
3296 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import * as os from 'os';
7
import * as path from '../common/path.js';
8
import * as pfs from './pfs.js';
9
10
// This is required, since parseInt("7-preview") will return 7.
11
const IntRegex: RegExp = /^\d+$/;
12
13
const PwshMsixRegex: RegExp = /^Microsoft.PowerShell_.*/;
14
const PwshPreviewMsixRegex: RegExp = /^Microsoft.PowerShellPreview_.*/;
15
16
const enum Arch {
17
x64,
18
x86,
19
ARM
20
}
21
22
let processArch: Arch;
23
switch (process.arch) {
24
case 'ia32':
25
processArch = Arch.x86;
26
break;
27
case 'arm':
28
case 'arm64':
29
processArch = Arch.ARM;
30
break;
31
default:
32
processArch = Arch.x64;
33
break;
34
}
35
36
/*
37
Currently, here are the values for these environment variables on their respective archs:
38
39
On x86 process on x86:
40
PROCESSOR_ARCHITECTURE is X86
41
PROCESSOR_ARCHITEW6432 is undefined
42
43
On x86 process on x64:
44
PROCESSOR_ARCHITECTURE is X86
45
PROCESSOR_ARCHITEW6432 is AMD64
46
47
On x64 process on x64:
48
PROCESSOR_ARCHITECTURE is AMD64
49
PROCESSOR_ARCHITEW6432 is undefined
50
51
On ARM process on ARM:
52
PROCESSOR_ARCHITECTURE is ARM64
53
PROCESSOR_ARCHITEW6432 is undefined
54
55
On x86 process on ARM:
56
PROCESSOR_ARCHITECTURE is X86
57
PROCESSOR_ARCHITEW6432 is ARM64
58
59
On x64 process on ARM:
60
PROCESSOR_ARCHITECTURE is ARM64
61
PROCESSOR_ARCHITEW6432 is undefined
62
*/
63
let osArch: Arch;
64
if (process.env['PROCESSOR_ARCHITEW6432']) {
65
osArch = process.env['PROCESSOR_ARCHITEW6432'] === 'ARM64'
66
? Arch.ARM
67
: Arch.x64;
68
} else if (process.env['PROCESSOR_ARCHITECTURE'] === 'ARM64') {
69
osArch = Arch.ARM;
70
} else if (process.env['PROCESSOR_ARCHITECTURE'] === 'X86') {
71
osArch = Arch.x86;
72
} else {
73
osArch = Arch.x64;
74
}
75
76
export interface IPowerShellExeDetails {
77
readonly displayName: string;
78
readonly exePath: string;
79
}
80
81
interface IPossiblePowerShellExe extends IPowerShellExeDetails {
82
exists(): Promise<boolean>;
83
}
84
85
class PossiblePowerShellExe implements IPossiblePowerShellExe {
86
constructor(
87
public readonly exePath: string,
88
public readonly displayName: string,
89
private knownToExist?: boolean) { }
90
91
public async exists(): Promise<boolean> {
92
if (this.knownToExist === undefined) {
93
this.knownToExist = await pfs.SymlinkSupport.existsFile(this.exePath);
94
}
95
return this.knownToExist;
96
}
97
}
98
99
function getProgramFilesPath(
100
{ useAlternateBitness = false }: { useAlternateBitness?: boolean } = {}): string | null {
101
102
if (!useAlternateBitness) {
103
// Just use the native system bitness
104
return process.env.ProgramFiles || null;
105
}
106
107
// We might be a 64-bit process looking for 32-bit program files
108
if (processArch === Arch.x64) {
109
return process.env['ProgramFiles(x86)'] || null;
110
}
111
112
// We might be a 32-bit process looking for 64-bit program files
113
if (osArch === Arch.x64) {
114
return process.env.ProgramW6432 || null;
115
}
116
117
// We're a 32-bit process on 32-bit Windows, there is no other Program Files dir
118
return null;
119
}
120
121
async function findPSCoreWindowsInstallation(
122
{ useAlternateBitness = false, findPreview = false }:
123
{ useAlternateBitness?: boolean; findPreview?: boolean } = {}): Promise<IPossiblePowerShellExe | null> {
124
125
const programFilesPath = getProgramFilesPath({ useAlternateBitness });
126
if (!programFilesPath) {
127
return null;
128
}
129
130
const powerShellInstallBaseDir = path.join(programFilesPath, 'PowerShell');
131
132
// Ensure the base directory exists
133
if (!await pfs.SymlinkSupport.existsDirectory(powerShellInstallBaseDir)) {
134
return null;
135
}
136
137
let highestSeenVersion: number = -1;
138
let pwshExePath: string | null = null;
139
for (const item of await pfs.Promises.readdir(powerShellInstallBaseDir)) {
140
141
let currentVersion: number = -1;
142
if (findPreview) {
143
// We are looking for something like "7-preview"
144
145
// Preview dirs all have dashes in them
146
const dashIndex = item.indexOf('-');
147
if (dashIndex < 0) {
148
continue;
149
}
150
151
// Verify that the part before the dash is an integer
152
// and that the part after the dash is "preview"
153
const intPart: string = item.substring(0, dashIndex);
154
if (!IntRegex.test(intPart) || item.substring(dashIndex + 1) !== 'preview') {
155
continue;
156
}
157
158
currentVersion = parseInt(intPart, 10);
159
} else {
160
// Search for a directory like "6" or "7"
161
if (!IntRegex.test(item)) {
162
continue;
163
}
164
165
currentVersion = parseInt(item, 10);
166
}
167
168
// Ensure we haven't already seen a higher version
169
if (currentVersion <= highestSeenVersion) {
170
continue;
171
}
172
173
// Now look for the file
174
const exePath = path.join(powerShellInstallBaseDir, item, 'pwsh.exe');
175
if (!await pfs.SymlinkSupport.existsFile(exePath)) {
176
continue;
177
}
178
179
pwshExePath = exePath;
180
highestSeenVersion = currentVersion;
181
}
182
183
if (!pwshExePath) {
184
return null;
185
}
186
187
const bitness: string = programFilesPath.includes('x86') ? ' (x86)' : '';
188
const preview: string = findPreview ? ' Preview' : '';
189
190
return new PossiblePowerShellExe(pwshExePath, `PowerShell${preview}${bitness}`, true);
191
}
192
193
async function findPSCoreMsix({ findPreview }: { findPreview?: boolean } = {}): Promise<IPossiblePowerShellExe | null> {
194
// We can't proceed if there's no LOCALAPPDATA path
195
if (!process.env.LOCALAPPDATA) {
196
return null;
197
}
198
199
// Find the base directory for MSIX application exe shortcuts
200
const msixAppDir = path.join(process.env.LOCALAPPDATA, 'Microsoft', 'WindowsApps');
201
202
if (!await pfs.SymlinkSupport.existsDirectory(msixAppDir)) {
203
return null;
204
}
205
206
// Define whether we're looking for the preview or the stable
207
const { pwshMsixDirRegex, pwshMsixName } = findPreview
208
? { pwshMsixDirRegex: PwshPreviewMsixRegex, pwshMsixName: 'PowerShell Preview (Store)' }
209
: { pwshMsixDirRegex: PwshMsixRegex, pwshMsixName: 'PowerShell (Store)' };
210
211
// We should find only one such application, so return on the first one
212
for (const subdir of await pfs.Promises.readdir(msixAppDir)) {
213
if (pwshMsixDirRegex.test(subdir)) {
214
const pwshMsixPath = path.join(msixAppDir, subdir, 'pwsh.exe');
215
return new PossiblePowerShellExe(pwshMsixPath, pwshMsixName);
216
}
217
}
218
219
// If we find nothing, return null
220
return null;
221
}
222
223
function findPSCoreDotnetGlobalTool(): IPossiblePowerShellExe {
224
const dotnetGlobalToolExePath: string = path.join(os.homedir(), '.dotnet', 'tools', 'pwsh.exe');
225
226
return new PossiblePowerShellExe(dotnetGlobalToolExePath, '.NET Core PowerShell Global Tool');
227
}
228
229
function findPSCoreScoopInstallation(): IPossiblePowerShellExe {
230
const scoopAppsDir = path.join(os.homedir(), 'scoop', 'apps');
231
const scoopPwsh = path.join(scoopAppsDir, 'pwsh', 'current', 'pwsh.exe');
232
233
return new PossiblePowerShellExe(scoopPwsh, 'PowerShell (Scoop)');
234
}
235
236
function findWinPS(): IPossiblePowerShellExe | null {
237
const winPSPath = path.join(
238
process.env.windir!,
239
processArch === Arch.x86 && osArch !== Arch.x86 ? 'SysNative' : 'System32',
240
'WindowsPowerShell', 'v1.0', 'powershell.exe');
241
242
return new PossiblePowerShellExe(winPSPath, 'Windows PowerShell', true);
243
}
244
245
/**
246
* Iterates through all the possible well-known PowerShell installations on a machine.
247
* Returned values may not exist, but come with an .exists property
248
* which will check whether the executable exists.
249
*/
250
async function* enumerateDefaultPowerShellInstallations(): AsyncIterable<IPossiblePowerShellExe> {
251
// Find PSCore stable first
252
let pwshExe = await findPSCoreWindowsInstallation();
253
if (pwshExe) {
254
yield pwshExe;
255
}
256
257
// Windows may have a 32-bit pwsh.exe
258
pwshExe = await findPSCoreWindowsInstallation({ useAlternateBitness: true });
259
if (pwshExe) {
260
yield pwshExe;
261
}
262
263
// Also look for the MSIX/UWP installation
264
pwshExe = await findPSCoreMsix();
265
if (pwshExe) {
266
yield pwshExe;
267
}
268
269
// Look for the .NET global tool
270
// Some older versions of PowerShell have a bug in this where startup will fail,
271
// but this is fixed in newer versions
272
pwshExe = findPSCoreDotnetGlobalTool();
273
if (pwshExe) {
274
yield pwshExe;
275
}
276
277
// Look for PSCore preview
278
pwshExe = await findPSCoreWindowsInstallation({ findPreview: true });
279
if (pwshExe) {
280
yield pwshExe;
281
}
282
283
// Find a preview MSIX
284
pwshExe = await findPSCoreMsix({ findPreview: true });
285
if (pwshExe) {
286
yield pwshExe;
287
}
288
289
// Look for pwsh-preview with the opposite bitness
290
pwshExe = await findPSCoreWindowsInstallation({ useAlternateBitness: true, findPreview: true });
291
if (pwshExe) {
292
yield pwshExe;
293
}
294
295
pwshExe = await findPSCoreScoopInstallation();
296
if (pwshExe) {
297
yield pwshExe;
298
}
299
300
// Finally, get Windows PowerShell
301
pwshExe = findWinPS();
302
if (pwshExe) {
303
yield pwshExe;
304
}
305
}
306
307
/**
308
* Iterates through PowerShell installations on the machine according
309
* to configuration passed in through the constructor.
310
* PowerShell items returned by this object are verified
311
* to exist on the filesystem.
312
*/
313
export async function* enumeratePowerShellInstallations(): AsyncIterable<IPowerShellExeDetails> {
314
// Get the default PowerShell installations first
315
for await (const defaultPwsh of enumerateDefaultPowerShellInstallations()) {
316
if (await defaultPwsh.exists()) {
317
yield defaultPwsh;
318
}
319
}
320
}
321
322
/**
323
* Returns the first available PowerShell executable found in the search order.
324
*/
325
export async function getFirstAvailablePowerShellInstallation(): Promise<IPowerShellExeDetails | null> {
326
for await (const pwsh of enumeratePowerShellInstallations()) {
327
return pwsh;
328
}
329
return null;
330
}
331
332