It is no secret that PowerCLI has lots of amazing options and well-thought trough features. But there is one feature that most of PowerCLI’s users take for granted, and most probably do not even realise that they are using what is called Object By Name or OBN. In this post I’ll show you one way of creating your own OBN, a home made Object By Name, which you can use in your own functions and modules.
OBN allows you to refer to a PowerCLI object by name, instead of passing an actual PowerCLI object. A classic example is “Get-VM -Datastore DS1“, where we are retrieving all VMs that live on a specific datastore. If one looks at the description of the Datastore parameter, it clearly states that a value of type StorageResource is expected, but we are able to provide the datastorename, a string. Under the cover, PowerCLI converts this string to the required PowerCLI object for the parameter.
When we are writing our own functions, it would be very handy to have the same functionality at our disposal. Define a parameter to be of the type of a PowerCLI object, but then be able to pass the name of the object, instead of the object itself.
The solution is here, with the MyOBN attribute. We now have the same functionality available, that was until now only available for PowerCLI cmdlets.
Update September 14th 2017
- Added support for VIServer
- Added support for arrays of objects
- Fixed an issue with the VirtualMachine object
Some History
To have a better understanding, I’ll show some example functions.
The first function, Invoke-Test1, uses a Datastore parameter, that accepts a PowerCLI Datastore object.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
Function Invoke-Test1{ param( [VMware.VimAutomation.ViCore.Types.V1.DatastoreManagement.Datastore]$Datastore ) Process{ $Datastore | Select Name,CapacityGB } } $dsName = 'DS1' $ds = Get-Datastore -Name $dsName Invoke-Test1 -Datastore $ds |
If we execute this, we get the Name of the VMs that live on the datastore.
But if we try to use the name of the datastore, as we are used to doing with the Datastore parameter on the Get-VM cmdlet for example, we get an error.
To get our function to behave more like OBN on the PowerCLI cmdlets, we can define the parameter as a general PSObject object. And perform the type casting inside the function. Something like this.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
Function Invoke-Test2{ param( [PSObject]$Datastore ) Process{ if($Datastore -is [System.String]){ $Datastore = Get-Datastore -Name $Datastore } Get-VM -Datastore $Datastore | select Name } } $dsName = 'DS1' $ds = Get-Datastore -Name $dsName Invoke-Test2 -Datastore $ds |
Our function now works with a Datastore object
but also with a String.
Note that this way of simulating the OBN functionality is far from fool-proof. There is for example no strict type checking, we could pass a VirtualMachine object on the Datastore parameter, and our function would throw an error.
When I first read about Custom Attributes, in a blog post, named Powershell: Creating and using custom attributes, from Kevin Marquette, I immediately saw the potential to get one step closer to a proper OBN in my functions.
For now, I have implemented the Attribute as a class, which implies it’s use is limited to PowerShell v5 or higher.
The Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
#requires -Version 5.0 class MyOBN:System.Management.Automation.ArgumentTransformationAttribute { [ValidateSet( 'Cluster','Datacenter','Datastore','DatastoreCluster','Folder', 'VirtualMachine','VirtualSwitch','VMHost','VIServer' )] [String]$Type MyOBN([string]$Type) { $this.Type = $Type } [object] Transform([System.Management.Automation.EngineIntrinsics]$engineIntrinsics,[object]$inputData) { if ($inputData -is [string]) { if (-NOT [string]::IsNullOrWhiteSpace( $inputData )) { $cmdParam = "-$(if($this.Type -eq 'VIServer'){'Server'}else{'Name'}) $($inputData)" $sCmd = @{ Command = "Get-$($this.Type.Replace('VirtualMachine','VM')) $($cmdParam)" } return (Invoke-Expression @sCmd) } } elseif($inputData.GetType().Name -match "$($this.Type)Impl") { return $inputData } elseif($inputData.GetType().Name -eq 'Object[]') { return ($inputData | %{ if($_ -is [String]) { return (Invoke-Expression -Command "Get-$($this.Type.Replace('VirtualMachine','VM')) -Name `$_") } elseif($_.GetType().Name -match "$($this.Type)Impl") { $_ } }) } throw [System.IO.FileNotFoundException]::New() } } |
Annotations
Line 1: this code requires PowerShell v5 or up
Line 3: we base the MyOBN class on the ArgumentTransformationAttribute class. We inherit the Transform method from this class. Our override of the Transform method will contain the logic to simulate the OBN functionality.
Line 5-8: currently the Transform method in the MyOBN class supports these PowerCLI types.
Line 11: We need a way to tell the Transform method, which objecttype we want to use. That’s why we add a property Type to the MyOBN class.
Line 9-12: the class has a constructor which allows us to initiate the Type property.
Line 13-44: the Transform method override is where all the magic happens
Line 15: we need to detect if the parameter is a String, in other words if the parameter was passed as an “Object By Name”.
Line 21: with the help of the Type property we invoke the correct PowerCLI cmdlet to convert the String to a PowerCLI obejct
Line 26-29: if the value passed to the parameter was already a PowerCLI object, we just pass that object along
Line 30-42: These lines handle the request for an array of supported objects
Sample Runs
To use this new parameter attribute is quite straightforward. Make sure the MyOBN class is loaded (this can be through dot-sourcing, adding it to your profile or just including it in your .ps1 file).
Then add the MyOBN attribute like any other parameter attribute. Make sure to specify the “Type” for which you want MyOBN to work. Like this
1 2 3 4 5 6 7 8 9 10 11 |
function Invoke-Test3{ [CmdletBinding()] param( [MyOBN('Datastore')] [VMware.VimAutomation.ViCore.Types.V1.DatastoreManagement.Datastore]$Datastore ) Process{ Get-VM -Datastore $Datastore } } |
We can now call our function with a Datastore object
but also with a String that contains the name of the Datastore.
And all that without adding any extra code in our function, besides the MyOBN attribute.
Enjoy!
Erwan
Hi Luc,
Thanks for those informations!
I guess I did not completly understand how type’s transformation works because I facing an issue with arrays…
In your example how could you manage the transformation of an array of datastores ?
Regards,
LucD
Hi Erwan,
You are seeing it correctly, currently the MyOBN attribute doesn’t accept arrays I’m afraid.
The only way around is to use a wrapper function that will call the actual function for each object in the array.
I’m currently testing some possibilities to support arrays.