I have a PowerShell script cmdlet that supports the -WhatIf
& -Confirm
parameters.
It does this by calling the $PSCmdlet.ShouldProcess()
method before performing the change.
The problem I have is that my Cmdlet is implemented by calling other Cmdlets and the -WhatIf
or -Confirm
parameters are not passed along to the Cmdlets I invoke.
How can I pass along the values of -WhatIf
and -Confirm
to the Cmdlets I call from my Cmdlet?
For example, if my Cmdlet is Stop-CompanyXyzServices
and it uses Stop-Service
to implement its action.
If -WhatIf
is passed to Stop-CompanyXyzServices
I want it to also be passed t开发者_开发技巧o Stop-Service.
Is this possible?
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'.
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.
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.
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)
}
}
}
Just so you wont get run around the block for hours by this question and the answers here, I would suggest that you read this article instead:
https://powershellexplained.com/2020-03-15-Powershell-shouldprocess-whatif-confirm-shouldcontinue-everything/#suppressing-nested-confirm-prompts
The answers presented here does not work for many cases and I see a danger in people implementing the answers here, without understanding the fundamentals.
Here is how a hacked it to work across scriptmodules:
精彩评论