Powershell: Enable-NetFirewallRule doesn't respect -WhatIf when called from advanced function

Created on 4 Apr 2018  路  7Comments  路  Source: PowerShell/PowerShell

Steps to reproduce

function TestEnableNetFirewallRule() {
    [CmdLetBinding(SupportsShouldProcess=$true)]
    param ([string]$DisplayGroup)

    Enable-NetFirewallRule -DisplayGroup $DisplayGroup
 }

 TestEnableNetFirewallRule -DisplayGroup "Remote Desktop" -WhatIf

Expected behavior

The firewall rule is not enabled, and the same output as when calling Enable-NetFirewallRule -DisplayGroup "Remote Desktop" -WhatIf:

What if: Enable-NetFirewallRule DisplayName: RemoteDesktop-Shadow-In-TCP
What if: Enable-NetFirewallRule DisplayName: RemoteDesktop-UserMode-In-UDP
What if: Enable-NetFirewallRule DisplayName: RemoteDesktop-UserMode-In-TCP

Actual behavior

No output, and the firewall rule for Remote Desktop is enabled

Environment data

> $PSVersionTable
Name                           Value
----                           -----
PSVersion                      5.1.16299.251
PSEdition                      Desktop
PSCompatibleVersions           {1.0, 2.0, 3.0, 4.0...}
BuildVersion                   10.0.16299.251
CLRVersion                     4.0.30319.42000
WSManStackVersion              3.0
PSRemotingProtocolVersion      2.3
SerializationVersion           1.1.0.1
Issue-Question Resolution-Answered

All 7 comments

$WhatIfPreference variable do not cross module boundaries:

New-Module {
    Set-StrictMode -Version Latest
    function f {
        [CmdletBinding(SupportsShouldProcess)]param()
        $WhatIfPreference # True
        $global:WhatIfPreference # False
        $local:WhatIfPreference # True
        g
    }
} | Out-Null
New-Module {
    Set-StrictMode -Version Latest
    function g {
        [CmdletBinding()]param()
        $WhatIfPreference # False
        $global:WhatIfPreference # False
        $local:WhatIfPreference # Error
    }
} | Out-Null
$WhatIfPreference # False
f -WhatIf

To complement @PetSerAl's helpful example with additional information:

Enable-NetFirewallRule is defined in a module and implemented as a (PowerShell) _function_ rather than as a compiled cmdlet.

Unfortunately, as @PetSerAl's example shows, functions imported from modules do not see (implicitly set) preference variables from the callers's scope (unless that scope happens to be the global one).

In the case at hand, your TestEnableNetFirewallRule function's [CmdLetBinding(SupportsShouldProcess)] attribute causes PowerShell to translate the -WhatIf switch into a _local_ $WhatIfPreference variable with (effective) value $True (it is technically a [switch] instance whose .IsPresent property reports $True and therefore acts like $True in a Boolean context).

However, due to Enable-NetFirewallRule living in a (different) _module_, it doesn't see TestEnableNetFirewallRule's local $WhatIfPreference value and therefore doesn't honor the original -WhatIf switch.

This problematic behavior is the subject of #4568.

Thank you for the in-depth explanation @mklement0. My knowledge of PowerShell is rather limited, and this difference in behavior between functions and compiled cmdlets was totally unknown to me (I guess I was just lucky when I posted #6342 a month ago, that it was an actual bug and not just a function/cmdlet issue).

From what I can read from #4568 which you mention, there doesn't seem to be any easy and straight-forward way of telling whether -WhatIf will behave as "expected" or not. So I guess the only thing I can do is to assume that it never will, and always test it? In any case it's an interesting discussion going on in the referenced issue thread.

@juliank: Thanks for the follow-up:

6342 is indeed a manifestation of the same problem after all, as I've only just now realized.

If you do agree that #4568 is a problem, I encourage you to give it a thumbs-up - surprisingly few people have, so far.

So I guess the only thing I can do is to assume that it never will, and always test it?

Yes - unfortunately; to spell out the cumbersome workaround, using splatting:

function TestEnableNetFirewallRule() {
    [CmdLetBinding(SupportsShouldProcess=$true)]
    param ([string]$DisplayGroup)

    $htWhatIfPassThru = @{ WhatIf = $WhatIfPreference }

    Enable-NetFirewallRule -DisplayGroup $DisplayGroup @htWhatIfPassThru
 }

Note that the above just covers -WhatIf; to cover all common parameters, you'd have to apply the same logic to all of them.

Thank you for the suggested workaround @mklement0. Splatting was new to me, so I would have gone for the even more cumbersome workaround surrounding my call to Enable-NetFirewallRule using if (ShouldProcess(..., "...")). And if I read it correctly, it is easily adjustable to also account for -Confirm, which will cover my scenarios in this case.

@juliank: Yes, it should work analogously for -Confirm / $ConfirmPreference.

This issue has been marked as answered and has not had any activity for 1 day. It has been closed for housekeeping purposes.

Was this page helpful?
0 / 5 - 0 ratings