Parameters in PowerShell functions: vague is good.
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
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
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.
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.
- First if the functions was passed a single string (via the pipe or as a parameter), it gets the matching VM(s), if any.
- If passed an array , or a string which resolved to an array, it calls itself recursively with each member of that array. Mike Kolitz (who’s helped out on the Hyper-V library), showed me a trick to simplify the process of a function calling another function using the same parameters – (or calling itself recursively). This uses the “Splatting” operator:
@$PsboundParameters
puts the contents of a variable into the command (James Brundage wrote it up, here .) Mike’s trick was to manipulate$psboundParameters
in the same way for all recursive calls so they didn’t have to worry about the details of Parameters (previously it was necessary to call functions differently depending on the parameters passed) , so two lines go into the template and only the function name changes each time it is used. - Finally If passed a single WMI object or a string which resolved to a single WMI object then it does whatever work is required. Having started to look at PowerShell remoting I’m starting to think I should check the .__CLASS property not the type of the object but the principle is the same either way.
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.
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.