PowerShell How-To

Parsing Command-Line Output with PowerShell

Here's a few ways to control legacy command-line utilities in PowerShell.

When writing PowerShell scripts, in a perfect world, we'd all just use cmdlets and functions. After all, the batch file days are behind us! Unfortunately, that's not always possible or even the best option. There are those occasional times when a legacy command-line utility exists that already solves the problem we have and it's a more pragmatic decision to use an existing tool. But, there are some drawbacks; capturing and working with the output is one of them.

We've had a concept called streams for a long time now. I'm talking about stdin, stdout, stderr, and so on. Command-line utilities were built with these streams in mind. PowerShell, on the other hand, understands these streams but has its own as well. When invoking command-line utilities from PowerShell, you'll sometimes run into issues where you expect what's returned from foo.exe to be captured in a variable or to throw and error, but it doesn't.

For example, let's say I'd like to run ping.exe and capture the output to a variable. I can intuitively do something like below which will capture the output that's returned from ping.exe into the variable.

PS> $pingOutput =  ping 4.4.4.4 -n 1
PS> $pingOutput

Pinging 4.4.4.4 with 32 bytes of data:
Request timed out.

Ping statistics for 4.4.4.4:
Packets: Sent = 1, Received = 0, Lost = 1 (100% loss)

Ping.exe works as you'd expect because, in this instance, ping.exe returned everything to stdout allowing me to capture everything into a variable. However, not all command-line utilities work like this, and it's hard to determine the behavior.

Another example is nslookup.exe. When running nslookup.exe from cmd, you'd get output that looks like this:

C:\> nslookup 4.4.4.4
Server:  sjc1pinf99ads03.genomichealth.com
Address:  10.3.40.12

*** sjc1pinf99ads03.genomichealth.com can't find 4.4.4.4: Non-existent domain

This may lead you to assume that entire output is from the same stream. You'd be wrong. In fact, nslookup.exe is returning both a stdout stream and stderr at the same time! Notice below that even though we're sending the output of nslookup to a variable, we still got something returned unlike ping.exe, and we captured something in the variable. This is because this output is stderr and not stdout.

PS> $output =  nslookup 4.4.4.4
*** sjc1pinf99ads03.genomichealth.com can't find 4.4.4.4: Non-existent domain
PS> $output
Server:  sjc1pinf99ads03.genomichealth.com
Address:  10.3.40.12

You'll find different command-line utilities behave differently and you'll have to perform some trial and error to determine when they return various streams. We can solve this by using an older method of redirecting the error stream into the stdout stream by using the > operator. By using the > operator, I can combine the error stream and stdout stream into one and force PowerShell to capture all of it in one shot.

<command>  2>&1
[Click on image for larger view.] Figure 1.

Redirecting this way works but notice what happens when I see what's inside of $output. PowerShell now recognizes this as an error stream and treats part of it like a standard PowerShell error returning the red error text.

Finally, another method to control command-line output is to use the Start-Process command. This PowerShell command allows you to explicitly redirect streams but not in memory. You must redirect to a file instead.

PS> $output = Start-Process -FilePath 'nslookup.exe'  -ArgumentList '4.4.4.4' -NoNewWindow -Wait -RedirectStandardError 'C:\err.txt'
Server:  sjc1pinf99ads03.genomichealth.com
Address:  10.3.40.12
PS> $output
PS> get-content -Path C:\err.txt
*** sjc1pinf99ads03.genomichealth.com can't find 4.4.4.4: Non-existent domain

Notice above that nothing went into $output. Start-Process doesn't return the output like this and has a RedirectStandardOutput parameter as well and unfortunately must be a text file path as well.

PS> Start-Process  -FilePath 'nslookup.exe' -ArgumentList '4.4.4.4' -NoNewWindow -Wait  -RedirectStandardError 'C:\err.txt' -RedirectStandardOutput c:\out.txt
PS> gc C:\out.txt
Server:  sjc1pinf99ads03.genomichealth.com
Address:  10.3.40.12

PS> gc C:\err.txt
*** sjc1pinf99ads03.genomichealth.com can't find 4.4.4.4: Non-existent domain

These were a couple of the major ways to redirect and to work with command-line output in PowerShell. For other ideas, check out more on the > operator.

About the Author

Adam Bertram is a 20-year veteran of IT. He's an automation engineer, blogger, consultant, freelance writer, Pluralsight course author and content marketing advisor to multiple technology companies. Adam also founded the popular TechSnips e-learning platform. He mainly focuses on DevOps, system management and automation technologies, as well as various cloud platforms mostly in the Microsoft space. He is a Microsoft Cloud and Datacenter Management MVP who absorbs knowledge from the IT field and explains it in an easy-to-understand fashion. Catch up on Adam's articles at adamtheautomator.com, connect on LinkedIn or follow him on Twitter at @adbertram or the TechSnips Twitter account @techsnips_io.


comments powered by Disqus
Most   Popular