@@ -8,7 +8,7 @@ import { Logger } from './logger';
88 */
99export class PowerShellExecutor {
1010 private logger : Logger ;
11- private static readonly POWERSHELL_EXECUTABLE = 'pwsh' ;
11+ private powershellExecutable : string | null = null ;
1212
1313 constructor ( ) {
1414 this . logger = Logger . getInstance ( ) ;
@@ -43,13 +43,71 @@ export class PowerShellExecutor {
4343 }
4444 }
4545
46+ /**
47+ * Detects which PowerShell executable is available on the system
48+ * Checks for pwsh first (PowerShell 7+), then falls back to powershell (Windows PowerShell 5.1)
49+ */
50+ private async detectPowerShellExecutable ( ) : Promise < string > {
51+ if ( this . powershellExecutable ) {
52+ return this . powershellExecutable ;
53+ }
54+
55+ const executablesToTry = [ 'pwsh' , 'powershell' ] ;
56+
57+ for ( const executable of executablesToTry ) {
58+ try {
59+ await this . testExecutable ( executable ) ;
60+ this . powershellExecutable = executable ;
61+ this . logger . info ( `Using PowerShell executable: ${ executable } ` ) ;
62+ return executable ;
63+ } catch ( error ) {
64+ this . logger . debug ( `${ executable } not available: ${ error } ` ) ;
65+ }
66+ }
67+
68+ throw new Error ( 'No PowerShell executable found. Please install PowerShell 7+ (pwsh) or ensure Windows PowerShell (powershell) is available.' ) ;
69+ }
70+
71+ /**
72+ * Tests if a PowerShell executable is available and working
73+ */
74+ private testExecutable ( executable : string ) : Promise < void > {
75+ return new Promise < void > ( ( resolve , reject ) => {
76+ const ps = childProcess . spawn (
77+ executable ,
78+ [ '-NoLogo' , '-NoProfile' , '-NonInteractive' , '-Command' , 'Write-Host "OK"' ] ,
79+ { shell : true }
80+ ) ;
81+
82+ let hasOutput = false ;
83+
84+ ps . stdout . on ( 'data' , ( ) => {
85+ hasOutput = true ;
86+ } ) ;
87+
88+ ps . on ( 'close' , ( code : number ) => {
89+ if ( code === 0 && hasOutput ) {
90+ resolve ( ) ;
91+ } else {
92+ reject ( new Error ( `Executable ${ executable } exited with code ${ code } or produced no output` ) ) ;
93+ }
94+ } ) ;
95+
96+ ps . on ( 'error' , ( error : Error ) => {
97+ // This typically happens when the executable is not found
98+ reject ( new Error ( `Executable ${ executable } not found or failed to start: ${ error . message } ` ) ) ;
99+ } ) ;
100+ } ) ;
101+ }
102+
46103 /**
47104 * Checks if PowerShell is available on the system
48105 */
49106 public async isPowerShellAvailable ( ) : Promise < boolean > {
50107 try {
108+ const executable = await this . detectPowerShellExecutable ( ) ;
51109 const output = await this . executeScript ( '' , [ '-Command' , 'Write-Host "$($PSVersionTable.PSVersion)"' ] ) ;
52- this . logger . info ( `Detected PowerShell version: ${ output . trim ( ) } ` ) ;
110+ this . logger . info ( `Detected PowerShell version using ${ executable } : ${ output . trim ( ) } ` ) ;
53111 return true ;
54112 } catch ( error ) {
55113 this . logger . warn ( `PowerShell not available or failed to execute: ${ error } ` ) ;
@@ -69,9 +127,11 @@ export class PowerShellExecutor {
69127 * - Supports both script file execution and direct command execution
70128 * - Filters out potentially dangerous arguments for security
71129 */
72- public executeScript ( scriptPath : string , args : string [ ] = [ ] ) : Promise < string > {
130+ public async executeScript ( scriptPath : string , args : string [ ] = [ ] ) : Promise < string > {
73131 this . logger . debug ( `Executing PowerShell script: ${ scriptPath } with args: ${ args . join ( ' ' ) } ` ) ;
74132
133+ const executable = await this . detectPowerShellExecutable ( ) ;
134+
75135 // Filter arguments to prevent potentially dangerous operations
76136 // Allow only safe PowerShell parameters and user arguments
77137 const allowedArgPrefixes = [
@@ -120,7 +180,7 @@ export class PowerShellExecutor {
120180 }
121181
122182 const ps = childProcess . spawn (
123- PowerShellExecutor . POWERSHELL_EXECUTABLE ,
183+ executable ,
124184 pwshArguments ,
125185 { shell : true }
126186 ) ;
0 commit comments