Creating PowerShell Function Failsafes with WhatIf

We can all agree that it’s essential to know what the command or script that we are about to execute will do. Knowing what command can do may require hours of reading code and documentation or testing in separate environments. But PowerShell offers a great tool to speed the development of our scripts up with WhatIf.

In this article, you will learn how to create a PowerShell function that supports the WhatIf parameter and how you can use it to make your PowerShell functions even more solid.

Prerequisites

This tutorial only uses local computer resources and will work on PowerShell version 5.1 and up. Preferably you should have an IDE or ISE for PowerShell of your choice installed. The code in the tutorial is not tested on older versions of PowerShell but it may work on them as well.

What is WhatIf?

WhatIf in PowerShell is a part of the ShouldProcess cmdlet functions in PowerShell. ShouldProcess can also prompt for confirmation and is basically a function that decides if it should process or not process code. This is primarily done with the help of an IF statement (as you will explore further down in this article).

Now let’s get started by creating our first simple WhatIf function.

Creating a basic function that supports WhatIf

A PowerShell function does not support using the WhatIf statement out of the box (but almost) and there’s a couple of things that differ from a plain PowerShell function – The CmdletBinding and SupportsShouldProcess. **You need to specify these at the beginning of the function for them to work as shown in the example below:

Function Test-WhatIf {
    [CmdletBinding(SupportsShouldProcess)]
    Param(
		    [String]$TestString
    )

    Write-Output "Test string: $TestString"
}

The function above supports the ShouldProcess functions that contain the WhatIf feature. But it does not yet use WhatIf as you notice when you run the function:

PS7> Test-WhatIf -TestString Foo -WhatIf

Test string: Foo

This is because you need to surround the code that you want to use WhatIf on with an IF statement that contains a call to $PSCmdlet.ShouldProcess with a description of the target and the action that it performs.

Function Test-WhatIf {
    [CmdletBinding(SupportsShouldProcess)]
    Param(
		    [String]$TestString
    )

    # $PSCmdlet.ShouldProcess first argument is for target, the second is for action
    If($PSCmdlet.ShouldProcess("Console", "Writing TestString '$TestString'")){
        Write-Output "Test string: $TestString"
    }
}

The result of the addition above is clearly seen when you run the function again:

PS7> Test-WhatIf -TestString Foo -WhatIf

What if: Performing the operation "Writing TestString 'Foo'" on target "Console".

You have now created a basic PowerShell function that supports WhatIf! But how should you use it and when? That you will learn about next.

How to use ShouldProcess in scripts

There are two uses when it comes to using ShouldProcess: Describing what it will do and testing if it can do it in a non-destructive manner (i.e no writes or no “dangerous writes”).

The describing part is automatically implemented since we supply ShouldProcess with a target and an action. But the second part can be more tricky – and while there is no must to implement it can make your scripts extremely robust.

The tests that you perform can perform everything from simple to complex tests ranging everywhere from testing write permissions to return mock data of a successful write.

Below you will see examples of a function that creates 5 files, but the latter one also performs tests if WhatIf is supplied that validates that you have permission to write to the specified directory:

Function New-TestFiles {
    [CmdletBinding(SupportsShouldProcess)]
    Param(
        [Parameter(Mandatory,ValueFromPipeline)]
        [String]$Directory,

        [Int]$Amount = 5
    )
    Begin{}
    Process {

        # Create $Amount number of files
        1..$Amount | ForEach-Object {
            
            $Target = Join-Path -Path $Directory -ChildPath "$_.txt"
            
            # ShouldProcess
            if($PSCmdlet.ShouldProcess($Target, "Create File")){
                Set-Content -Value "No content" -Path $Target
            }
        }
    }
    End {}
}

The output that you get by running the following against a directory where you don’t have write permission looks like this:

PS7> New-TestFiles -Directory C:\\Windows -WhatIf 

What if: Performing the operation "Create File" on target "C:\\Windows\\1.txt".
What if: Performing the operation "Create File" on target "C:\\Windows\\2.txt".
What if: Performing the operation "Create File" on target "C:\\Windows\\3.txt".
What if: Performing the operation "Create File" on target "C:\\Windows\\4.txt".
What if: Performing the operation "Create File" on target "C:\\Windows\\5.txt".

It shows that it’s performing “Create File” even if you don’t have write permission to C:\Windows. This can be misleading and using ShouldProcess without any validation that it can perform the actions if WhatIf is specified can remove the point of using it in many cases.

Let’s add some tests to the previous functions by adding them to an ElseIf statement after the If statement that contains the call to ShouldProcess:

Function New-TestFiles {
    [CmdletBinding(SupportsShouldProcess)]
    Param(
        [Parameter(Mandatory,ValueFromPipeline)]
        [String]$Directory,

        [Int]$Amount = 5
    )
    Begin{}
    Process {

        # Create $Amount number of files
        1..$Amount | ForEach-Object {

            $Target = Join-Path -Path $Directory -ChildPath "$_.txt"
            
            # ShouldProcess
            If($PSCmdlet.ShouldProcess($Target, "Create File")){
                Set-Content -Value "No content" -Path $Target
            }
            ElseIf($WhatIfPreference){
                $TestFile = Join-Path -Path $Directory -ChildPath "testfile.tst"
                Try {
                    [Io.File]::OpenWrite($TestFile).close()
										Remove-Item -Path $TestFile -WhatIf:$False -ErrorAction SilentlyContinue
                }
                Catch {
                    Write-Warning "$Directory is not writeable!"
                }
            }
        }
    }
    End {}
}

The test that was added in this version of the function will write to a test file and remove it to validate that you have the permission to write to the directory.

Always use ElseIf($WhatIfPreference){<code>} when you want to perform tests with WhatIf since ShouldProcess also takes care of Confirms (i.e. “Do you want to run this?”). This will make sure that the tests only run when “WhatIf” is specified.

If it can’t write a file to the directory it will return a warning as you can see below:

PS7> New-TestFiles -Directory C:\\Windows -WhatIf

What if: Performing the operation "Create File" on target "C:\\Windows\\1.txt".
WARNING: C:\\Windows is not writeable!
What if: Performing the operation "Create File" on target "C:\\Windows\\2.txt".
WARNING: C:\\Windows is not writeable!
What if: Performing the operation "Create File" on target "C:\\Windows\\3.txt".
WARNING: C:\\Windows is not writeable!
What if: Performing the operation "Create File" on target "C:\\Windows\\4.txt".
WARNING: C:\\Windows is not writeable!
What if: Performing the operation "Create File" on target "C:\\Windows\\5.txt".
WARNING: C:\\Windows is not writeable!

Now that you learned how to use WhatIf in your PowerShell functions from scratch it’s time to learn some more tricks when it comes to WhatIf as promised.

Good to know about WhatIf

While implementing the WhatIf feature into your functions is pretty straight forward there are some things that you should know about that can hopefully speed up the development of your scripts. It can also save you from a lot of headaches and even downtime.

WhatIf is inherited by all cmdlets called in the function

If properly implemented by the cmdlets in your function. This means that you can “piggy-back” on other peoples WhatIf implementation which is especially useful if they contain tests.

You can see that for yourself by using the function below:

Function Test-WhatIf {
    [CmdletBinding(SupportsShouldProcess)]
    param(
        
    )
    Set-Content -Path "C:\\temp\\whatif\\test.txt" -Value "Test"
}

If you run it you will get the following result:

PS7> Test-WhatIf -WhatIf

What if: Performing the operation "Set Content" on target "Path: C:\\temp\\whatif\\test.txt".

Free Write-Verbose

ShouldProcess does not only output on WhatIf – but on verbose as well! This shortens the code needed for functions that need verbose output implemented by quite a bit.

You can use the function below to demonstrate it:

Function Test-WhatIf {
    [CmdletBinding(SupportsShouldProcess)]
    param()
    If($PSCmdlet.ShouldProcess("Console", "Writing output")){
        Write-Output "Hello"
    }
}

If you run the above function with the -Verbose switch you will get the following result:

PS7> Test-WhatIf -Verbose

VERBOSE: Performing the operation "Writing output" on target "Console".
Hello

Some 3rd party PowerShell functions have improperly implemented WhatIf

Be aware, this was years ago an even bigger problem but has become quite rare in the last few years.

Some 3rd party functions look like they support WhatIf but it is not implemented! This is because the authors of the cmdlets either have added SupportsShouldProcess at the top of the function or added WhatIf as a switch parameter.

This means that you may in some cases add the -WhatIf switch to a command in the belief that it won’t perform any harmful actions – but it will if it is not implemented throughout all write operations in the function.

Summary

You have now learned how to create your own PowerShell functions that support WhatIf! It adds to the length of your code but is an important feature to have – Especially in critical environments or when you share your codes with others.