How NOT to propagate -WhatIf and -Confirm?

Viewed 959

It seems the default behaviour of Powershell is to propagate -WhatIf and -Confirm to all functions called from within a function that was called with -WhatIf or -Confirm.

But what if (pun intended) I don't want that ? What if all I want is to protect a bit of code that performs an actual change on my system and not the rest of my code ?

Here is an example:

function Set-Something {
    [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'Medium')]
    [OutputType([string])] 

    Param (
        [Parameter(Mandatory = $false, ValueFromPipeline = $true)]
        [string] $ComputerName = $env:COMPUTERNAME
    )

    Process {
        New-Item -Path "beforeChange" -Type File | Out-Null
        Remove-Item -Path "beforeChange" -ErrorAction SilentlyContinue

        # Here is the bit of code that needs to be "protected"
        if ($PSCmdlet.ShouldProcess($ComputerName, "Do something")) {
            # Actually do something on $ComputerName
            New-Item -Path "inChange" -Type File | Out-Null
            Remove-Item -Path "inChange" -ErrorAction SilentlyContinue
        }

        New-Item -Path "afterChange" -Type File | Out-Null
        Remove-Item -Path "afterChange" -ErrorAction SilentlyContinue
    }
}

"-"*50 + "Set-Something -WhatIf" + "-"*50 
Set-Something -WhatIf
"-"*50 + "Set-Something -Confirm" + "-"*50 
Set-Something -Confirm

The Set-Something function only performs system-altering stuff in the protected bit. It also uses temporary files, but New-Item and Remove-Item shouldn't trigger WhatIf or Confirm. Unfortunately that's not how it works:

--------------------------------------------------Set-Something -WhatIf--------------------------------------------------
What if: Performing the operation "Create File" on target "Destination: C:\Users\GO7\beforeChange".
What if: Performing the operation "Do something" on target "GEOCED".
What if: Performing the operation "Create File" on target "Destination: C:\Users\GO7\afterChange".

I could add -WhatIf:$false -Confirm:$false to each and every function that supports ShouldProcess but that would be tedious.

So What I've done so far is :

  1. save the $WhatIfPreference and $ConfirmPreference at the top of my function
  2. reset those preferences to their default value
  3. restore the saved preferences before the protected block
  4. reset the preferences again inside the block and outside the block

Like so:

function Set-Something2 {
    [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'Medium')]
    [OutputType([string])] 

    Param (
        [Parameter(Mandatory = $false, ValueFromPipeline = $true)]
        [string] $ComputerName = $env:COMPUTERNAME
    )

    Begin {
        # Save the ShouldProcess parameters for later use
        $WhatIfPref = $WhatIfPreference
        $ConfirmPref = $ConfirmPreference

        # Reset ShouldProcess parameters to their default value
        $WhatIfPreference = 0
        $ConfirmPreference = 'High'
    }

    Process {
        New-Item -Path "beforeChange" -Type File | Out-Null
        Remove-Item -Path "beforeChange" -ErrorAction SilentlyContinue

        # We restore the previously saved ShouldProcess parameters just before the "protected" bit of code
        $WhatIfPreference = $WhatIfPref
        $ConfirmPreference = $ConfirmPref

        # Here is the bit of code that needs to be "protected"
        if ($PSCmdlet.ShouldProcess($ComputerName, "Do something")) {
            # Inside the protected bit, we reset ShouldProcess parameters to their default value
            $WhatIfPreference = 0
            $ConfirmPreference = 'High'

            # Actually do something on $ComputerName
            New-Item -Path "inChange" -Type File | Out-Null
            Remove-Item -Path "inChange" -ErrorAction SilentlyContinue
        }

        # After the protected bit, we reset ShouldProcess parameters to their default value
        $WhatIfPreference = 0
        $ConfirmPreference = 'High'

        New-Item -Path "afterChange" -Type File | Out-Null
        Remove-Item -Path "afterChange" -ErrorAction SilentlyContinue
    }
}

"-"*50 + "Set-Something2 -WhatIf" + "-"*50 
Set-Something2 -WhatIf
"-"*50 + "Set-Something2 -Confirm" + "-"*50 
Set-Something2 -Confirm

The result is as I want it:

--------------------------------------------------Set-Something2 -WhatIf--------------------------------------------------
What if: Performing the operation "Do something" on target "GEOCED".
--------------------------------------------------Set-Something2 -Confirm--------------------------------------------------

Confirm
Are you sure you want to perform this action?
Performing the operation "Do something" on target "GEOCED".
[Y] Yes  [A] Yes to All  [N] No  [L] No to All  [S] Suspend  [?] Help (default is "Y"):

But is that how I'm supposed to do it ? I don't find it very elegant...

2 Answers

To work around this on specific functions where I didn't want -WhatIf to be passed down I set it explicitly to $false.

For example from an Azure script which needs to set a subscription context so later read-only queries work properly:

Set-AzContext -Subscription $Subscription -WhatIf:$false

This prevents Set-AzContext from inheriting the -WhatIf from my main script but still allows me to selectively use $PSCmdlet.ShouldProcess() later on for more dangerous changes.

Related