We can start a process in PowerShell many different ways. We've got the PowerShell Start-Process and Invoke-Expression cmdlets, we can call the executable directly or use the ampersand (&) to invoke expressions. The most common way is to use Start-Process because it's probably the most intuitive. PowerShell is known for it's intuitive approach to command naming and Start-Process is an excellent choice. However, that command is limited.

If you're a beginner/intermediate PowerShell scripter, be sure to check out my FREE mini-course on Building a PowerShell Tool! 9,000+ words of deep explanation and insights on how to build a PowerShell tool.

To understand why Start-Process and all of these other commands are limited you first have to understand how a typical EXE file works. When an EXE file is run, it performs whatever action it was designed to do; it pings something (ping), starts a software installer (setup), looks up some DNS record (nslookup), whatever. For this part, Start-Process and other commands to start a process work great. It's simple. The limitation comes when that EXE returns some output.

Streams

An EXE file like many other executable files not limited to Windows has a concept of standard streams. Standard streams are how executable files return output. These streams come in three flavors; stdin, stdout, and stderr. Stdin is the stream that can get passed into the executable which we won't be focusing on here. Stdout is the stream that the executable uses to send normal non-error output.

In PowerShell terms, think of stdout as what Write-Output returns. When an error occurs (depending on if the developer of the executable wrote it correctly), the executable should return output via stderr. This is a stream that's set aside for returning any error information.

These streams allow users of these executable files to differentiate between what's typical output and what's an error. Streams have been around for decades and Windows thus PowerShell know all about them and have adopted their own.

Exit Codes

An exit code, return code or result code is another age-old concept that executable files follow as well. An exit code is an integer that allows the executable to signal a status to the user when it exits.

There are some standard exit codes that programs are supposed to follow like if the exit code is 0, everything is fine, exit code 3010 means it needs a reboot and so on. PowerShell can capture this exit code a few different ways like using the $LastExitCode automatic variable. Exit codes are another method that the executable file communicates to the user.

Capturing Streams and Exit Codes

Now that you understand what we're working with let's use an example. To keep it simple, I'll use the good ol' ping.exe. First, I'll ping google.com which will return a successful result. We aren't using PowerShell start process here.

PS C:\> ping google.com
Pinging google.com [2607:f8b0:4004:80b::200e] with 32 bytes of data:

Reply from 2607:f8b0:4004:80b::200e: time=61ms
Reply from 2607:f8b0:4004:80b::200e: time=65ms
Reply from 2607:f8b0:4004:80b::200e: time=80ms
Reply from 2607:f8b0:4004:80b::200e: time=78ms

Ping statistics for 2607:f8b0:4004:80b::200e:

Packets: Sent = 4, Received = 4, Lost = 0 (0% loss),
Approximate round trip times in milli-seconds:
Minimum = 61ms, Maximum = 80ms, Average = 71ms

PS> $LastExitCode
0
PS> $?
True

I'll now purposefully ping a host that doesn't resolve a DNS name and will fail. Notice the value of $LastExitCode.

PS> ping googlllll.com

Ping request could not find host googlllll.com. Please check the name and try again.
PS> $LASTEXITCODE
1

You saw that the exit code differs because the result of ping was different but you didn't see streams because natively PowerShell doesn't understand the difference when it comes to executable files. The text you see in the console is white; not the red error text you know and love.

We can capture the streams and redirect to files a few different ways like using > and 2> like below. This is the old school way.

PS> ping googllll.com > output.msg 2> output.err
PS> Get-Content .\output.err
PS> Get-Content .\output.msg

Ping request could not find host googllll.com. Please check the name and try again.

Notice though that, in this case, even though ping.exe is returning an exit code of 1 (as shown above), that output is still going to stdout. This is common. What stream and exit code that's returned is completely up to the developer and comes in a lot of different styles, unfortunately.

If you'd like to go the "new school" way you could use Start-Process and use the -RedirectStandardError and -RedirectStandardOutput parameters, but they'd still just go to files.

Limitations of Start-Process

You can see that starting a process and returning some common output isn't too common. On top of that, PowerShell doesn't handle the streams and exit codes too well. If I were to use Start-Process to run ping.exe and want to track what happened in the result I'd have to do something like this every time I wanted to run an executable file.

PS> Start-Process -FilePath -NoNewWindow ping -ArgumentList 'google.com' -RedirectStandardOutput output.txt -RedirectStandardError err.txt
PS> $LastExitCode
PS> Get-Content -Path output.txt
PS> Get-Content -Path err.txt

I still wouldn't get my error text either!

Let's fix all of this. Download a small function I created called Invoke-Process from the PowerShell Gallery.

Install-Script 'Invoke-Process' . Invoke-Process.ps1

Now run ping using a valid host and see what happens.

start-process
Invoke-Process -FilePath ping -ArgumentList 'google.com'

Notice how we get the same output. This is good!

Now run ping using an invalid host.

PowerShell error
Invoke-Process -FilePath ping -ArgumentList 'googlddde.com'

Now notice that we get the typical red error text as we'd expect if something went wrong. Invoke-Process uses Start-Process under the covers to capture stdout, stderr and the exit code and then sends the output to whatever stream it's supposed to go to. This is how it should be!

Too bad PowerShell didn't make it easier to work with streams and exit codes from executable files, but I suppose nowadays, we can add that in there now that it's open source!

Join the Jar Tippers on Patreon

It takes a lot of time to write detailed blog posts like this one. In a single-income family, this blog is one way I depend on to keep the lights on. I'd be eternally grateful if you could become a Patreon patron today!

Become a Patron!