@@ -34,6 +34,21 @@ export enum OperatingSystem {
3434 Linux ,
3535}
3636
37+ export type SupportedPlatform =
38+ | "windows-x64"
39+ | "windows-arm64"
40+ | "macos-x64"
41+ | "macos-arm64"
42+ | "linux-x64"
43+ | "linux-aarch64" ;
44+
45+ export interface IAdditionalPowerShellLocation {
46+ readonly name : string ;
47+ readonly path : string ;
48+ readonly platform : SupportedPlatform ;
49+ readonly weight ?: number ;
50+ }
51+
3752export interface IPlatformDetails {
3853 operatingSystem : OperatingSystem ;
3954 isOS64Bit : boolean ;
@@ -46,6 +61,46 @@ export interface IPowerShellExeDetails {
4661 readonly supportsProperArguments : boolean ;
4762}
4863
64+ export function getSupportedPlatform (
65+ platformDetails : IPlatformDetails ,
66+ processArchitecture : NodeJS . Architecture = process . arch ,
67+ ) : SupportedPlatform | undefined {
68+ switch ( platformDetails . operatingSystem ) {
69+ case OperatingSystem . Windows :
70+ switch ( processArchitecture ) {
71+ case "x64" :
72+ return "windows-x64" ;
73+ case "arm64" :
74+ return "windows-arm64" ;
75+ default :
76+ return undefined ;
77+ }
78+
79+ case OperatingSystem . MacOS :
80+ switch ( processArchitecture ) {
81+ case "x64" :
82+ return "macos-x64" ;
83+ case "arm64" :
84+ return "macos-arm64" ;
85+ default :
86+ return undefined ;
87+ }
88+
89+ case OperatingSystem . Linux :
90+ switch ( processArchitecture ) {
91+ case "x64" :
92+ return "linux-x64" ;
93+ case "arm64" :
94+ return "linux-aarch64" ;
95+ default :
96+ return undefined ;
97+ }
98+
99+ case OperatingSystem . Unknown :
100+ return undefined ;
101+ }
102+ }
103+
49104export function getPlatformDetails ( ) : IPlatformDetails {
50105 let operatingSystem = OperatingSystem . Unknown ;
51106
@@ -91,6 +146,8 @@ export class PowerShellExeFinder {
91146 // Additional configured PowerShells
92147 private additionalPowerShellExes : Record < string , string > ,
93148 private logger ?: ILogger ,
149+ private additionalPowerShellLocations : IAdditionalPowerShellLocation [ ] = [ ] ,
150+ private processArchitecture : NodeJS . Architecture = process . arch ,
94151 ) { }
95152
96153 /**
@@ -158,7 +215,11 @@ export class PowerShellExeFinder {
158215
159216 // Also show any additionally configured PowerShells
160217 // These may be duplicates of the default installations, but given a different name.
161- for await ( const additionalPwsh of this . enumerateAdditionalPowerShellInstallations ( ) ) {
218+ const configuredPowerShells =
219+ this . additionalPowerShellLocations . length > 0
220+ ? this . enumerateAdditionalPowerShellLocations ( )
221+ : this . enumerateAdditionalPowerShellInstallations ( ) ;
222+ for await ( const additionalPwsh of configuredPowerShells ) {
162223 if ( await additionalPwsh . exists ( ) ) {
163224 yield additionalPwsh ;
164225 } else if ( ! additionalPwsh . suppressWarning ) {
@@ -269,97 +330,123 @@ export class PowerShellExeFinder {
269330 * without checking for their existence.
270331 */
271332 public async * enumerateAdditionalPowerShellInstallations ( ) : AsyncIterable < IPossiblePowerShellExe > {
272- for ( const versionName in this . additionalPowerShellExes ) {
273- if (
274- Object . prototype . hasOwnProperty . call (
275- this . additionalPowerShellExes ,
276- versionName ,
277- )
278- ) {
279- let exePath : string | undefined = utils . stripQuotePair (
280- this . additionalPowerShellExes [ versionName ] ,
281- ) ;
282- if ( ! exePath ) {
283- continue ;
284- }
333+ yield * this . enumerateConfiguredPowerShellInstallations (
334+ Object . entries ( this . additionalPowerShellExes ) ,
335+ ) ;
336+ }
285337
286- exePath = untildify ( exePath ) ;
287- const args : [ string , undefined , boolean , boolean ] =
288- // Must be a tuple type and is suppressing the warning
289- [ versionName , undefined , true , true ] ;
338+ /**
339+ * Iterates through the configured additional PowerShell locations for the current platform,
340+ * without checking for their existence.
341+ */
342+ public async * enumerateAdditionalPowerShellLocations ( ) : AsyncIterable < IPossiblePowerShellExe > {
343+ const supportedPlatform = getSupportedPlatform (
344+ this . platformDetails ,
345+ this . processArchitecture ,
346+ ) ;
347+ if ( ! supportedPlatform ) {
348+ return ;
349+ }
290350
291- // Always search for what the user gave us first, but with the warning
292- // suppressed so we can display it after all possibilities are exhausted
293- let pwsh = new PossiblePowerShellExe ( exePath , ...args ) ;
294- if ( await pwsh . exists ( ) ) {
295- yield pwsh ;
296- continue ;
297- }
351+ yield * this . enumerateConfiguredPowerShellInstallations (
352+ this . additionalPowerShellLocations
353+ . filter ( ( location ) => location . platform === supportedPlatform )
354+ // Higher weight wins. Equal weights preserve definition order.
355+ . sort ( ( a , b ) => ( b . weight ?? 0 ) - ( a . weight ?? 0 ) )
356+ . map ( ( location ) : [ string , string ] => [
357+ location . name ,
358+ location . path ,
359+ ] ) ,
360+ ) ;
361+ }
298362
299- // Also search for `pwsh[.exe]` and `powershell[.exe]` if missing
363+ private async * enumerateConfiguredPowerShellInstallations (
364+ configuredPowerShells : Iterable < readonly [ string , string ] > ,
365+ ) : AsyncIterable < IPossiblePowerShellExe > {
366+ for ( const [ versionName , configuredPath ] of configuredPowerShells ) {
367+ let exePath : string | undefined =
368+ utils . stripQuotePair ( configuredPath ) ;
369+ if ( ! exePath ) {
370+ continue ;
371+ }
372+
373+ exePath = untildify ( exePath ) ;
374+ const args : [ string , undefined , boolean , boolean ] =
375+ // Must be a tuple type and is suppressing the warning
376+ [ versionName , undefined , true , true ] ;
377+
378+ // Always search for what the user gave us first, but with the warning
379+ // suppressed so we can display it after all possibilities are exhausted
380+ let pwsh = new PossiblePowerShellExe ( exePath , ...args ) ;
381+ if ( await pwsh . exists ( ) ) {
382+ yield pwsh ;
383+ continue ;
384+ }
385+
386+ // Also search for `pwsh[.exe]` and `powershell[.exe]` if missing
387+ if (
388+ this . platformDetails . operatingSystem === OperatingSystem . Windows
389+ ) {
390+ // Handle Windows where '.exe' and 'powershell' are things
300391 if (
301- this . platformDetails . operatingSystem ===
302- OperatingSystem . Windows
392+ ! exePath . endsWith ( "pwsh.exe" ) &&
393+ ! exePath . endsWith ( "powershell.exe" )
303394 ) {
304- // Handle Windows where '.exe' and 'powershell' are things
305395 if (
306- ! exePath . endsWith ( "pwsh.exe " ) &&
307- ! exePath . endsWith ( "powershell.exe " )
396+ exePath . endsWith ( "pwsh" ) ||
397+ exePath . endsWith ( "powershell" )
308398 ) {
309- if (
310- exePath . endsWith ( "pwsh" ) ||
311- exePath . endsWith ( "powershell" )
312- ) {
313- // Add extension if that was missing
314- pwsh = new PossiblePowerShellExe (
315- exePath + ".exe" ,
316- ...args ,
317- ) ;
318- if ( await pwsh . exists ( ) ) {
319- yield pwsh ;
320- continue ;
321- }
322- }
323- // Also add full exe names (this isn't an else just in case
324- // the folder was named "pwsh" or "powershell")
325- pwsh = new PossiblePowerShellExe (
326- path . join ( exePath , "pwsh.exe" ) ,
327- ...args ,
328- ) ;
329- if ( await pwsh . exists ( ) ) {
330- yield pwsh ;
331- continue ;
332- }
399+ // Add extension if that was missing
333400 pwsh = new PossiblePowerShellExe (
334- path . join ( exePath , "powershell .exe") ,
401+ exePath + " .exe",
335402 ...args ,
336403 ) ;
337404 if ( await pwsh . exists ( ) ) {
338405 yield pwsh ;
339406 continue ;
340407 }
341408 }
342- } else if ( ! exePath . endsWith ( "pwsh" ) ) {
343- // Always just 'pwsh' on non-Windows
409+
410+ // Also add full exe names (this isn't an else just in case
411+ // the folder was named "pwsh" or "powershell")
344412 pwsh = new PossiblePowerShellExe (
345- path . join ( exePath , "pwsh" ) ,
413+ path . join ( exePath , "pwsh.exe " ) ,
346414 ...args ,
347415 ) ;
348416 if ( await pwsh . exists ( ) ) {
349417 yield pwsh ;
350418 continue ;
351419 }
352- }
353420
354- // If we're still being iterated over, no permutation of the given path existed so yield an object with the warning unsuppressed
355- yield new PossiblePowerShellExe (
356- exePath ,
357- versionName ,
358- false ,
359- undefined ,
360- false ,
421+ pwsh = new PossiblePowerShellExe (
422+ path . join ( exePath , "powershell.exe" ) ,
423+ ...args ,
424+ ) ;
425+ if ( await pwsh . exists ( ) ) {
426+ yield pwsh ;
427+ continue ;
428+ }
429+ }
430+ } else if ( ! exePath . endsWith ( "pwsh" ) ) {
431+ // Always just 'pwsh' on non-Windows
432+ pwsh = new PossiblePowerShellExe (
433+ path . join ( exePath , "pwsh" ) ,
434+ ...args ,
361435 ) ;
436+ if ( await pwsh . exists ( ) ) {
437+ yield pwsh ;
438+ continue ;
439+ }
362440 }
441+
442+ // If we're still being iterated over, no permutation of the given path existed so yield an object with the warning unsuppressed
443+ yield new PossiblePowerShellExe (
444+ exePath ,
445+ versionName ,
446+ false ,
447+ undefined ,
448+ false ,
449+ ) ;
363450 }
364451 }
365452
0 commit comments