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+ import { checkIfFileExists , checkIfDirectoryExists } from "./utils" ;
1010
1111const WindowsPowerShell64BitLabel = "Windows PowerShell (x64)" ;
1212const WindowsPowerShell32BitLabel = "Windows PowerShell (x86)" ;
1313
14- const LinuxExePath = "/usr/bin/pwsh" ;
14+ const LinuxExePath = "/usr/bin/pwsh" ;
1515const LinuxPreviewExePath = "/usr/bin/pwsh-preview" ;
1616
17- const SnapExePath = "/snap/bin/pwsh" ;
18- const SnapPreviewExePath = "/snap/bin/pwsh-preview" ;
17+ const SnapExePath = "/snap/bin/pwsh" ;
18+ const SnapPreviewExePath = "/snap/bin/pwsh-preview" ;
1919
20- const MacOSExePath = "/usr/local/bin/pwsh" ;
20+ const MacOSExePath = "/usr/local/bin/pwsh" ;
2121const MacOSPreviewExePath = "/usr/local/bin/pwsh-preview" ;
2222
2323export enum OperatingSystem {
@@ -97,17 +97,21 @@ export class PowerShellExeFinder {
9797 /**
9898 * Returns the first available PowerShell executable found in the search order.
9999 */
100- public getFirstAvailablePowerShellInstallation ( ) : IPowerShellExeDetails {
101- for ( const pwsh of this . enumeratePowerShellInstallations ( ) ) {
100+ public async getFirstAvailablePowerShellInstallation ( ) : Promise < IPowerShellExeDetails > {
101+ for await ( const pwsh of this . enumeratePowerShellInstallations ( ) ) {
102102 return pwsh ;
103103 }
104104 }
105105
106106 /**
107107 * Get an array of all PowerShell executables found when searching for PowerShell installations.
108108 */
109- public getAllAvailablePowerShellInstallations ( ) : IPowerShellExeDetails [ ] {
110- return Array . from ( this . enumeratePowerShellInstallations ( ) ) ;
109+ public async getAllAvailablePowerShellInstallations ( ) : Promise < IPowerShellExeDetails [ ] > {
110+ const array : IPowerShellExeDetails [ ] = [ ] ;
111+ for await ( const pwsh of this . enumeratePowerShellInstallations ( ) ) {
112+ array . push ( pwsh ) ;
113+ }
114+ return array ;
111115 }
112116
113117 /**
@@ -137,18 +141,18 @@ export class PowerShellExeFinder {
137141 * PowerShell items returned by this object are verified
138142 * to exist on the filesystem.
139143 */
140- public * enumeratePowerShellInstallations ( ) : Iterable < IPowerShellExeDetails > {
144+ public async * enumeratePowerShellInstallations ( ) : AsyncIterable < IPowerShellExeDetails > {
141145 // Get the default PowerShell installations first
142- for ( const defaultPwsh of this . enumerateDefaultPowerShellInstallations ( ) ) {
143- if ( defaultPwsh && defaultPwsh . exists ( ) ) {
146+ for await ( const defaultPwsh of this . enumerateDefaultPowerShellInstallations ( ) ) {
147+ if ( defaultPwsh && await defaultPwsh . exists ( ) ) {
144148 yield defaultPwsh ;
145149 }
146150 }
147151
148152 // Also show any additionally configured PowerShells
149153 // These may be duplicates of the default installations, but given a different name.
150154 for ( const additionalPwsh of this . enumerateAdditionalPowerShellInstallations ( ) ) {
151- if ( additionalPwsh && additionalPwsh . exists ( ) ) {
155+ if ( additionalPwsh && await additionalPwsh . exists ( ) ) {
152156 yield additionalPwsh ;
153157 }
154158 }
@@ -159,7 +163,7 @@ export class PowerShellExeFinder {
159163 * Returned values may not exist, but come with an .exists property
160164 * which will check whether the executable exists.
161165 */
162- private * enumerateDefaultPowerShellInstallations ( ) : Iterable < IPossiblePowerShellExe > {
166+ private async * enumerateDefaultPowerShellInstallations ( ) : AsyncIterable < IPossiblePowerShellExe > {
163167 // Find PSCore stable first
164168 yield this . findPSCoreStable ( ) ;
165169
@@ -174,7 +178,7 @@ export class PowerShellExeFinder {
174178 yield this . findPSCoreWindowsInstallation ( { useAlternateBitness : true } ) ;
175179
176180 // Also look for the MSIX/UWP installation
177- yield this . findPSCoreMsix ( ) ;
181+ yield await this . findPSCoreMsix ( ) ;
178182
179183 break ;
180184 }
@@ -213,7 +217,7 @@ export class PowerShellExeFinder {
213217 }
214218
215219 /**
216- * Iterates through the configured additonal PowerShell executable locations,
220+ * Iterates through the configured additional PowerShell executable locations,
217221 * without checking for their existence.
218222 */
219223 private * enumerateAdditionalPowerShellInstallations ( ) : Iterable < IPossiblePowerShellExe > {
@@ -227,7 +231,7 @@ export class PowerShellExeFinder {
227231 }
228232 }
229233
230- private findPSCoreStable ( ) : IPossiblePowerShellExe {
234+ private async findPSCoreStable ( ) : Promise < IPossiblePowerShellExe > {
231235 switch ( this . platformDetails . operatingSystem ) {
232236 case OperatingSystem . Linux :
233237 return new PossiblePowerShellExe ( LinuxExePath , "PowerShell" ) ;
@@ -236,11 +240,11 @@ export class PowerShellExeFinder {
236240 return new PossiblePowerShellExe ( MacOSExePath , "PowerShell" ) ;
237241
238242 case OperatingSystem . Windows :
239- return this . findPSCoreWindowsInstallation ( ) ;
243+ return await this . findPSCoreWindowsInstallation ( ) ;
240244 }
241245 }
242246
243- private findPSCorePreview ( ) : IPossiblePowerShellExe {
247+ private async findPSCorePreview ( ) : Promise < IPossiblePowerShellExe > {
244248 switch ( this . platformDetails . operatingSystem ) {
245249 case OperatingSystem . Linux :
246250 return new PossiblePowerShellExe ( LinuxPreviewExePath , "PowerShell Preview" ) ;
@@ -249,7 +253,7 @@ export class PowerShellExeFinder {
249253 return new PossiblePowerShellExe ( MacOSPreviewExePath , "PowerShell Preview" ) ;
250254
251255 case OperatingSystem . Windows :
252- return this . findPSCoreWindowsInstallation ( { findPreview : true } ) ;
256+ return await this . findPSCoreWindowsInstallation ( { findPreview : true } ) ;
253257 }
254258 }
255259
@@ -260,10 +264,10 @@ export class PowerShellExeFinder {
260264
261265 const dotnetGlobalToolExePath : string = path . join ( os . homedir ( ) , ".dotnet" , "tools" , exeName ) ;
262266
263- return new PossiblePowerShellExe ( dotnetGlobalToolExePath , ".NET Core PowerShell Global Tool" ) ;
267+ return new PossiblePowerShellExe ( dotnetGlobalToolExePath , ".NET Core PowerShell Global Tool" , undefined , false ) ;
264268 }
265269
266- private findPSCoreMsix ( { findPreview } : { findPreview ?: boolean } = { } ) : IPossiblePowerShellExe {
270+ private async findPSCoreMsix ( { findPreview } : { findPreview ?: boolean } = { } ) : Promise < IPossiblePowerShellExe > {
267271 // We can't proceed if there's no LOCALAPPDATA path
268272 if ( ! process . env . LOCALAPPDATA ) {
269273 return null ;
@@ -272,7 +276,7 @@ export class PowerShellExeFinder {
272276 // Find the base directory for MSIX application exe shortcuts
273277 const msixAppDir = path . join ( process . env . LOCALAPPDATA , "Microsoft" , "WindowsApps" ) ;
274278
275- if ( ! fileExistsSync ( msixAppDir ) ) {
279+ if ( ! await checkIfDirectoryExists ( msixAppDir ) ) {
276280 return null ;
277281 }
278282
@@ -282,6 +286,7 @@ export class PowerShellExeFinder {
282286 : { pwshMsixDirRegex : PowerShellExeFinder . PwshMsixRegex , pwshMsixName : "PowerShell (Store)" } ;
283287
284288 // We should find only one such application, so return on the first one
289+ // TODO: Use VS Code async fs API for this.
285290 for ( const subdir of fs . readdirSync ( msixAppDir ) ) {
286291 if ( pwshMsixDirRegex . test ( subdir ) ) {
287292 const pwshMsixPath = path . join ( msixAppDir , subdir , "pwsh.exe" ) ;
@@ -301,9 +306,9 @@ export class PowerShellExeFinder {
301306 return new PossiblePowerShellExe ( SnapPreviewExePath , "PowerShell Preview Snap" ) ;
302307 }
303308
304- private findPSCoreWindowsInstallation (
309+ private async findPSCoreWindowsInstallation (
305310 { useAlternateBitness = false , findPreview = false } :
306- { useAlternateBitness ?: boolean ; findPreview ?: boolean } = { } ) : IPossiblePowerShellExe {
311+ { useAlternateBitness ?: boolean ; findPreview ?: boolean } = { } ) : Promise < IPossiblePowerShellExe > {
307312
308313 const programFilesPath : string = this . getProgramFilesPath ( { useAlternateBitness } ) ;
309314
@@ -314,13 +319,7 @@ export class PowerShellExeFinder {
314319 const powerShellInstallBaseDir = path . join ( programFilesPath , "PowerShell" ) ;
315320
316321 // Ensure the base directory exists
317- try {
318- const powerShellInstallBaseDirLStat = fs . lstatSync ( powerShellInstallBaseDir ) ;
319- if ( ! powerShellInstallBaseDirLStat . isDirectory ( ) )
320- {
321- return null ;
322- }
323- } catch {
322+ if ( ! await checkIfDirectoryExists ( powerShellInstallBaseDir ) ) {
324323 return null ;
325324 }
326325
@@ -366,7 +365,7 @@ export class PowerShellExeFinder {
366365
367366 // Now look for the file
368367 const exePath = path . join ( powerShellInstallBaseDir , item , "pwsh.exe" ) ;
369- if ( ! fs . existsSync ( exePath ) ) {
368+ if ( ! await checkIfFileExists ( exePath ) ) {
370369 continue ;
371370 }
372371
@@ -413,7 +412,7 @@ export class PowerShellExeFinder {
413412 displayName = WindowsPowerShell32BitLabel ;
414413 }
415414
416- winPS = new PossiblePowerShellExe ( winPSPath , displayName , { knownToExist : true } ) ;
415+ winPS = new PossiblePowerShellExe ( winPSPath , displayName , true ) ;
417416
418417 if ( useAlternateBitness ) {
419418 this . alternateBitnessWinPS = winPS ;
@@ -479,40 +478,20 @@ export function getWindowsSystemPowerShellPath(systemFolderName: string) {
479478 "powershell.exe" ) ;
480479}
481480
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-
493481interface IPossiblePowerShellExe extends IPowerShellExeDetails {
494- exists ( ) : boolean ;
482+ exists ( ) : Promise < boolean > ;
495483}
496484
497485class PossiblePowerShellExe implements IPossiblePowerShellExe {
498- public readonly exePath : string ;
499- public readonly displayName : string ;
500-
501- private knownToExist : boolean ;
502-
503486 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- }
487+ public readonly exePath : string ,
488+ public readonly displayName : string ,
489+ private knownToExist ?: boolean ,
490+ public readonly supportsProperArguments : boolean = true ) { }
512491
513- public exists ( ) : boolean {
492+ public async exists ( ) : Promise < boolean > {
514493 if ( this . knownToExist === undefined ) {
515- this . knownToExist = fileExistsSync ( this . exePath ) ;
494+ this . knownToExist = await checkIfFileExists ( this . exePath ) ;
516495 }
517496 return this . knownToExist ;
518497 }
0 commit comments