private/Get-CommandVersion.ps1
|
function Get-CommandVersion { <# .SYNOPSIS Gets the version output of a CLI command using process-based execution. .DESCRIPTION Runs '<command> --version' using System.Diagnostics.Process with stdin redirected and immediately closed. This prevents CLIs (like Claude Code) from detecting piped stdin and switching to pipe/print mode, which causes errors when using PowerShell's & operator with 2>&1 redirection. .PARAMETER Command The command name to get the version for. .OUTPUTS System.String - The first non-empty line of version output, or $null on failure. #> param( [Parameter(Mandatory)] [string]$Command ) # Prefer Application type (actual exe) over Functions/Aliases that install scripts may create $cmd = Get-Command $Command -CommandType Application -ErrorAction SilentlyContinue | Select-Object -First 1 if ($null -eq $cmd) { $cmd = Get-Command $Command -ErrorAction SilentlyContinue } if ($null -eq $cmd) { return $null } $exePath = $cmd.Source if ([string]::IsNullOrWhiteSpace($exePath)) { return $null } $process = $null try { $psi = New-Object System.Diagnostics.ProcessStartInfo $psi.UseShellExecute = $false $psi.CreateNoWindow = $true $psi.RedirectStandardOutput = $true $psi.RedirectStandardError = $true $psi.RedirectStandardInput = $true # .cmd/.bat files on Windows must run via cmd.exe $extension = [System.IO.Path]::GetExtension($exePath).ToLowerInvariant() if ($extension -in '.cmd', '.bat') { $psi.FileName = 'cmd.exe' $psi.Arguments = "/c `"$exePath`" --version" } else { $psi.FileName = $exePath $psi.Arguments = '--version' } $process = New-Object System.Diagnostics.Process $process.StartInfo = $psi $process.Start() | Out-Null # Close stdin immediately to prevent interactive prompts $process.StandardInput.Close() $stdout = $process.StandardOutput.ReadToEnd() $stderr = $process.StandardError.ReadToEnd() if (-not $process.WaitForExit(10000)) { try { $process.Kill() } catch { } return $null } # Check stdout first, fall back to stderr (some CLIs output version to stderr) $result = ($stdout -split "`r?`n" | Where-Object { $_.Trim() } | Select-Object -First 1) if ([string]::IsNullOrWhiteSpace($result)) { $result = ($stderr -split "`r?`n" | Where-Object { $_.Trim() } | Select-Object -First 1) } if ([string]::IsNullOrWhiteSpace($result)) { return $null } return $result } catch { return $null } finally { if ($null -ne $process) { try { $process.Dispose() } catch { } } } } |