associating vmware drives with guest os drives in powershell
estimated read time: 6-8 minutesSometimes I get frustrated with how something is done and I put my head down and get to work. In this case it's working with VMWare disks.
On occasion we have to expand our virtual disks due to normal data growth. To do this we have to make a request to our System Operations team (they are a great team BTW and super responsive), in that request we have to include the exact size of the disk we need to expand because there is no easy way to tell which disk is which from inside the guest VM. This is where my frustration comes in. So I got to work and a few iterations of code later I found a solution.
The Common Thread
I must have poured over objects running Get-Member
for hours trying to find the common thread that tied the Windows disk to the VMWare disk. In the end it was pretty simple: "physical" order
When configuring disks in VCenter you can configure one or more SCSI controllers:
These are numbered starting with 0. If you look at the disks themselves, you will see they are identified by the controller id + the disk id for that controller:
So how do the disks look in Windows? Well, you might think that the disk number in Windows would honor the order the disks are attached tot he virtual controllers, but you would be wrong. Just like I was. To see the disks in order of disk number, run the following code:
Invoke-Command -ComputerName server1.markw.dev -ScriptBlock {
Get-PhysicalDisk | Sort-Object -Property DeviceID
} `
| Select-Object -Property `
DeviceID,
@{Name="Size";Expression={"$($_.Size/1GB)GB"}}
In my example case I see the following:
DeviceID Size
-------- ----
0 50GB
1 10GB
2 50GB
3 70GB
4 32GB
5 25GB
6 75GB
If you refer to the screenshots from VCenter you'll see the order of the disks does NOT match. So what next? Well, we'll fast forward, but next I did a lot of work poking around in Windows and trying various commands in PowerShell piped into Get-Member
. What I eventually found was pretty simple, the PhysicalLocation
and DeviceID
properties:
Invoke-Command -ComputerName server1.markw.dev -ScriptBlock {
Get-PhysicalDisk | Sort-Object -Property DeviceID
} `
| Select-Object -Property `
PhysicalLocation,
DeviceID,
@{Name="Size";Expression={"$($_.Size/1GB)GB"}} -Unique `
| Sort-Object -Property `
PhysicalLocation,
DeviceID
This gave me the following output:
PhysicalLocation DeviceID Size
---------------- -------- ----
SCSI0 0 50GB
SCSI0 1 10GB
SCSI0 2 50GB
SCSI1 5 25GB
SCSI1 6 75GB
SCSI2 3 70GB
SCSI2 4 32GB
If we again refer back to our screenshot, this disk order matches!
Let's put it all together now (NOTE: This function requires the VMWare PowerCLI PowerShell modules):
function Get-VMDiskInformation {
<#
.SYNOPSIS
Returns information about the VMWare volumes and associated
Windows drives on a given guest.
.DESCRIPTION
Function connects to vSphere and a given Windows guest and
returns the Windows phyical disk information mapped to the
disks in VCenter.
.PARAMETER VSphereServer
FQDN of vSphereServer that hosts the VM
.PARAMETER ComputerFQDN
FQDN of the target server
.PARAMETER Credentials
An option parameter to take a credential object in. This
is then used to authenticate with VSphere
#>
[CmdletBinding()]
param
(
[Parameter(Mandatory=$True)][ValidateNotNullOrEmpty()]
[string]$VSphereServer,
[Parameter(Mandatory=$True)][ValidateNotNullOrEmpty()]
[string]$ComputerFQDN,
$Credentials
)
Import-Module VMware.VimAutomation.Core
# get user credentials to connect to vSphere
if ( -not $Credentials ) {
$Credentials = $(Get-Credential)
}
# initialise arrays
$g_mounts = @()
$results = @()
# set error action
$ErrorActionPreference = "Stop"
# connecting to vSphere
$Start = Get-Date
Write-Host "Connection to VSphere Server..."
$vSphere = Get-VIServer -Server $VSphereServer -Credential $Credentials -Force
# retrieving VM object
Write-Host "Getting VM Object..."
$VMName = Get-View -Viewtype VirtualMachine -Property guest.hostname, name `
| Where-Object { $_.guest.hostname -eq $ComputerFQDN } `
| Select-Object -ExpandProperty Name
$VM = Get-VM -Server $vSphere.Name -Name $VMName
Write-Host "Getting Disks..."
# getting disk information
$v_disks = $VM | Get-HardDisk | Sort-Object -Property Id
$Session = New-CimSession -ComputerName $ComputerFQDN
# Some servers return duplicate entries when grabbing the physical disks,
# so we first grab the in-use serial numbers to filter the physical disks
$g_disk_serials = Get-Disk -CimSession $Session | Select-Object -ExpandProperty SerialNumber
# get physical disk info, but only for disks with serial numbers that appear
# in `Get-Disk`. We NEED the physical location to properly map the drives.
$g_disks = Get-PhysicalDisk -CimSession $Session `
| Where-Object { $_.SerialNumber -in $g_disk_serials } `
| Select-Object -Property PhysicalLocation,DeviceID,SerialNumber -Unique `
| Sort-Object -Property PhysicalLocation,DeviceID
Write-Verbose "Disk Info:"
Write-Verbose " - VMWare Disks: $($v_disks.Count)"
$v_disks | ForEach-Object {
Write-Verbose " - $($_)"
}
Write-Verbose " - Guest Disks: $($g_disks.Count)"
$g_disks | ForEach-Object {
Write-Verbose " - $($_)"
}
Write-Host "Finding mount points..."
# adding disk information to g_mounts array, ignore empty mount paths
$CimPartInfo = Get-Partition -CimSession $Session `
| Where-Object { $_.AccessPaths } `
| Select-Object -Property DiskNumber,AccessPaths,PartitionNumber
foreach ($CimPart in ($CimPartInfo | Where-Object { $_.AccessPaths } )) {
$g_mounts += [PSCustomObject]@{
Path = $CimPart.AccessPaths[0]
DiskNumber = $CimPart.DiskNumber
PartitionNumber = $CimPart.PartitionNumber
}
}
Write-Verbose "Mount Point Info:"
$g_mounts | ForEach-Object {
Write-Verbose " - ID: $($_.DiskNumber) - $($_.Path)"
}
# map the vm disks to the guest disks (include mount points if used)
for($i=0;$i -lt $v_disks.Count;$i++){
$v_disk = $v_disks[$i]
$g_disk = $g_disks[$i]
$g_mount = $g_mounts | Where-Object { $_.DiskNumber -eq $g_disk.DeviceId -and $_.Path }
# adding disk information to results array
if ( $g_drives -notcontains 900 ) {
$results += [PSCustomObject]@{
# In cases where a disk has multiple partitions and multiple mount points
# we only care about reporting the disk number once.
DiskNumber = $g_mount.DiskNumber | Select -First 1
PartitionNumber = $g_mount.PartitionNumber
VMWareDisk = $v_disk.Name
DiskSerial = $g_disk.SerialNumber
Path = $g_mount.Path
CapacityGB = $v_disk.CapacityGB
}
}
}
# return results array
$results
}
This function does a little more than we covered above, but to keep it simple it does the following:
- Gets the virtual disk associated with a given guest
- Gets the physical disks according to Windows for the given guest
- Uses the
PhysicalLocation
andDeviceID
properties to match the disks up with their VMWare counterparts
This function goes a step farther as well and tells you which mountpoint the disk is mounted on (if mountpoints are used).
Conclusion
This project was a LOT harder than it should have been. I did a lot of searching and never really found a simple way to do this. Most of the posts I came across recommended using the disk size to do the matching but this just felt really hacky to me. In the final version of this function I also access data from a Pure Storage array to identify the specific VVol associated with the guest disk, but that seemed a little to specific for a blog post. If you are interested in that part, send me an email.