redirect - salida - Powershell: redirigir el stderr del archivo ejecutable o variable, pero aún así tener stdout ir a la consola
powershell output as table (3)
Estoy escribiendo un script para descargar varios repositorios de GitHub. Aquí está el comando para descargar un repositorio:
git clone "$RepositoryUrl" "$localRepoDirectory"
Cuando ejecuto este comando, se muestra una buena información de progreso en la ventana de la consola que quiero mostrar.
El problema es que también quiero ser capaz de detectar si se han producido errores durante la descarga. Encontré esta publicación que habla acerca de redirigir las diferentes transmisiones , así que probé:
(git clone "$RepositoryUrl" "$localRepoDirectory") 2> $errorLogFilePath
Esto canaliza cualquier error de stderr a mi archivo, pero ya no muestra la información de progreso agradable en la consola.
Puedo usar el Tee-Object así:
(git clone "$RepositoryUrl" "$localRepoDirectory") | Tee-Object -FilePath $errorLogFilePath
y todavía obtengo el buen resultado de progreso, pero esto se convierte en el stdout del archivo, no stderr; Solo me preocupa detectar errores.
¿Hay alguna manera de que pueda almacenar cualquier error que ocurra en un archivo o (preferiblemente) una variable, mientras que también se sigue transmitiendo la información de progreso a la ventana de la consola? Tengo la sensación de que la respuesta podría estar en redirigir varias secuencias a otras transmisiones, como se analiza en esta publicación , pero no estoy muy seguro.
Cualquier sugerencia es apreciada ¡Gracias!
======== Actualización =======
No estoy seguro de si el archivo git.exe es diferente al ejecutable típico, pero he realizado más pruebas y esto es lo que he encontrado:
$output = (git clone "$RepositoryUrl" "$localRepoDirectory")
$ output siempre contiene el texto "Cloning into ''[localRepoDirectory]'' ...", ya sea que el comando se haya completado correctamente o haya producido un error. Además, la información de progreso aún se escribe en la consola al hacer esto. Esto me lleva a pensar que la información de progreso no está escrita en stdout, sino en alguna otra transmisión.
Si se produce un error, el error se escribe en la consola, pero en el color de primer plano blanco habitual, no el rojo típico para los errores y el amarillo para las advertencias. Cuando esto se llama desde una función de cmdlet y el comando falla con un error, el error NO se devuelve mediante el parámetro -ErrorVariable (o -WarningVariable) de la función (sin embargo, si hago mi propio error de escritura que se devuelve mediante -ErrorVariable ) Esto me lleva a pensar que git.exe no escribe en stderr, pero cuando lo hacemos:
(git clone "$RepositoryUrl" "$localRepoDirectory") 2> $errorLogFilePath
el mensaje de error se escribe en el archivo, así que eso me hace pensar que escribe en stderr. Así que ahora estoy confundido ...
======== Actualización 2 =======
Entonces, con la ayuda de Byron, intenté un par de soluciones más utilizando un nuevo proceso, pero todavía no puedo obtener lo que quiero. Cuando uso un nuevo proceso, nunca obtengo el buen progreso escrito en la consola.
Los 3 nuevos métodos que he probado ambos usan este bit de código en común:
$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"
Método 1 - Ejecutar en nuevo proceso y leer salida después:
$process.Start()
$process.WaitForExit()
Write-Host Output - $process.StandardOutput.ReadToEnd()
Write-Host Errors - $process.StandardError.ReadToEnd()
Método 2 - Obtenga salida de forma sincronizada:
$process.Start()
while (!$process.HasExited)
{
Write-Host Output - $process.StandardOutput.ReadToEnd()
Write-Host Error Output - $process.StandardError.ReadToEnd()
Start-Sleep -Seconds 1
}
Aunque parece que escribiría el resultado mientras se ejecuta el proceso, no escribe nada hasta que finaliza el proceso.
Método 3: obtener salida de forma asíncrona:
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
}
Esto genera datos de salida mientras el proceso está funcionando, lo que es bueno, pero aún no muestra la información de progreso agradable :(
Puede hacer esto poniendo el comando git clone dentro de una función avanzada, por ejemplo:
function Clone-Git {
[CmdletBinding()]
param($repoUrl, $localRepoDir)
git clone $repoUrl $localRepoDir
}
Clone-Git $RepositoryUrl $localRepoDirectory -ev cloneErrors
$cloneErrors
Si usa System.Diagnostics.Process para iniciar git, puede redirigir todo el error y la salida.
Solo tuve que resolver este problema para 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 1s
}
Por supuesto, puede envolver esto en una función con argumentos apropiados ...
Creo que tengo tu respuesta. Estoy trabajando con Powershell por un tiempo y he creado varios sistemas de compilación. Lo siento si el script es un poco largo, pero funciona.
$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
El archivo de registro debe estar en el ámbito global porque la rutina que ejecuta el procesamiento de eventos no se encuentra en el ámbito del script.
Muestra de mi archivo de registro:
Fuera - Clonando en '''' ... ERROR - Revisando archivos: 22% (666/2971)
ERROR: comprobación de archivos: 23% (684/2971)
ERROR: comprobación de archivos: 24% (714/2971)