11// Copyright (c) Microsoft Corporation.
22// Licensed under the MIT License.
33
4- import * as child_process from "child_process" ;
54import * as fs from "fs" ;
65import * as os from "os" ;
76import * as path from "path" ;
87import * as process from "process" ;
98import { 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" )
1012
1113const WindowsPowerShell64BitLabel = "Windows PowerShell (x64)" ;
1214const WindowsPowerShell32BitLabel = "Windows PowerShell (x86)" ;
1315
14- const LinuxExePath = "/usr/bin/pwsh" ;
16+ const LinuxExePath = "/usr/bin/pwsh" ;
1517const LinuxPreviewExePath = "/usr/bin/pwsh-preview" ;
1618
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" ;
1921
20- const MacOSExePath = "/usr/local/bin/pwsh" ;
22+ const MacOSExePath = "/usr/local/bin/pwsh" ;
2123const MacOSPreviewExePath = "/usr/local/bin/pwsh-preview" ;
2224
2325export enum OperatingSystem {
@@ -36,6 +38,7 @@ export interface IPlatformDetails {
3638export interface IPowerShellExeDetails {
3739 readonly displayName : string ;
3840 readonly exePath : string ;
41+ readonly supportsProperArguments : boolean ;
3942}
4043
4144export function getPlatformDetails ( ) : IPlatformDetails {
@@ -97,17 +100,21 @@ export class PowerShellExeFinder {
97100 /**
98101 * Returns the first available PowerShell executable found in the search order.
99102 */
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 ( ) ) {
102105 return pwsh ;
103106 }
104107 }
105108
106109 /**
107110 * Get an array of all PowerShell executables found when searching for PowerShell installations.
108111 */
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 ;
111118 }
112119
113120 /**
@@ -137,18 +144,18 @@ export class PowerShellExeFinder {
137144 * PowerShell items returned by this object are verified
138145 * to exist on the filesystem.
139146 */
140- public * enumeratePowerShellInstallations ( ) : Iterable < IPowerShellExeDetails > {
147+ public async * enumeratePowerShellInstallations ( ) : AsyncIterable < IPowerShellExeDetails > {
141148 // 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 ( ) ) {
144151 yield defaultPwsh ;
145152 }
146153 }
147154
148155 // Also show any additionally configured PowerShells
149156 // These may be duplicates of the default installations, but given a different name.
150157 for ( const additionalPwsh of this . enumerateAdditionalPowerShellInstallations ( ) ) {
151- if ( additionalPwsh && additionalPwsh . exists ( ) ) {
158+ if ( additionalPwsh && await additionalPwsh . exists ( ) ) {
152159 yield additionalPwsh ;
153160 }
154161 }
@@ -159,7 +166,7 @@ export class PowerShellExeFinder {
159166 * Returned values may not exist, but come with an .exists property
160167 * which will check whether the executable exists.
161168 */
162- private * enumerateDefaultPowerShellInstallations ( ) : Iterable < IPossiblePowerShellExe > {
169+ private async * enumerateDefaultPowerShellInstallations ( ) : AsyncIterable < IPossiblePowerShellExe > {
163170 // Find PSCore stable first
164171 yield this . findPSCoreStable ( ) ;
165172
@@ -174,7 +181,7 @@ export class PowerShellExeFinder {
174181 yield this . findPSCoreWindowsInstallation ( { useAlternateBitness : true } ) ;
175182
176183 // Also look for the MSIX/UWP installation
177- yield this . findPSCoreMsix ( ) ;
184+ yield await this . findPSCoreMsix ( ) ;
178185
179186 break ;
180187 }
@@ -213,7 +220,7 @@ export class PowerShellExeFinder {
213220 }
214221
215222 /**
216- * Iterates through the configured additonal PowerShell executable locations,
223+ * Iterates through the configured additional PowerShell executable locations,
217224 * without checking for their existence.
218225 */
219226 private * enumerateAdditionalPowerShellInstallations ( ) : Iterable < IPossiblePowerShellExe > {
@@ -227,7 +234,7 @@ export class PowerShellExeFinder {
227234 }
228235 }
229236
230- private findPSCoreStable ( ) : IPossiblePowerShellExe {
237+ private async findPSCoreStable ( ) : Promise < IPossiblePowerShellExe > {
231238 switch ( this . platformDetails . operatingSystem ) {
232239 case OperatingSystem . Linux :
233240 return new PossiblePowerShellExe ( LinuxExePath , "PowerShell" ) ;
@@ -236,11 +243,11 @@ export class PowerShellExeFinder {
236243 return new PossiblePowerShellExe ( MacOSExePath , "PowerShell" ) ;
237244
238245 case OperatingSystem . Windows :
239- return this . findPSCoreWindowsInstallation ( ) ;
246+ return await this . findPSCoreWindowsInstallation ( ) ;
240247 }
241248 }
242249
243- private findPSCorePreview ( ) : IPossiblePowerShellExe {
250+ private async findPSCorePreview ( ) : Promise < IPossiblePowerShellExe > {
244251 switch ( this . platformDetails . operatingSystem ) {
245252 case OperatingSystem . Linux :
246253 return new PossiblePowerShellExe ( LinuxPreviewExePath , "PowerShell Preview" ) ;
@@ -249,7 +256,7 @@ export class PowerShellExeFinder {
249256 return new PossiblePowerShellExe ( MacOSPreviewExePath , "PowerShell Preview" ) ;
250257
251258 case OperatingSystem . Windows :
252- return this . findPSCoreWindowsInstallation ( { findPreview : true } ) ;
259+ return await this . findPSCoreWindowsInstallation ( { findPreview : true } ) ;
253260 }
254261 }
255262
@@ -260,10 +267,11 @@ export class PowerShellExeFinder {
260267
261268 const dotnetGlobalToolExePath : string = path . join ( os . homedir ( ) , ".dotnet" , "tools" , exeName ) ;
262269
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 ) ;
264272 }
265273
266- private findPSCoreMsix ( { findPreview } : { findPreview ?: boolean } = { } ) : IPossiblePowerShellExe {
274+ private async findPSCoreMsix ( { findPreview } : { findPreview ?: boolean } = { } ) : Promise < IPossiblePowerShellExe > {
267275 // We can't proceed if there's no LOCALAPPDATA path
268276 if ( ! process . env . LOCALAPPDATA ) {
269277 return null ;
@@ -272,7 +280,7 @@ export class PowerShellExeFinder {
272280 // Find the base directory for MSIX application exe shortcuts
273281 const msixAppDir = path . join ( process . env . LOCALAPPDATA , "Microsoft" , "WindowsApps" ) ;
274282
275- if ( ! fileExistsSync ( msixAppDir ) ) {
283+ if ( ! await utils . checkIfDirectoryExists ( msixAppDir ) ) {
276284 return null ;
277285 }
278286
@@ -282,6 +290,7 @@ export class PowerShellExeFinder {
282290 : { pwshMsixDirRegex : PowerShellExeFinder . PwshMsixRegex , pwshMsixName : "PowerShell (Store)" } ;
283291
284292 // We should find only one such application, so return on the first one
293+ // TODO: Use VS Code async fs API for this.
285294 for ( const subdir of fs . readdirSync ( msixAppDir ) ) {
286295 if ( pwshMsixDirRegex . test ( subdir ) ) {
287296 const pwshMsixPath = path . join ( msixAppDir , subdir , "pwsh.exe" ) ;
@@ -301,9 +310,9 @@ export class PowerShellExeFinder {
301310 return new PossiblePowerShellExe ( SnapPreviewExePath , "PowerShell Preview Snap" ) ;
302311 }
303312
304- private findPSCoreWindowsInstallation (
313+ private async findPSCoreWindowsInstallation (
305314 { useAlternateBitness = false , findPreview = false } :
306- { useAlternateBitness ?: boolean ; findPreview ?: boolean } = { } ) : IPossiblePowerShellExe {
315+ { useAlternateBitness ?: boolean ; findPreview ?: boolean } = { } ) : Promise < IPossiblePowerShellExe > {
307316
308317 const programFilesPath : string = this . getProgramFilesPath ( { useAlternateBitness } ) ;
309318
@@ -314,13 +323,7 @@ export class PowerShellExeFinder {
314323 const powerShellInstallBaseDir = path . join ( programFilesPath , "PowerShell" ) ;
315324
316325 // 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 ) ) {
324327 return null ;
325328 }
326329
@@ -366,7 +369,7 @@ export class PowerShellExeFinder {
366369
367370 // Now look for the file
368371 const exePath = path . join ( powerShellInstallBaseDir , item , "pwsh.exe" ) ;
369- if ( ! fs . existsSync ( exePath ) ) {
372+ if ( ! await utils . checkIfFileExists ( exePath ) ) {
370373 continue ;
371374 }
372375
@@ -413,7 +416,7 @@ export class PowerShellExeFinder {
413416 displayName = WindowsPowerShell32BitLabel ;
414417 }
415418
416- winPS = new PossiblePowerShellExe ( winPSPath , displayName , { knownToExist : true } ) ;
419+ winPS = new PossiblePowerShellExe ( winPSPath , displayName , true ) ;
417420
418421 if ( useAlternateBitness ) {
419422 this . alternateBitnessWinPS = winPS ;
@@ -479,40 +482,20 @@ export function getWindowsSystemPowerShellPath(systemFolderName: string) {
479482 "powershell.exe" ) ;
480483}
481484
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-
493485interface IPossiblePowerShellExe extends IPowerShellExeDetails {
494- exists ( ) : boolean ;
486+ exists ( ) : Promise < boolean > ;
495487}
496488
497489class PossiblePowerShellExe implements IPossiblePowerShellExe {
498- public readonly exePath : string ;
499- public readonly displayName : string ;
500-
501- private knownToExist : boolean ;
502-
503490 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 ) { }
512495
513- public exists ( ) : boolean {
496+ public async exists ( ) : Promise < boolean > {
514497 if ( this . knownToExist === undefined ) {
515- this . knownToExist = fileExistsSync ( this . exePath ) ;
498+ this . knownToExist = await utils . checkIfFileExists ( this . exePath ) ;
516499 }
517500 return this . knownToExist ;
518501 }
0 commit comments