Capturing standard out and error with Start-Process

232,690

Solution 1

That's how Start-Process was designed for some reason. Here's a way to get it without sending to file:

$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

Solution 2

In the code given in the question, I think that reading the ExitCode property of the initiation variable should work.

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

Note that (as in your example) you need to add the -PassThru and -Wait parameters (this caught me out for a while).

Solution 3

IMPORTANT:

We have been using the function as provided above by LPG.

However, this contains a bug you might encounter when you start a process that generates a lot of output. Due to this you might end up with a deadlock when using this function. Instead use the adapted version below:

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
  }
}

Further information on this issue can be found at MSDN:

A deadlock condition can result if the parent process calls p.WaitForExit before p.StandardError.ReadToEnd and the child process writes enough text to fill the redirected stream. The parent process would wait indefinitely for the child process to exit. The child process would wait indefinitely for the parent to read from the full StandardError stream.

Solution 4

I also had this issue and ended up using Andy's code to create a function to clean things up when multiple commands need to be run.

It'll return stderr, stdout, and exit codes as objects. One thing to note: the function won't accept .\ in the path; full paths must be used.

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
    }
}

Here's how to use it:

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

Solution 5

I really had troubles with those examples from Andy Arismendi and from LPG. You should always use:

$stdout = $p.StandardOutput.ReadToEnd()

before calling

$p.WaitForExit()

A full example is:

$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
Share:
232,690
jzbruno
Author by

jzbruno

Updated on July 10, 2022

Comments

  • jzbruno
    jzbruno almost 2 years

    Is there a bug in PowerShell's Start-Process command when accessing the StandardError and StandardOutput properties?

    If I run the following I get no output:

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

    But if I redirect the output to a file I get the expected result:

    $process = Start-Process -FilePath ping -ArgumentList localhost -NoNewWindow -PassThru -Wait -RedirectStandardOutput stdout.txt -RedirectStandardError stderr.txt
    
    • mjsr
      mjsr over 12 years
      In this specific case do you really need Start-process?...$process= ping localhost # would save the output in the process variable.
    • jzbruno
      jzbruno over 12 years
      True. I was looking for a cleaner way to handle return and arguments. I ended up writing the script like you showed.
    • scuba88
      scuba88 about 2 years
      @mjsr Any way to get the output and ExitCode doing it without Start-process? I need to know if the command succeeded, but would be nice to pass through the output for error message.
  • jzbruno
    jzbruno over 12 years
    I am accepting your answer. I wish they wouldn't have created properties that aren't used, it is very confusing.
  • Andy Arismendi
    Andy Arismendi over 12 years
    @jzbruno The PowerShell team didn't create the StandardOutput/StandardError properties... They are part of the underlying System.Diagnostics.Process object . However they are only available when the UseShellExecute property is set to false. So it depends on how the PowerShell team implemented Start-Process behind the scenes... Unfortunately I can't look at the source code :-(
  • Ralph Willgoss
    Ralph Willgoss over 11 years
    If you have trouble running a process this way, see accepted answer here stackoverflow.com/questions/11531068/…, which has a slight modification to the WaitForExit and StandardOutput.ReadToEnd
  • Maverick
    Maverick over 11 years
    When u use the -verb runAs it does not allow theh -NoNewWindow or the Redirection Options
  • codepoke
    codepoke about 11 years
    This code will deadlock under some conditions due to both StdErr and StdOut being synchronously read to the end. msdn.microsoft.com/en-us/library/…
  • James Manning
    James Manning almost 11 years
    @codepoke - it's slightly worse than that - since it does the WaitForExit call first, even if it only redirected one of them, it could deadlock if the stream buffer gets filled up (since it doesn't attempt to read from it until the process has exited)
  • Kiquenet
    Kiquenet over 9 years
    I'm pretty sure Windows also won't allow you to redirect standard input/output/error across the admin/non-admin security boundary. You'll have to find a different way to get output from the program running as admin - Reference: stackoverflow.com/a/8690661 Any final solution with full source code sample application ? IMHO, better samples for minimize learning curve are real applications with full source code and good patterns.
  • Lockszmith
    Lockszmith over 8 years
    Good idea, but it seems the syntax isn't working for me. Shouldn't the parameter list use the param( [type]$ArgumentName ) syntax? can you add an example call to this function?
  • O.O
    O.O over 7 years
    What if argumentlist contains a variable? It doesn't seem to expand.
  • JJones
    JJones over 7 years
    you would put the argument list in quotes. Would that work ? ... $process = Start-Process -FilePath ping -ArgumentList " -t localhost -n 1" -NoNewWindow -PassThru -Wait
  • Rosberg Linhares
    Rosberg Linhares over 7 years
    What if I don't want to wait for the process to end?
  • crokusek
    crokusek about 7 years
    I wish stdout and stderr where merged line by line in time as the dos redirect would do. Also echoed live to console would be nice.
  • Manfred
    Manfred about 7 years
    @RosbergLinhares in case you want to see the output before the process exits, this answer may help: stackoverflow.com/a/14061481/411428
  • bergmeister
    bergmeister over 6 years
    This code still deadlocks due to the synchronous call to ReadToEnd(), which your link to MSDN describes as well.
  • CJBS
    CJBS over 6 years
    Where did you read that "You should always use: $p.StandardOutput.ReadToEnd() before $p.WaitForExit()"? If there's output on the buffer that is exhausted, following more output at a later time, that will be missed if the line of execution is on WaitForExit and the process hasn't finished (and subsequently outputs more stderr or stdout)....
  • CJBS
    CJBS over 6 years
    Regarding my comment above, I later saw the comments on the accepted answer regarding deadlocking and buffer overflow in cases of large output, but that aside, I would expect that just because the buffer is read to the end, it doesn't mean the process has completed, and there could thus be more output that's missed. Am I missing something?
  • rhellem
    rhellem over 6 years
    This now seems to have solved my issue. I must admit that I do not fully understand why it did hang, but it seems that empty stderr blocked the process to finish. Strange thing, since it did work for a long period of time, but suddenly right before Xmas it started failing, causing a lot of Java-processes to hang.
  • dornadigital
    dornadigital over 6 years
    Any way to set this object up and still redirect the StandardError to a file? I'm interested in how you're accessing both the ExitCode and StandardError.
  • Murali Dhar Darshan
    Murali Dhar Darshan over 4 years
    how to show the output in powershell window as well as log it to a log file? Is it possible?
  • Dragas
    Dragas about 4 years
    Cannot use -NoNewWindow with -Verb runAs
  • Eboubaker
    Eboubaker about 4 years
    how i can run the targeted executable as adminstrator like what start-process let's you do with runAs
  • Andy Arismendi
    Andy Arismendi about 4 years
    @ZOLDIK use $pinfo.Verb = "runas"
  • Eboubaker
    Eboubaker about 4 years
    @AndyArismendi okay, using $pinfo.UseShellExecute = $true , makes it work but i can't redirect the output of the process (i have to remove the redirect part of the code)
  • Peter Duniho
    Peter Duniho almost 4 years
    @CJBS: "just because the buffer is read to the end, it doesn't mean the process has completed" -- it does mean that. In fact, that's why it can deadlock. Reading "to the end" doesn't mean "read whatever's there now". It means start reading, and don't stop until the stream is closed, which is the same as the process terminating.
  • Lupuz
    Lupuz almost 4 years
    Regarding "One thing to note: the function won't accept .\ in the path; full paths must be used.": You could use: > $pinfo.FileName = Resolve-Path $commandPath
  • rel.foo.fighters
    rel.foo.fighters almost 3 years
    If I still wants to write the output to a file, what is the correct syntax?
  • Sergio Cabral
    Sergio Cabral over 2 years
    -NoNewWindow !!! nice !! thanks
  • ashrasmun
    ashrasmun over 2 years
    Whenever I add -NoNewWindow I get a "CategoryInfo : InvalidArgument: (:) [Start-Process], ParameterBindingException FullyQualifiedErrorId : AmbiguousParameterSet,Microsoft.PowerShell.Commands.StartPro‌​cessCommand" error. Without it the underlying powershell script works.
  • STLDev
    STLDev over 2 years
    This script works, except that output generated by the external process can be displayed in an out-of-order fashion. I built a small console app that simply echos out the parameters passed into it, in the order they appear on its command-line. When I execute it via this script, the output generated is displayed out of order.
  • Paul Williams
    Paul Williams almost 2 years
    @STLDev - you're correct. Please see my updated answer. In our application we were only using it for visual monitoring as the job ran, not capturing output, so it tolerable.
  • Mike
    Mike almost 2 years
    yet another bookmark in my "why windows sucks" folder