There is a theme which I’ve found has come up in several places with PowerShell. Giving flexibility to the user can mean being vague about parameters – or at least not being excessively precise. Consider these examples from my Hyper-V library for PowerShell
1. The user can specify one or more virtual machine(s) using one or more string(s) which contains the name(s) Save-VM London-DC or Save-VM *DC or Save-VM London*,Paris* 2. The user can get virtual machine objects with one command and pipe these into another command Get-vm –running | Stop-VM
Save-VM London-DC
Save-VM *DC
Save-VM London*,Paris*
Get-vm –running | Stop-VM
3. The user can mix Objects and strings $MyVms = Get-vm –server Wallace,Gromit | where { (Get-VMSettings $_).note –match “LAB1”} start-vm –wait “London-DC”, $MyVMs
$MyVms = Get-vm –server Wallace,Gromit | where { (Get-VMSettings $_).note –match “LAB1”} start-vm –wait “London-DC”, $MyVMs
The last one searches servers “Wallace” and “Gromit” for VMs, narrows the list to those used in lab1 and starts London-DC on the local server followed by the VMs in Lab1.
In an earlier post I showed a couple some things about how parameters changed for V2 of PowerShell instead of writing Param($VM) , I can now write Param( [parameter(Mandatory = $true, ValueFromPipeline = $true)]$VM ) which ensures the parameter is present and saves me the work that was needed in V1 to pick up a piped object. I’m now realizing there is risk of being too prescriptive with the parameter: V2 allows 8 different validation attributes, in addition to the data type which was there in V1. In my Hyper-V library I ended up with a lot of functions designed like this one.
Param($VM)
Param( [parameter(Mandatory = $true, ValueFromPipeline = $true)]$VM )
Function Stop-VM{ Param( [parameter(Mandatory = $true, ValueFromPipeline = $true)]$VM, $Server = ".") Process{ 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]) { #Do the work of the function $vm.RequestStateChange(3) } }}
Function Stop-VM{ Param( [parameter(Mandatory = $true, ValueFromPipeline = $true)]$VM,
$Server = ".")
Process{ 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]) { #Do the work of the function $vm.RequestStateChange(3) }
}}
The 3 if statements give me the ability to work with all the permuations.
@$PsboundParameters
$psboundParameters
It’s pretty clear that specifying a type for $VM will be a hindrance; I know some people would want to use the [ValidateNotNullOrEmpty] attribute for it and I think that’s a mistake too: if I do $MyVms = Get-vm –running ; stop-vm –wait $MyVMs ; that shouldn’t error if no VMs are running and Stop-VM is handed an empty list. Similarly I specified in the Hyper-V library that $Server must be a single string: actually it will work perfectly well with an array of strings, I decided to go back and remove any validation from the parameter.
$MyVms = Get-vm –running ; stop-vm –wait $MyVMs
$Server
The logic I am now working to says if a parameter is taken by one function with the sole intent of passing it to a another, leave the validation to the next function. Philosophically “proper” programmers tend to think that every function should validate its parameters – those who write quick scripts tend to be happier about being loose with validation and error trapping.