1
1
// Copyright (c) Microsoft Corporation.
2
2
// Licensed under the MIT License.
3
3
4
- import * as child_process from "child_process" ;
5
4
import * as fs from "fs" ;
6
5
import * as os from "os" ;
7
6
import * as path from "path" ;
8
7
import * as process from "process" ;
9
8
import { IPowerShellAdditionalExePathSettings } from "./settings" ;
9
+ // This uses require so we can rewire it in unit tests!
10
+ // tslint:disable-next-line:no-var-requires
11
+ const utils = require ( "./utils" )
10
12
11
13
const WindowsPowerShell64BitLabel = "Windows PowerShell (x64)" ;
12
14
const WindowsPowerShell32BitLabel = "Windows PowerShell (x86)" ;
13
15
14
- const LinuxExePath = "/usr/bin/pwsh" ;
16
+ const LinuxExePath = "/usr/bin/pwsh" ;
15
17
const LinuxPreviewExePath = "/usr/bin/pwsh-preview" ;
16
18
17
- const SnapExePath = "/snap/bin/pwsh" ;
18
- const SnapPreviewExePath = "/snap/bin/pwsh-preview" ;
19
+ const SnapExePath = "/snap/bin/pwsh" ;
20
+ const SnapPreviewExePath = "/snap/bin/pwsh-preview" ;
19
21
20
- const MacOSExePath = "/usr/local/bin/pwsh" ;
22
+ const MacOSExePath = "/usr/local/bin/pwsh" ;
21
23
const MacOSPreviewExePath = "/usr/local/bin/pwsh-preview" ;
22
24
23
25
export enum OperatingSystem {
@@ -36,6 +38,7 @@ export interface IPlatformDetails {
36
38
export interface IPowerShellExeDetails {
37
39
readonly displayName : string ;
38
40
readonly exePath : string ;
41
+ readonly supportsProperArguments : boolean ;
39
42
}
40
43
41
44
export function getPlatformDetails ( ) : IPlatformDetails {
@@ -97,17 +100,21 @@ export class PowerShellExeFinder {
97
100
/**
98
101
* Returns the first available PowerShell executable found in the search order.
99
102
*/
100
- public getFirstAvailablePowerShellInstallation ( ) : IPowerShellExeDetails {
101
- for ( const pwsh of this . enumeratePowerShellInstallations ( ) ) {
103
+ public async getFirstAvailablePowerShellInstallation ( ) : Promise < IPowerShellExeDetails > {
104
+ for await ( const pwsh of this . enumeratePowerShellInstallations ( ) ) {
102
105
return pwsh ;
103
106
}
104
107
}
105
108
106
109
/**
107
110
* Get an array of all PowerShell executables found when searching for PowerShell installations.
108
111
*/
109
- public getAllAvailablePowerShellInstallations ( ) : IPowerShellExeDetails [ ] {
110
- return Array . from ( this . enumeratePowerShellInstallations ( ) ) ;
112
+ public async getAllAvailablePowerShellInstallations ( ) : Promise < IPowerShellExeDetails [ ] > {
113
+ const array : IPowerShellExeDetails [ ] = [ ] ;
114
+ for await ( const pwsh of this . enumeratePowerShellInstallations ( ) ) {
115
+ array . push ( pwsh ) ;
116
+ }
117
+ return array ;
111
118
}
112
119
113
120
/**
@@ -137,18 +144,18 @@ export class PowerShellExeFinder {
137
144
* PowerShell items returned by this object are verified
138
145
* to exist on the filesystem.
139
146
*/
140
- public * enumeratePowerShellInstallations ( ) : Iterable < IPowerShellExeDetails > {
147
+ public async * enumeratePowerShellInstallations ( ) : AsyncIterable < IPowerShellExeDetails > {
141
148
// Get the default PowerShell installations first
142
- for ( const defaultPwsh of this . enumerateDefaultPowerShellInstallations ( ) ) {
143
- if ( defaultPwsh && defaultPwsh . exists ( ) ) {
149
+ for await ( const defaultPwsh of this . enumerateDefaultPowerShellInstallations ( ) ) {
150
+ if ( defaultPwsh && await defaultPwsh . exists ( ) ) {
144
151
yield defaultPwsh ;
145
152
}
146
153
}
147
154
148
155
// Also show any additionally configured PowerShells
149
156
// These may be duplicates of the default installations, but given a different name.
150
157
for ( const additionalPwsh of this . enumerateAdditionalPowerShellInstallations ( ) ) {
151
- if ( additionalPwsh && additionalPwsh . exists ( ) ) {
158
+ if ( additionalPwsh && await additionalPwsh . exists ( ) ) {
152
159
yield additionalPwsh ;
153
160
}
154
161
}
@@ -159,7 +166,7 @@ export class PowerShellExeFinder {
159
166
* Returned values may not exist, but come with an .exists property
160
167
* which will check whether the executable exists.
161
168
*/
162
- private * enumerateDefaultPowerShellInstallations ( ) : Iterable < IPossiblePowerShellExe > {
169
+ private async * enumerateDefaultPowerShellInstallations ( ) : AsyncIterable < IPossiblePowerShellExe > {
163
170
// Find PSCore stable first
164
171
yield this . findPSCoreStable ( ) ;
165
172
@@ -174,7 +181,7 @@ export class PowerShellExeFinder {
174
181
yield this . findPSCoreWindowsInstallation ( { useAlternateBitness : true } ) ;
175
182
176
183
// Also look for the MSIX/UWP installation
177
- yield this . findPSCoreMsix ( ) ;
184
+ yield await this . findPSCoreMsix ( ) ;
178
185
179
186
break ;
180
187
}
@@ -213,7 +220,7 @@ export class PowerShellExeFinder {
213
220
}
214
221
215
222
/**
216
- * Iterates through the configured additonal PowerShell executable locations,
223
+ * Iterates through the configured additional PowerShell executable locations,
217
224
* without checking for their existence.
218
225
*/
219
226
private * enumerateAdditionalPowerShellInstallations ( ) : Iterable < IPossiblePowerShellExe > {
@@ -227,7 +234,7 @@ export class PowerShellExeFinder {
227
234
}
228
235
}
229
236
230
- private findPSCoreStable ( ) : IPossiblePowerShellExe {
237
+ private async findPSCoreStable ( ) : Promise < IPossiblePowerShellExe > {
231
238
switch ( this . platformDetails . operatingSystem ) {
232
239
case OperatingSystem . Linux :
233
240
return new PossiblePowerShellExe ( LinuxExePath , "PowerShell" ) ;
@@ -236,11 +243,11 @@ export class PowerShellExeFinder {
236
243
return new PossiblePowerShellExe ( MacOSExePath , "PowerShell" ) ;
237
244
238
245
case OperatingSystem . Windows :
239
- return this . findPSCoreWindowsInstallation ( ) ;
246
+ return await this . findPSCoreWindowsInstallation ( ) ;
240
247
}
241
248
}
242
249
243
- private findPSCorePreview ( ) : IPossiblePowerShellExe {
250
+ private async findPSCorePreview ( ) : Promise < IPossiblePowerShellExe > {
244
251
switch ( this . platformDetails . operatingSystem ) {
245
252
case OperatingSystem . Linux :
246
253
return new PossiblePowerShellExe ( LinuxPreviewExePath , "PowerShell Preview" ) ;
@@ -249,7 +256,7 @@ export class PowerShellExeFinder {
249
256
return new PossiblePowerShellExe ( MacOSPreviewExePath , "PowerShell Preview" ) ;
250
257
251
258
case OperatingSystem . Windows :
252
- return this . findPSCoreWindowsInstallation ( { findPreview : true } ) ;
259
+ return await this . findPSCoreWindowsInstallation ( { findPreview : true } ) ;
253
260
}
254
261
}
255
262
@@ -260,10 +267,11 @@ export class PowerShellExeFinder {
260
267
261
268
const dotnetGlobalToolExePath : string = path . join ( os . homedir ( ) , ".dotnet" , "tools" , exeName ) ;
262
269
263
- return new PossiblePowerShellExe ( dotnetGlobalToolExePath , ".NET Core PowerShell Global Tool" ) ;
270
+ // The dotnet installed version of PowerShell does not support proper argument parsing, and so it fails with our multi-line startup banner.
271
+ return new PossiblePowerShellExe ( dotnetGlobalToolExePath , ".NET Core PowerShell Global Tool" , undefined , false ) ;
264
272
}
265
273
266
- private findPSCoreMsix ( { findPreview } : { findPreview ?: boolean } = { } ) : IPossiblePowerShellExe {
274
+ private async findPSCoreMsix ( { findPreview } : { findPreview ?: boolean } = { } ) : Promise < IPossiblePowerShellExe > {
267
275
// We can't proceed if there's no LOCALAPPDATA path
268
276
if ( ! process . env . LOCALAPPDATA ) {
269
277
return null ;
@@ -272,7 +280,7 @@ export class PowerShellExeFinder {
272
280
// Find the base directory for MSIX application exe shortcuts
273
281
const msixAppDir = path . join ( process . env . LOCALAPPDATA , "Microsoft" , "WindowsApps" ) ;
274
282
275
- if ( ! fileExistsSync ( msixAppDir ) ) {
283
+ if ( ! await utils . checkIfDirectoryExists ( msixAppDir ) ) {
276
284
return null ;
277
285
}
278
286
@@ -282,6 +290,7 @@ export class PowerShellExeFinder {
282
290
: { pwshMsixDirRegex : PowerShellExeFinder . PwshMsixRegex , pwshMsixName : "PowerShell (Store)" } ;
283
291
284
292
// We should find only one such application, so return on the first one
293
+ // TODO: Use VS Code async fs API for this.
285
294
for ( const subdir of fs . readdirSync ( msixAppDir ) ) {
286
295
if ( pwshMsixDirRegex . test ( subdir ) ) {
287
296
const pwshMsixPath = path . join ( msixAppDir , subdir , "pwsh.exe" ) ;
@@ -301,9 +310,9 @@ export class PowerShellExeFinder {
301
310
return new PossiblePowerShellExe ( SnapPreviewExePath , "PowerShell Preview Snap" ) ;
302
311
}
303
312
304
- private findPSCoreWindowsInstallation (
313
+ private async findPSCoreWindowsInstallation (
305
314
{ useAlternateBitness = false , findPreview = false } :
306
- { useAlternateBitness ?: boolean ; findPreview ?: boolean } = { } ) : IPossiblePowerShellExe {
315
+ { useAlternateBitness ?: boolean ; findPreview ?: boolean } = { } ) : Promise < IPossiblePowerShellExe > {
307
316
308
317
const programFilesPath : string = this . getProgramFilesPath ( { useAlternateBitness } ) ;
309
318
@@ -314,13 +323,7 @@ export class PowerShellExeFinder {
314
323
const powerShellInstallBaseDir = path . join ( programFilesPath , "PowerShell" ) ;
315
324
316
325
// Ensure the base directory exists
317
- try {
318
- const powerShellInstallBaseDirLStat = fs . lstatSync ( powerShellInstallBaseDir ) ;
319
- if ( ! powerShellInstallBaseDirLStat . isDirectory ( ) )
320
- {
321
- return null ;
322
- }
323
- } catch {
326
+ if ( ! await utils . checkIfDirectoryExists ( powerShellInstallBaseDir ) ) {
324
327
return null ;
325
328
}
326
329
@@ -366,7 +369,7 @@ export class PowerShellExeFinder {
366
369
367
370
// Now look for the file
368
371
const exePath = path . join ( powerShellInstallBaseDir , item , "pwsh.exe" ) ;
369
- if ( ! fs . existsSync ( exePath ) ) {
372
+ if ( ! await utils . checkIfFileExists ( exePath ) ) {
370
373
continue ;
371
374
}
372
375
@@ -413,7 +416,7 @@ export class PowerShellExeFinder {
413
416
displayName = WindowsPowerShell32BitLabel ;
414
417
}
415
418
416
- winPS = new PossiblePowerShellExe ( winPSPath , displayName , { knownToExist : true } ) ;
419
+ winPS = new PossiblePowerShellExe ( winPSPath , displayName , true ) ;
417
420
418
421
if ( useAlternateBitness ) {
419
422
this . alternateBitnessWinPS = winPS ;
@@ -479,40 +482,20 @@ export function getWindowsSystemPowerShellPath(systemFolderName: string) {
479
482
"powershell.exe" ) ;
480
483
}
481
484
482
- function fileExistsSync ( filePath : string ) : boolean {
483
- try {
484
- // This will throw if the path does not exist,
485
- // and otherwise returns a value that we don't care about
486
- fs . lstatSync ( filePath ) ;
487
- return true ;
488
- } catch {
489
- return false ;
490
- }
491
- }
492
-
493
485
interface IPossiblePowerShellExe extends IPowerShellExeDetails {
494
- exists ( ) : boolean ;
486
+ exists ( ) : Promise < boolean > ;
495
487
}
496
488
497
489
class PossiblePowerShellExe implements IPossiblePowerShellExe {
498
- public readonly exePath : string ;
499
- public readonly displayName : string ;
500
-
501
- private knownToExist : boolean ;
502
-
503
490
constructor (
504
- pathToExe : string ,
505
- installationName : string ,
506
- { knownToExist = false } : { knownToExist ?: boolean } = { } ) {
507
-
508
- this . exePath = pathToExe ;
509
- this . displayName = installationName ;
510
- this . knownToExist = knownToExist || undefined ;
511
- }
491
+ public readonly exePath : string ,
492
+ public readonly displayName : string ,
493
+ private knownToExist ?: boolean ,
494
+ public readonly supportsProperArguments : boolean = true ) { }
512
495
513
- public exists ( ) : boolean {
496
+ public async exists ( ) : Promise < boolean > {
514
497
if ( this . knownToExist === undefined ) {
515
- this . knownToExist = fileExistsSync ( this . exePath ) ;
498
+ this . knownToExist = await utils . checkIfFileExists ( this . exePath ) ;
516
499
}
517
500
return this . knownToExist ;
518
501
}
0 commit comments