Oct 17, 2016

How to Map VMware Virtual Disks and Windows Drive Volumes with a PowerShell Script

The PowerShell script (using PowerCLI) I discuss in this article maps virtual disks of a VMware vSphere host to volumes on Windows drives. This information is useful if you have to extend the storage space of Windows volumes.





Let’s say I have a VMware VM with Windows Server as the guest operating system. The host has three SCSI controllers and 10 drives on each controller. Some drives have different sizes and some are the same.

At some point, the drive space runs low and you’re looking at the ticket to add space to the volumes of a VM with tens of virtual disks. Of course, the admin working on this server just sees the Windows volumes. But to complete this task successfully, I need to know how the Windows volumes correspond to VM disks.

If you are looking at the VM from the VMware vSphere client, everything looks nice and simple:
 
SCSI controllers with assigned drives on vSphere client

But when you looking on the drives from Windows, you’ll notice the problem.


 
Windows Disk Management is not showing SCSI controllers

As you can see in the above screenshot, SCSI controllers are not visible in Windows Disk Management. For instance, if I have three different Windows volumes that I want to expand and a bunch of other drives, some of them potentially having the same size as those three, how am I going to determine where I need to add the space? There is no explicit dependency between the VMware virtual drive number and the drive number in Windows.

That’s where PowerShell can help. The script below gets all drives from the VMware virtual machine and the corresponding Windows guest, matches them to each other, and then saves the result to the .csv file.

Add-PSSnapin "Vmware.VimAutomation.Core"
Connect-VIServer -Server myvcenter.com
$VM = get-vm "testsql" #Replace this string with your VM name
$VMSummaries = @()
$DiskMatches = @()
$VMView = $VM | Get-View
    ForEach ($VirtualSCSIController in ($VMView.Config.Hardware.Device | Where {$_.DeviceInfo.Label -match "SCSI Controller"}))
        {
        ForEach ($VirtualDiskDevice  in ($VMView.Config.Hardware.Device | Where {$_.ControllerKey -eq $VirtualSCSIController.Key}))
            {
            $VMSummary = "" | Select VM, HostName, PowerState, DiskFile, DiskName, DiskSize, SCSIController, SCSITarget
            $VMSummary.VM = $VM.Name
            $VMSummary.HostName = $VMView.Guest.HostName
            $VMSummary.PowerState = $VM.PowerState
            $VMSummary.DiskFile = $VirtualDiskDevice.Backing.FileName
            $VMSummary.DiskName = $VirtualDiskDevice.DeviceInfo.Label
            $VMSummary.DiskSize = $VirtualDiskDevice.CapacityInKB * 1KB
            $VMSummary.SCSIController = $VirtualSCSIController.BusNumber
            $VMSummary.SCSITarget = $VirtualDiskDevice.UnitNumber
            $VMSummaries += $VMSummary
            }
        }
$Disks = Get-WmiObject -Class Win32_DiskDrive -ComputerName $VM.Name
$Diff = $Disks.SCSIPort | sort-object -Descending | Select -last 1
foreach ($device in $VMSummaries)
   {
    $Disks | % {if((($_.SCSIPort - $Diff) -eq $device.SCSIController) -and ($_.SCSITargetID -eq $device.SCSITarget))
           {
             $DiskMatch = "" | Select VMWareDisk, VMWareDiskSize, WindowsDeviceID, WindowsDiskSize
             $DiskMatch.VMWareDisk = $device.DiskName
             $DiskMatch.WindowsDeviceID = $_.DeviceID.Substring(4)
             $DiskMatch.VMWareDiskSize = $device.DiskSize/1gb
             $DiskMatch.WindowsDiskSize =  [decimal]::round($_.Size/1gb)
             $DiskMatches+=$DiskMatch
            }
        }
   }
$DiskMatches | export-csv -path "c:\temp\$($VM.Name)drive_matches.csv"


Let’s go line by line through the script.

Add-PSSnapin "Vmware.VimAutomation.Core"
Connect-VIServer -Server myvcenter.com
The first two lines add the VMware PowerCLI snapin to the current session and create a connection to the VMware vCenter server.

$VM = get-vm "testsql"
This one is just getting the VM object data and storing it in the $VM variable.

$VMSummaries = @()
$DiskMatches = @()
Those two lines above create hash tables to save the VMware and Windows SCSI controllers and drives’ data.

 $VMView = $VM | Get-View
There is no way to get the SCSI controller data from the VMware VM using default fields, so I’m kicking the Get-View cmdlet to retrieve the corresponding .net object information.

ForEach ($VirtualSCSIController in ($VMView.Config.Hardware.Device | Where {$_.DeviceInfo.Label -match "SCSI Controller"}) {
The loop above gets all SCSI controller data from the VM and iterates through them.

ForEach ($VirtualDiskDevice  in ($VMView.Config.Hardware.Device | Where {$_.ControllerKey -eq $VirtualSCSIController.Key})) {
Now I’m doing something similar to the previous operation with all the VM disks. I iterate through the disks to find those that are connected to the SCSI controller from the previous loop.

$VMSummary = "" | Select VM, HostName, PowerState, DiskFile, DiskName, DiskSize, SCSIController, SCSITarget
            $VMSummary.VM = $VM.Name
            $VMSummary.HostName = $VMView.Guest.HostName
            $VMSummary.PowerState = $VM.PowerState
            $VMSummary.DiskFile = $VirtualDiskDevice.Backing.FileName
            $VMSummary.DiskName = $VirtualDiskDevice.DeviceInfo.Label
            $VMSummary.DiskSize = $VirtualDiskDevice.CapacityInKB * 1KB
            $VMSummary.SCSIController = $VirtualSCSIController.BusNumber
            $VMSummary.SCSITarget = $VirtualDiskDevice.UnitNumber
            $VMSummaries += $VMSummary
            }
        }
In the above lines, I put all the information together. The first line sets up the hash table to store the information about the VM drives. The following eight lines populate this table with the information about the VM name, the VM guest name, its power state, and other drive and SCSI controller details. The last line adds the current record to the hash table $VMSummaries.

I now have all the needed information about the VMware SCSI controller and disks. Next, I have to get the same kind of data from Windows.

$Disks = Get-WmiObject -Class Win32_DiskDrive -ComputerName $VM.Name
To do that, I’m utilizing Get-WmiObject PowerShell cmdlet, reaching for the Win32_DiskDrive object, which stores the Windows physical disk’s information. I’m putting each Windows physical disk data into $Disks variable.

$Diff = $Disks.SCSIPort | sort-object -Descending | Select -last 1





Now comes the trickiest part. VMware and Windows have different starting numbers for SCSI controllers. VMware always begins with 0 and Windows just uses an arbitrary number. The Windows numbers are incremented one by one from the start number, which can’t be less than 1.

Thus, what I really need to know is the number of the first controller. To extract this number, I’m reaching to the SCSIPort property of each Windows drive object and then find the minimal value using the Sort-Object. This is the first Windows SCSI controller number, which corresponds to the VMware SCSI controller 0. I’m storing the number into the $Diff variable and compare it later with the VMware controller numbers.

ForEach ($device in $VMSummaries)
   {
    $Disks | % {if((($_.SCSIPort - $Diff) -eq $device.SCSIController) -and ($_.SCSITargetID -eq $device.SCSITarget))         {
Here, I’m looping through the $VMSummaries hash table records and then again through the contents of the $Disks variable to compare the disk and controllers data I’ve taken from VMware with the information I got from Windows.

Then I’m checking if the current Windows disk SCSIPort number corresponds to the SCSI controller number I’ve got from VMware. To do so, I’m subtracting the number of the first Windows SCSI controller stored in the $Diff variable from the number of the current controller in the loop and then comparing the result with the VMware SCSI controller number. If the numbers are the same, I’m good to go.

Once I know that this is the right SCSI controller, I need to compare the disk’s SCSI target ID. Fortunately, those numbers correspond to each other in VMware and Windows, so I don’t need to do any transformations. If the controller number and the disk number are equal, I’ve found the right drive. Now is the time to save the information:
$DiskMatch = "" | Select VMWareDisk, VMWareDiskSize, WindowsDeviceID, WindowsDiskSize
             $DiskMatch.VMWareDisk = $device.DiskName
             $DiskMatch.WindowsDeviceID = $_.DeviceID.Substring(4)
             $DiskMatch.VMWareDiskSize = $device.DiskSize/1gb
             $DiskMatch.WindowsDiskSize =  [decimal]::round($_.Size/1gb)
             $DiskMatches+=$DiskMatch
            }
        }

   }
As you can see, I’m putting information about the corresponding VMware and Windows drives into the hash table.
$DiskMatches | export-csv -path "c:\temp\$($VM.Name)drive_matches.csv" 

The very last line exports all gathered information into a .csv file. At the end, we have a nice table that maps two sets of drives together.

Post a Comment

 
TECH SUPPORT © 2012-2016