programing

시작 프로세스에서 표준 출력 및 오류 캡처

fastcode 2023. 4. 10. 22:18
반응형

시작 프로세스에서 표준 출력 및 오류 캡처

PowerShell에 버그가 있습니까?Start-Process액세스 시 명령어StandardError그리고.StandardOutput프로퍼티?

다음을 실행하면 출력이 표시되지 않습니다.

$process = Start-Process -FilePath ping -ArgumentList localhost -NoNewWindow -PassThru -Wait
$process.StandardOutput
$process.StandardError

그러나 출력을 파일로 리디렉션하면 예상된 결과를 얻을 수 있습니다.

$process = Start-Process -FilePath ping -ArgumentList localhost -NoNewWindow -PassThru -Wait -RedirectStandardOutput stdout.txt -RedirectStandardError stderr.txt

어떤 이유에선지 그렇게 설계되었죠파일로 전송하지 않고 얻을 수 있는 방법은 다음과 같습니다.

$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

질문의 코드는, 개시 변수의 ExitCode 속성을 읽어낼 수 있다고 생각합니다.

$process = Start-Process -FilePath ping -ArgumentList localhost -NoNewWindow -PassThru -Wait
$process.ExitCode

(예시와 같이) 를 추가할 필요가 있습니다.-PassThru그리고.-Wait파라미터(이 때문에 잠시 당황했습니다)

중요:

우리는 LPG에서 제공하는 기능을 사용해 왔습니다.

그러나 여기에는 많은 출력을 생성하는 프로세스를 시작할 때 발생할 수 있는 버그가 포함되어 있습니다.이로 인해 이 기능을 사용할 때 교착 상태가 발생할 수 있습니다.대신 다음과 같은 적응 버전을 사용합니다.

Function Execute-Command ($commandTitle, $commandPath, $commandArguments)
{
  Try {
    $pinfo = New-Object System.Diagnostics.ProcessStartInfo
    $pinfo.FileName = $commandPath
    $pinfo.RedirectStandardError = $true
    $pinfo.RedirectStandardOutput = $true
    $pinfo.UseShellExecute = $false
    $pinfo.Arguments = $commandArguments
    $p = New-Object System.Diagnostics.Process
    $p.StartInfo = $pinfo
    $p.Start() | Out-Null
    [pscustomobject]@{
        commandTitle = $commandTitle
        stdout = $p.StandardOutput.ReadToEnd()
        stderr = $p.StandardError.ReadToEnd()
        ExitCode = $p.ExitCode
    }
    $p.WaitForExit()
  }
  Catch {
     exit
  }
}

이 문제에 대한 자세한 내용은 MSDN에서 확인할 수 있습니다.

부모 프로세스가 p를 호출하면 교착 상태가 발생할 수 있습니다.p 전에 Wait For Exit.Standard Error(표준 에러)ReadToEnd 및 자식 프로세스는 리디렉션된 스트림을 채우기에 충분한 텍스트를 씁니다.부모 프로세스는 자식 프로세스가 종료될 때까지 무기한 대기합니다.하위 프로세스는 부모가 StandardError 스트림 전체를 읽을 때까지 무기한 대기합니다.

저도 이런 문제가 있어서 Andy의 코드를 사용하여 여러 명령어를 실행해야 할 때 정리하는 기능을 만들었습니다.

stderr, stdout 및 exit 코드를 객체로 반환합니다.주의사항: 함수가 받아들이지 않음.\전체 경로를 사용해야 합니다.

Function Execute-Command ($commandTitle, $commandPath, $commandArguments)
{
    $pinfo = New-Object System.Diagnostics.ProcessStartInfo
    $pinfo.FileName = $commandPath
    $pinfo.RedirectStandardError = $true
    $pinfo.RedirectStandardOutput = $true
    $pinfo.UseShellExecute = $false
    $pinfo.Arguments = $commandArguments
    $p = New-Object System.Diagnostics.Process
    $p.StartInfo = $pinfo
    $p.Start() | Out-Null
    $p.WaitForExit()
    [pscustomobject]@{
        commandTitle = $commandTitle
        stdout = $p.StandardOutput.ReadToEnd()
        stderr = $p.StandardError.ReadToEnd()
        ExitCode = $p.ExitCode
    }
}

사용 방법은 다음과 같습니다.

$DisableACMonitorTimeOut = Execute-Command -commandTitle "Disable Monitor Timeout" -commandPath "C:\Windows\System32\powercfg.exe" -commandArguments " -x monitor-timeout-ac 0"

Andy ArismendiLPG에서 나온예들로 인해 나는 정말로 어려움을 겪었다.항상 다음을 사용해야 합니다.

$stdout = $p.StandardOutput.ReadToEnd()

부르기 전에

$p.WaitForExit()

완전한 예는 다음과 같습니다.

$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
$stdout = $p.StandardOutput.ReadToEnd()
$stderr = $p.StandardError.ReadToEnd()
$p.WaitForExit()
Write-Host "stdout: $stdout"
Write-Host "stderr: $stderr"
Write-Host "exit code: " + $p.ExitCode

다음은 다른 powershell 프로세스(시리얼화)에서 출력을 얻기 위한 복잡한 방법입니다.

start-process -wait -nonewwindow powershell 'ps | Export-Clixml out.xml'
import-clixml out.xml

강조하겠습니다.-nonewwindow표준 출력 및 표준 오류를 표시하려면 적어도 로컬 화면에서 다음을 수행합니다.

start-process -wait cmd '/c dir' -nonewwindow

 Volume in drive C is Windows
 Volume Serial Number is 2AC6-626F

 Directory of C:\users\me\foo

11/24/2022  11:40 AM    <DIR>          .
11/24/2022  11:40 AM    <DIR>          ..
11/24/2022  11:40 AM               330 file.json
               1 File(s)            330 bytes
               2 Dir(s)  25,042,915,328 bytes free
start-process -wait cmd '/c dir foo' -nonewwindow

 Volume in drive C is Windows
 Volume Serial Number is 2AC6-626F

 Directory of C:\users\me\foo

File Not Found

여기 다른 사람들이 이 스레드에 올린 예를 바탕으로 제가 만든 것이 있습니다.이 버전에서는 콘솔 창이 숨겨지고 출력 표시 옵션이 제공됩니다.

function Invoke-Process {
    [CmdletBinding(SupportsShouldProcess)]
    param
        (
        [Parameter(Mandatory)]
        [ValidateNotNullOrEmpty()]
        [string]$FilePath,

        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [string]$ArgumentList,

        [ValidateSet("Full","StdOut","StdErr","ExitCode","None")]
        [string]$DisplayLevel
        )

    $ErrorActionPreference = 'Stop'

    try {
        $pinfo = New-Object System.Diagnostics.ProcessStartInfo
        $pinfo.FileName = $FilePath
        $pinfo.RedirectStandardError = $true
        $pinfo.RedirectStandardOutput = $true
        $pinfo.UseShellExecute = $false
        $pinfo.WindowStyle = 'Hidden'
        $pinfo.CreateNoWindow = $true
        $pinfo.Arguments = $ArgumentList
        $p = New-Object System.Diagnostics.Process
        $p.StartInfo = $pinfo
        $p.Start() | Out-Null
        $result = [pscustomobject]@{
        Title = ($MyInvocation.MyCommand).Name
        Command = $FilePath
        Arguments = $ArgumentList
        StdOut = $p.StandardOutput.ReadToEnd()
        StdErr = $p.StandardError.ReadToEnd()
        ExitCode = $p.ExitCode
        }
        $p.WaitForExit()

        if (-not([string]::IsNullOrEmpty($DisplayLevel))) {
            switch($DisplayLevel) {
                "Full" { return $result; break }
                "StdOut" { return $result.StdOut; break }
                "StdErr" { return $result.StdErr; break }
                "ExitCode" { return $result.ExitCode; break }
                }
            }
        }
    catch {
        exit
        }
}

예:Invoke-Process -FilePath "FQPN" -ArgumentList "ARGS" -DisplayLevel Full

stdout과 stderr를 모두 얻으려면 다음을 사용합니다.

Function GetProgramOutput([string]$exe, [string]$arguments)
{
    $process = New-Object -TypeName System.Diagnostics.Process
    $process.StartInfo.FileName = $exe
    $process.StartInfo.Arguments = $arguments

    $process.StartInfo.UseShellExecute = $false
    $process.StartInfo.RedirectStandardOutput = $true
    $process.StartInfo.RedirectStandardError = $true
    $process.Start()

    $output = $process.StandardOutput.ReadToEnd()   
    $err = $process.StandardError.ReadToEnd()

    $process.WaitForExit()

    $output
    $err
}

$exe = "cmd"
$arguments = '/c echo hello 1>&2'   #this writes 'hello' to stderr

$runResult = (GetProgramOutput $exe $arguments)
$stdout = $runResult[-2]
$stderr = $runResult[-1]

[System.Console]::WriteLine("Standard out: " + $stdout)
[System.Console]::WriteLine("Standard error: " + $stderr)

답변 개선 - 고객님이 괜찮으시다면Start-JobStart-Process

와 는, STDOUT 「STDERR」라고 하는 되어 있는 되었습니다.$job.ChildJobs[0].Output ★★★★★★★★★★★★★★★★★」$job.ChildJobs[0].Error스크립트가 실행될 때.따라서 이러한 값을 폴링하여 정기적으로 작성할 수 있습니다.★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★

그러나 스트림이 아니므로 수동으로 시작 인덱스를 배열에 추적해야 합니다.

, 를 STDOUT로 표시합니다.$job.ChildJobs[0].Output그리고 이 데모에서는 약간의 보너스로 호출 스크립트는 PS7, 백그라운드 작업은 PS5입니다.

$scriptBlock = {
  Param ([int]$param1, [int]$param2)
  $PSVersionTable
  Start-Sleep -Seconds 1
  $param1 + $param2
}

$parameters = @{
  ScriptBlock = $scriptBlock
  ArgumentList = 1, 2
  PSVersion = 5.1 # <-- remove this line for PS7
}

$timeoutSec = 5
$job = Start-Job @parameters
$job.ChildJobs[0].Output
$index = $job.ChildJobs[0].Output.Count

while ($job.JobStateInfo.State -eq [System.Management.Automation.JobState]::Running) {
  Start-Sleep -Milliseconds 200
  $job.ChildJobs[0].Output[$index]
  $index = $job.ChildJobs[0].Output.Count
  if (([DateTime]::Now - $job.PSBeginTime).TotalSeconds -gt $timeoutSec) {
    throw "Job timed out."
  }
}

지적하신 바와 같이 저의 원래 답변은 출력을 인터리브할 수 있습니다.이는 PowerShell에서의 이벤트 처리 제한 사항입니다.그것은 고칠 수 있는 문제가 아니다.

원래 답변은 사용하지 마십시오.관심을 위해 여기에 남겨둡니다.

, 「 」ReadToEnd()선택사항이 아닙니다.고급 루프를 수행할 수 있지만 IMO를 사용하는 가장 깨끗한 방법은 스트림을 무시하는 것입니다.OutputDataReceived/ErrorDataReceived★★★★★★★★★★★★★★★★★★★★★★★★★★★★★이 접근방식을 통해 다른 사용자가 언급한 스레드화 문제도 회피할 수 있습니다.

Powershell을 사용하다히,는add_OutputDataReceived 적어도 PowerShell 51에서는 인 것 .PowerShell 5.1을 참조하십시오.)를 해결하려면 을 합니다.Register-ObjectEvent.

$stdout = New-Object System.Text.StringBuilder
$stderr = New-Object System.Text.StringBuilder

$proc = [System.Diagnostics.Process]@{
  StartInfo = @{
    FileName = 'ping.exe'
    Arguments = 'google.com'
    RedirectStandardOutput = $true
    RedirectStandardError = $true
    UseShellExecute = $false
    WorkingDirectory = $PSScriptRoot
  }
}

$stdoutEvent = Register-ObjectEvent $proc -EventName OutputDataReceived -MessageData $stdout -Action {
  $Event.MessageData.AppendLine($Event.SourceEventArgs.Data)
}

$stderrEvent = Register-ObjectEvent $proc -EventName ErrorDataReceived -MessageData $stderr -Action {
  $Event.MessageData.AppendLine($Event.SourceEventArgs.Data)
}

$proc.Start() | Out-Null
$proc.BeginOutputReadLine()
$proc.BeginErrorReadLine()
Wait-Process -Id $proc.Id -TimeoutSec 5

if ($proc.HasExited) {
  $exitCode = $proc.ExitCode
}
else {
  Stop-Process -Force -Id $proc.Id
  $exitCode = -1
}

# Be sure to unregister.  You have been warned.
Unregister-Event $stdoutEvent.Id
Unregister-Event $stderrEvent.Id
Write-Output $stdout.ToString()
Write-Output $stderr.ToString()
Write-Output "Exit code: $exitCode"
  • 표시된 코드는 해피 패스입니다(stderr은 비어 있습니다).
  • 패스를 하려면 , 「」를 합니다.-TimeoutSec로로 합니다..5
  • path를하려면 sad path(stderr)를 합니다.FileName로로 합니다.'cmd' ★★★★★★★★★★★★★★★★★」Arguments로로 합니다./C asdf

표준 시스템을 반환하는 내 버전의 기능은 다음과 같습니다.진단.3가지 새로운 속성으로 처리

Function Execute-Command ($commandTitle, $commandPath, $commandArguments)
{
    Try {
        $pinfo = New-Object System.Diagnostics.ProcessStartInfo
        $pinfo.FileName = $commandPath
        $pinfo.RedirectStandardError = $true
        $pinfo.RedirectStandardOutput = $true
        $pinfo.UseShellExecute = $false
        $pinfo.WindowStyle = 'Hidden'
        $pinfo.CreateNoWindow = $True
        $pinfo.Arguments = $commandArguments
        $p = New-Object System.Diagnostics.Process
        $p.StartInfo = $pinfo
        $p.Start() | Out-Null
        $stdout = $p.StandardOutput.ReadToEnd()
        $stderr = $p.StandardError.ReadToEnd()
        $p.WaitForExit()
        $p | Add-Member "commandTitle" $commandTitle
        $p | Add-Member "stdout" $stdout
        $p | Add-Member "stderr" $stderr
    }
    Catch {
    }
    $p
}

& &quot;&quot;와 하는 것도 .--%시작 프로세스 대신 - 명령 및/또는 오류 출력을 쉽게 파이핑 및 처리할 수 있습니다.

  • 이스케이프 파라미터를 변수에 입력하다
  • 의론을 변수로 하다
$deploy= "C:\Program Files\IIS\Microsoft Web Deploy V3\msdeploy.exe"
$esc = '--%'
$arguments ="-source:package='c:\temp\pkg.zip' -verb:sync"
$output = & $deploy $esc $arguments 

그러면 매개 변수가 간섭 없이 실행 파일에 전달되고 시작 프로세스의 문제를 해결할 수 있습니다.

Stderr와 Stdout을 하나의 변수로 결합합니다.

$output = & $deploy $esc $arguments 2>&1

Stderr 및 Stdout에 대한 개별 변수 가져오기

$err = $( $output = & $deploy $esc $arguments) 2>&1

언급URL : https://stackoverflow.com/questions/8761888/capturing-standard-out-and-error-with-start-process

반응형