Powershell scripting: recommended way to implement ShouldProcess when function calls are nested? Powershell scripting: recommended way to implement ShouldProcess when function calls are nested? powershell powershell

Powershell scripting: recommended way to implement ShouldProcess when function calls are nested?


You can't really refer to $WhatIf or $Verbose since these are synthesized for you i.e. these variables don't exist in your function. If the user specifies them then you can get at them via $PSBoundParameters but if the user didn't specify then obviously they won't be in this hashtable.

When you pass a value to a switch PowerShell will do the typical coercion process to attempt to convert the specified value to a bool. Since $whatif isn't defined this evals to a $null which results in the switch value being set to $true. This is presumably because it sees the switch is explicitly specified with effectively no value which is the equivalent of just specifying -Whatif with no value. You can see this when you trace the parameter binding:

function Foo{    [CmdletBinding(SupportsShouldProcess=1)]    param()    Process    {        $PSBoundParameters    }}Trace-Command -name ParameterBinding -expr {Foo -whatif:$xyzzy} -PSHostDEBUG: BIND NAMED cmd line args [Foo]DEBUG:   BIND arg [] to parameter [WhatIf]DEBUG:     COERCE arg to [System.Management.Automation.SwitchParameter]DEBUG:       Arg is null or not present, type is SWITCHPARAMTER, value is true.DEBUG:         BIND arg [True] to param [WhatIf] SUCCESSFULDEBUG: BIND POSITIONAL cmd line args [Foo]DEBUG: MANDATORY PARAMETER CHECK on cmdlet [Foo]DEBUG: CALLING BeginProcessingDEBUG: CALLING EndProcessing

The $WhatIfPreference and $VerbosePreference gets set appropriately in outer based on whether outer was called with -verbose or -whatif. I can see that those values propagate to inner just fine. It would seem that there is a PowerShell bug with $pscmdlet.ShouldProcess. It doesn't seem to be honoring the value of $VerbosePreference in this case. You could try passing through -Verbose to inner like so:

inner VerbosePassthru -Verbose:($VerbosePreference -eq 'Continue')

Another option is to use Get-Variable -Scope like so:

function Outer{    [CmdletBinding(SupportsShouldProcess=1)]    param()    Process    {        $pscmdlet.ShouldProcess("Outer process", '') > $null        inner        #inner -Verbose:($VerbosePreference -eq 'Continue')    }}function Inner{    [CmdletBinding(SupportsShouldProcess=1)]    param()    Process    {        $pscmdlet = (Get-Variable -Scope 1 -Name PSCmdlet).Value        $pscmdlet.ShouldProcess("Inner process", '') > $null        "Inner $VerbosePreference"    }}Outer -Verbose

I'm not sure I like this because it implies that you know outer is 1 level above inner. You could "walk" the scope stack looking for the next PSCmdlet variable up the stack. This effectively gets rid of having to pass in PSCmdlet (which is gross) but it's still a hack. You should consider filing a bug on MS Connect about this.


I was looking to write exactly the same question, and I am writing this almost 7 years later. I am surprised that Microsoft's PowerShell team have not fixed this yet. I have reproduced the issue with PowerShell Version 6 Preview (latest version).

I have come up with a simple workaround, that is, inside the Inner function, we create and run a scriptblock, setting the -Verbose flag by checking $VerbosePreference which is correctly set to Continue, even though it is not respected by ShouldProcess:

Function Outer {    [CmdletBinding(SupportsShouldProcess=$true, ConfirmImpact="Medium")]    param([string]$Name)    Process {        Write-Host "Outer called";        Inner $Name    }}Function Inner {    [CmdletBinding(SupportsShouldProcess=$true, ConfirmImpact="Medium")]    param([string]$Name)    Process {        if (-not ($PSBoundParameters.ContainsKey('Verbose'))) {            $PSBoundParameters.Add('Verbose', [bool]$VerbosePreference -eq 'Continue');        }        & {            [CmdletBinding(SupportsShouldProcess=$true, ConfirmImpact="Medium")]            param([string]$Name)            if ($PSCmdlet.ShouldProcess($Name, "Inner")) {                Write-Host "Inner called";            }        } @PSBoundParameters;    }}Export-ModuleMember *