Which Write Is Right For PowerShell?

powershell-hero-16-9-ratio
If there is one cmdlet that confuses PowerShell beginners more than anything, it is Write-Host. Newcomers see commands like Write-Output, Write-Host, and if running PowerShell 5.0 Write-Information. Determining which one to use can be a bit overwhelming. So let me make it simple: For now you can ignore Write-Information. If you have a bit more experience, you can find an intro article here.

Instead let’s focus on Write-Output, which has aliases of write and echo and Write-Host: What’s the difference? To answer this question we first need to take a step back and look at how PowerShell works. Think of PowerShell as an engine that you can install into different vehicles. These vehicles are referred to as the hosting applications. Out of the box Microsoft lets you run PowerShell inside the console application (cmd.exe) and the PowerShell ISE. But other applications can also host PowerShell. A number of years ago tools like PowerGUI and PowerShell Plus were popular and also hosted the PowerShell engine. Commercial products like PowerShell Studio from SAPIEN Technologies also host PowerShell. And while there are guidelines for how applications should host PowerShell, there may be subtle differences in implementation that might mean a different experience for you.
Ultimately, the choice of writing option comes down to your choice of destination. In other words, where is the cmdlet writing to? You have two choices: the pipeline or the host. There are perfectly valid reasons for writing to either destination, but you need to understand why. Here’s an example of PowerShell function written by someone just getting started. This is not someone’s actual script but something I wrote based on things I’ve seen.

Function Get-StoppedService {
Param([string]$Computername = $env:computername)
$s = Get-Service -ComputerName $Computername
$stopped = $s | where {$_.Status -eq 'Stopped'}
foreach ($service in $stopped) {
    Write-Host $service
 }
}

The user runs the function, and as far as they know, it works and meets their needs.

Using Write-Host
Using Write-Host (Image Credit: Jeff Hicks)

But does it really? Later they try to use the function in a command like this:

get-stoppedservice -Computername chi-p50 | out-file c:\work\services.txt

But when they look at the file, nothing is in it. This happened because Write-Host wrote to the hosting application, in this case the console. If it helps, think of this as writing to the screen. But nothing was written to the PowerShell pipeline. If that happens, then there’s nothing for subsequent commands, like Out-File to do. The recommended method is to use Write-Output.

Function Get-StoppedService {
Param([string]$Computername = $env:computername)
$s = Get-Service -ComputerName $Computername
$stopped = $s | where {$_.Status -eq 'Stopped'}
foreach ($service in $stopped) {
    Write-Output $service
 }
}

The difference is dramatic.

Using Write-Output
Using Write-Output (Image Credit: Jeff Hicks)

Instead of writing some piece of text to the screen, we are now writing an object to the pipeline. PowerShell’s formatting system handles presenting the object to the screen so that you can read it. And now the Out-File command will also work because objects are being written to the pipeline.

Before we get too far, let me point out that this iteration also indicates “text” thinking. You don’t need to manually enumerate and write each result to the pipeline like you might have done in VBScript. Instead let PowerShell handle it.

Function Get-StoppedService {
Param([string]$Computername = $env:computername)
$s = Get-Service -ComputerName $Computername
$stopped = $s | where {$_.Status -eq 'Stopped'}
Write-Output $Stopped
}

Personally, I don’t think you even need to specify Write-Output. $Stopped would have sufficed because, by default, PowerShell is going to write the variable to the pipeline.
The takeaway should be to always write to the pipeline. Write-Host does you no good and, according to some in the PowerShell community, is a great sin.
So does that mean you should never use Write-Host? Of course not. Write-Host has some nifty features such as the ability to display text in different colors for the foreground and background. You might use this to provide informational messages or feedback that are not part of the expected pipelined output. Remember that whatever you display with Write-Host must be a string.
Here’s a revised version of the simple function:

Function Get-StoppedService {
Param([string]$Computername = $env:computername)
Write-Host "Getting services from $computername" -ForegroundColor Cyan
$s = Get-Service -ComputerName $Computername
Write-Host "Found a total of $($s.count) services" -ForegroundColor Green
$stopped = $s | where {$_.Status -eq 'Stopped'}
Write-Host "Found a total of $($stopped.count) stopped services" -ForegroundColor Green
$Stopped
}

The Write-Host messages will be interspersed with the function output. Or if you save the pipelined results to a variable, you can still see the Write-Host messages.

Integrating Write-Host
Integrating Write-Host (Image Credit: Jeff Hicks)

Remember that there is no way to save any of the Write-Host output, unless you are using a PowerShell transcript. And of course because that is a text file, there won’t be any color. So you may think, “Cool. I’ll use colors with Write-Host and not worry about it.”
As the name suggests, Write-Host is writing to the hosting application. In the case of the console and PowerShell ISE, my function would work as expected. But there is no guarantee that other hosted applications will honor the foreground,color parameter. You may not have any way of anticipating what host someone might use to run your command. Because of this, I would discourage you from relying too much on Write-Host. There are better ways to provide feedback such as Verbose statements or the Write-Progress cmdlet.

And before anyone calls me out: Yes, I have written a number of PowerShell tools that explicitly take advantage of the color features of Write-Host. But I do so knowing that all I can do is view the information on the screen and that I need to use the console or ISE. I have also been creating PowerShell tools and scripts for 10 years so I understand how to bend the rules and the consequences.
If you are just getting started, learn to write objects to the pipeline. If you don’t feel the need to type ‘Write-Output’, you’ll never be confused. Let PowerShell do the work.