How to write a PowerShell function to use Confirm, Verbose and WhatIf

In my last blog post I showed how to run a script with the WhatIf parameter. This assumes that the commands within the script have been written to use the common parameters Confirm, Verbose and WhatIf.

Someone asked me how to make sure that any functions that they write will be able to do this.

it is very easy

When we define our function we are going to add [cmdletbinding(SupportsShouldProcess)] at the top

function Set-FileContent {
[cmdletbinding(SupportsShouldProcess)]
Param()

and every time we perform an action that will change something we put that code inside a code block like this

if ($PSCmdlet.ShouldProcess("The Item" , "The Change")) {
    # place code here
}

and alter The Item and The Change as appropriate.

I have created a snippet for VS Code to make this quicker for me. To add it to your VS Code. Click the settings button bottom right, Click User Snippets, choose the powershell json and add the code below between the last two }’s (Don’t forget the comma)

,
		"IfShouldProcess": {
		"prefix": "IfShouldProcess",
		"body": [
			"if ($$PSCmdlet.ShouldProcess(\"The Item\" , \"The Change\")) {",
			"   # Place Code here",
			"}"
				],
			"description": "Shows all the colour indexes for the Excel colours"
	}

and save the powershell.json file

Then when you are writing your code you can simply type “ifs” and tab and the code will be generated for you

As an example I shall create a function wrapped around Set-Content just so that you can see what happens.

function Set-FileContent {
    [cmdletbinding(SupportsShouldProcess)]
    Param(
        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [string]$Content,
        [Parameter(Mandatory = $true)]
        [ValidateScript( {Test-Path $_ })]
        [string]$File
    )
    if ($PSCmdlet.ShouldProcess("$File" , "Adding $Content to ")) {
        Set-Content -Path $File -Value $Content
    }
}

I have done this before because if the file does not exist then Set-Content will create a new file for you, but with this function I can check if the file exists first with the ValidateScript before running the rest of the function.

As you can see I add variables from my PowerShell code into the “The Item” and “The Change”. If I need to add a property of an object I use $($Item.Property).

So now, if I want to see what my new function would do if I ran it without actually making any changes I have -WhatIf added to my function automagically.

Set-FileContent -File C:\temp\number1\TextFile.txt -Content "This is the New Content" -WhatIf

If I want to confirm any action I take before it happens I have -Confirm

Set-FileContent -File C:\temp\number1\TextFile.txt -Content "This is the New Content" -Confirm

As you can see it also give the confirm prompts for the Set-Content command

You can also see the verbose messages with

Set-FileContent -File C:\temp\number1\TextFile.txt -Content "This is the New Content" -Verbose

So to summarise, it is really very simple to add Confirm, WhatIf and Verbose to your functions by placing  [cmdletbinding(SupportsShouldProcess)] at the top of the function and placing any code that makes a change inside

if ($PSCmdlet.ShouldProcess("The Item" , "The Change")) {

with some values that explain what the code is doing to the The Item and The Change.

Bonus Number 1 – This has added support for other common parameters as well – Debug, ErrorAction, ErrorVariable, WarningAction, WarningVariable, OutBuffer, PipelineVariable, and OutVariable.

Bonus Number 2 – This has automatically been added to your Help

Bonus Number 3 – This has reduced the amount of comments you need to write and improved other peoples understanding of what your code is supposed to do 🙂 People can read your code and read what you have entered for the IfShouldProcess and that will tell them what the code is supposed to do 🙂

Now you have seen how easy it is to write more professional PowerShell functions

How to run a PowerShell script file with Verbose, Confirm or WhatIf

Before you run a PowerShell command that makes a change to something you should check that it is going to do what you expect. You can do this by using the WhatIf parameter for commands that support it. For example, if you wanted to create a New SQL Agent Job Category you would use the awesome dbatools module and write some code like this

New-DbaAgentJobCategory -SqlInstance ROB-XPS -Category 'Backup'

before you run it, you can check what it is going to do using

New-DbaAgentJobCategory -SqlInstance ROB-XPS -Category 'Backup' -WhatIf

which gives a result like this

This makes it easy to do at the command line but when we get confident with PowerShell we will want to write scripts to perform tasks using more than one command. So how can we ensure that we can check that those will do what we are expecting without actually running the script and see what happens? Of course, there are Unit and integration testing that should be performed using Pester when developing the script but there will still be occasions when we want to see what this script will do this time in this environment.

Lets take an example. We want to place our SQL Agent jobs into specific custom categories depending on their name. We might write a script like this

<#
.SYNOPSIS
Adds SQL Agent Jobs to categories and creates the categories if needed

.DESCRIPTION
Adds SQL Agent Jobs to categories and creates the categories if needed. Creates
Backup', 'Index', 'TroubleShooting','General Info Gathering' categories and adds
the agent jobs depending on name to the category

.PARAMETER Instance
The Instance to run the script against
#>

Param(
    [string]$Instance
)

$Categories = 'Backup', 'Index','DBCC', 'TroubleShooting', 'General Info Gathering'

$Categories.ForEach{
    ## Create Category if it doesnot exist
    If (-not  (Get-DbaAgentJobCategory -SqlInstance $instance -Category $PSItem)) {
        New-DbaAgentJobCategory -SqlInstance $instance -Category $PSItem -CategoryType LocalJob
    }
}

## Get the agent jobs and iterate through them
(Get-DbaAgentJob -SqlInstance $instance).ForEach{
    ## Depending on the name of the Job - Put it in a Job Category
    switch -Wildcard ($PSItem.Name) {
        '*DatabaseBackup*' { 
            Set-DbaAgentJob -SqlInstance $instance -Job $PSItem -Category 'Backup'
        }
        '*Index*' { 
            Set-DbaAgentJob -SqlInstance $instance -Job $PSItem -Category 'Index'
        }
        '*DatabaseIntegrity*' { 
            Set-DbaAgentJob -SqlInstance $instance -Job $PSItem -Category 'DBCC'
        }
        '*Log SP_*' { 
            Set-DbaAgentJob -SqlInstance $instance -Job $PSItem -Category 'TroubleShooting'
        }
        '*Collection*' { 
            Set-DbaAgentJob -SqlInstance $instance -Job $PSItem -Category 'General Info Gathering'
        }
        ## Otherwise put it in the uncategorised category
        Default {
            Set-DbaAgentJob -SqlInstance $instance -Job $PSItem -Category '[Uncategorized (Local)]'
        }
    }
}

You can run this script against any SQL instance by calling  it and passing an instance parameter from the command line like this

 & C:\temp\ChangeJobCategories.ps1 -instance ROB-XPS

If you wanted to see what would happen, you could edit the script and add the WhatIf parameter to every changing command but that’s not really a viable solution. What you can do is

$PSDefaultParameterValues['*:WhatIf'] = $true

this will set all commands that accept WhatIf to use the WhatIf parameter. This means that if you are using functions that you have written internally you must ensure that you write your functions to use the common parameters

Once you have set the default value for WhatIf as above, you can simply call your script and see the WhatIf output

 & C:\temp\ChangeJobCategories.ps1 -instance ROB-XPS

which will show the WhatIf output for the script

Once you have checked that everything is as you expected then you can remove the default value for the WhatIf parameter and run the script

$PSDefaultParameterValues['*:WhatIf'] = $false
& C:\temp\ChangeJobCategories.ps1 -instance ROB-XPS

and get the expected output

If you wish to see the verbose output or ask for confirmation before any change you can set those default parameters like this

## To Set Verbose output
$PSDefaultParameterValues['*:Verbose'] = $true

## To Set Confirm
$PSDefaultParameterValues['*:Confirm'] = $true

and set them back by setting to false