On PowerShell function design: vague can be good.
There is a problem which comes up in several places in PowerShell – that is helping the user by being vague about parameter types. Consider these examples from my Hyper-V library for PowerShell
-
The user can specify a machine using a string which contains its name
Save-VM London-DC or Save-VM \*DC, or Save-VM London\*,Paris\*
-
The user can get virtual machine objects with one command and pipe these into another command
Get-VM –Running | Stop-VM
-
The user can mix objects and strings
$MyVMs = Get-VM –server Wallace, Grommit | Where { (Get-VMSettings $_).note –match "LAB1"}
Start-VM –Wait "London-DC", $MyVMs
The last one searches servers “Wallace” and “Grommit” 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 a post I made a few days back about adding Edit to your profile I showed a couple of aspects of about piping objects that became easier in V2 of PowerShell,
Instead of writing Param($VM)
, I can now write
Mandatory=$true
makes sure I have a parameter from somewhere, and
ValueFromPipeLine
is all I need to get it from the pipeline. PowerShell offers a
ValueFromPipeLineByPropery
option which looks at the piped object for a property
which matches the parameter name or a declared alias for it. I could use that to
reduce a VM object to its name, but doing so would lose the server information
(which I need in example 3 above) and it gets in the way of piping strings into
functions, so this is not the place to use it.
Allowing an array gives me a problem if an array member expand to more than
one VM (when it’s a an with a wildcard). The code for my “Edit” function won’t cope
with being handed an array of file objects or an array of arrays, but it doesn’t
need to, because I wouldn’t work like that. But things I’m putting out for
others need to work the way different users might expect, this needs to handle
arrays in arrays (like "london-DC", $myVMs
) or arrays of VM objects ($myVMs
),
so time for my old friend recursion, and a function like this:
This says, if we got passed a single string (via the pipe or as a parameter), we
get the matching VM(s), if any.
If we were passed an array , or a string which resolved to an array, we call the function again with each member of that array.
If we were passed a single WMI object or a string which resolved to a single WMI object, then we do the work required.
There’s one thing wrong with this, and that is that it stops the VM without any warning I covered this back here.
/2009/06/25/more-powershell-a-change-you-should-make-for-v2-1.aspx
It is easy to support ShouldProcess
; there is level at which Confirm
prompts are
turned on automatically, (controlled by $confirmPreference
) and we can say that
the function’s impact is high
– and at the default value the confirm prompt will appear
even if the user doesn’t ask for it.
Nearly there, but we have two problems still to solve: and a simplification to
make the message. First the simplification; Mike Kolitz (who’s helped out on
the Hyper-V library), introduced me to this trick : when a function calls
another function using the same parameters (or calls itself recursively) - if
there are many parameters it can be a pain. But PowerShell has a “Splatting”
operator. @PsboundParameters
puts the contents of a variable into the command.
(James Brundage, who I’ve mentioned before wrote it
up.)
You can manipulate $psboundParameters
, so Mike had a clever generic way of
recursively calling functions.
In other words, remove the parameter that is being expanded, and re-call the function with the remaining parameters, specifying only the one being expanded. As James’ post shows it makes life a lot easier when you have a bunch of switches.
OK, now, the message
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"):
will appear for every
VM, even if we select Yes to all or No to all, because each time
Stop-VM
is called it gets a new instance of $pscmdlet
. And, what if we don’t
want the message at all – for example in a script which kills the VMs and rolls back to
an earlier snapshot. Jason Shirk, one of the active guys in our internal
PowerShell alias pointed out first you can have a –Force
switch and secondly you
don’t need to use the function’s OWN instance of `pscmdlet – why not pass one
instance around? So the function morphed into this
So now, $PSC
either gets passed in as a parameter or it picks up $pscmdlet
and then gets passed on
to anything else we call – in this case recursive calls to the same function. And
–Force
is there to trump everything. And that’s what I have implemented in
dozens of places in my library.
This post originally appeared on my technet blog.