Is Write-Host really harmful?

Last January I left the world of scrappy Internet companies to join the Windows Automation team at a Fortune 500 firm. The group was just starting to warm up to PowerShell and one of my first tasks was to give a presentation on what “Good PowerShell” looks like. I took a few scripts we’d been working on and polished them up. Verb-Noun. Get-Help. -WhatIf. Parameterization. Discovering things from the environment instead of asking for them. Embracing the Pipeline. Error handling and using Exceptions. That sorta stuff.

And then I presented. This is how you do things, and this is why you do them. These are choices that I made, and this is why I chose them.

It went over well enough. But. My scripts put a fair amount of output on the console via Write-Host, and someone challenged that. In support of their argument they sent me several links, one of which was Write-Host Considered Harmful. By Jeffrey Snover. The guy responsible for PowerShell.

*Gulp*

The meat of Jeffrey’s argument isn’t wrong. Write-Host is problematic in certain circumstances. This is entirely because Write-Host bypasses redirection within PowerShell. There is no way to capture Write-Host output within a single PowerShell session.

The upside is that Write-Host also bypasses the pipeline. So if a script is emitting objects to the pipeline, Write-Host will not stick a bunch of nasty text in the middle of your PSObject nirvana. This code:

Write-Host "Hello World." > .\out.txt

Doesn’t put anything in .\out.txt.

"Hello World." | Write-Host | ForEach-Object { Write-Host "Goodbye World." }
Hello World.

The above code never says Goodbye because Write-Host put nothing on the pipeline. This pattern is absolutely a misuse of Write-Host, and if you do that you will quickly discover your mistake and correct it.

Now, what happens when a function is called that uses Write-Host and returns objects?

function n { 
     Write-Host "Helpful Message."
     return "Useful Object." 
}

n | ForEach-Object { Write-Host ("Pipeline Received: " + $PSItem) }
Helpful Message.
Pipeline Received: Useful Object.

As we’ve already covered, Write-Host output doesn’t pollute the pipeline with text, so the pipeline has my Useful Object while the console got a Helpful Message. What’s wrong with that? Jeffrey says everything. I say it’s fine for nearly all use cases.

Neither of us are wrong.

To really understand Jeffrey’s position you need to read his original Monad Manifesto. In the traditional command-line world, when you write something like a | b | c, all of those subsequent steps are reading one program’s text output to find the information you need and do something useful with it. Sed, grep, regular expressions. Your local *nix geek loves these tools and does wonderful things with them, but in Jeffrey’s words acting on unstructured text is “clumsy, lossy, imprecise.” It would be much better if a could output its objects directly to b and c. That’s what PowerShell (Monad) delivered.

Jeffrey wants your scripts to emit objects and not fall back into the world of consuming unstructured text. A noble cause.

But Write-Host doesn’t produce text that other PowerShell scripts can consume.

And if your PowerShell scripts are being called by something that flattens Write-Host‘s output into STDOUT along with everything else, guess what? You’ve left the realm of objects and are back to unstructured text, Write-Host or not.

Now, I do believe that Write-Host should only be used in one place: the body of a script that is meant to be invoked by itself or as the final stage of a pipeline.

I don’t use Write-Host in modules, scripts meant to be dot-sourced, or functions within a stand-alone a script. My reasoning for this is that functions are intended to be called by other PowerShell scripts or commands and writing to the console should be purely the domain of the caller. My functions return objects, Throw errors, and use Write-Verbose, Write-Warning, Write-Debug, and Write-Progress for other information that might be useful.

When I’m writing a script that isn’t going to emit objects, because it’s being invoked by an automation system that can’t use them or by a person to perform a complex process, Write-Host is a perfectly reasonable thing to use, and I’m not going to apologize for it. Not even to Jeffrey “I invented PowerShell” Snover.

5 thoughts on “Is Write-Host really harmful?

  1. Hello! You mention the solution at the end of your post – why would you chose Write-Host when you have Write-Verbose, Write-Debug, and other options available?

    Even if there is ‘no harm’ in using Write-Host in the situation you mention, readers of your code may pick up the bad habit, and if your code is re-used elsewhere, it may be carried over.

    Reminds me of the folks who post SQL snippets that don’t parameterize their queries. There might not be human input to allow injection, but you’ve now given an example query that a beginner might assume is okay. And your code might end up somewhere where there IS opportunity for injection.

    Cheers!

    Like

    • If you get all the way to the bottom of Jeffrey Snover’s screed, it’s right there:

      The other scenario to use Write-Host is when you really do want to generate a UX.

      Jeffrey’s post would have been better titled “Don’t use Write-Host in Cmdlets because the caller should choose the UX.” Use Write-Verbose and implement CmdletBinding for the -Verbose switch. Use Write-Progress if that’s appropriate and provide a switch to suppress it without globally changing $ProgressPreference. Write-Warning, Write-Debug, yada yada.

      Don’t point people to Jeffrey’s post when you’re looking at a script that isn’t a Cmdlet and is in fact using Write-Host to provide UX.

      Like

  2. Ah, something I feel passionately about.

    I’m pro-Write-Host because 99% of the time you are likely to be running Cmdlets tailored to your environment in a console and you want to see what it’s doing. The rest of the time you’ve set up a job to automatically (and are still wanting to capture some kind of output for historical purposes).

    “So use Write-Verbose or Write-Debug instead,” people say. This is just extra work. I might run hundreds of command lines a day, day in and day out, and have to tack -Verbose or -Debug onto every one of them. Why would I do that when I don’t have to?

    “So just set the $VerbosePreference variable,” people say. Now you’re burdening other people with remembering to do this. It’s just a matter of time for people to ask why nothing is being output on screen, and now you need to burden them with the above information too, or setting up their $profile.

    Meanwhile every line has an ugly “VERBOSE:” tag on the front. Why?! All to conform to a dumb rule that really has no basis because as you pointed out it doesn’t interfere with the holy pipeline. Frankly listening to people make others feel bad for making harmless use of a core part of the language makes me think they are bad people.

    I have seen the opposite though; cmdlets that do colour output, multi-threading, custom logging frameworks to output to screen and file, and with progress bars, all at the same time. In those cases I wish they went for a simpler methodology and cut down on the crap (I firmly believe threading should stay outside of Cmdlets because it makes debugging them later excruciating – leave it for the caller to segregate their workloads and implement it as they see fit).

    Like

    • It’s just a matter of time for people to ask why nothing is being output on screen

      That’s the thing right there. If you’re handing off a script that isn’t a Cmdlet returning objects, people expect some output and Write-Host is the way to provide that.

      The person who was pushing me hardest to avoid Write-Host was working on one of the longest top-down scripts I’ve ever seen. No functions. Leaking random object output from the Cmdlets it was calling to the console because they weren’t being redirected or assigned. Instead of Write-Host it used a Cmdlet found on the Internet to output log files formatted for CMTrace, and so to watch the progress of this script, and to know that if it had succeeded or not, you were supposed to run CMTrace on the logs in parallel. We were also supposed to run this script within the ISE so that if it died we could restart it from the next line.

      Ugh. And this guy had spent five years as a Microsoft Senior PFE before joining us.

      There are places that I will agree that Write-Host doesn’t belong. But a blanket statement that “Write-Host is harmful” is crap.

      Like

Leave a comment