How do you support PowerShell's -WhatIf & -Confirm parameters in a Cmdlet that calls other Cmdlets?

后端 未结 4 438
隐瞒了意图╮
隐瞒了意图╮ 2020-12-14 01:42

I have a PowerShell script cmdlet that supports the -WhatIf & -Confirm parameters.

It does this by calling the $PSCmdlet.Should

相关标签:
4条回答
  • 2020-12-14 02:14

    Updated per @manojlds comment

    Cast $WhatIf and $Confirm to Boolean and pass the values to the the underlying cmdlet:

    function Stop-CompanyXyzServices
    {
        [CmdletBinding(SupportsShouldProcess=$true,ConfirmImpact='High')]
    
        Param(
            [Parameter(
                Position=0,
                ValueFromPipeline=$true,
                ValueFromPipelineByPropertyName=$true
            )]      
            [string]$Name
        )
    
    
        process
        {
            if($PSCmdlet.ShouldProcess($env:COMPUTERNAME,"Stop service '$Name'"))
            {                   
                Stop-Service $name -WhatIf:([bool]$WhatIf) -Confirm:([bool]$confirm)
            }                       
        }
    }
    
    0 讨论(0)
  • 2020-12-14 02:21

    Here is a complete solution based on @Rynant and @Shay Levy's answers:

    function Stop-CompanyXyzServices
    {
        [CmdletBinding(SupportsShouldProcess=$true,ConfirmImpact='Medium')]
    
        Param(
            [Parameter(
                Position=0,
                ValueFromPipeline=$true,
                ValueFromPipelineByPropertyName=$true
            )]      
            [string]$Name
        )
    
        process
        {
            if($PSCmdlet.ShouldProcess($env:COMPUTERNAME,"Stop XYZ services '$Name'")){  
                ActualCmdletProcess
            }
            if([bool]$WhatIfPreference.IsPresent){
                ActualCmdletProcess
            }
        }
    }
    
    function ActualCmdletProcess{
    # add here the actual logic of your cmdlet, and any call to other cmdlets
    Stop-Service $name -WhatIf:([bool]$WhatIfPreference.IsPresent) -Confirm:("Low","Medium" -contains $ConfirmPreference)
    }
    

    We have to see if -WhatIf is passed separately as well so that the whatif can be passed on to the individual cmdlets. ActualCmdletProcess is basically a refactoring so that you don't call the same set of commands again just for the WhatIf. Hope this helps someone.

    0 讨论(0)
  • 2020-12-14 02:31

    Passing parameters explicitly

    You can pass the -WhatIf and -Confirm parameters with the $WhatIfPreference and $ConfirmPreference variables. The following example achieves this with parameter splatting:

    if($ConfirmPreference -eq 'Low') {$conf = @{Confirm = $true}}
    
    StopService MyService -WhatIf:([bool]$WhatIfPreference.IsPresent) @conf
    

    $WhatIfPreference.IsPresent will be True if the -WhatIf switch is used on the containing function. Using the -Confirm switch on the containing function temporarily sets $ConfirmPreference to low.

    Passing parameters implicitly

    Since the -Confirm and -WhatIf temporarily set the $ConfirmPreference and $WhatIfPreference variables automatically, is it even necessary to pass them?

    Consider the example:

    function ShouldTestCallee {
        [cmdletBinding(SupportsShouldProcess=$true,ConfirmImpact='Medium')] 
        param($test)
    
        $PSCmdlet.ShouldProcess($env:COMPUTERNAME,"Confirm?")
    }
    
    
    function ShouldTestCaller {
        [cmdletBinding(SupportsShouldProcess=$true)]
        param($test)
    
        ShouldTestCallee
    }
    
    $ConfirmPreference = 'High'
    ShouldTestCaller
    ShouldTestCaller -Confirm
    

    ShouldTestCaller results in True from ShouldProcess()

    ShouldTestCaller -Confirm results in an confirm prompt even though I didn't pass the switch.

    Edit

    @manojlds answer made me realize that my solution was always setting $ConfirmPreference to 'Low' or 'High'. I have updated my code to only set the -Confirm switch if the confirm preference is 'Low'.

    0 讨论(0)
  • 2020-12-14 02:37

    After some googling I came up with a good solution for passing common parameters along to called commands. You can use the @ splatting operator to pass along all the parameters that were passed to your command. For example, if

    Start-Service -Name ServiceAbc @PSBoundParameters

    is in the body of your script powershell will pass all the parameters that were passed to your script to the Start-Service command. The only problem is that if your script contains say a -Name parameter it will be passed too and PowerShell will complain that you included the -Name parameter twice. I wrote the following function to copy all the common parameters to a new dictionary and then I splat that.

    function Select-BoundCommonParameters
    {
        [CmdletBinding()]
        param(
            [Parameter(Mandatory=$true)]
            $BoundParameters
        )
        begin
        {
            $boundCommonParameters = New-Object -TypeName 'System.Collections.Generic.Dictionary[string, [Object]]'
        }
        process
        {
            $BoundParameters.GetEnumerator() |
                Where-Object { $_.Key -match 'Debug|ErrorAction|ErrorVariable|WarningAction|WarningVariable|Verbose' } |
                ForEach-Object { $boundCommonParameters.Add($_.Key, $_.Value) }
    
            $boundCommonParameters
        }
    }
    

    The end result is you pass parameters like -Verbose along to the commands called in your script and they honor the callers intention.

    0 讨论(0)
提交回复
热议问题