Dump Virtual Machine Info as XML (powershell)

Hi All,

I was recently asked to provide a comprehensive list of VM’s and their currently provisioned hardware. We have a need to duplicate this view in the future, so I decided to script it. My org also does charge-forward, so this dump of information is useful.

I used the script by Arne Fokkema “PowerCLI: Virtual Machine Disk (VMDK) info v2: Analyze data with Excel” as a base.

I initially tried to use the built-in XML export option, however the formatting wasn’t very nice, so I decided to build a custom schema and populate that instead. Using this approach allowed me to include all the detail I wanted, nested appropriately… for easy collation and searching later in excel.

So running through the script, this is how it works.

Firstly, like a good programmer, define the global variables.

#--- Parameters ----------------------------------------------------------------------------------------------
#XML Template
$XMLTemplateFile = "C:/tmp/VMachines_Template2.xml"
#Output XML File
$XMLOutput = "c:/tmp/VMachines.xml"
#Use Shortname?
$UseShortname = "True"
#Get Application details?
$GetAppdocInfo = "False"

Ignore the AppdocInfo flag…. that’s for another day 😉

Now, since we’re creating a custom XML output we need to define the template.

#Define the XML Template
$XMLTemplate = @'
... etc
'@
$XMLTemplate | Out-File $XMLTemplateFile -encoding UTF8

$XMLVMachines = New-Object XML
$XMLVMachines.load($XMLTemplateFile)
$XMLVMachine = @($XMLVMachines.VMachines.VMachine)[0]

The template is defined inline, written out to a temporary template file, then read back in as an array of XML objects (with only one object so far, the template). Once that’s done, we can populate the array by cloning the template and adding data.

Now we have a custom XML object to work with, we can begin collecting the required data.

#Get list of VM and ESXHost objects for vCenter
$vms = get-view -ViewType VirtualMachine | Where-Object {-not $_.config.template}
$ESXHosts = get-view -ViewType HostSystem

I had to collect data about the VMs as well as the hosts to get VLAN information… which brings me to the next part.

Get all the port groups.

#Get all the Portgroups (for the VLAN ID)
$PortGroups= @()
foreach($ESXHost in $ESXHosts){
foreach($ESXHostPortGroup in $ESXHost.config.network.portgroup){
$PortGroups += $ESXHostPortGroup.spec
}
}

Because I wanted the VLAN ID for each vNIC, I had to get the portgroup information. I later matched on portgroup name… perhaps there is a better way of doing this?

My environment is particuarly complex with approximately 30 VLANs trunked to all hosts, so having this information per vNIC is important for me

So now we have a custom XML object, all the VM’s as standard objects and a list of the portgroup/vlan ID info. Its time to loop through the VM’s and get the information out.The first step is to create a new XML object from the template for each VM.

foreach($vm in $vms){
$Temp = "" | Select-Object FileName, DataStore, capacityinGB, Match, DevType, vNicPg, VMDiskTotalGb
#Define a custom VMachine PSObject
$VMachine = $XMLVMachine.Clone()

Now comes a bit of org-specific code which may help you (or not). Previously we had vm names definied as “OU-DISTRO-Number (basic description)”… long story.I know this isn’t best practice and the problems with folder naming etc…. working on it. But for the moment I want to exclude the description from my vm identifier in this output. I did this with some RegEx (regular expression).

If($UseShortname -eq "True"){
$Temp.Match = $vm.Name -match '^[a-zA-Z0-9\-]*'
If($Temp.Match) {
$VMachine.VMName = [String]$matches[0]
}else {
$VMachine.VMName = [String]$vm.Name
}
}else {
$VMachine.VMName = [String]$vm.Name
}

Then we can get the non-nested data (Name, Memory, numCpu, etc)

$VMachine.VMMem = [String]$vm.summary.config.memorySizeMB
$VMachine.VMCpu = [String]$vm.summary.config.numCpu
$VMachine.VMVNics = [String]$vm.summary.config.numEthernetCards
$VMachine.VMVDisks = [String]$vm.summary.config.numVirtualDisks
$VMachine.OSVersion = [String]$vm.summary.config.guestFullName

Now the tricky part, getting the nested devices.

foreach($dev in $vm.config.hardware.Device){
$Temp = "" | Select-Object FileName, DataStore, capacityinGB, Match, DevType, vNicPg, VMDiskTotalGb
$PortGroup = ""
#vDisk Info
if($dev.GetType().Name -eq "VirtualDisk"){
#Create a new VDisk XML Object
$VDisk = (@($VMachine.VDisks.VDisk)[0]).Clone()
#Populate the data into the XML object from the vCenter object $vm
$VDisk.vDiskLabel = [String]$dev.DeviceInfo.Label
#Just want the Datastore shortname
$Temp.FileName = $dev.Backing.FileName
$Temp.Match = $Temp.FileName -match '^\[.*\]'
$Temp.DataStore = $matches[0]
$VDisk.DataStore = [String]$Temp.DataStore
#Calculate the Disk Size in GB
$Temp.CapacityInGb = [int]($dev.CapacityInKB / 1048576)
$VMachine.VMDiskTotalGb = [String]([int]$VMachine.VMDiskTotalGb + [int]$Temp.CapacityInGb)
$VDisk.CapacityInGb = [String]$Temp.CapacityInGb
#Append the VDisk to the VDisks
$VMachine.VDisks.AppendChild($VDisk) > $null
}
#vNIC Info
if(($dev.GetType().Name -eq "VirtualPCNet32") -or ($dev.GetType().Name -eq "VirtualVmxnet") -or ($dev.GetType().Name -eq "VirtualVmxnet2") -or ($dev.GetType().Name -eq "VirtualVmxnet3") -or ($dev.GetType().Name -eq "VirtualE1000")){
#Create a new VDisk XML Object
$VNic = (@($VMachine.VNics.VNic)[0]).Clone()
$VNic.VnicLabel = $dev.DeviceInfo.Label
#Match the VNic to Portgroup and get the VlanId
$PortGroup = @($PortGroups | Where-Object {$_.name -eq $dev.DeviceInfo.summary})[0]
$VNic.VnicVlan = [String]$PortGroup.VlanId
#Append the VNic to the VNics
$VMachine.VNics.AppendChild($VNic) > $null
}
}

As you can see, devices are stored in the array “$vm.config.hardware.devices”, so we loop through all those, grabbing out the ones we’re interested in… vNics and vDisks. Each device is created as a child of either “VNics or VDisks” objects.

Now we have all the devices and information. However, there are still some blank devices, so lets do a little cleanup before adding the xml VM object to the array.

#Clear blank devices
$VMachine.VDisks.VDisk | Where-Object {$_.VDiskLabel -eq ""} | ForEach-Object {[Void]$VMachine.VDisks.RemoveChild($_)}
$VMachine.VNics.VNic | Where-Object {$_.VNicLabel -eq ""} | ForEach-Object {[Void]$VMachine.VNics.RemoveChild($_)}
#Add VM to VMachines array
$XMLVMachines.VMachines.AppendChild($VMachine) > $null

Then all that’s left is to clear blank VM’s from the array and save the array as an XML file.

#Clear blank VMs
$VMachines.VMachines.VMachine | Where-Object {$_.VMName -eq ""} | ForEach-Object {[Void]$VMachines.VMachines.RemoveChild($_)}

#Export to XML
$XMLVMachines.Save($XMLOutput)

And the full Code. I’ll also upload it, to preserve the right quotes…. you’ll want to use that one.

#
# Sources:     http://ict-freak.nl/2009/10/11/powercli-virtual-machine-disk-vmdk-info-v2-analyze-data-with-excel/
#            http://powershell.com/cs/blogs/tobias/archive/2009/02/02/xml-part-2-write-add-and-change-xml-data.aspx
#
# Notes : Written to export VM objects from vCenter for reports.
#
# Author : Doug Youd
#
#-------------------------------------------------------------------------------------------------------------

#--- Parameters ----------------------------------------------------------------------------------------------
#XML Template
$XMLTemplateFile = "C:/tmp/VMachines_Template2.xml"
#Output XML File
$XMLOutput = "c:/tmp/VMachines.xml"
#Use Shortname?
$UseShortname = "True"
#Get Application details?
$GetAppdocInfo = "False"

#--- Main ----------------------------------------------------------------------------------------------------

#Define the XML Template
$XMLTemplate = @'
<VMachines version="1.0">
    <VMachine>
        <VMName></VMName>
        <VMCpu></VMCpu>
        <VMMem></VMMem>
        <VMVnics></VMVnics>
        <VMVdisks></VMVdisks>
        <VMDisktotalGb></VMDisktotalGb>
        <OSVersion></OSVersion>
        <VHwversion></VHwversion>
        <Services>
            <Service>
                <ServiceName></ServiceName>
                <ServiceCat></ServiceCat>
                <ServicePriority></ServicePriority>
                <ServiceOwner></ServiceOwner>
            </Service>
        </Services>
        <VDisks>
            <VDisk>
                <VDiskLabel></VDiskLabel>
                <DataStore></DataStore>
                <CapacityInGB></CapacityInGB>
            </VDisk>
        </VDisks>
        <VNics>
            <VNic>
                <VNicLabel></VNicLabel>
                <VNicVlan></VNicVlan>
            </VNic>
        </VNics>
        <Stats>
            <Cpu>
                <AvgCpu></AvgCpu>
                <MaxCpu></MaxCpu>
            </Cpu>
            <Memory>
                <AvgMem></AvgMem>
                <MaxMem></MaxMem>
            </Memory>
            <DiskIo>
                <AvgDiskIo></AvgDiskIo>
                <MaxDiskIo</MaxDiskIo>
            </DiskIo>
            <NetIo>
                <AvgNetIo></AvgNetIo>
                <MaxNetIo</MaxNetIo>
            </NetIo>
        </Stats>
    </VMachine>
</VMachines>
'@
$XMLTemplate | Out-File $XMLTemplateFile -encoding UTF8

$XMLVMachines = New-Object XML
$XMLVMachines.load($XMLTemplateFile)
$XMLVMachine = @($XMLVMachines.VMachines.VMachine)[0]


#Get list of VM and ESXHost objects for vCenter
$vms = get-view -ViewType VirtualMachine | Where-Object {-not $_.config.template}
$ESXHosts = get-view -ViewType HostSystem 

#Get all the Portgroups (for the VLAN ID)
$PortGroups= @()
foreach($ESXHost in $ESXHosts){
    foreach($ESXHostPortGroup in $ESXHost.config.network.portgroup){
        $PortGroups += $ESXHostPortGroup.spec
    }
}

foreach($vm in $vms){
    $Temp = "" | Select-Object FileName, DataStore, capacityinGB, Match, DevType, vNicPg, VMDiskTotalGb
    #Define a custom VMachine PSObject
    $VMachine = $XMLVMachine.Clone()

    #Populate Basic VM Info
    If($UseShortname -eq "True"){
        $Temp.Match = $vm.Name -match '^[a-zA-Z0-9\-]*'
        If($Temp.Match) {
            $VMachine.VMName = [String]$matches[0]
        }else {
            $VMachine.VMName = [String]$vm.Name
        }
    }else {
        $VMachine.VMName = [String]$vm.Name
    }
    $VMachine.VMMem = [String]$vm.summary.config.memorySizeMB
    $VMachine.VMCpu = [String]$vm.summary.config.numCpu
    $VMachine.VMVNics    = [String]$vm.summary.config.numEthernetCards
    $VMachine.VMVDisks = [String]$vm.summary.config.numVirtualDisks
    $VMachine.OSVersion = [String]$vm.summary.config.guestFullName

    #VM Device Info
    foreach($dev in $vm.config.hardware.Device){
        $Temp = "" | Select-Object FileName, DataStore, capacityinGB, Match, DevType, vNicPg, VMDiskTotalGb
        $PortGroup = ""
            #vDisk Info
               if($dev.GetType().Name -eq "VirtualDisk"){
                #Create a new VDisk XML Object
                $VDisk = (@($VMachine.VDisks.VDisk)[0]).Clone()
                #Populate the data into the XML object from the vCenter object $vm
                $VDisk.vDiskLabel = [String]$dev.DeviceInfo.Label
                #Just want the Datastore shortname
                $Temp.FileName = $dev.Backing.FileName
                $Temp.Match = $Temp.FileName -match '^\[.*\]'
                $Temp.DataStore = $matches[0]
                $VDisk.DataStore = [String]$Temp.DataStore
                #Calculate the Disk Size in GB
                $Temp.CapacityInGb = [int]($dev.CapacityInKB / 1048576)
                $VMachine.VMDiskTotalGb = [String]([int]$VMachine.VMDiskTotalGb + [int]$Temp.CapacityInGb)
                $VDisk.CapacityInGb = [String]$Temp.CapacityInGb
                #Append the VDisk to the VDisks
                $VMachine.VDisks.AppendChild($VDisk) > $null
               }
            #vNIC Info
            if(($dev.GetType().Name -eq "VirtualPCNet32") -or ($dev.GetType().Name -eq "VirtualVmxnet") -or ($dev.GetType().Name -eq "VirtualVmxnet2") -or ($dev.GetType().Name -eq "VirtualVmxnet3") -or ($dev.GetType().Name -eq "VirtualE1000")){
                #Create a new VDisk XML Object
                $VNic = (@($VMachine.VNics.VNic)[0]).Clone()
                $VNic.VnicLabel = $dev.DeviceInfo.Label
                #Match the VNic to Portgroup and get the VlanId
                $PortGroup = @($PortGroups | Where-Object {$_.name -eq $dev.DeviceInfo.summary})[0]
                $VNic.VnicVlan = [String]$PortGroup.VlanId
                #Append the VNic to the VNics
                $VMachine.VNics.AppendChild($VNic) > $null
            }
    }
    #Clear blank devices
    $VMachine.VDisks.VDisk | Where-Object {$_.VDiskLabel -eq ""} | ForEach-Object {[Void]$VMachine.VDisks.RemoveChild($_)}
    $VMachine.VNics.VNic | Where-Object {$_.VNicLabel -eq ""} | ForEach-Object {[Void]$VMachine.VNics.RemoveChild($_)}
    #Add VM to VMachines array
    $XMLVMachines.VMachines.AppendChild($VMachine) > $null
}
#Clear blank VMs
$VMachines.VMachines.VMachine | Where-Object {$_.VMName -eq ""} | ForEach-Object {[Void]$VMachines.VMachines.RemoveChild($_)}

#Export to XML
$XMLVMachines.Save($XMLOutput)

You can download the script Here. Just change the extension to “.ps1” and modify the directories to suit your application.

Enjoy. :)

4 Responses to “Dump Virtual Machine Info as XML (powershell)”

Leave a Reply for DJ

The opinions expressed on this site are my own and not necessarily those of my employer.

All code, documentation etc is my own work and is licensed under Creative Commons and you are free to use it, at your own risk.

I assume no liability for code posted here, use it at your own risk and always sanity-check it in your environment.