Summary: June Blender explains how to understand and use streams in Windows PowerShell.
Microsoft Scripting Guy, Ed Wilson, is here. Today guest blogger, June Blender, explains how to understand and use streams in Windows PowerShell. To read more from June, see these Hey, Scripting Guy! Blog posts.
Note Since the writing of this post, some of the information has changed with the introduction of the information stream in Windows PowerShell 5.0. Read about it here: Welcome to the Powershell Information Stream.
And now, here's June…
My first introduction to streams in computing was a really positive one. A friend emailed me a short video of my son and his friends helping clear debris from a nearby stream after a flood. I clicked the attachment in the email, and I got the most insightful error I've ever seen:
Error: Cannot render the stream
"Wow," I thought. "That is a really sophisticated error message. I wonder if it can render the trees or just my adorable son."
It turns out that the error referred to a video stream, not the watery one near the school. But that experience prepared me to learn about streams in Windows PowerShell.
Windows PowerShell has multiple message streams. The engine sends different types of messages down each stream. As shown in the following image, the streams are parallel, so they don't interfere with each other.
Streams are critical in a language that uses piping. When you pipe objects from one cmdlet to another, you don't want the receiving cmdlet to receive errors, warnings, debugging messages, or verbose messages along with the objects that it's designed to process.
So, the pipe operator (|) actually pipes objects down the output stream (stream #1). The receiving cmdlet doesn't see errors, warnings, verbose messages, or debugging messages, because they're sent in other streams.
Windows PowerShell also uses the output stream for assignments, like assigning (or saving) a value in a variable. This explains why you can save an output message in a variable, for example:
PS C:\> $message = Write-Output -Message "Output message"
PS C:\> $message
Output message
But you can't save a verbose message in a variable:
PS C:\> $message = Write-Verbose -Message "Verbose message" -Verbose
VERBOSE: Verbose message
PS C:\> $message
PS C:\>
To save a verbose message in a variable, you need to redirect the message from the verbose stream (stream #4) to the output stream (stream #1). To do this, you use the "from-4-to-1" (4>&1) redirection operator, as shown here:
PS C:\ps-test> $message = Write-Verbose -Message "Verbose message" -Verbose 4>&1
PS C:\ps-test> $message
VERBOSE: Verbose message
If you haven't yet memorized the redirection operators, you can find them in the about_Redirection Help topic.
I was reminded of this lesson when discussing the infamous Write-Host cmdlet with some colleagues. We all know that you're not supposed to use Write-Host. Windows PowerShell MVP, Don Jones, says that Write-Host kills puppies. Windows PowerShell inventor, Jeffrey Snover, says that Write-Host is harmful. But they both agree that it's fine to use it under very limited conditions.
One of those conditions is when you want to talk to your user, but you don't want the output to interfere with or "pollute" your object stream. If you use the Write-Output cmdlet, the output messages are sent down the output stream (stream #1) along with the objects that you're piping to the next cmdlet. The receiving cmdlet better know how to handle the message strings and how to distinguish them from other strings that you pipe to it.
Write-Host does not pollute the output stream. But which stream does Write-Host use?
To find out, I wrote a little function that writes a host message and a message to each stream:
function Write-Messages
{
Write-Host "Host message"
Write-Output "Output message"
Write-Verbose "Verbose message"
Write-Warning "Warning message"
Write-Error "Error message"
Write-Debug "Debug message"
}
When I used the standard redirection operator to write the messages to a file, all of the messages were display on the console, except for the output message, which was written to the output file.
PS C:\> Write-Messages > OutputFile.txt
Host message
Write-Messages : Error message
At line:1 char:1
+ Write-Messages > OutputFile.txt
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [Write-Error], WriteErrorException
+ FullyQualifiedErrorId : Microsoft.PowerShell.Commands.WriteErrorException,Write-Messages
WARNING: Warning message
VERBOSE: Verbose message
DEBUG: Debug message
PS C:\> Get-Content OutputFile.txt
Output message
Then, I ran the same function, but I used the redirection operation that directs all streams to a file (*>). This time, only the host message appeared on the console. All of the other messages (those in message streams) were written to the output file.
PS C:\ps-test> Write-Messages *> .\OutputFile.txt
Host message
PS C:\ps-test> Get-Content OutputFile.txt
Output message
Write-Messages : Error message
At line:1 char:1
+ Write-Messages *> .\OutputFile.txt
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [Write-Error], WriteErrorException
+ FullyQualifiedErrorId : Microsoft.PowerShell.Commands.WriteErrorException,Write-Messages
Warning message
Verbose message
Debug message
This happens because Write-Host is not written to any stream. It's sent to the host program, and the host program decides what to do with it. The Windows PowerShell console and Windows PowerShell ISE display host messages on the console. But other host programs might do something entirely different. The behavior can vary with the program that is hosting Windows PowerShell.
You cannot redirect a Write-Host message or assign it to a variable, even if you're very clever with redirection operators.
So, how do you talk to the user in a way that doesn't pollute the output stream but can be run in background without interaction?
The best way to talk to the user is by using Write-Verbose. Verbose messages go to the verbose stream (stream #4), but the user can redirect them. Of course, users see verbose messages only if they run your script with the Verbose common parameter or when they change VerbosePreference to Continue. But that's a much better alternative than writing to the output stream (which can mess up your piping), and it is better than writing messages to the host program (which can't be suppressed or redirected).
My first impression of streams was that they're very useful and sophisticated. That's my current impression, too!
—————
function Write-Messages
{
[CmdletBinding()]
param()
Write-Host "Host message"
Write-Output "Output message"
Write-Verbose "Verbose message"
Write-Warning "Warning message"
Write-Error "Error message"
Write-Debug "Debug message"
}
# Writes all messages to console.
Write-Messages -Verbose -Debug
# Writes output to the file
# Writes all other messages to console.
Write-Messages -Verbose -Debug > .\OutputFile.txt
# Writes all output except Host messages to file
Write-Messages -Verbose -Debug *> .\OutputFile.txt
# Writes all output (except Host messages) to output stream, then saves them in a file.
Write-Messages -Verbose -Debug *>&1 | Out-File -FilePath .\OutputFile.txt
# Writes all messages to console and then saves all but host messages in a file.
Write-Messages -Verbose -Debug *>&1 | Tee-Object -FilePath .\OutputFile.txt
~June
Thanks, June!
I invite you to follow me on Twitter and Facebook. If you have any questions, send email to me at scripter@microsoft.com, or post your questions on the Official Scripting Guys Forum. See you tomorrow. Until then, peace.
Ed Wilson, Microsoft Scripting Guy
0 comments