Write-Host Considered Harmful

When you are writing or reviewing PowerShell scripts, I’d like you to remember the following rule of thumb:

Write-Host is almost always the wrong thing to do because it interferes with automation.   There are typically two reasons why people use Write-Host:

To convey results.
The problem with using Write-Host to convey results is that they go directly to a display.  That means that they can’t be used by another script to automate a larger task.  Whenever you write a script, you are addressing a particular problem at hand and you are putting a tool in your toolbox that can be used to solve larger problems down the road.  If you use Write-Host to convey results, you have a useless tool in your toolbox.

The correct cmdlet to use is Write-Output.  Using Write-Output will display the results to the screen when you run you script by itself but, it will also allow your script to be used in a pipeline (or foreach loop) and have the results used by other scripts/cmdlets.

To convey comforting information to the user (e.g. “I’m about to do this”).
This is super useful thing to do (I wish more people did more of it) but it is critical that you do it in the write way.  What’s that saying about “the road to hell being paved with good intentions”?  If you use Write-Host to do this, you are paving a road to hell.PowerShell is about automation.  Show comforting information is often helpful the first couple of times you run a script but then after that, it because an annoyance.  When you use Write-Host, the user is not able to say when they want this information and when they don’t.

The correct tool is the Write-Verbose cmdlet.  This cmdlet allows the user to control whether they see the information or not by invoking your script with the -VERBOSE flag or not.Information sent to Write-Host goes directly to a UI with no ability to capture it for a later time.  If you read the About_Redirection help, you’ll see that PowerShell gives you the ability to redirect various streams of information.  The following command will take the output stream of Test-Script and output it to the file o.txt:

PS> Test-Script | Out-File o.txt

If Test-Script had verbose information and you did this:

PS> Test-Script -Verbose | Out-File o.txt

The verbose stream would go to the screen and the output stream would go to the file.  Using PowerShell redirection, you can “redirect” the verbose stream (referred to as “4” for historical reasons) into the output stream (referred to as “1”) and then both sets of data will be captured into the file:

PS> Test-Script -Verbose 4>&1  | Out-File o.txt

The syntax is clearly not obvious but it is worth learning (or at least knowing about) because I can guarantee that you’ll have times when this is exactly the thing you need – but, if the script used Write-Host – it won’t work.

So if Write-Host is almost always the wrong thing to use, you might wonder why it is there in the first place.  The answer is in the phrase “almost always”.  I often use Write-Host when I’m writing a throw away script or function.  It is much faster and simpler to use it than to use Write-Verbose and then specify -VERBOSE.  For a throw away script – it doesn’t matter.  The key is to throw it away!  If you don’t intend to throw it away, you shouldn’t use Write-Host.  I always know the scripts I’m going to throw away because they are named T.PS1.

The other scenario to use Write-Host is when you really do want to generate a UX.  Write-Host has a number of nice features like the the ability to colorize text that are great to use when you really do intend to generate a UX.  For example outputting a graph or my personal favorite:

PS>  iex (New-Object Net.WebClient).DownloadString(“http://bit.ly/e0Mw9w“)

Cheers!
Jeffrey

35 thoughts on “Write-Host Considered Harmful

  1. Write-Verbose seems to have a lot of baggage that make it a less then attractive option.

    First, the -Verbose option doesn’t appear to actually do anything out of the box. Is there an assumption the script has wired up a Param() handling for it to adjust $VerbosePreference?

    Which brings us to $VerbosePreference: It’s powerful…too powerful IMHO. It’s trying to serve too many masters and it’s global. Setting it one way in a parent script may inadvertently affect the behavior of a child script in functional way.

    And then there’s the output… all forcing a prefix of “VERBOSE: “. I’m not sure if there’s a variable that can be set to turn that off? I couldn’t find one, but it makes sense that it would be adjustable somehow (if only for i18n reasons).

    And the “verbose stream” it writes to. Outside of PS it ends up as STDOUT, not STDERR (of course, Write-Host has this issue too). Real world code frequently has to interoperable with tools crafted by other means. The only PS way I know of to actually write cleanly to the real stderr stream is [Console]::Error.WriteLine(“message”), which although cumbersome, at least is correct (by the conventions of stdio). PS isn’t a particularly good neighbor.

    The power of piping from one thing to the next is great, except when it isn’t, which turns out is most of the time.

    I personally prefer something more like traditional, like:

    function verbose($msg) {
    if ($VERBOSE) {
    [Console]::Error.WriteLine($msg)
    }
    }

    Or if we’re getting really fancy:

    function verbose($level, $msg) {
    if ($VERBOSE_LEVEL -ge $level) {
    [Console]::Error.WriteLine($msg)
    }
    }

    $VERBOSE_LEVEL = 1
    verbose 1 “testing one”
    verbose 2 “testing two”

  2. The Verbose stream, (along with the other data streams) can be accessed directly in the child jobs of PS jobs, while the job is running. You can use it to provide an alternate data stream to report progress without doing a Receive-Job.

    As far as changing it in child processes, you can set the scope on any variable to Private and prevent it from being seen by child scopes, and they will go back to the global scope setting:

    $VerbosePreference = ‘SilentlyContinue’
    $VerbosePreference

    }

  3. It’s probably a waste of time to reply to Byron here, but I’m going to do it anyway, for the sake of any other readers who may be inexperienced enough to take his criticisms at face value…

    It’s true the -Verbose doesn’t do anything by default. You need to enable common parameters by specifying the CmdletBinding attribute before your param block (even if you have no parameters) like this (you can put them on multiple lines, but I’m not sure how the comments here will render): [CmdletsBinding()]param()

    I’m frustrated that the debug stream is hard to use because -Debug doesn’t just set it to “Continue” the way -Verbose does with the verbose stream. You can output to that with Write-Debug.

    These streams aren’t understood by other applications — that’s one of the points of the post: the only output that’s “passed on” to interact with other tools is stdout (Write-OUtput) and stderr (Write-Error). However, inside PowerShell, you can redirect each of these streams (individually or all at once) to file(s) … or capture them in variables, etc (as long as you enable CmdletBinding: maybe we’ll get a future post from Jeffrey about how CmdletBinding ought to be used as a matter of course).

    The “VERBOSE: ” prefix that you see in the console is ADDED by the host when the verbose output is displayee there, (and different hosts may do different things). It’s not there when you redirect the stream to a file.

    I won’t address the comment that pipelines aren’t useful. Feel free to write PowerShell the same way you write C#, Perl or VBS if that’s your bag. Just keep in mind that other people do find it useful, so when you write scripts for reuse, you should Write-Output what needs to be output so it can be consumed by others, and use the other streams as appropriate.

    If you can’t live with just Output, Verbose, Debug, Warning, Error, and Progress … feel free to make up your own verbose levels — you can even use standard logging libraries like Log4Net and NLog (there are even modules for these on PoshCode.org). But please don’t do that when you’re writing redistributable module packages, because the rest of the PowerShell world is going to use the 5+1 standard streams, and you obviously want to be a good neighbor.

    • Thanks for the reply Joel. The pointer to use CmdletBinding is particularly handy, I’ll need to read more on that.

      Write-Error is a complete non-starter due to how much it pollutes the output; clearly intended for an entirely different use case.

      I still wouldn’t use Write-Verbose for nearly any of the use cases I typically run into (simple running status output), if for no other reason than the VERBOSE: issue. You say the host adds adds this to the output…I’m not sure why it matters where it gets added (the Write-Verbose declaration or the runtime host), it gets added either way? If anything other than PS is the one redirecting the output to a file (common), it gets added.

      Additionally, that “different hosts may do different things” with respect to output is a bug, not a feature. If true, it’s just one more reason to avoid Write-Verbose.

      When it comes down to it Write-Verbose is simply unwieldy. A user has to understand HOW much of the often bazaar underpinnings of PowerShell just to print a trivial line of status information without baggage and obtuse side effects?

      The request is a simple one, it should have a simple answer. It does in every other language. The more I learn about Write-Verbose the more I understand why Write-Host is so popular, and will continue to be so.

      Re pipes, I’m an old Unix hack at heart. I very much get the usefulness of pipelines. PS’s expansion on the theme to first class objects is profound, but the additional expansion of them as a primary design pattern may be a bridge too far. They frequently make code both more difficult to read and more difficult to manage error (and status info ;-) ) handling.

      • The question of “simple running status output” has a simple answer: Write-Progress. That isn’t for stuff you might want to log (you MUST use Write-Verbose for that, since you can’t log Write-Host), but it’s perfect for showing the current “running status” so the user has a clue what’s going on. I believe this system is far superior to the “options” available in most other languages (and of course, let’s not forget that all the other options for logging that are available in .net are available in PowerShell as well: including verbose levels and trace output, etc).

        Incidentally, search for “print status line from python script” (or perl, or ruby) in your favorite search engine… compared to having Write-Progress built in, those options are barbaric — and let’s not forget that Write-Progress works over remoting, background jobs, and from graphical tools like the Windows Server Manger and those designed with ShowUI.

        The reason why it matters that “VERBOSE: ” is added by the host is because the only time it shows up is when the verbose output is printed to the host, as opposed to being redirected or stored in a variable — both of which are simpler parameter options for verbose, debug, warning, and even error output with any command that uses CmdletBinding (or any command that’s wrapped in a command that uses CmdletBinding).

        As a side note: I have to say I think you’re making a mountain out of a molehill when it comes to that, it’s a useful label so you differentiate verbose/trace output from ACTUAL output and tell what you’re looking at. That’s important because when a user is automating and redirecting output or consuming it a the pipeline, they know that the verbose output isn’t part of the pipeline, and they don’t have to worry about filtering out those status lines.

        Ok, enough. I disagree with you about the ways that hosts can change output (but none of the console hosts have, anyway), and about how hard it is to learn about $VerbosePreference too, but those are just a matter of opinion, so I’ll just leave it at that except for this post-script:

        As far as I can tell, the majority of your problems stem from the fact that you’re apparently working in another environment and trying to call PowerShell.exe from there like calling a perl script from bash … which ends up redirecting all streams that are enabled (except error) to stdout. If that’s what you’re doing, then it’s true that the only way to get output that is just displayed on screen is with Write-Host OR Write-Progress. I doubt that you’re the only person who’s ever done that, but I think I can safely say the use case is a tiny minority that Microsoft isn’t concerned with. PowerShell is designed so you can call powershell.exe with parameters, but it’s not really designed to be called as PART of a non-PowerShell automation script, and you loose most all of the richness of Powershell when you call it that way rather than through the .Net APIs (although there is a CLIXML streaming option) — the parameters are really for task scheduling more than anything else.

        • I’m not sure what you’re talking about; I log Write-Host output all the time. It hits stdout, which is trivial to consume or redirect with any technology?

          Write-Progress is good eyecandy, but its utility I feel is overrated. By “status” I care about what’s actually happening, when, why, often viewed in hindsight. Think more of an info log and less thumb-twiddling-progress-bar. And Write-Progress doesn’t provide any output whatsoever in non-interactive use (nearly everything I do), regardless $ProgressPreference is set to.

          You might question why I wouldn’t just call the logging library of my choice and be done with it. It’s a valid rebuttal. From experience, I tend to frown on most (automation) code that attempts to do its own logging internally, for the simple fact that it encourages every developer to reinvent the wheel which results in log files and status saved in files or APIs scattered all over. Logging standards don’t work in practice (politics, contractors, mergers, cross-platform issues, cross-language issues, etc, etc). And more often than not they do it badly…logging everything that doesn’t matter and than failing to catch the fatal exception that gets reported no where other than the console…which isn’t saved because they thought they were “logging everything” already.

          The result of that means we have to save that console output *anyway*, just in case.

          You say, “it’s not really designed to be called as PART of a non-PowerShell automation script”, and I wholeheartedly agree. What I feel is that PowerShell goes out of its way to be a walled garden, often (needlessly) hostile to inter-operating with anything else (other than .Net of course).

          But…I don’t have the luxury of throwing out every other technology that exists in the world when leveraging PowerShell. And neither I suspect do many others, which would go a long way to explain the seemingly ubiquitous reality of Write-Host and the like.

          Jeffrey’s blog is a plea to those unwashed masses of PS users to evolve and stop using Write-Host and instead embrace Write-Verbose and friends. He however, much like you, doesn’t seem to understand how and why folks are using Write-Host in the first place…

          • Well put Byron!
            I have nothing against the additional streams, but PowerShell will have to play well with established conventions for a long time and that is not as straightforward as I would like.
            I work in a large enterprise where the preferred mechanisms to invoke scheduled scripts are Control-M, or plain CMD. I have no choice but to limit my output to the equivalent of stdout and stderr, or occasionally write directly to a CSV file. Most of the scheduled tasks run out of office hours where yellow or red formatting is meaningless, but it is vital to *capture* that red text along with the plain text in the log files.

  4. Pingback: Updated Console Graphing in PowerShell

  5. When I first began playing with VMRole in Azure (almost 2 years ago) and scripting deployments to headless machines I quickly learned this lesson.
    At that time I simply began putting quotes around the text I wanted output: “output this” as I could capture it with out-file, >>, running in a console, a debugger.
    I honestly have not used Write-Host since.

    • That is an interesting angle. I’ve done the same myself in many tools, thinking that it was just shorthand for Write-Host.

      As it turns out, wrapping text with double quotes casts it as a string, which is then echoed out to the console. Interesting…

  6. I understand the reasoning behind using the write-debug and write-verbose options instead of write-host and agree they are correct when designing composable systems, but in most of my functions I DONT want the text output included in the pipeline, so write-output would not be the correct choice.

    I also like using the color options of write-host to make it easy to spot section headers and results (similar to write-warning and write-error). In this sense it really is more like creating UI elements, with the actual results being sent to the output pipeline as objects. Combine that with something like the Get-ConsoleAsHtml.ps1 script from http://stackoverflow.com/a/7996783/17373 and you can get full color logs that are easy for a human to quickly understand.

    And as for logging information from write-host, I usually create a custom write function that checks to see if it is running in a host that supports colors. Something like:

    if($Host.UI.RawUI.ForegroundColor -ne $null){
    Write-Host … -ForegroundColor …
    } else {
    Write-Output …
    }

    Which means if you execute ‘powershell.exe … > log.txt’ then all the write-host calls are converted to write-output calls so they are included in the text log. I wish the transcription features were more robust and supported color, but I’ve had so many issues with them not being supported in different environments (PowerGui, ise, remoting, …) that I usually just use my own logging.

  7. I would go a step further and say do not just use Write-Verbose and Write-Output. Do construct useful output data as objects, even for the smallest of scripts for example in a script calculating the lastlogon timestamp value from each DC for a specific user:


    [pscustomobject]@{
    Identity = $identity
    LastLogon = $calculatedLastLogon
    LastLogonDC = $calculatedDC
    }

  8. Pingback: Write-Host ist schädlich für Ihren Windows PowerShell Code › Windows PowerShell Community

  9. I can’t believe nobody ran the line of code or clicked on the link…or who did and failed to comment on it!
    Kudos to you Mr. Snover, for one of the most clever and unexpected examples of one of the first memes that existed before the Internet made them an hourly occurrence!

  10. Pingback: Write-Host – The gremlin of PowerShell | Jeff Wouters's Blog

  11. Oooh… This is awesome!

    I had no idea that Write-Verbose tied in with the -verbose switch! I thought it would just output some yellow text like in the Exchange Management Shell.

    Also very good point about Write-Output. To shame with my scripts, I must rewrite them all!

  12. This is ridiculous. If it was “almost always wrong” to use then there wouldn’t be any redeeming qualities; by his own post there are at least 3 reasons to use Write-Host and it has “colorization” of text, a feature not available in the other commands.

    I write a lot of scripts that get used by less experienced admins and I use the colorization frequently to clarify results: red for failure, yellow for working or waiting status, and green for success.

    And as far as Write-Host output becoming “annoying” from the initial “comforting” intention, that is dependent on your own customers, know your audience and implement the correct tool for the job.

    The intention of this post was useful, just don’t pick such as sensationalized title, something more akin to “Graduate from using Write-Host by using these commands instead” would better serve your audience.

    • I agree with most everyone here, fwiw. I find it ironic that when I was living in LISP the discussion then was similar (princ, prin1, print, and so on, with enclosed parens of course, ha ha). Then there were debates about iteration versus (mapcar) and lambda expressions, (apply) and so on. The only takeaway I’ve seen over the years is that it’s nice to have some guidelines, but don’t view them as concrete Jersey walls. I enjoyed this article and got some useful tips from it, as well as all of the replies above. Thank you!

  13. Pingback: Understanding Streams, Redirection, and Write-Host in PowerShell - Hey, Scripting Guy! Blog - Site Home - TechNet Blogs

  14. Pingback: Powershell Tip- No Write-Host | PS C:>Write-Blog

  15. One word: -NoNewLine. Get that parameter added to these other output cmdlets and then I may seriously entertain minimizing my use of the of Write-Host cmdlet (but not completely of course). Otherwise, you won’t convert me on this, but I appreciate the information and opinions provided in this post; very informative and entertaining :).

  16. Pingback: Set-Cs2013Features.ps1 - changelog - Ehlo World!

  17. Pingback: Attaching All Databases with PowerShell–Refactoring out Write-Host | Voice of the DBA

  18. So I’ve started using Write-Verbose, Write-Warning and Write-Output after reading this article…but I’m having some issues with the output that I’m getting from Write-Error.

    Here is a simple example of it’s use in a script.

    1..5 | foreach{
    if ($_ -eq 5){
    Write-Error “Invalid result”
    }
    }
    When the error show up i get the following.

    1..5 | foreach{
    if ($_ -eq 5){
    Write-Error “Invalid result”
    }
    } : Invalid result
    + CategoryInfo : NotSpecified: (:) [Write-Error], WriteErrorException
    + FullyQualifiedErrorId : Microsoft.PowerShell.Commands.WriteErrorException
    I don’t understand why it displays all of the code above the error. it just makes the output on screen look extremely messy, especially in long script. For example i am currently developing a script to automate our user creation, modification, disabling and deletion process and it is several hundred lines, so if i want to write a non terminating error, I don’t want to see the entire code above it….That’s just silly.

    Any suggestions. I thought about using write-warning, however it really is a non terminating error that i want to display…so i’m trying to use best practice.

    Thanks

  19. Terrific piece and great nod to Dijkstra!

    I DO wish we could suppress the VERBOSE: prefix when it’s just going to the screen, though! Perhaps in PowerShell 6.0.

    Thanks Jeff.

  20. Personally, I don’t bother with all this verbosity nonsense unless I’m tasked with actually creating something that is genuinely required to perform as a “tool”. Honestly, I think too many people are getting carried away with the Church of Don Jones thinking. At the end of the day, I have a wife and a kid, so spending needless hours creating every single one of my Powershell scripts to be completely reusable with perfect outputs and associated logging would be a waste of the time I should be spending with them.
    Powershell can be dirty or clean, there are pros and cons to both of these approaches – the obvious main difference is speed. Just consider that 95% of scripts will never be reused, not because they weren’t structured correctly in the first place, but because they’re often highly specific to the task for which they were written.
    One might argue that if the scripts were more modular, that the time would be saved in the end, but that’s simply specious in my experience. Further, I find that most of these “modules”, just become obfuscated forms of standard commands, which only makes anyone who has to try to read the script say “ugh, seriously”.

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>