Redirect PowerShell-重定向可执行文件';s stderr到文件或变量,但仍有stdout到控制台

Redirect PowerShell-重定向可执行文件';s stderr到文件或变量,但仍有stdout到控制台,redirect,powershell,stream,stderr,powershell-3.0,Redirect,Powershell,Stream,Stderr,Powershell 3.0,我正在编写一个脚本,从GitHub下载几个存储库。以下是下载存储库的命令: git clone "$RepositoryUrl" "$localRepoDirectory" 当我运行这个命令时,它会在控制台窗口中显示一些我想要显示的进度信息 问题是,我还希望能够检测下载时是否发生任何错误。我找到了,所以我试着: (git clone "$RepositoryUrl" "$localRepoDirectory") 2> $errorLogFilePath 这会将所有错误从stderr传输

我正在编写一个脚本,从GitHub下载几个存储库。以下是下载存储库的命令:

git clone "$RepositoryUrl" "$localRepoDirectory"
当我运行这个命令时,它会在控制台窗口中显示一些我想要显示的进度信息

问题是,我还希望能够检测下载时是否发生任何错误。我找到了,所以我试着:

(git clone "$RepositoryUrl" "$localRepoDirectory") 2> $errorLogFilePath
这会将所有错误从stderr传输到我的文件,但不再在控制台中显示良好的进度信息

我可以像这样使用T形对象:

(git clone "$RepositoryUrl" "$localRepoDirectory") | Tee-Object -FilePath $errorLogFilePath
我仍然得到了很好的进度输出,但这会将stdout传输到文件,而不是stderr;我只关心检测错误

是否有一种方法可以存储文件或(最好)变量中发生的任何错误,同时仍将进度信息通过管道传输到控制台窗口?我有一种感觉,答案可能在于,但我不是很确定

=======更新=======

我不确定git.exe是否与典型的可执行文件不同,但我做了更多的测试,以下是我的发现:

$output = (git clone "$RepositoryUrl" "$localRepoDirectory")
$output始终包含文本“克隆到“[localRepoDirectory]”…”,无论命令是否成功完成或产生错误。此外,执行此操作时,进度信息仍会写入控制台。这让我觉得进度信息不是通过stdout编写的,而是通过其他流编写的

如果发生错误,则会将错误写入控制台,但前景色通常为白色,而不是典型的红色表示错误,黄色表示警告。当从cmdlet函数中调用此命令且命令失败并出现错误时,不会通过函数的-ErrorVariable(或-WarningVariable)参数返回错误(但是如果我自己执行写入错误,则会通过-ErrorVariable返回错误)。这让我想到git.exe不会写入stderr,但当我们这样做时:

(git clone "$RepositoryUrl" "$localRepoDirectory") 2> $errorLogFilePath
错误消息被写入文件,因此我认为它确实写入了stderr。所以现在我很困惑

=======更新2=======

因此,在拜伦的帮助下,我尝试了一些使用新流程的解决方案,但仍然无法得到我想要的。当使用一个新的进程时,我从来没有将好的进程写入控制台

我尝试过的三种新方法都使用了这段代码:

$process = New-Object System.Diagnostics.Process
$process.StartInfo.Arguments = "clone ""$RepositoryUrl"" ""$localRepoDirectory"""
$process.StartInfo.UseShellExecute = $false
$process.StartInfo.RedirectStandardOutput = $true
$process.StartInfo.RedirectStandardError = $true
$process.StartInfo.CreateNoWindow = $true
$process.StartInfo.WorkingDirectory = $WORKING_DIRECTORY
$process.StartInfo.FileName = "git"
方法1-在新进程中运行,然后读取输出:

$process.Start()
$process.WaitForExit()
Write-Host Output - $process.StandardOutput.ReadToEnd()    
Write-Host Errors - $process.StandardError.ReadToEnd()
方法2-同步获取输出:

$process.Start()
while (!$process.HasExited)
{
    Write-Host Output - $process.StandardOutput.ReadToEnd()
    Write-Host Error Output - $process.StandardError.ReadToEnd()

    Start-Sleep -Seconds 1
}
Register-ObjectEvent -InputObject $process -EventName "OutputDataReceived" -Action {Write-Host Output Data - $args[1].Data }
Register-ObjectEvent -InputObject $process -EventName "ErrorDataReceived" -Action { Write-Host Error Data - $args[1].Data }
$process.Start()
$process.BeginOutputReadLine()
$process.BeginErrorReadLine()
while (!$process.HasExited)
{
    Start-Sleep -Seconds 1
}
尽管这看起来像是在进程运行时写入输出,但它在进程退出后才写入任何内容

方法3-异步获取输出:

$process.Start()
while (!$process.HasExited)
{
    Write-Host Output - $process.StandardOutput.ReadToEnd()
    Write-Host Error Output - $process.StandardError.ReadToEnd()

    Start-Sleep -Seconds 1
}
Register-ObjectEvent -InputObject $process -EventName "OutputDataReceived" -Action {Write-Host Output Data - $args[1].Data }
Register-ObjectEvent -InputObject $process -EventName "ErrorDataReceived" -Action { Write-Host Error Data - $args[1].Data }
$process.Start()
$process.BeginOutputReadLine()
$process.BeginErrorReadLine()
while (!$process.HasExited)
{
    Start-Sleep -Seconds 1
}

这会在进程运行时输出数据,这很好,但它仍然不会显示良好的进度信息:(

您可以通过将git clone命令放入高级函数中来实现,例如:

function Clone-Git {
    [CmdletBinding()]
    param($repoUrl, $localRepoDir)

    git clone $repoUrl $localRepoDir
}

Clone-Git $RepositoryUrl $localRepoDirectory -ev cloneErrors

$cloneErrors

如果使用
System.Diagnostics.Process
启动Git,则可以重定向所有错误和输出

我必须为Inkscape解决这个问题:

$si = New-Object System.Diagnostics.ProcessStartInfo
$si.Arguments = YOUR PROCESS ARGS
$si.UseShellExecute = $false
$si.RedirectStandardOutput = $true
$si.RedirectStandardError = $true
$si.WorkingDirectory = $workingDir
$si.FileName = EXECUTABLE LOCATION
$process = [Diagnostics.Process]::Start($si)
while (!($process.HasExited))
{
    // Do what you want with strerr and stdout
    Start-Sleep -s 1  // Sleep for 1 second
}

当然,您可以将其包装在一个带有适当参数的函数中…

我想我有您的答案。我正在使用PowerShell一段时间,并创建了几个生成系统。如果脚本有点长,很抱歉,但它可以工作

$dir = <your dir>
$global:log = <your log file which must be in the global scope> # Not global = won't work

function Create-Process {
    $process = New-Object -TypeName System.Diagnostics.Process
    $process.StartInfo.CreateNoWindow = $false
    $process.StartInfo.RedirectStandardError = $true
    $process.StartInfo.UseShellExecute = $false
    return $process
}

function Terminate-Process {
    param([System.Diagnostics.Process]$process)

    $code = $process.ExitCode
    $process.Close()
    $process.Dispose()
    Remove-Variable process
    return $code
}

function Launch-Process {
    param([System.Diagnostics.Process]$process, [string]$log, [int]$timeout = 0)

    $errorjob = Register-ObjectEvent -InputObject $process -EventName ErrorDataReceived -SourceIdentifier Common.LaunchProcess.Error -action {
        if(-not [string]::IsNullOrEmpty($EventArgs.data)) {
            "ERROR - $($EventArgs.data)" | Out-File $log -Encoding ASCII -Append
            Write-Host "ERROR - $($EventArgs.data)"
        }
    }
    $outputjob = Register-ObjectEvent -InputObject $process -EventName OutputDataReceived -SourceIdentifier Common.LaunchProcess.Output -action {
        if(-not [string]::IsNullOrEmpty($EventArgs.data)) {
            "Out - $($EventArgs.data)" | Out-File $log -Encoding ASCII -Append
            Write-Host "Out - $($EventArgs.data)"
        }
    }

    if($errorjob -eq $null) {
        "ERROR - The error job is null" | Out-File $log -Encoding ASCII -Append
        Write-Host "ERROR - The error job is null"
    }

    if($outputjob -eq $null) {
        "ERROR - The output job is null" | Out-File $log -Encoding ASCII -Append
        Write-Host "ERROR - The output job is null"
    }

    $process.Start() 
    $process.BeginErrorReadLine()

    if($process.StartInfo.RedirectStandardOutput) {
        $process.BeginOutputReadLine() 
    }

    $ret = $null
    if($timeout -eq 0)
    {
        $process.WaitForExit()
        $ret = $true
    }
    else
    {
        if(-not($process.WaitForExit($timeout)))
        {
            Write-Host "ERROR - The process is not completed, after the specified timeout: $($timeout)"
            $ret = $false
        }
        else
        {
            $ret = $true
        }
    }

    # Cancel the event registrations
    Remove-Event * -ErrorAction SilentlyContinue
    Unregister-Event -SourceIdentifier Common.LaunchProcess.Error
    Unregister-Event -SourceIdentifier Common.LaunchProcess.Output
    Stop-Job $errorjob.Id
    Remove-Job $errorjob.Id
    Stop-Job $outputjob.Id
    Remove-Job $outputjob.Id

    $ret
}

$repo = <your repo>

$process = Create-Process
$process.StartInfo.RedirectStandardOutput = $true
$process.StartInfo.FileName = "git.exe"
$process.StartInfo.Arguments = "clone $($repo)"
$process.StartInfo.WorkingDirectory = $dir
Launch-Process $process $global:log
Terminate-Process $process
$dir=
$global:log=#Not global=不起作用
函数创建过程{
$process=新对象-TypeName System.Diagnostics.process
$process.StartInfo.CreateNoWindow=$false
$process.StartInfo.RedirectStandardError=$true
$process.StartInfo.UseShellExecute=$false
返回$process
}
函数终止过程{
参数([System.Diagnostics.Process]$Process)
$code=$process.ExitCode
$process.Close()
$process.Dispose()
删除可变进程
返回$code
}
功能启动过程{
param([System.Diagnostics.Process]$Process,[string]$log,[int]$timeout=0)
$errorjob=注册ObjectEvent-InputObject$process-EventName ErrorDataReceived-SourceIdentifier Common.LaunchProcess.Error-action{
if(-not[string]::IsNullOrEmpty($EventArgs.data)){
“错误-$($EventArgs.data)”|输出文件$log-编码ASCII-追加
写入主机“错误-$($EventArgs.data)”
}
}
$outputjob=注册ObjectEvent-InputObject$process-EventName OutputDataReceived-SourceIdentifier Common.LaunchProcess.Output-action{
if(-not[string]::IsNullOrEmpty($EventArgs.data)){
“Out-$($EventArgs.data)”| Out文件$log-编码ASCII-追加
写入主机“Out-$($EventArgs.data)”
}
}
如果($errorjob-eq$null){
“错误-错误作业为空”|输出文件$log-编码ASCII-追加
写入主机“错误-错误作业为空”
}
if($outputjob-eq$null){
“错误-输出作业为空”|输出文件$log-编码ASCII-追加
写入主机“错误-输出作业为空”
}
$process.Start()
$process.BeginErrorReadLine()
if($process.StartInfo.RedirectStandardOutput){
$process.BeginOutputReadLine()
}
$ret=$null
如果($timeout-eq 0)
{
$process.WaitForExit()
$ret=$true
}
其他的
{
if(-not($process.WaitForExit($timeout)))
{
写入主机“错误-在指定的超时后,进程未完成:$($timeout)”
$ret=$false
}
其他的
{
$ret=$true
}
}
#取消活动注册
删除事件*-ErrorAction SilentlyContinue
注销事件-SourceIdentifier Common.LaunchProcess.Error
注销事件-SourceIdentifier Common.LaunchProcess.Output
停止作业$errorjob.Id
删除作业$errorjob.Id
停止作业$outputjob.Id
删除作业$outputjob.Id
$ret
}
$repo=
$process=创建进程
$process.StartInfo.RedirectStandardOutput