Thoughts On When To Use – Write-Host, Write-Output, Write-Debug, Write-Warning, Write-Verbose & Write-Error/Throw

PowerShell has matured as the automation tool of choice on the Microsoft platform, be it on Windows or Azure. However, there is no official guidance on best-practices and standards around some things. At times, bloggers do things incorrectly in their examples thereby reinforcing bad practices. Hopefully, this small post will help connect some dots for you! Please comment if I am stating something that is not a generally accepted best practice. Specifically, we are going to glance at the following cmdlets

  • Write-Host
  • Write-Output
  • Write-Debug
  • Write-Warning
  • Write-Error/Throw
  • Write-Verbose
  • Write-Progress

First things first – CmdletBinding:

Let us say you have a simple function

function Get-Sum($a, $b)
    $a + $b

This function (bad example to emulate) has two parameters “a” and “b” and it adds them. When you run this, the prompts look like below:


This is just as we expected. However, what if we wanted the other parameters like the below ones that you see as part of the built-in cmdlets?


Actually, it turns out that it is quite simple to add that support in your own function by by simply adding the CmdletBinding decorator to your function as shown below

function Get-Sum
    param (
        [int] $a,
        [int] $b

    $a + $b

Notice the minor changes we made

  • We added [CmdletBinding()]
  • Defined the param block with datatypes for our parameters

Now, if we tried to run our Get-Sum function, the advanced function parameters magically appear in our own function!


Let us alter our function to add a Write-Verbose statement

function Get-Sum
    param (
        [int] $a,
        [int] $b

    Write-Verbose ("About to add [{0}] and [{1}]" -f $a, $b)
    $a + $b

Now when I run the function with the “-verbose” switch in addition to the two parameters, I also get the “verbose” output.

PS C:\Windows\system32> Get-Sum -a 1 -b 2 -Verbose
VERBOSE: About to add [1] and [2]

When I run it without the “-verbose” switch, I get just the output

PS C:\Windows\system32> Get-Sum -a 1 -b

This is very typical in PowerShell where you sometimes run with the “-verbose” switch when you want to troubleshoot and you don’t when you are running it on a day to day basis. The other switches/parameters that you see in the picture also similar. You use them on an “as needed” basis.

What we just did was that we turned our function into what is called an “advanced function” that offers support for native cmdlet like functionality in your own functions. With this knowledge, what we will see below will make a bit more sense.


This is quite simply the cmdlet to use to write information to the console that you want the user of your script see. The output written this way is always displayed. It has color support too! You could be creative and use colors in a way that makes sense for your application while conveying the intent. Write-Ouput may behave like Write-Host but that is for a different purpose as we will see.

Basic usage

Write-Host "This text will be displayed on the console!"

will display the text on the console.

Writing in color!

Write-Host "This is yellow text on red background!" -BackgroundColor Red -ForegroundColor Yellow

Below is the output:
This is yellow text on red background!

Writing on the same line (with no newline)

[int] $i = 0
foreach($i in @(1..100)){
    Write-Host '.' -NoNewline

Below is the output


Note: If you want to display progress information use Write-Progress instead!


This cmdlet is used to write output to the pipeline. If it is part of a pipeline, the output is passed down to the receiving command. The values written this way can be captured into a variable. It gets written to the console if it is not passed down the pipeline or assigned to a variable. This is also the same as simply stating something like a variable or a constant (makes it a return value) on its own line by itself within your code, without any assignment. The output written this way can be discarded by piping the to Out-Null. Let us see some examples:

Pass-thru the pipeline

Write-Output “My return value” | Out-GridView

GridView displays “My return value”

Assign to a variable

$myString = Write-Output "My return value"

Console displays “My return value”

Writing output without using Write-Output cmdlet

#Usage: Get-Sum -a 9 -b 5
function Get-Sum($a, $b)
   $a + $b


Get-Sum -a 9 -b 5

In the function above, “$a + $b” is the same as saying “Write-Output ($a + $b)”. The output gets written to the pipeline either way. You could also use the “Return $value” syntax to make it explicit that you are returning a value and exiting the current scope. With the other two methods, execution will continue and possibly output more values (by design). In this aspect, PowerShell differs from other languages whose functions typically have a single return value (simple or complex datatype).

Returning values “intact”

Sometimes, complex datatypes get mangled by PowerShell after being output. They get turned into simpler datatypes one level down in the object hierarchy. For example when a .NET DataTable is returned, it may turn that into a array of .NET DataRow objects which is the immediate child type in the object hierarchy of DataTable. To prevent this, just prefix the return line with a “comma”. This will prevent PowerShell from un-nesting/interpreting your output.

function Get-DataTable()
   #Some logic...followed by return value

   #The comma prefix will write to pipeline with value intact!


This cmdlet lets you discard any output as if the output was never done. It not only suppresses console output messages but also throws away actual output returned to the pipeline by other commands. This comes in handy if you have written all of your scripts with a lot of output to the console and for a specific need, you do not want to clutter up the screen (or) you have no need for the return value a function produces.

Write-Output "Send text the console?" |


If you are running with the “-Debug” switch, then control will break on the line of code that has “Write-Debug” allowing you to step through code from that statement forward. It is a pretty cool feature if you think about it. Typically, you run code without out the Debug switch. However, there will come a time when you want to break inside your main loops where you have the most important logic and step through code without having to dig through all the code. Sprinkle your code with a couple of “Write-Debug” statements per function where you think that you may want to start examining variable values and start stepping through the individual statements.


Write-Warning has a color coding that catches the attention of the user. Use this when you want to let the user know that something is not normal but the code has to continue anyway as it not a big enough problem to stop execution. Examples of this are when you want to copy files over to a new location and the files already exist in that target location. If the business process allows overwriting the target, it still might be a good idea to let the user know that you are overwriting with “Write-Warning” type messages.


Write-Error is to be used for non-terminating errors – meaning, the execution will continue past the Write-Error line after writing the error to the error stream. Write-Error has a lot of parameters that lets you customize it. The key among them is the Category parameter. Although it used to signal non-terminating errors, you can in fact stop the execution by setting the environment $ErrorActionPreference.

$ErrorActionPreference = "Stop";

For example, you could write a non-terminating error as shown below

Write-Error "Unable to connect to server." -Category ConnectionError

Write-Error vs. Throw

Use Throw when you want the program execution to stop (unless there was a try/catch to handle the error and resume from it).

PS C:\Windows\system32> throw
At line:1 char:1
+ throw
+ ~~~~~
    + CategoryInfo          : OperationStopped: (:) [], RuntimeException
    + FullyQualifiedErrorId : ScriptHalted

PS C:\Windows\system32> throw "Server not found. Unable to continue"
Server not found. Unable to continue
At line:1 char:1
+ throw "Server not found. Unable to continue"
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : OperationStopped: (Server not found. Unable to continue:String) [], RuntimeException
    + FullyQualifiedErrorId : Server not found. Unable to continue

when Throw is used inside a function and if there are no try/catch blocks up the chain of calls, the execution will stop and the console will display the error “thrown”.


This is the most commonly used of all the ones discussed. Basically, this is all the extra information you want to see as your program is running if it was started with the “-Verbose” switch. Typically code is run without that switch. I usually use for every step within a function. In addition to giving me an idea of what is being worked on, if an error happens, I also get the step that failed (even when not running with -Verbose switch)!

Please take a look at the functions in my older post belowto see how Write-Verbose is used in conjunction with error handling (using $stepName).


I see questions online that ask how to display a stream of dots or something to that effect if processing is in progress. The best way to convey that aspect is using Write-Progress instead of any of the above methods.

Nested loop progress

Write-Progress can even do nested loop progress. i.e., If there are three levels of nesting in loops, three progress displays may be done for each level of nesting.

$outerLoopMax = 255
$innerLoopMax = 126

for ($outerCounter=1; $outerCounter -lt $outerLoopMax; $outerCounter++) {
  Write-Progress -Activity "Main loop progress:" `
                   -PercentComplete ([int](100 * $outerCounter / $outerLoopMax)) `
                   -CurrentOperation ("Completed {0}%" -f ([int](100 * $outerCounter / $outerLoopMax))) `
                   -Status ("Outer loop working on item [{0}]" -f $outerCounter) `
                   -Id 1

    Start-Sleep -Milliseconds 100

    for ($innerCounter=1; $innerCounter -lt $innerLoopMax; $innerCounter++) {
      Write-Progress -Activity "Inner loop progress:" `
                       -PercentComplete ([int](100 * $innerCounter / $innerLoopMax)) `
                       -CurrentOperation ("Completed {0}%" -f ([int](100 * $innerCounter / $innerLoopMax))) `
                       -Status ("Inner loop working on item [{0}]" -f $innerCounter) `
                       -Id 2 `
                       -ParentId 1

        Start-Sleep -Milliseconds 10

Note that to do nested progress like this, give a different Id value for the parent and the child Write-Progress and in the child Write-Progress, set the ParentId parameter to the Id value of the parent.


Although this is a very short post, it hopefully underlines the context in which the “Write” related commands are generally used. If there is one take-away from this post, you should use Write-Verbose a lot! The perfect PowerShell script/function uses all of the above in the right mix for the best user/developer experience.

10 thoughts on “Thoughts On When To Use – Write-Host, Write-Output, Write-Debug, Write-Warning, Write-Verbose & Write-Error/Throw

  1. Thank you for this post. I was looking for an answer on when to use which write-*** command and now have a fair understanding. I also take your advice with me “If there is one take-away from this post, you should use Write-Verbose a lot! “.

    P.S: I am wondering if I should change my comments to Write-Verbose commands since they can be useful while debugging. Something to try and experiment. Thanks again!

    1. Thanks Pramod. That’s exactly what I do. My comments become write-verbose. I certainly encourage using write-verbose a lot. Just be careful inside loops.

      Thanks, Jana.

  2. Thank you for this write-up! This was exactly what I was looking for today. I’ve also bookmarked it to review in more detail to improve one of my heavily used scripts and to check out your other “best practices” tagged posts.

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s