In the previous post I talked about the template I have for many of my PowerShell functions. And I've talked before about adding support for ShouldProcess to functions which change the state of the system ( that allows –confirm, -prompt, –whatif and –verbose switches). Since then I’ve learnt that functions can identify the level of impact they have – this ties into the $confirmPreference variable to turn confirmation prompts on without needing to specify them explicitly. Adding shouldProcess support turns my function template into this: Function Stop-VM{ [CmdletBinding( SupportsShouldProcess=$True, ConfirmImpact='High')] Param( [parameter(Mandatory = $true, ValueFromPipeline = $true)]$VM , $Server = "." ) Process{ if ($VM –is [String]) {$VM = GetVM –vm $vm –server $server} if ($VM –is [array]) {$VM | foreach-object {Stop-VM –vm $_ –server $server}} if ($VM –is [System.Management.ManagementObject] ` –and $pscmdlet.shouldProcess($vm.ElementName, "Power-Off VM without Saving")) { $vm.RequestStateChange(3) } }}
ShouldProcess
–confirm, -prompt, –whatif
–verbose
$confirmPreference
Function Stop-VM{ [CmdletBinding( SupportsShouldProcess=$True, ConfirmImpact='High')]
Param( [parameter(Mandatory = $true, ValueFromPipeline = $true)]$VM , $Server = "." )
Process{ if ($VM –is [String]) {$VM = GetVM –vm $vm –server $server} if ($VM –is [array]) {$VM | foreach-object {Stop-VM –vm $_ –server $server}} if ($VM –is [System.Management.ManagementObject] ` –and $pscmdlet.shouldProcess($vm.ElementName, "Power-Off VM without Saving")) {
$vm.RequestStateChange(3)
}
}}
There is one nuisance when dealing with more than one VM: we will get the following message for every VM
Confirm Are you sure you want to perform this action? Performing operation "Power-Off VM without Saving" on Target "London-DC". [Y] Yes [A] Yes to All [N] No [L] No to All [S] Suspend [?] Help (default is "Y"): n
even if we select “Yes to all” or “No to all”. Each recursive call to Stop-VM has its own instance of $psCMDLET. And if confirmImpact is set to High, that will cause a script to stop and prompt. Jason Shirk, one of the active guys in our internal PowerShell alias pointed out first you can have a –force switch to prevent the prompt appearing and secondly you don’t need to use the functions’ OWN instances of $psCMDLET: why not pass one instance around? So my function template morphed into the following:
Stop-VM
$psCMDLET
–force
$psCMDLET:
Function Stop-VM{ [CmdletBinding( SupportsShouldProcess=$True, ConfirmImpact='High')] Param( [parameter(Mandatory = $true, ValueFromPipeline = $true)]$VM , $Server = "." , $PSC, [Switch]$force) Process{ if ($psc -eq $null) {$psc = $pscmdlet} if (-not $PSBoundParameters.psc) {$PSBoundParameters.add("psc",$psc)} if ($VM –is [String]) {$VM = GetVM –vm $vm –server $server} if ($VM –is [array]) {[Void]$PSBoundParameters.Remove("VM") $VM | ForEach-object {Stop-Vm -VM $_ @PSBoundParameters}} if ($VM -is [System.Management.ManagementObject] ` –and ($force -or $psc.shouldProcess($vm.ElementName, "Power-Off VM without Saving"))){ $vm.RequestStateChange(3) } }}
Function Stop-VM{
[CmdletBinding( SupportsShouldProcess=$True, ConfirmImpact='High')]
Param( [parameter(Mandatory = $true, ValueFromPipeline = $true)]$VM , $Server = "." , $PSC, [Switch]$force)
Process{ if ($psc -eq $null) {$psc = $pscmdlet}
if (-not $PSBoundParameters.psc) {$PSBoundParameters.add("psc",$psc)} if ($VM –is [String]) {$VM = GetVM –vm $vm –server $server} if ($VM –is [array]) {[Void]$PSBoundParameters.Remove("VM") $VM | ForEach-object {Stop-Vm -VM $_ @PSBoundParameters}}
if ($VM -is [System.Management.ManagementObject] ` –and ($force -or $psc.shouldProcess($vm.ElementName, "Power-Off VM without Saving"))){ $vm.RequestStateChange(3) }
So now the first function called sets $PSC, which is passed to all other functions - in this case recursive calls to this function – which use it in place of their instance of $psCmdlet. And –force is there to trump everything.
$PSC
$psCmdlet