While working in a VMware environment with thousands of VMs, I needed to make a spreadsheet containing a lot of of the data listed in vCenter for each VM, including their custom attributes (a.k.a. Annotations). Rather than using the vSphere Client’s built-in “Export List” option, I wrote a PowerShell script to do it instead. Since it seemed like something others may find useful, I’m posting it here.
The reason I didn’t simply click on the “Virtual Machines” tab in vSphere, right-click the column headings, include columns I wanted, and click File -> Export List is because I’ve had trouble with vCenter’s info being incomplete if I didn’t first slowly scroll down through it all before exporting it. (i.e., if vSphere doesn’t first display it, it won’t export and you’ll have an incomplete CSV file!) Also, vSphere doesn’t have an option to export the following fields which these scripts will capture:
- ClusterName
- OSBooted time
- VMXname
- VMDKname
- DatastoreName
- NumCpuShares
- CpuLimitMHZ
- CpuReservationMHZ
- NumMemShares
- LimitMB
- SnapshotCount
- VLANid
- Portgroup
A script also just makes things easier and less time consuming. If someone asks for some data, you can run this PowerShell script and go do something else. Then, when you come back later, your CSV file is waiting for you. So, while it may take a few hours to extract all the data you’re looking for, it only takes 30 seconds of your actual time to generate that CSV, since you can be doing other things while it runs in the background. However, if you need that data ASAP, then exporting directly from vSphere may be your best option, though it’s tedious, since this script can take a while to run.
So, on to the script!
This script was written for VMware 5.5, but I see no reason why it wouldn’t work in other versions. Also I should mention that bits of this code may have been borrowed from/inspired by other pages around the net, but I can’t cite sources, since I’ve long since forgotten where anything I adapted came from. (Sorry!)
To allow PowerShell to talk to vCenter and be able to run this script, you first must install the VMware vSphere PowerCLI. For me it was VMware-PowerCLI-5.5.0-1295336.exe, but you can get whatever your current version is.
Here’s the script… Scroll down to read important notes and additional comments about it below!
#### Comment out unneeded lines down below to make this run faster!! #############################################################################$StopWatch = [system.diagnostics.stopwatch]::startNew()Write-Host " "Write-Host "Loading snapin and connecting to vcenter.domain.com - please wait..."Add-PSSnapin -Name VMware.VimAutomation.CoreConnect-VIServer vcenter.domain.com -WarningAction SilentlyContinue$allLines = @()$VMsNotFoundFile = "C:\Temp\VMsNotFound.txt"$CSVOutputFile = "C:\Temp\VMreport.csv"############################################################################# Uncomment these lines to use only the VMs listed in a text file with this layout:## vmname1## vmname2## vmname3## ...### Begin section for reading VMs from text file:# (Comment out section below (all VMs) when using this.)$InputFile = 'C:\Temp\vmnames.txt'Write-Host " "Write-Host "OK, now reading in server info. (This could take a while if you have more than a few servers.)"$null | Out-File $VMsNotFoundFile$reader = New-Object IO.StreamReader $InputFilewhile($reader.ReadLine() -ne $null){ $TotalVMs++ }Get-Content -Path $InputFile | %{$vm = Get-VM -Name $_ -ErrorAction SilentlyContinue## End section for VMs in text file.######################################################################################################################################################## Begin lines for *all* VMs in vCenter:## (Comment out above section (VMs from text) when using this.)# Write-Host " "# Write-Host "OK. Now counting VMs - stand by..."## $TotalVMs = Get-VM# $TotalVMsCount = $TotalVMs.count## Write-Host " "# Write-Host "Here we go! Collecting server info... (Go get a cup of coffee - this will take a while...)"# Get-VM | `# ForEach-Object {# $VM = $_## End lines for all VMs in vCenter.###########################################################################$i++$percentdone = (($i / $TotalVMs) * 100)$percentdonerounded = "{0:N0}" -f $percentdoneWrite-Progress -Activity "Gathering data from VMs" -CurrentOperation "Working on VM: $VM (VM $i of $TotalVMs)" -Status "$percentdonerounded% complete" -PercentComplete $percentdone$VMview = $VM | Get-View$VMResourceConfiguration = $VM | Get-VMResourceConfigurationIF (!$vm){Write-Host "$_ can't be found in VMware!"$_ | Out-File $VMsNotFoundFile -Append}else{$VMHardDisks = $VM | Get-HardDisk$HardDisksSizesGB = @()$Temp = $VMHardDisks | ForEach-Object { $HardDisksSizesGB += [Math]::Round($_.CapacityKB/1MB) }$VmdkSizeGB = ""$Temp = $HardDisksSizesGB | ForEach-Object { $VmdkSizeGB += "$_+" }$VmdkSizeGB = $VmdkSizeGB.TrimEnd("+")$TotalHardDisksSizeGB = 0$Temp = $HardDisksSizesGB | ForEach-Object { $TotalHardDisksSizeGB += $_ }$VMDKnames = @()$Temp = $VMHardDisks | ForEach-Object { $VMDKnames += $_.Filename.Split("/")[1] }$Snapshots = $VM | Get-Snapshot$Report = "" | Select-Object VMname,ESXname,Host,ClusterName,Powerstate,OSBooted,MemoryGB,vCPUcount,vNICcount,IPaddresses,VMXname,VMDKname,VmdkSizeGB,TotalVmdkSizeGB,DiskFree,DiskUsed,DatastoreName,ToolsVersion,ToolsUpdate,NumCpuShares,CpuLimitMHZ,CpuReservationMHZ,NumMemShares,ReservationsMB,LimitMB,SnapshotCount,GuestOS,HardwareVersion,VLANid,Portgroup,Notes,AnnoBackEx,AnnoBackWeek,AnnoBusiness,AnnoChgMgmt,AnnoCostCtr,AnnoBLAHBLAH$Report.VMName = $VM.name$Report.ESXname = $VM.VMHost$Report.Host = $vm.VMHost.name$Report.ClusterName = ($VM | Get-Cluster).Name$Report.Powerstate = $vm.Powerstate$Report.OSBooted = (Invoke-VMScript -VM $VM -ScriptText 'systeminfo| find "System Boot Time"' -ScriptType Bat -ErrorAction SilentlyContinue | Select -ExpandProperty ScriptOutput) -replace 'System Boot Time: ([^-]+)', '$1'$Report.MemoryGB = $VM.MemoryMB/1KB$Report.vCPUcount = $VM.NumCpu$Report.vNICcount = $VM.Guest.Nics.Count$Report.IPaddresses = [string]::Join(',',$VM.Guest.IPAddress)$Report.VMXname = $VMview.Config.Files.VmPathName.Split("/")[1]$Report.VMDKname = [string]::Join(',',$VMDKnames)$Report.VmdkSizeGB = $VmdkSizeGB$Report.TotalVmdkSizeGB = $TotalHardDisksSizeGB$Report.DiskFree = [Math]::Round((($vm.Guest.Disks | Measure-Object -Property FreeSpace -Sum).Sum / 1GB),2)$Report.DiskUsed = $Report.TotalVmdkSizeGB - $Report.DiskFree$Report.DatastoreName = [string]::Join(',',($VMview.Config.DatastoreUrl | %{$_.Name}))$Report.ToolsVersion = $VMview.Config.Tools.ToolsVersion$Report.ToolsUpdate = $VMview.Guest.ToolsStatus$Report.NumCpuShares = $VMResourceConfiguration.NumCPUShares$Report.CpuLimitMHZ = $VMResourceConfiguration.CpuLimitMhz$Report.CpuReservationMHZ = $VMResourceConfiguration.CpuReservationMhz$Report.NumMemShares = $VMResourceConfiguration.NumMemShares$Report.ReservationsMB = $VMResourceConfiguration.MemReservationMB$Report.LimitMB = $VMResourceConfiguration.MemLimitMB$Report.SnapshotCount = (@($VM | Get-Snapshot)).Count$Report.GuestOS = $VM.Guest.OSFullName$Report.HardwareVersion = $VM.Version$tmpVLANId = Get-VirtualPortgroup -VM $vm | %{$_.VlanId}if(!$tmpVLANId){$tmpVLANId = ""}$Report.VLANid = [string]::Join(',',$tmpVLANId)$tmpPG = Get-VirtualPortgroup -VM $vm | %{$_.Name}if(!$tmpPG){$tmpPG = ""}$Report.Portgroup = [string]::Join(',',$tmpPG)$Report.Notes = $VM | Select-Object -ExpandProperty NotesIf (($Report.AnnoBackEx = ($VM | Get-Annotation -CustomAttribute "BackupExcluded") -replace 'BackupExcluded:([^-]+)', '$1') -eq "BackupExcluded:") { $Report.AnnoBackEx = "" } else { $Report.AnnoBackEx = ($VM | Get-Annotation -CustomAttribute "BackupExcluded") -replace 'BackupExcluded:([^-]+)', '$1' }If (($Report.AnnoBackWeek = ($VM | Get-Annotation -CustomAttribute "BackupWeekly") -replace 'BackupWeekly:([^-]+)', '$1') -eq "BackupWeekly:") { $Report.AnnoBackWeek = "" } else { $Report.AnnoBackWeek = ($VM | Get-Annotation -CustomAttribute "BackupWeekly") -replace 'BackupWeekly:([^-]+)', '$1' }If (($Report.AnnoBusiness = ($VM | Get-Annotation -CustomAttribute "Business") -replace 'Business:([^-]+)', '$1') -eq "Business:") { $Report.AnnoBusiness = "" } else { $Report.AnnoBusiness = ($VM | Get-Annotation -CustomAttribute "Business") -replace 'Business:([^-]+)', '$1' }If (($Report.AnnoChgMgmt = ($VM | Get-Annotation -CustomAttribute "ChangeMgmt") -replace 'ChangeMgmt:([^-]+)', '$1') -eq "ChangeMgmt:") { $Report.AnnoChgMgmt = "" } else { $Report.AnnoChgMgmt = ($VM | Get-Annotation -CustomAttribute "ChangeMgmt") -replace 'ChangeMgmt:([^-]+)', '$1' }If (($Report.AnnoCostCtr = ($VM | Get-Annotation -CustomAttribute "Cost Center") -replace 'Cost Center:([^-]+)', '$1') -eq "Cost Center:") { $Report.AnnoCostCtr = "" } else { $Report.AnnoCostCtr = ($VM | Get-Annotation -CustomAttribute "Cost Center") -replace 'Cost Center:([^-]+)', '$1' }If (($Report.AnnoBLAHBLAH = ($VM | Get-Annotation -CustomAttribute "BLAHBLAH") -replace 'BLAHBLAH:([^-]+)', '$1') -eq "BLAHBLAH:") { $Report.AnnoBLAHBLAH = "" } else { $Report.AnnoBLAHBLAH = ($VM | Get-Annotation -CustomAttribute "BLAHBLAH") -replace 'BLAHBLAH:([^-]+)', '$1' }$allLines += $Report}}$allLines | Export-Csv $CSVOutputFile -NoTypeInformationWrite-Host " "$StopWatch.ElapsedWrite-Host " "Write-Host "Done! See above for elapsed time. Go look in C:\Temp for the files."
I didn’t put many comments in the script, but from the comments that are there, the first thing you’ll notice is that this is actually two scripts in one. In its current state, it’s set up to read in servers from a list you put in a text file, and it’ll pull in data about those servers only. However, if you want to read in info on all VMs in vCenter instead, then comment out the lines about importing from text and uncomment the lines below that section.
You’ll also want to modify the line that specifies the “VIServer” (i.e., vCenter server) you’re connecting to, to match your environment.
Also, verify that the 3 lines in the top part defining file names & paths are acceptable before running the script.
Then, as the first line in the script suggests, scroll down and comment out lines for anything that you don’t actually need data on. This takes a long time to run, so you don’t want to make it take any longer than necessary by collecting unimportant data. (Remember to also delete the corresponding name in the “Select-Object” line where the column headings are defined.)
Then save your changes and run the script.
Other things to Note:
- The scripts don’t register values for OS Booted time & Disk Free space for non-Windows VMs.
- Be sure the vmnames.txt file doesn’t have extra line breaks after the last server name or you’ll get errors!
- Rarely, you may see red errors when it tries accessing a specific VM saying something like: “The object has already been deleted or has not been completely created.” This issue may be caused by mismatched snapshot object id in vCenter database. The mismatch can be triggered by migrating the powered off virtual machine to a different ESX host then restarting vpxd service. More info available on VMware’s web site.
Nifty features of this script:
- Shows a progress bar at the top, giving % complete and number of complete of total.
- Displays elapsed time at end (good for testing to judge how long bigger exports will take).
- If you’re reading from a list of VMs in a text file, it will output a list of any VMs it couldn’t find, both to the screen and to a “vmnotfound.txt” file for later troubleshooting.
- I could’ve made this a little tighter and more efficient, but I intentionally listed each piece of VM data on its own line so you can more easily comment out the values you don’t need when trying to save on running time.
- Outputs results to CSV file.
- This script can collect any of the following data:
 VM name
 ESX name
 Host
 Cluster Name
 Power state
 OS Booted Time
 Memory GB
 vCPU count
 vNIC count
 IP addresses
 VMX name
 VMDK name
 Vmdk Size GB
 Total Vmdk Size GB
 Disk space Free
 Disk space Used
 Datastore Name
 Tools Version
 Tools Last Updated
 Number of CPU Shares
 CPU Limit MHZ
 CPU Reservation MHZ
 Number of Memory Shares
 Reservations MB
 Limit MB
 Snapshot Count
 Guest OS
 Hardware Version
 VLAN ID
 Port group
 Notes
 Annotation: BackupExcluded
 Annotation: BackupWeekly
 Annotation: Business
 Annotation: ChangeMgmt
 Annotation: Cost Center
 Annotation: BLAHBLAH (by now you get the idea!)
I hope this helps someone – it took me a little while to put together the way I wanted it, so I’m posting it as much for my own future reference as yours. :) If you know of any more data that can be collected, or more efficient ways to do anything, let me know in the comments below.