Capture program stdout and stderr to separate variables

后端 未结 5 1303
小蘑菇
小蘑菇 2020-11-28 10:29

Is it possible to redirect stdout from an external program to a variable and stderr from external programs to another variable in one run?

For example:



        
相关标签:
5条回答
  • 2020-11-28 10:44

    The easiest way to do this is to use a file for the stderr output, e.g.:

    $output = & myprogram.exe 'argv[0]', 'argv[1]' 2>stderr.txt
    $err = get-content stderr.txt
    if ($LastExitCode -ne 0) { ... handle error ... }
    

    I would also use $LastExitCode to check for errors from native console EXE files.

    0 讨论(0)
  • 2020-11-28 10:52

    This is also an alternative that I have used to redirect stdout and stderr of a command line while still showing the output during PowerShell execution:

    $command = "myexecutable.exe my command line params"
    
    Invoke-Expression $command -OutVariable output -ErrorVariable errors
    Write-Host "STDOUT"
    Write-Host $output
    Write-Host "STDERR"
    Write-Host $errors
    

    It is just another possibility to supplement what was already given.

    Keep in mind this may not always work depending upon how the script is invoked. I have had problems with -OutVariable and -ErrorVariable when invoked from a standard command line rather than a PowerShell command line like this:

    PowerShell -File ".\FileName.ps1"
    

    An alternative that seems to work under most circumstances is this:

    $stdOutAndError = Invoke-Expression "$command 2>&1"
    

    Unfortunately, you will lose output to the command line during execution of the script and would have to Write-Host $stdOutAndError after the command returns to make it "a part of the record" (like a part of a Jenkins batch file run). And unfortunately it doesn't separate stdout and stderr.

    0 讨论(0)
  • 2020-11-28 10:58

    One option is to combine the output of stdout and stderr into a single stream, then filter.

    Data from stdout will be strings, while stderr produces System.Management.Automation.ErrorRecord objects.

    $allOutput = & myprogram.exe 2>&1
    $stderr = $allOutput | ?{ $_ -is [System.Management.Automation.ErrorRecord] }
    $stdout = $allOutput | ?{ $_ -isnot [System.Management.Automation.ErrorRecord] }
    
    0 讨论(0)
  • 2020-11-28 11:02

    You should be using Start-Process with -RedirectStandardError -RedirectStandardOutput options. This other post has a great example of how to do this (sampled from that post below):

    $pinfo = New-Object System.Diagnostics.ProcessStartInfo
    $pinfo.FileName = "ping.exe"
    $pinfo.RedirectStandardError = $true
    $pinfo.RedirectStandardOutput = $true
    $pinfo.UseShellExecute = $false
    $pinfo.Arguments = "localhost"
    $p = New-Object System.Diagnostics.Process
    $p.StartInfo = $pinfo
    $p.Start() | Out-Null
    $p.WaitForExit()
    $stdout = $p.StandardOutput.ReadToEnd()
    $stderr = $p.StandardError.ReadToEnd()
    Write-Host "stdout: $stdout"
    Write-Host "stderr: $stderr"
    Write-Host "exit code: " + $p.ExitCode
    
    0 讨论(0)
  • 2020-11-28 11:07

    Separately, preserving formatting

    cls
    function GetAnsVal {
        param([Parameter(Mandatory=$true, ValueFromPipeline=$true)][System.Object[]][AllowEmptyString()]$Output,
              [Parameter(Mandatory=$false, ValueFromPipeline=$true)][System.String]$firstEncNew="UTF-8",
              [Parameter(Mandatory=$false, ValueFromPipeline=$true)][System.String]$secondEncNew="CP866"
        )
        function ConvertTo-Encoding ([string]$From, [string]$To){#"UTF-8" "CP866" "ASCII" "windows-1251"
            Begin{
                $encFrom = [System.Text.Encoding]::GetEncoding($from)
                $encTo = [System.Text.Encoding]::GetEncoding($to)
            }
            Process{
                $Text=($_).ToString()
                $bytes = $encTo.GetBytes($Text)
                $bytes = [System.Text.Encoding]::Convert($encFrom, $encTo, $bytes)
                $encTo.GetString($bytes)
            }
        }
        $all = New-Object System.Collections.Generic.List[System.Object];
        $exception = New-Object System.Collections.Generic.List[System.Object];
        $stderr = New-Object System.Collections.Generic.List[System.Object];
        $stdout = New-Object System.Collections.Generic.List[System.Object]
        $i = 0;$Output | % {
            if ($_ -ne $null){
                if ($_.GetType().FullName -ne 'System.Management.Automation.ErrorRecord'){
                    if ($_.Exception.message -ne $null){$Temp=$_.Exception.message | ConvertTo-Encoding $firstEncNew $secondEncNew;$all.Add($Temp);$exception.Add($Temp)}
                    elseif ($_ -ne $null){$Temp=$_ | ConvertTo-Encoding $firstEncNew $secondEncNew;$all.Add($Temp);$stdout.Add($Temp)}
                } else {
                    #if (MyNonTerminatingError.Exception is AccessDeniedException)
                    $Temp=$_.Exception.message | ConvertTo-Encoding $firstEncNew $secondEncNew;
                    $all.Add($Temp);$stderr.Add($Temp)
                }   
             }
        $i++
        }
        [hashtable]$return = @{}
        $return.Meta0=$all;$return.Meta1=$exception;$return.Meta2=$stderr;$return.Meta3=$stdout;
        return $return
    }
    Add-Type -AssemblyName System.Windows.Forms;
    & C:\Windows\System32\curl.exe 'api.ipify.org/?format=plain' 2>&1 | set-variable Output;
    $r = & GetAnsVal $Output
    $Meta2=""
    foreach ($el in $r.Meta2){
        $Meta2+=$el
    }
    $Meta2=($Meta2 -split "[`r`n]") -join "`n"
    $Meta2=($Meta2 -split "[`n]{2,}") -join "`n"
    [Console]::Write("stderr:`n");
    [Console]::Write($Meta2);
    [Console]::Write("`n");
    $Meta3=""
    foreach ($el in $r.Meta3){
        $Meta3+=$el
    }
    $Meta3=($Meta3 -split "[`r`n]") -join "`n"
    $Meta3=($Meta3 -split "[`n]{2,}") -join "`n"
    [Console]::Write("stdout:`n");
    [Console]::Write($Meta3);
    [Console]::Write("`n");
    
    0 讨论(0)
提交回复
热议问题