Useful PowerCLI scripts for storage migrations with RDMs

I'm currently supporting a large VMware Storage migration project affecting MS Exchange 2010 server VMs that use lots of Raw Device Mappings (RDM) for mailbox storage. This was a good opportunity to further improve my Powershell / PowerCLI skills - I wrote some scripts to gather the data needed in a friendly format, and I thought it would be a good idea to share them here.

1. List all RDMs in the environment

The first script creates a csv table of all RDMs that are in use by VMs. Each RDM is listed with the following information:
  • VMName: (Display) name of the VM that has the RDM attached to it
  • GuestDevName: The name of the disk in the VM's hardware configuration (e.g. "Hard disk 2")
  • GuestDevID: The SCSI controller and device ID of the disk in the VM's configuration (e.g. "scsi0:1")
  • VMHost: The ESXi host that runs the VM
  • HDFileName: The location and name of the RDM mapping file
  • HDMode: whether the RDM is in virtual or physical compatibility mode
  • HDSize: the size of the RDM
  • RuntimeName: The runtime name of the host's LUN that represents the RDM (e.g. "vmhba1:C0:T2:L61")
  • CanonicalName: Its canonical name (naaid)
The resulting csv file can be imported into Excel (or your favorite Spreadsheet program) where it can be nicely filtered and processed further:

So here is the script with some notes:
$vcenter = "name-of-your-vcenter-server-here"
$outputFile = "c:\temp\All-RDMs-" + (get-date -Format yyyy-MM-dd-HHmm) + ".csv"

"Connecting vCenter servers ..."
Connect-VIServer $vcenter -AllLinked

$report = @()
$luns = @{}

"Getting VM(s). Be patient, this can take up to an hour ..."

$vms = Get-VM | Get-View
("Got " + $vms.Count + " VMs ...")

foreach($vm in $vms) {
     ("Processing VM " + $vm.Name + " ...")
     $ctl = $null
     $esx = $null
     write-host -NoNewLine "   Scanning VM's devices for RDMs ..."
     foreach($dev in $vm.Config.Hardware.Device){
          if(($dev.gettype()).Name -eq "VirtualDisk"){
               if(($dev.Backing.CompatibilityMode -eq "physicalMode") -or ($dev.Backing.CompatibilityMode -eq "virtualMode")){
                    if ($ctl -eq $null) {
                       " Found at least one ..."
                       "   Getting VM's SCSI controllers ..."
                       $ctl = Get-ScsiController -VM ($vm).Name
                    if ($esx -eq $null) {
                        write-host -NoNewLine "   Getting VM's host ..."
                        $esx = (Get-View $vm.Runtime.Host).Name
                        write-host (": " + $esx)
                    if ($luns[$esx] -eq $null) {
                        ("   Getting SCSI LUNs of host " + $esx + " ...")
                        $luns[$esx] = Get-ScsiLun -VmHost $esx -luntype disk
                    $row = "" | select VMName, GuestDevName, GuestDevID, VMHost, HDFileName, HDMode, HDsize, RuntimeName, CanonicalName
                    $row.VMName = $vm.Name
                    $row.GuestDevName = $dev.DeviceInfo.Label
                    $SCSIBus = ($ctl | where {$_.ExtensionData.Key -eq $dev.ControllerKey}).ExtensionData.BusNumber
                    $SCSIID = $dev.UnitNumber
                    $row.GuestDevID = "scsi" + $SCSIBus + ":" + $SCSIID
                    $row.VMHost = $esx
                    $row.HDFileName = $dev.Backing.FileName
                    $row.HDMode = $dev.Backing.CompatibilityMode
                    $row.HDSize = $dev.CapacityInKB
                    $lun = ($luns[$esx] | where {$_.ExtensionData.Uuid -eq $dev.Backing.LunUuid})
                    $row.CanonicalName = $lun.CanonicalName
                    $row.RuntimeName = $lun.RuntimeName
                    $report += $row
     if ($ctl -eq $null) { " None found." }

"Exporting report data to $outputFile ..."
$report | Export-CSV -Path $outputFile
"All done."
The script looks at all VMs of the environment (it connects to a vCenter server and all linked ones if you use linked mode with multiple vCenter servers). Each VM is checked if it contains disk devices with an RDM backing (line 20-22). If yes then the relevant VM configuration information is determined, and the host that runs the VM is queried to get the LUN identifiers (line 28-36).

There are several costly operations executed by the script that can take from multiple seconds to few minutes to execute in large scale environments:
  • Getting the SCSI controller objects of a VM (line 26)
  • Getting the view on the host that runs the VM (line 30)
  • Getting the SCSI LUNs of a host (line 35)
To optimize its execution time the script makes sure that these operations are not run multiple times on the same VM or host by caching the results (e.g. the hosts' LUNs in the hashed array $luns), and that helps a lot when VMs have multiple RDMs or a host runs multiple VMs with RDMs.

Nevertheless the script took about one hour to execute in our environment with 5.400 VMs (of which 25 had about 400 RDMs in total). The "Get-VM | Get-View" (line 4) takes most of this time, so you may want to limit the selection here to a set of VMs of which you already know that they use RDMs.

2. List the WWNs of all hosts

During the project some inconsistencies were discovered between the documented (port WWN based) SAN zoning and what storage the hosts were really seeing. I recommend having a really good concept of how to maintain this information, especially in large environments with lots of VMware and Storage admins.

I wrote a second script to help clean up the mess: It creates a table listing each host (grouped by cluster) with the device names and WWNs of all active FC HBAs. In Excel it looks like this:

and here is the code:
$vcenter = "name-of-your-vcenter-server-here"
$outputFile = "c:\temp\All-WWNs-" + (get-date -Format yyyy-MM-dd-HHmm) + ".csv"

"Connecting vCenter servers ..."
Connect-VIServer $vcenter -AllLinked

$report = @()

foreach ($cluster in (Get-Cluster)) {
    ("Processing cluster " + $cluster.Name + " ...")
    foreach ($vhost in (Get-VMHost -Location $cluster.Name)) {
        ("   Processing host " + $vhost.Name + " ...")
        $row = "" | select Cluster, VMHost, Dev1, WWN1, Dev2, WWN2, Dev3, WWN3, Dev4, WWN4
        $row.Cluster = $cluster.Name
        $row.VMHost = $vhost.Name
        foreach ($hba in (Get-VMHostHba -VMHost $vhost -Type FibreChannel)) {
            if ($hba.ExtensionData.Status -eq "online") {
                $wwn = ("{0:X}" -f $hba.PortWorldWideName) -replace '(..(?!$))','$1:'
                if ($row.WWN1 -eq $null) {
                    $row.Dev1 = $hba.Device
                    $row.WWN1 = $wwn
                } elseif ($row.WWN2 -eq $null) {
                    $row.Dev2 = $hba.Device
                    $row.WWN2 = $wwn
                } elseif ($row.WWN3 -eq $null) {
                    $row.Dev3 = $hba.Device
                    $row.WWN3 = $wwn
                } elseif ($row.WWN4 -eq $null) {
                    $row.Dev4 = $hba.Device
                    $row.WWN4 = $wwn
        $report += $row

"Exporting report data to $outputFile ..."
$report | Export-CSV -Path $outputFile
"All done."
This time the code is quite straight forward. Most of the coding time was probably consumed by the fancy looking line #19 where the Port WWN (which is really available as a numerical object) is converted into the hexadecimal and colon separated format that we are all used to ;-)

The script filters for HBAs that have an online status (line 18), because I noticed that a lot of the hosts (which are HP Blade servers) had additional unused HBAs that showed up with status Unknown, probably a consequence of how their VirtualConnect server profiles were created. Please note that the script will not list more than four active HBAs by host, but it should be easy to modify it to support more than this if needed.

I hope that other people find these scripts useful when doing similar work!

This post first appeared on the VMware Front Experience Blog and was written by Andreas Peetz. Follow him on Twitter to keep up to date with what he posts.

1 comment:

  1. Thanks for your scripting. Just a thought: maybe you would like to disconnect from the vcenter at the end of the scripting?


***** All comments will be moderated! *****
- Please post only comments or questions that are related to this post's contents!
- Advertising and link spamming will not be tolerated!