The VM estate that I manage is large: there are more than 20 different clusters and over 300 hosts of varying ages and hardware levels – as a consequence there are various different versions of ESX and ESXi running. Upgrading the hosts is somewhat akin to painting the Forth Bridge, a never-ending task. So keeping the thousands of VMs at the correct hardware and VMtools versions can be a bit of a losing battle.
What if I want a report showing me all the VM names, Hardware Version, VMtools Status, Tools Version Status and Tools Version – how do I go about finding that?
Firstly, finding the right data involves building up your query, and this is the process I typically follow. I know that the VM tools, and the VM hardware both relate to the VM itself, so I want to get a VM object (for this example I’ll use a vCOPs “UI VM”). It all starts with a simple “get” – Get-VM:
Get-VM “UI VM”
I have to stress that this first simple query is the foundation for the whole solution, because once we have built the query we can send multiple VMs through the pipeline. If I want to get all VMs in a Location, I can use the –Location parameter. In this example I’ll use the VApp “vCOPs”, but you can supply a ResourcePool, VApp, VMHost, Folder, Cluster or Datacenter. You could also use “Get-VM” on it’s own to get any VM on the connected vCenter.
Get-VM –Location “vCOPs”
Great! I can see some VMs – but that’s only showing a couple of properties by default and there are a lot more to see. The easiest way to see all of them is to use the Select-Object command – I’ll pipe the output of Get-VM into the Select-Object command, which has been shortened to “Select”, and the “*” indicates I want to select everything.
Get-VM “UI VM” | Select *
The results show a lot more data: including a “Version” property! I can see this VM Version is labelled “v7”, so there’s our first bit of info. What we can’t see here is our VMtools status, but that’s definitely something I’d expect to be part of the VM’s information, so we can use a tool called “Get-View” to search for any information relating to the VM object we’ve retrieved.
So, I still want to “Get-VM” and I still want just one VM for now. I can pipe that into the “Get-View” command, and then again I want to select every property the object can show me:
Get-VM “UI VM” | Get-View | Select *
You can see now that the object returned is a whole list of objects that relate to the VM we asked for a view of. The property “Compatibility” is actually an object of type “VMware.Vim.VirtualMachineCapability” that is contained within the view object I just created.
I can examine the objects individually to see what information they contain, so I’m going to take a guess at the “Guest” property as it looks like it will hold the information I want to see. This is similar to before so we can build up the query – I still want to Get-VM and I still want to Get-View of that VM, but this time I want to assign that to a variable “$MyVM”. I don’t need to select because it’ll just assign the whole object to the variable by default.
$MyVM = Get-VM “UI VM” | Get-View
Now I can interrogate the object’s properties by piping them into a Select-Object command:
$MyVM.Guest | Select *
Jackpot! Now I can see ToolsStatus, ToolsVersionStatus, ToolsVersion! (Don’t worry about the funky version number, this is an appliance and the tools are a 3rd party version). This is great, now I can narrow the query down to the precise objects I want to see:
$MyVM.Guest | Select ToolsStatus, ToolsVersionStatus, ToolsVersion
OK, now it’s going to get a little more complicated – bear with me!
Because I want to audit 2000 VMs, I don’t want to create a $MyVM object for each one – it’s just not practical, would defeat the object of automating, and would be no fun either. This is where PowerShell’s pipeline comes into it’s own, but we need to understand one last way of using the select command.
If I want to evaluate an expression and put it’s returned value into the properties I’m listing out, I need to create a name/expression pair in the select command. It looks like this:
Select @{Name=”My Custom Name”; Expression={ <My Expression> }}
This will output a column called “My Custom Name” and populate it with the results of any expression I put in the expression field. How does this look in real life? Back to my original query – I want the VM name, Hardware Version, VMtools Status, Tools Version Status and Tools Version to audit the estate.
As ever it starts with the Get-VM, then Get-View of that VM. Next I want to select those specific properties of the view that have my required info, so I can select the Name property – that’s a basic select.
Get-VM “UI VM” | Get-View | Select Name
Next I want to query the “Config” property of the view (this contains the VM hardware version) and put that into a variable. the “$_” is called a back reference and refers to the object that was passed through the pipeline, in this case the View of the VM we originally queried. So the expression becomes $_.Config.Version.
@{Name=”Hardware Version”; Expression={$_.Config.Version}}
Still with me?
Next we want to select the properties we looked at earlier: ToolsStatus, ToolsVersionStatus, ToolsVersion. I know that they’re in the “Guest” property object contained in the “View” object, so I can construct the Select queries in exactly the same way:
@{Name=”Tools Status”; Expression={$_.Guest.ToolsStatus}} @{Name=”Tools Version Status”; Expression={$_.Guest.ToolsVersionStatus}} @{Name=”Tools Version”; Expression={$_.Guest.ToolsVersion}}
Now I can string it all together to create my query – once it’s all together it’s pretty a daunting jumble of stuff. I always work in this way, breaking it down into manageable chunks before stitching it together. There aren’t many people who can just write this stuff off the top of their heads.
Get-VM “UI VM” | Get-View | Select Name,@{Name=”Hardware Version”; Expression={$_.Config.Version}},@{Name=”Tools Status”; Expression={$_.Guest.ToolsStatus}},@{Name=”Tools Version Status”; Expression={$_.Guest.ToolsVersionStatus}},@{Name=”Tools Version”; Expression={$_.Guest.ToolsVersion}}
The really cool thing is that now I have this query I can stick any number of VMs into the pipeline – any result of Get-VM! For example, I can get those two VMs from the vCOPs VApp using the –Location property.
The very last thing to add to the pipeline is to be able to analyze this data correctly, I need to have it in a format I can mange – output onto the screen is not so easy! For this we can use the Export-CSV command which will dump the contents into a CSV file that we can manipulate in Excel or another spreadsheet app. The arguments are a simply the file name we want to export to, and “-NoType” which just formats it better for Excel. The final query looks like this:
Get-VM “UI VM” | Get-View | Select Name,@{Name=”Hardware Version”; Expression={$_.Config.Version}},@{Name=”Tools Status”; Expression={$_.Guest.ToolsStatus}},@{Name=”Tools Version Status”; Expression={$_.Guest.ToolsVersionStatus}},@{Name=”Tools Version”; Expression={$_.Guest.ToolsVersion}} | Export-CSV c:\VMTools.csv –NoType
Now it’s just a case of remediating all these VMs!
I hope that this back to basics approach to using PowerCLI to tackle a real world problem will help people not only with this specific task, but also a methodology for taking on other tasks too. PowerCLI is massively powerful, but the initial learning curve can be steep – the rewards are worth it!