Oct 17, 2016

How to Match Physical Drives to Volume Labels with PowerShell

The idea of this article and a script described came to me couple of days ago after one of the readers commented on my article about VMware virtual disks and Windows drive volumes. The question was: is there any way to match physical drives to volume labels in Windows with PowerShell.





I knew that there is no straightforward way to do that, because Windows stores information about physical disks and their controllers in one object, relationship information between physical disks and partitions in another, and information about logical disks and partitions in a third.

Thus, we have to combine the different information sources to get the appropriate result.

$vm = "sandbox01"
Function get-match($vm){
$VM = get-vm $vm
$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 -Credential $Credential
$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"
return $DiskMatches
}

$WinDevIDs = get-match $vm
$DiskDrivesToDiskPartition = Get-WmiObject -Class Win32_DiskDriveToDiskPartition -ComputerName $vm
$WinDevsToDrives = @()
foreach($ID in $WinDevIDs){
   $PreRes = $null
   $PreRes = $DiskDrivesToDiskPartition.__RELPATH -match $ID.WindowsDeviceID
   for($i=0;$i -lt $PreRes.Count;$i++){
      $matches =$null
      $WinDev = "" | Select PhysicalDrive, DiskAndPart
      $PreRes[$i] -match '.*(Disk\s#\d+\,\sPartition\s#\d+).*'
      $WinDev.PhysicalDrive = $ID.WindowsDeviceID
      $WinDev.DiskAndPart = $matches[1]
      $WinDevsToDrives+=$WinDev
     }
}

$LogicalDiskToPartition = Get-WmiObject -Class Win32_LogicalDiskToPartition -ComputerName $vm
$final = @()
foreach($drive in $WinDevsToDrives){
   $matches =$null
   $WinDevVol = "" | Select PhysicalDrive, DiskAndPart, VolumeLabel
   $WinDevVol.PhysicalDrive = $drive.PhysicalDrive
   $WinDevVol.DiskAndPart = $drive.DiskAndPart
   $Res = $LogicalDiskToPartition.__RELPATH -match $drive.DiskAndPart
   $Res[0] -match '.*Win32_LogicalDisk.DeviceID=\\"([A-Q]\:).*'
   if($matches){
       $WinDevVol.VolumeLabel = $matches[1]
      }
   $final+=$WinDevVol

  }
$final | Export-Csv -Path "c:\temp\$($vm)volume_matches.csv"

The get-match function is an important part of the script. It matches the VMware hard disks to the Windows physical drives. I already explained how this part works in my previous article. Thus, I will only discuss the new code below.

$WinDevIDs = get-match $vm
Here, I’m saving the results of the get-match function into the $WinDevIDs variable. The screenshot below displays the contents of the variable.

Get match function results
Get match function results

Now I need to get the partitions and then the volume labels for those physical drives:

$DiskDrivesToDiskPartition = Get-WmiObject -Class Win32_DiskDriveToDiskPartition -ComputerName $vm

In order to do that I need to get information about physical disk to partition mappings from the Win32_DiskDriveToPartition WMI object and to store this data in the $DiskDrivesToDiskPartition variable.

This information comes in a pretty raw format, as you can see below:


Win32_DiskDriveToPartition WMI object

The only valuable pieces of information for me are the disk and partition numbers and physical drive numbers, because we can use these numbers to determine the relationship between the physical disks and the partitions that reside on them.

$WinDevsToDrives = @()
foreach($ID in $WinDevIDs){
   $PreRes = $null
   $PreRes = $DiskDrivesToDiskPartition.__RELPATH -match $ID.WindowsDeviceID
   for($i=0;$i -lt $PreRes.Count;$i++){
      $matches =$null
      $WinDev = "" | Select PhysicalDrive, DiskAndPart
      $PreRes[$i] -match '.*(Disk\s#\d+\,\sPartition\s#\d+).*'
      $WinDev.PhysicalDrive = $ID.WindowsDeviceID
      $WinDev.DiskAndPart = $matches[1]
      $WinDevsToDrives+=$WinDev
     }
}


The “foreach” loop above goes through every physical drive in the $WinDevIDs variable and compares the physical drive number to information extracted from the __RELPATH field of the Win32_DiskDriveToDiskPartition object. If a match is found, the whole __RELPATH field goes into the $PreRes variable.

In order to find the disk and partition numbers which correspond to the current physical drive number, I use a for loop, going through each record in the $PreRes variable comparing it to a regex expression to find the relevant records.

Then I store the physical drive number, and disk/partition information, which I extract from service variable $matches, to the fields of the $WinDev hash table. At the end, all information is stored into the $WinDevsToDrives variable. The contents of $WinDevsToDrives then looks like this:
Contents of WinDevsToDrives 
Contents of WinDevsToDrives
$LogicalDiskToPartition = Get-WmiObject -Class Win32_LogicalDiskToPartition – ComputerName $vm 

After I’ve figured out which partitions reside on the particular physical drives, comes the final part: matching this information to volume labels. To do that, I’m firstly getting the information about the relationship between logical drives and partitions from the Win32_LogicalDiskToPartition object. Below you see the properties of the object:
 
Win32_LogicalDiskToPartition object
$final = @()
foreach($drive in $WinDevsToDrives){
   $matches =$null
   $WinDevVol = "" | Select PhysicalDrive, DiskAndPart, VolumeLabel
   $WinDevVol.PhysicalDrive = $drive.PhysicalDrive
   $WinDevVol.DiskAndPart = $drive.DiskAndPart
   $Res = $LogicalDiskToPartition.__RELPATH -match $drive.DiskAndPart
   $Res[0] -match '.*Win32_LogicalDisk.DeviceID=\\"([A-Q]\:).*'
   if($matches){
       $WinDevVol.VolumeLabel = $matches[1]
      }
   $final+=$WinDevVol
  }
$final | Export-Csv -Path "c:\temp\$($vm)volume_matches.csv"
Next, I create the $final hash table. Then I loop through every record in the $WinDevsToDrives variable, which I’ve got on the previous step, and fill it with the contents of the $WinDevVol.PhysicalDrive and the $WinDevVol.DiskAndPart fields, together with the corresponding information from the $WinDevsToDrives variable.

In the next step I’m comparing the disk and partition information stored in the DiskAndPart field of the $WinDevsToDrives variable to the data I’ve got from the __RELPATH field of the Win32_LogicalDiskToPartition object. If a match is found, I’m using regex to extract the logical disk label from this data, and saving it to the $WinDevVol.VolumeLabel hash table field using the  service variable $matches. Finally, I put all the information into the $final hash table and export this hash table to a .csv file. 





Below you can see screenshots from two .csv files this script generated. One displays the known matches between VMware disks and Windows drives, and another one the matches between Windows drives and volumes:

 
VMware disks to Windows drives matches

Windows drives to volumes matches 
Windows drives to volumes matches

Post a Comment

 
TECH SUPPORT © 2012 - Designed by INFOSBIRD