A Better PowerShell Start Process
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.
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-Proces and other commands to start a process work great. It’s simple. The limitation comes when that EXE returns some output.
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.
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 C:\> $LastExitCode
PS C:\> $?
I’ll now purposefully ping a host that doesn’t resolve a DNS name and will fail. Notice the value of
PS C:\> ping googlllll.com
Ping request could not find host googlllll.com. Please check the name and try again.
PS C:\> $LASTEXITCODE
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
2> like below. This is the old school way.
PS C:\> ping googllll.com > output.msg 2> output.err
PS C:\> Get-Content .\output.err
PS C:\> 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
-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.
Start-Process -FilePath -NoNewWindow ping -ArgumentList 'google.com' -RedirectStandardOutput output.txt -RedirectStandardError err.txt
Get-Content -Path output.txt
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.
Now run ping using a valid host and see what happens.
Notice how we get the same output. This is good!
Now run ping using an invalid host.
Now notice that we get the typical red error text as we’d expect if something went wrong.
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!