Private/Invoke-NativeProcess.ps1

# Invoke-NativeProcess.ps1
# Helper function for invoking native processes safely

function Invoke-NativeProcess {
    [CmdletBinding()]
    [OutputType([PSCustomObject])]
    param(
        [Parameter(Mandatory = $true)]
        [string]$FilePath,
        
        [Parameter(Mandatory = $false)]
        [string[]]$ArgumentList = @(),
        
        [Parameter(Mandatory = $false)]
        [string]$WorkingDirectory = (Get-Location).Path,
        
        [Parameter(Mandatory = $false)]
        [int]$TimeoutSeconds = 30,
        
        [Parameter(Mandatory = $false)]
        [System.Text.Encoding]$Encoding = [System.Text.Encoding]::UTF8
    )
    
    try {
        # Create process start info
        $ProcessStartInfo = [System.Diagnostics.ProcessStartInfo]@{
            FileName = $FilePath
            Arguments = ($ArgumentList -join ' ')
            WorkingDirectory = $WorkingDirectory
            UseShellExecute = $false
            RedirectStandardOutput = $true
            RedirectStandardError = $true
            CreateNoWindow = $true
            StandardOutputEncoding = $Encoding
            StandardErrorEncoding = $Encoding
        }
        
        # Start the process
        $Process = [System.Diagnostics.Process]::Start($ProcessStartInfo)
        
        # Wait for completion with timeout
        $Completed = $Process.WaitForExit($TimeoutSeconds * 1000)
        
        if (-not $Completed) {
            $Process.Kill()
            throw [System.TimeoutException]::new("Process timed out after $TimeoutSeconds seconds")
        }
        
        # Read output streams
        $StandardOutput = $Process.StandardOutput.ReadToEnd()
        $StandardError = $Process.StandardError.ReadToEnd()
        $ExitCode = $Process.ExitCode
        
        # Clean up
        $Process.Dispose()
        
        return [PSCustomObject]@{
            ExitCode = $ExitCode
            StandardOutput = $StandardOutput
            StandardError = $StandardError
            Success = ($ExitCode -eq 0)
            TimedOut = $false
        }
        
    } catch [System.TimeoutException] {
        return [PSCustomObject]@{
            ExitCode = -1
            StandardOutput = ""
            StandardError = $_.Exception.Message
            Success = $false
            TimedOut = $true
        }
    } catch {
        return [PSCustomObject]@{
            ExitCode = -1
            StandardOutput = ""
            StandardError = $_.Exception.Message
            Success = $false
            TimedOut = $false
        }
    }
}

function Test-NativeCommand {
    [CmdletBinding()]
    [OutputType([bool])]
    param(
        [Parameter(Mandatory = $true)]
        [string]$Command
    )
    
    try {
        if ($PSVersionTable.PSVersion.Major -le 5 -or [System.Environment]::OSVersion.Platform -eq 'Win32NT') {
            # Windows: Use where.exe
            $Result = Invoke-NativeProcess -FilePath "where.exe" -ArgumentList @($Command) -TimeoutSeconds 5
        } else {
            # Unix: Use which
            $Result = Invoke-NativeProcess -FilePath "which" -ArgumentList @($Command) -TimeoutSeconds 5
        }
        
        return $Result.Success
    } catch {
        return $false
    }
}