Press "Enter" to skip to content

Matching VMDKs to Windows drive letters using PowerCLI

Joshua Stenhouse 1

Recently one of the largest Zerto customers (10,000 protected VMs) reached out to me asking for help. It was early Friday evening, I already had a beer open, so I thought why not? Using PowerCLI they needed to match VMDKs to Windows drive letters in order to mark them as SWAP disks in their VM onboarding script. Pretty important as this saves bandwidth by Zerto not replicating changes to page files, TempDBs and SQL backup disks. In this post I’ll show you how to do this with no network access to the guest VM.

VMDiskLayout
Which disk is which!

A quick google search turned up many examples of matching VMDKs to drive letters, but the majority relied on Get-WmiObject. This requires network access to the Guest which they don’t have, ruling out this option.

I next found a great example from Cody Hosterman here which doesn’t need network access to the guest VM, but it does rely on the advanced VM setting Disk.EnableUUID being enabled. Sod’s law they didn’t even have this setting present, never mind enabled, and adding it would require a reboot. Not a viable option for 10,000 VMs!

VMConfigDiskUUID

My solution was to combine the Disk.EnableUUID element of Cody’s module along with his use of ConvertTo-CSV to get usable data from PowerShell inside Invoke-VMScript (genius idea!). Iterating through each VMDK I match on disk UUID if present, if not then match based on the disk size in bytes. I also record which data point the disk was matched on with UUID being the preferred. Why? Because if you have 2 disks the same size and no disk UUID the script can only tell you all the possible drive letters. But this won’t happen to you, because you followed good practice by having each a different size, didn’t you?

The end result is this:

MatchingDiskByUUIDHere’s the result of running the script against the same VM but with disk UUID disabled:MatchingDiskBySizeBelow are 2 example scripts for you to download. One includes a prompt for credentials within the guest VM, the other will just pass on the credentials of the user running the script. The only major requirement other than PowerShell 5.1+ is the guest VM be on Server 2012 or above as it runs Get-Partition inside the VM. I’ve done my best to make sure both are easy to read/understand, and you can get them here:

MatchingDriveLettersToVMDKsv1.zip

Extract it to C:\MatchingDriveLettersToVMDKsv1\ and run the script required. Or you can copy and paste them from below, starting with the version which prompts for guest creds:

################################################
# Configure the variables below for the vCenter
################################################
$VMName = "SQL16-VM01"
$vCenter = "192.168.1.10"
$ScriptDirectory = "C:\MatchingDriveLettersToVMDKsv1"
################################################
# Running the script, nothing to change below
################################################
#######################
# Importing Guest VM credentials
#######################
# Setting credential file
$VMCredentialsFile = $ScriptDirectory + "\VMCredentials.xml"
# Testing if file exists
$VMCredentialsFileTest =  Test-Path $VMCredentialsFile
# IF doesn't exist, prompting and saving credentials
IF ($VMCredentialsFileTest -eq $False)
{
$VMCredentials = Get-Credential -Message "Enter VM script run credentials"
$VMCredentials | EXPORT-CLIXML $VMCredentialsFile -Force
}
ELSE
{
# Importing credentials
$VMCredentials = IMPORT-CLIXML $VMCredentialsFile
}
#######################
# Importing vCenter credentials
#######################
# Setting credential file
$vCenterCredentialsFile = $ScriptDirectory + "\vCenterCredentials.xml"
# Testing if file exists
$vCenterCredentialsFileTest =  Test-Path $vCenterCredentialsFile
# IF doesn't exist, prompting and saving credentials
IF ($vCenterCredentialsFileTest -eq $False)
{
$vCenterCredentials = Get-Credential -Message "Enter vCenter login credentials"
$vCenterCredentials | EXPORT-CLIXML $vCenterCredentialsFile -Force
}
ELSE
{
# Importing credentials
$vCenterCredentials = IMPORT-CLIXML $vCenterCredentialsFile
}
#######################
# Installing then importing PowerCLI module
#######################
$PowerCLIModuleCheck = Get-Module -ListAvailable VMware.PowerCLI
IF ($PowerCLIModuleCheck -eq $null)
{
Install-Module -Name VMware.PowerCLI –Scope CurrentUser -Confirm:$false -AllowClobber
}
# Importing PowerCLI
Import-Module VMware.PowerCLI
#######################
# Connecting to vCenter
#######################
Connect-VIServer -Server $vCenter -Credential $vCenterCredentials
#####################
# Getting VM guest disk info
#####################
$VMGuestDiskScript = Invoke-VMScript -ScriptText {
# Creating alphabet array
$Alphabet=@()
65..90|ForEach{$Alphabet+=[char]$_}
# Getting drive letters inside the VM where the drive letter is in the alphabet
$DriveLetters = Get-Partition | Where-Object {($Alphabet -match $_.DriveLetter)} | Select -ExpandProperty DriveLetter
# Reseting serials
$DiskArray = @()
# For each drive letter getting the serial number
ForEach ($DriveLetter in $DriveLetters)
{
# Getting disk info
$DiskInfo = Get-Partition -DriveLetter $DriveLetter | Get-Disk | Select *
$DiskSize = $DiskInfo.Size
$DiskUUID = $DiskInfo.SerialNumber
# Formatting serial to match in vSphere, if not null
IF ($DiskSerial -ne $null)
{
$DiskSerial = $DiskSerial.Replace("_","").Replace(".","")
}
# Adding to array
$DiskArrayLine = New-Object PSObject
$DiskArrayLine | Add-Member -MemberType NoteProperty -Name "DriveLetter" -Value "$DriveLetter"
$DiskArrayLine | Add-Member -MemberType NoteProperty -Name "SizeInBytes" -Value "$DiskSize"
$DiskArrayLine | Add-Member -MemberType NoteProperty -Name "UUID" -Value "$DiskUUID"
$DiskArray += $DiskArrayLine
}
# Converting Disk Array to CSV data format
$DiskArrayData = $DiskArray | ConvertTo-Csv
# Returning Disk Array CSV data to main PowerShell script
$DiskArrayData
# End of invoke-vmscript below
} -VM $VMName -ToolsWaitSecs 120 -GuestCredential $VMCredentials
# Pulling the serials from the invoke-vmscript and trimming blank spaces
$VMGuestDiskCSVData = $VMGuestDiskScript.ScriptOutput.Trim()
# Converting from CSV format
$VMGuestDiskData = $VMGuestDiskCSVData | ConvertFrom-Csv
# Hostoutput of VM Guest Data
"
VMGuestDiskData:" 
$VMGuestDiskData | Format-Table -AutoSize
#####################
# Building list of VMDKs for the Customer VM
#####################
# Creating array
$VMDKArray = @()
# Getting VMDKs for the VM
$VMDKs = Get-VM $VMName | Get-HardDisk
# For Each VMDK building table array
ForEach($VMDK in $VMDKs)
{
# Getting VMDK info
$VMDKFile = $VMDK.Filename
$VMDKName = $VMDK.Name
$VMDKControllerKey = $VMDK.ExtensionData.ControllerKey
$VMDKUnitNumber = $VMDK.ExtensionData.UnitNumber
$VMDKDiskDiskSizeInGB = $VMDK.CapacityGB
$VMDKDiskDiskSizeInBytes = $VMDK.ExtensionData.CapacityInBytes
# Getting UUID
$VMDKUUID = $VMDK.extensiondata.backing.uuid.replace("-","")
# Using Controller key to get SCSI bus number
$VMDKBus = $VMDK.Parent.Extensiondata.Config.Hardware.Device | Where {$_.Key -eq $VMDKControllerKey}
$VMDKBusNumber = $VMDKBus.BusNumber
# Creating SCSI ID
$VMDKSCSIID = "scsi:"+ $VMDKBusNumber + ":" + $VMDKUnitNumber
# Matching VMDK to drive letter based on UUID first, if no serial UUID matching on size in bytes
$VMDKDiveLetter = $VMGuestDiskData | Where-Object {$_.UUID -eq $VMDKUUID} | Select -ExpandProperty DriveLetter
$VMDKMatchOn = "UUID"
IF ($VMDKDiveLetter -eq $null)
{
$VMDKDiveLetter = $VMGuestDiskData | Where-Object {$_.SizeInBytes -eq $VMDKDiskDiskSizeInBytes} | Select -ExpandProperty DriveLetter
$VMDKMatchOn = "Size"
}
# Matching drive letter for marking SWAP disk
IF ($SWAPDriveLetters -match $VMDKDiveLetter)
{
$VMDKSwap = "true"
}
ELSE
{
$VMDKSwap = "false"
}
# Creating array of VMDKs
$VMDKArrayLine = New-Object PSObject
$VMDKArrayLine | Add-Member -MemberType NoteProperty -Name "VM" -Value $VMName
$VMDKArrayLine | Add-Member -MemberType NoteProperty -Name "DiskName" -Value $VMDKName
$VMDKArrayLine | Add-Member -MemberType NoteProperty -Name "DriveLetter" -Value $VMDKDiveLetter
$VMDKArrayLine | Add-Member -MemberType NoteProperty -Name "MatchedOn" -Value $VMDKMatchOn
$VMDKArrayLine | Add-Member -MemberType NoteProperty -Name "DiskSizeGB" -Value $VMDKDiskDiskSizeInGB
$VMDKArrayLine | Add-Member -MemberType NoteProperty -Name "SCSIBus" -Value $VMDKBusNumber
$VMDKArrayLine | Add-Member -MemberType NoteProperty -Name "SCSIUnit" -Value $VMDKUnitNumber
$VMDKArrayLine | Add-Member -MemberType NoteProperty -Name "SCSIID" -Value $VMDKSCSIID
$VMDKArrayLine | Add-Member -MemberType NoteProperty -Name "DiskUUID" -Value $VMDKUUID
$VMDKArrayLine | Add-Member -MemberType NoteProperty -Name "DiskSizeBytes" -Value $VMDKDiskDiskSizeInBytes
$VMDKArrayLine | Add-Member -MemberType NoteProperty -Name "DiskFile" -Value $VMDKFile
$VMDKArray += $VMDKArrayLine
}
#####################
# Final host output of VMDK array
#####################
$VMDKArray | Format-Table -AutoSize
#####################
# End of script
#####################

Without guest creds:

################################################
# Configure the variables below for the vCenter
################################################
$VMName = "SQL16-VM01"
$vCenter = "192.168.1.10"
$ScriptDirectory = "C:\MatchingDriveLettersToVMDKsv1"
################################################
# Running the script, nothing to change below
################################################
#######################
# Importing vCenter credentials
#######################
# Setting credential file
$vCenterCredentialsFile = $ScriptDirectory + "\vCenterCredentials.xml"
# Testing if file exists
$vCenterCredentialsFileTest =  Test-Path $vCenterCredentialsFile
# IF doesn't exist, prompting and saving credentials
IF ($vCenterCredentialsFileTest -eq $False)
{
$vCenterCredentials = Get-Credential -Message "Enter vCenter login credentials"
$vCenterCredentials | EXPORT-CLIXML $vCenterCredentialsFile -Force
}
ELSE
{
# Importing credentials
$vCenterCredentials = IMPORT-CLIXML $vCenterCredentialsFile
}
#######################
# Installing then importing PowerCLI module
#######################
$PowerCLIModuleCheck = Get-Module -ListAvailable VMware.PowerCLI
IF ($PowerCLIModuleCheck -eq $null)
{
Install-Module -Name VMware.PowerCLI –Scope CurrentUser -Confirm:$false -AllowClobber
}
# Importing PowerCLI
Import-Module VMware.PowerCLI
#######################
# Connecting to vCenter
#######################
Connect-VIServer -Server $vCenter -Credential $vCenterCredentials
#####################
# Getting VM guest disk info
#####################
$VMGuestDiskScript = Invoke-VMScript -ScriptText {
# Creating alphabet array
$Alphabet=@()
65..90|ForEach{$Alphabet+=[char]$_}
# Getting drive letters inside the VM where the drive letter is in the alphabet, can't filter on null or empty for some reason
$DriveLetters = Get-Partition | Where-Object {($Alphabet -match $_.DriveLetter)} | Select -ExpandProperty DriveLetter
# Reseting serials
$DiskArray = @()
# For each drive letter getting the serial number
ForEach ($DriveLetter in $DriveLetters)
{
# Getting disk info
$DiskInfo = Get-Partition -DriveLetter $DriveLetter | Get-Disk | Select *
$DiskSize = $DiskInfo.Size
$DiskUUID = $DiskInfo.SerialNumber
# Formatting serial to match in vSphere, if not null
IF ($DiskSerial -ne $null)
{
$DiskSerial = $DiskSerial.Replace("_","").Replace(".","")
}
# Adding to array
$DiskArrayLine = New-Object PSObject
$DiskArrayLine | Add-Member -MemberType NoteProperty -Name "DriveLetter" -Value "$DriveLetter"
$DiskArrayLine | Add-Member -MemberType NoteProperty -Name "SizeInBytes" -Value "$DiskSize"
$DiskArrayLine | Add-Member -MemberType NoteProperty -Name "UUID" -Value "$DiskUUID"
$DiskArray += $DiskArrayLine
}
# Converting Disk Array to CSV data format
$DiskArrayData = $DiskArray | ConvertTo-Csv
# Returning Disk Array CSV data to main PowerShell script
$DiskArrayData
# End of invoke-vmscript below
} -VM $VMName -ToolsWaitSecs 120
# Pulling the serials from the invoke-vmscript and trimming blank spaces
$VMGuestDiskCSVData = $VMGuestDiskScript.ScriptOutput.Trim()
# Converting from CSV format
$VMGuestDiskData = $VMGuestDiskCSVData | ConvertFrom-Csv
# Hostoutput of VM Guest Data
"
VMGuestDiskData:" 
$VMGuestDiskData | Format-Table -AutoSize
#####################
# Building list of VMDKs for the Customer VM
#####################
# Creating array
$VMDKArray = @()
# Getting VMDKs for the VM
$VMDKs = Get-VM $VMName | Get-HardDisk
# For Each VMDK building table array
ForEach($VMDK in $VMDKs)
{
# Getting VMDK info
$VMDKFile = $VMDK.Filename
$VMDKName = $VMDK.Name
$VMDKControllerKey = $VMDK.ExtensionData.ControllerKey
$VMDKUnitNumber = $VMDK.ExtensionData.UnitNumber
$VMDKDiskDiskSizeInGB = $VMDK.CapacityGB
$VMDKDiskDiskSizeInBytes = $VMDK.ExtensionData.CapacityInBytes
# Getting UUID
$VMDKUUID = $VMDK.extensiondata.backing.uuid.replace("-","")
# Using Controller key to get SCSI bus number
$VMDKBus = $VMDK.Parent.Extensiondata.Config.Hardware.Device | Where {$_.Key -eq $VMDKControllerKey}
$VMDKBusNumber = $VMDKBus.BusNumber
# Creating SCSI ID
$VMDKSCSIID = "scsi:"+ $VMDKBusNumber + ":" + $VMDKUnitNumber
# Matching VMDK to drive letter based on UUID first, if no serial UUID matching on size in bytes
$VMDKDiveLetter = $VMGuestDiskData | Where-Object {$_.UUID -eq $VMDKUUID} | Select -ExpandProperty DriveLetter
$VMDKMatchOn = "UUID"
IF ($VMDKDiveLetter -eq $null)
{
$VMDKDiveLetter = $VMGuestDiskData | Where-Object {$_.SizeInBytes -eq $VMDKDiskDiskSizeInBytes} | Select -ExpandProperty DriveLetter
$VMDKMatchOn = "Size"
}
# Matching drive letter for marking SWAP disk
IF ($SWAPDriveLetters -match $VMDKDiveLetter)
{
$VMDKSwap = "true"
}
ELSE
{
$VMDKSwap = "false"
}
# Creating array of VMDKs
$VMDKArrayLine = New-Object PSObject
$VMDKArrayLine | Add-Member -MemberType NoteProperty -Name "VM" -Value $VMName
$VMDKArrayLine | Add-Member -MemberType NoteProperty -Name "DiskName" -Value $VMDKName
$VMDKArrayLine | Add-Member -MemberType NoteProperty -Name "DriveLetter" -Value $VMDKDiveLetter
$VMDKArrayLine | Add-Member -MemberType NoteProperty -Name "MatchedOn" -Value $VMDKMatchOn
$VMDKArrayLine | Add-Member -MemberType NoteProperty -Name "DiskSizeGB" -Value $VMDKDiskDiskSizeInGB
$VMDKArrayLine | Add-Member -MemberType NoteProperty -Name "SCSIBus" -Value $VMDKBusNumber
$VMDKArrayLine | Add-Member -MemberType NoteProperty -Name "SCSIUnit" -Value $VMDKUnitNumber
$VMDKArrayLine | Add-Member -MemberType NoteProperty -Name "SCSIID" -Value $VMDKSCSIID
$VMDKArrayLine | Add-Member -MemberType NoteProperty -Name "DiskUUID" -Value $VMDKUUID
$VMDKArrayLine | Add-Member -MemberType NoteProperty -Name "DiskSizeBytes" -Value $VMDKDiskDiskSizeInBytes
$VMDKArrayLine | Add-Member -MemberType NoteProperty -Name "DiskFile" -Value $VMDKFile
$VMDKArray += $VMDKArrayLine
}
#####################
# Final host output of VMDK array
#####################
$VMDKArray | Format-Table -AutoSize
#####################
# End of script
#####################

Happy scripting,

  1. Joe Joe

    This did not work in our environment. My userid did not have VMWare permission to invoke-vmscript . However, I was able to take the script it invoked and issued a powershell invoke-command -computername <windowsservername. -scriptblock { } and it worked. I had permissions to remotely run powershell commands but not the vmware path. Had to modify the outputs from csv format to standard object output but it was simple to do. Thanks, mapping the vmdk to windows hard disks has been a nagging issue. With this, I can run the command on all of our servers and store in a file for reference. It is key when trying to remove or enlarge disks to now which vmdk to work with. Good job.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.

%d