src/vs/base/node/powershell.ts

330 LOC · 103 covered · 227 uncovered · 16 ranges · 2282 concepts · 1 introducers · 1071 tests

File neighbourhood

The centred file is linked to every concept that introduces one of its ranges, every test that runs code from the file, and the gray connector concepts standing between those tests and the file's own introducer concepts. Undirected links join concepts to every file where they introduce source and concepts to the tests they introduce; arrows show specialization between the displayed concepts and bridge only concepts omitted from this view. Concept colors match the source ranges below; connector concepts have no source color and are shown in gray.

Focused file, its introducer and connector concepts, their introduced files, and tests that run code from the file

In the embedded map, ordinary wheel input scrolls the page; use the visible controls to zoom and drag to pan. Open the full-screen map for canvas navigation: wheel pans, Ctrl/Command plus wheel zooms, and arrow keys pan when this region is focused. On touch screens, open the full-screen map to pan or pinch. If JavaScript or WebGL is unavailable, use the related-file, concept, and source links on this page.

Graph controls are ready.

Interactive rendering requires JavaScript and WebGL. Use the related-file, concept, and source links on this page while the interactive map is unavailable.

1 > /*--------------------------------------------------------------------------------------------- powershell.ts ×16
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': powershell.ts ×16
28 > case 'arm64':
29 processArch = Arch.ARM;
30 break;
31 > default: powershell.ts ×16
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') { powershell.ts ×16
69 osArch = Arch.ARM;
70 > } else if (process.env['PROCESSOR_ARCHITECTURE'] === 'X86') { powershell.ts ×16
71 osArch = Arch.x86;
72 > } else { powershell.ts ×16
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) { }
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 }
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 }
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 }
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 }
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 }
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 }
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 }
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 }
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 }
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 }