PowerShell has become de-facto tool of choice for automation in Microsoft world from long time and slowly it is winning over hearts of the Linux administrators as well. Just like with other programming languages, there are many ways to do the same thing in PowerShell. However they differ in little subtle ways. You may or may not notice them in your day to day usage, but if you learn those subtleties, you can quickly improve the performance and results of your automation. This blog post is about one of the such cases only.
There are many ways to output the information in PowerShell using various Write- Cmdlets. We will be specifically discussing about below cmdlets:
- Write-Host
- Write-Output
- Write-Debug
- Write-Warning
- Write-Error
- Write-Verbose
- Write-Progress
About CmdletBinding()
We did not discussed about it anywhere above. It is important to learn first about cmdletbinding() and why it is very important to learn in PowerShell. Let’s say that you have written a function like below in PowerShell:
function Get-Sum($num1, $num2){ return ($num1+$num2) }
Now, let’s try to call this function in IDE. If we try to pass parameters to this function, there is almost no information on what needs to be passed:
If you have noticed, this is a little bit different from what we have seen with the built-in PowerShell cmdlets. If you run any built-in PowerShell cmdlets, you will see below options:
It turns out, achieving above is very easy. We need to only add couple of lines to our function:
function Get-Sum { [CmdletBinding()] Param( $num1, $num2 ) return ($num1+$num2) }
Notice that we added [CmdletBinding()] in a new line and we moved paramters inside a special block named Param(). Now if we call above function, we would see something like:
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.
Write-Host Cmdlet
Write-Host is the most commonly used and most commonly abused Cmdlet of various Write- Cmdlets. However this is probably the first cmdlet that you would learn in the PowerShell. It is used to write information to the host that you want the used of your script to see. The output information written using this cmdlet is always sent to host. For example,
Write-Host "Hello, World !!!"
Above code, will result in output like below:
It has other options like color support:
Write-Host "Hello, World !!!" -ForegroundColor Red -BackgroundColor Yellow
which results in output like below:
One of the other important options is -NoNewline Switch which lets you print subsequent message in the same line. It is to be noted that Write-Host is host dependent. In the console, it is essentially doing [console]::WriteLine.
This cmdlet is very useful if you are running any script interactively.
Write-Output Cmdlet
This cmdlet is used to print information to the pipeline, so that it can be consumed by next cmdlet in the pipeline. If there is no cmdlet at the end of the pipeline, it sends the output to current output stream.
So below two commands are equivalent:
Get-Service Get-Service | Write-Output
Also, it sends the data across pipeline as .NET Objects. It is built-in into almost every PowerShell cmdlet. So you would rarely need to use it.
Difference between Write-Host and Write-Output Cmdlet
There are two important differences between Write-Host and Write-Output cmdlets:
First, Write-Host handles data as text but Write-Output handles data as Objects. Secondly, Write-Output sends data to the pipeline unlike Write-Host which sends data to the current host. For example, consider below code:
$var1 = "Hello, Write-Host" | Write-Host $var2 = "Hello, Write-Output" | Write-Output
Checking output of above code would be something like below:
I have seen some people using Write-Output to return objects and would strongly argue against it. If you indeed need to return something as part of function execution, use explicit keyword return followed by the object that you want to return.
Write-Debug Cmdlet
Generally, you run your code without the Debug switch. However, there will be a time, when you want to break inside your funciton and step through the logic written one line at a time. If you are running with the -Debug switch, then control will break on the line of code that has Write-Debug statement, allowing you to step through code from that statement forward. For example, consider below code:
Function Get-NumberTable{ [CmdletBinding()] Param( $Number ) 1..10 | ForEach-Object { Write-Host ($Number*$_) } } Get-NumberTable 10
Above code will let you print a table of 10. Let’s assume that we have made a mistake somewhere in the code and inserted a Write-Debug statement. Then if we call our function with -Debug switch, it would break at the statement:
If the function is not called using -Debug Switch, nothing happens and function will be executed as usual. So Write-Debug cmdlet allows to occasionally break into function at the time needed.
Write-Warning Cmdlet
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. I t is also useful in scenarios, where you would like to print out before performing a critical operation like deletion or overwrite.
For example, consider below code:
Function New-Directory{ [CmdletBinding()] Param( $Path ) if(Test-Path $Path){ Write-Warning "This path already exists" } New-Item -ItemType Directory -Path $Path } New-Directory -Path 'C:\System Volume Information'
which results in output like below:
Write-Error Cmdlet
Write-Error is to display error messages to the users. The default behavior is to continue the execution after displaying error message. Write-Error has a lot of parameters that lets you customize it. The key among them is the Category parameter. For example, you could write a non-terminating error as shown below
Write-Error "Unable to connect to server." -Category ConnectionError
Although it used to signal non-terminating errors, you can in fact stop the execution by setting the environment $ErrorActionPreference as below:
$ErrorActionPreference = "Stop"
Write-Error Vs Throw
Throw is generally used when you want the program execution to stop (unless there was a try/catch to handle the error and resume from it).
Using try-catch block inside a function is generally overkill. If you need to stop program execution, you can set the $ErrorActionPreference to stop as specified above. Also, if you need to continue program execution, it is the default behavior or you can set $ErrorActionPreference to SilentlyContinue or just Continue.
Write-Verbose Cmdlet
This is the probably the most important and widely used cmdlet of all the ones discussed till now. Basically, this is all the extra information you want to see as your program is running if it was started with the -Verbose switch. So this command is very helpful in displaying all the extra information needed at the time of execution. If the function is called without -verbose switch, all Write-Verbose statements are skipped. For example, consider below code:
Function Get-NumberTable{ [CmdletBinding()] Param( $Number ) 1..10 | ForEach-Object { Write-Verbose "Current step is $_" Write-Host ($Number*$_) } }
If we call above function with -verbose switch, we should see below output:
And, if we call above function without -verbose switch, we should see below output:
Write-Progress Cmdlet
Write-Progress cmdlet is used to display the progress of a particular section of code. This is very useful if you are performing long running operations. 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. For example, consider below PowerShell code:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
$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 | |
} | |
} |
If we run above code, we should see something like below: