Learn about Windows PowerShell
Summary: Microsoft Scripting Guy, Ed Wilson, talks about handling output to the Windows PowerShell console while using the pipeline.
Microsoft Scripting Guy, Ed Wilson, is here. The Scripting Wife and I are enjoying Virginia Beach. We have met some really cool and smart people here at the Mark Minasi Conference. We had a chance to go to the huge naval museum here. It was cool. In the museum, they have different types of exhibits. Some of them have a button that you push, and you listen to it, then you go to the next exhibit. Others have drawings and diagrams that you read, and then you go to the next exhibit. Still others have brochures that you can skim and take with you when you leave the museum.
In a way, Windows PowerShell output behaves the save way. Some things we write to the Windows PowerShell console, others we write to files. Some things display to the console and write to files, and still others appear and then they are gone, never to return. It is the ease-of-use plus the diversity of options for creating output from inside Windows PowerShell that is a source of confusion for beginning (and even for some advanced) Windows PowerShell scripters. Simply put…
Like the child who comes home from the grocery store with a loaf of bread and 12 candy bars, we often do not make the best choices. Just like the child with the dozen candy bars and a loaf of bread, some of the options seem too compelling. We get over-awed with the choices—in short, we zone out into a sort of geek trance. In the following command, I use the Get-Command cmdlet, the Sort-Object cmdlet, the Select-Object cmdlet, and the Format-Wide cmdlet to produce a list of the more common output and formatting cmdlets. Twenty cmdlets appear on this list (Measure-Command tells me that). The dizzying array of choices are shown here.
PS C:\> gcm -Verb write, out, tee, format | sort verb | select name | fw -a
Format-Table Format-Wide Format-Custom Format-List Out-Null
Out-Printer Out-String Out-Host Out-Default Out-File
Out-GridView Tee-Object Write-Progress Write-Output Write-Warning
Write-Verbose Write-Error Write-Debug Write-Host Write-EventLog
The easiest way to write output is not to use any of the twenty cmdlets listed previously. Rather, it is to place the output into a string. There are two types of strings in Windows PowerShell, the expanding string and the literal string. The difference is twofold; an expanding string uses double quotation marks, and a literal string uses single quotation marks. The practical application of these different types of strings is also twofold. A literal string means that what you type is what you get. This is, in fact, the way that strings worked in other languages (for example in VBScript). The difference, with VBScript anyway, is that the string indicator was the double quotation mark.
The following example illustrates how a literal string works. It uses single quotation marks and contains a variable. When the string displays to the Windows PowerShell console, the variable appears exactly as typed. That is, the variable does not expand to display the value that the variable contains.
In the following example, in the first line, a string assigns to the variable $a. The double quotation marks do this, but you could just as easily (and with no difference in the outcome of the code) use single quotation marks for this string. This is because the string contains no variables to expand. In the second line of code, a pair of single quotation marks delineates the string. This time, the string contains two variables. The output displays to the Windows PowerShell concole, but the values contained inside the $a variables do not unravel. Instead, only the literal $a appears on the outputted line.
PS C:\> $a = "this is a string contained in a variable"
PS C:\> 'The value of the $a variable is actually $a'
The value of the $a variable is actually $a
If you replace the single quotation marks in the second line of code with double quotation marks, the difference is dramatic. For each of the two locations where the $a variable appears in the code, the value contained in the variable replaces the literal variable name. The resulting output is not very useful, but it is different.
PS C:\> "The value of the $a variable is actually $a"
The value of the this is a string contained in a variable variable is actually this
is a string contained in a variable
What you might want to do is to use the expanding or unraveling feature of the expanding string for the second appearance of the $a variable, but suppress it for the first appearance. To suppress the expansion of the first variable, use the grave accent (also called back-tick) character (`) immediately prior to the first appearance of the $a variable. This is shown here.
PS C:\> "The value of the `$a variable is actually $a"
The value of the $a variable is actually this is a string contained in a variable
These examples of using literal and expanding strings are shown with their associated output in the image that follows.
Most of the time, beginners who are learning Windows PowerShell use the Write-Host cmdlet to display output to the Windows PowerShell console. One reason for doing this is that people coming from a VBScript background (where they used “Wscript.Echo” to write output) feel that they need to do something similar to this. They find Write-Host, and they see it as the direct replacement. Next, they proceed with a line-by-line adaptation of their previous VBScript to the new Windows PowerShell script. Unfortunately, this does not take advantage of an inherent Windows PowerShell strength —it works with objects, not strings.
There are occasions when using Write-Host is the right thing to do. For example, if you are at the end of a command and you want to take advantage of the Foreground or the Background color abilities of the Write-Host cmdlet, the command makes sense. On the other hand, if you are not using the color capabilities of the Write-Host cmdlet, it makes sense to use the Write-Output cmdlet instead. The reason is that Write-Host is a terminal cmdlet—that is, after you use it, you can do nothing else. If you try to pipe the output from the Write-Host cmdlet to a text file, no error generates, but the text file is empty. This is because no objects return from the Write-Host cmdlet. The commands are shown here.
The Out-File cmdlet creates the writehost.txt file, but because Write-Host passes no objects along the pipeline, the text file contains no text. The empty file is shown here.
You do not need to write complicated duplicate code to write data to the Windows PowerShell console and to write to a file. In fact, you do not need to store the data in an intermediate variable—it can be done with a single command. The cmdlet to use is Tee-Object. The Tee-Object cmdlet accepts a single input and then splits the output to multiple locations. It can display data to the Windows PowerShell console and write it to a file. It can also display to the console and write back to a variable. The basic syntax is shown here.
"output from the Tee-Object" | Tee-Object -FilePath c:\fso\tee.txt
The command and its associated output are shown in the following image.
A quick check of the text file reveals that the text file does indeed contain the text output to the console. The Tee.txt file is shown here.
You are not limited to only sending the output to the Windows PowerShell console and a file, or to a variable and the console. This is because the cmdlet splits the output. You can use a variable to hold part of the output, and you can still send it to a text file. When you have the output in a variable, you can easily display the contents of the variable to the console. This is shown here.
PS C:\> $t = "output from the Tee-Object. Test 2" | Tee-Object -FilePath c:\fso\tee2.
PS C:\> $t
output from the Tee-Object. Test 2
That is about all there is for now. I invite you to follow me on Twitter and Facebook. If you have any questions, send email to me at firstname.lastname@example.org, or post your questions on the Official Scripting Guys Forum. See you tomorrow. Until then, peace.
Ed Wilson, Microsoft Scripting Guy
One of the things I have struggled with my scripts is capturing a verbose output.
copy-item c:\testing\test.pdf C:\testing2 -verbose
The verbose comes out to the screen beautifully but I can't seem to figure out how to capture it to a file. I would like to capture to a file so that I can make sure the script was doing what I was thinking it was doing.
@Neil, you will not be able to get the output from the verbose stream to a file using Out-File, etc. One temporary solution I can think about is to Start-Transcript, run your Copy-Item command, and then Stop-Transcript, and the transcript file created should have the output from the Copy-Item command.
I'd also be interested in knowing if there are any other ways in which this can be achieved.