A couple of weeks ago, Adam Bertram wrote a post which got me really excited. As an aside, if you’re not following Adam you’re missing out. He posts ridiculously often and writes high quality posts about all kinds of cool stuff. I’m writing about one such post.
Anyway, his post was about using the PSTypeName property of PSCustomObjects and the PSTypeName() parameter attribute to restrict function parameters based on a “fake” type. By “fake”, I mean that there isn’t necessarily a .NET type with a matching name, and even if there was, these objects aren’t those types. An example might help:
First, by including a special property called PSTypeName in the PSCustomObject, the object “becomes” that type.
$obj=[pscustomobject]@{PSTypeName='Mike'; DisplayName='My custom object'} $obj | Get-Member
So now we have an object which thinks it is of type “Mike”.
Using the PSTypeName() parameter attribute, we can create a parameter which only accepts objects of that type!
function Get-DisplayName{ param([PSTypeName('Mike')]$obj) $obj.DisplayName }
Calling this function with our object and a string shows that it only accepted our object.
The text of the error is as follows:
Get-DisplayName : Cannot bind argument to parameter ‘obj’, because PSTypeNames of the argument do not match the PSTypeName
required by the parameter: Mike.
It’s probably worth mentioning that we can’t just put [Mike] as the parameter type. PowerShell really wants “real” types in that situation.
So, now we can easily create our own types of objects without dealing with C# or PowerShell classes, and still get the “type” validated in a function parameter….Sweet!
But wait! Back in 2009, I wrote about modifying objects in order to get special formatting or special new members from PS1XML files, through PowerShell’s awesome Extended Type System. I did that by looking at the PSObject underlying the object (all objects in PowerShell do this), and inserting a value into the PSTypeNames array.
It turns out that the PSTypeName() parameter attribute recognizes objects that are “customized” this way as well:
$obj2=get-service | select-object -first 1 $obj2.PSOBject.TypeNames.Insert(0,'MIKE') $obj2 | Get-Member
Now that we have the object (which is really a ServiceController object, but we told it to pretend to be a Mike), let’s try it in the Get-DisplayName function we wrote above:
So, we can use the PSTypeNames() parameter attribute for any object which has had its PSTypeNames() fiddled with. That’s great for me, because I get a lot of objects from a database, so they’re really DataRow objects. I add an entry to PSTypeNames to help represent what kind of objects they came from (e.g. what table or query they came from) but until I found out about this I was stuck using ValidateScript() to check parameters.
What do you think? Is this something you can see a use for?
Let me know in the comments or on social media.
–Mike
P.S. Adam says that Kirk Munro was the original source of this information. He’s awesome too, so I’m not really surprised.
P.P.S…Kirk commented below that Oisín Grehan and Jason Shirk were his sources in this. Great teamwork in the PowerShell community!
Giving credit where credit is due, part of this tip came to me from another PowerShell MVP, Oisín Grehan (using a PSTypeName attribute for parameters). The other part came a member of the PowerShell team (Jason Shirk IIRC).
Thanks Kirk! I’ll add two more names (good ones, btw) to the list.
This is pretty neat indeed! 🙂
This problem get’s solved in when using PowerShell classes 🙂