When expanding the disk size of a VMWare virtual machine or deleting a disk, sometimes it is hard to understand which VMware virtual disk matches the specific Windows VM disk. If there are few disks and they differ by their size, it is easy to find the disk you need. But what to do if several VMDK (or RDM) disks of the same size or several virtual SCSI controllers have been created for a VM? How to avoid errors and select the disk a Windows administrator asks you to expand (or shrink)?
In this article we’ll see how to match Windows disks and virtual disks (VMDK) on a VMWare VM.
How to Get SCSI Device Number in Windows and VMWare?
Open the Disk Management console (diskmgmt.msc
) in Windows (in our example, it is Windows Server 2016). The SCSI controller name and SCSI device number are not displayed in the list of disks. To get the SCSI device number, right-click a disk and select Properties. As you can see, the information about the device port for VMWare Virtual disk SCSI Disk Device is shown in the Location field of the General tab.
- Location 160 = SCSI Bus Controller 0
- Target ID 1 = device SCSI ID is 1
Join the data you see and get the SCSI disk address: SCSI(0:1).
Then open the virtual machine properties in your VMWare vSphere Client. Find the disk that has the same Virtual Device Node number as the ID you have got. In our example, it is SCSI(0:1) Hard Disk 2.
If multiple virtual disks with different SCSI controllers are configured on a virtual machine (you can add up to 4 SCSI controllers with 16 disks each to a virtual machine), it is quite challenging to find a SCSI device number manually. Also please note that SCSI controller numbers in Windows and VMWare may differ.
How to Match Windows Disk with VMDK by UUID/Serial Number Using PowerShell?
Another way to map VMWare virtual disks to disks inside a guest VM is to compare their unique disk IDs. In VMWare this attribute is called UUID (Unique ID), and in Windows – a Serial Number. Let’s see how to get UUID and SerialNumber of a virtual disk using PowerShell.
EnableUUID=TRUE
parameter enabled. It means that a guest OS must see virtual disk IDs.To get the information about disks in Windows, you can use the Storage module cmdlets or WMI queries. Since we still have some VMs running Windows Server 2008 R2 that do not have the Storage module, we will use WMI.
To get a SCSI controller number, a SCSI device number on it, a serial number of a virtual disk (SerialNumber/UUID), a disk size and disk number in Windows, run this PowerShell command:
$DiskInfo = foreach ($disk in Get-WmiObject Win32_DiskDrive) { [pscustomobject]@{ "DeviceID"=$disk.DeviceID; "Caption"=$disk.Caption; "Capacity (GB)"=[math]::Round($disk.size / 1GB,0); "SerialNumber" =$disk.SerialNumber "SCSIControllerNum"=$disk.scsiport; "SCSIDeviceNum"=$disk.scsitargetid; } } $DiskInfo|ft
In our example, Windows has detected three disks:
- PHYSICALDRIVE0: SCSI Port 0, SCSI Target 0, Serial 6000c2939b157427dadbace321ed4973
- PHYSICALDRIVE1: SCSI Port 0, SCSI Target 1, Serial 6000c2950ee961954909938642bb03b4
- PHYSICALDRIVE1: SCSI Port 4, SCSI Target 10, Serial 6000c2995fc3c4928d6650596bb02cef
Then let’s try to get SCSI controller numbers and UUIDs of the disks specified in the settings of the VMWare virtual machine. To view the VM settings, use the VMware PowerCLI cmdlets.
Import-Module VMware.VimAutomation.Core -ErrorAction SilentlyContinue connect-viserver ber-vmware1 $vmName="ber-man01" $vmHardDisks = Get-VM -Name $vmName | Get-HardDisk $vmDatacenterView = Get-VM -Name $vmName | Get-Datacenter | Get-View $virtualDiskManager = Get-View -Id VirtualDiskManager-virtualDiskManager $vmresults = @() foreach ($vmHardDisk in $vmHardDisks) { $string = $vmHardDisk.Filename $vmHardDiskUuid = ($vmHardDisk.ExtensionData.Backing.Uuid | ForEach-Object {$_.replace(' ','').replace('-','')}) $vmresult = "" | Select-Object vmHardDiskDatastore,vmHardDiskVmdk,vmHardDiskName,vmHardDiskSize,vmHardDiskUuid $vmresult.vmHardDiskDatastore = $vmHardDisk.filename.split(']')[0].split('[')[1] $vmresult.vmHardDiskVmdk = $vmHardDisk.filename.split(']')[1].trim() $vmresult.vmHardDiskName = $vmHardDisk.Name $vmresult.vmHardDiskSize = $vmHardDisk.CapacityGB $vmresult.vmHardDiskUuid = $vmHardDiskUuid $vmresults += $vmresult } $vmresults | ft
This script will connect to the vCenter (or ESXi) server and get the list of disks for the specified VM. The result must contain the DataStore name, VMDK file path, disk number, disk size and UUID.
Then you can manually match the disks you see in the guest Windows OS with VMWare virtual disks by their UUIDs.
If you have administrator permissions in the guest OS of the VM, you can match Windows disks and VMWare VMDK files using a more convenient PowerShell script. The script connects the guest Windows OS over the network, collects the information about its local disks and matches them with VMWare VMDKs.
Here is the full code of the PowerShell script:
Import-Module VMware.VimAutomation.Core -ErrorAction SilentlyContinue connect-viserver ber-vmware1 $vmName = "ber-man01" $WinHostName = "ber-mansrv01.woshub.com" #Get the list of disks of a VMWare virtual machine $vmDisks = Get-VM -Name $vmName | Get-HardDisk $vmDatacenterView = Get-VM -Name $vmName | Get-Datacenter | Get-View $virtualDiskManager = Get-View -Id VirtualDiskManager-virtualDiskManager # Enter the administrator credentials to access the guest Windows $cred = if ($cred){$cred}else{Get-Credential} # Getting the list of Windows disks and partitions using WMI $winDisk = Get-WmiObject -Class Win32_DiskDrive -ComputerName $WinHostName -Credential $cred $diskToDriveVolume = Get-WmiObject Win32_DiskDrive -ComputerName $WinHostName -Credential $cred| % { $disk = $_ $partitions = "ASSOCIATORS OF " + "{Win32_DiskDrive.DeviceID='$($disk.DeviceID)'} " + "WHERE AssocClass = Win32_DiskDriveToDiskPartition" Get-WmiObject -Query $partitions -ComputerName $WinHostName -Credential $cred| % { $partition = $_ $drives = "ASSOCIATORS OF " + "{Win32_DiskPartition.DeviceID='$($partition.DeviceID)'} " + "WHERE AssocClass = Win32_LogicalDiskToPartition" Get-WmiObject -Query $drives -ComputerName $WinHostName -Credential $cred| % { New-Object -Type PSCustomObject -Property @{ Disk = $disk.DeviceID DriveLetter = $_.DeviceID VolumeName = $_.VolumeName } } } } #Getting a disk serial number foreach ($disk in $winDisk) { $disk | Add-Member -MemberType NoteProperty -Name AltSerialNumber -Value $null $diskSerialNumber = $disk.SerialNumber if ($disk.Model -notmatch 'VMware Virtual disk SCSI Disk Device') { if ($diskSerialNumber -match '^\S{12}$'){$diskSerialNumber = ($diskSerialNumber | foreach {[byte[]]$bytes = $_.ToCharArray(); $bytes | foreach {$_.ToString('x2')} } ) -join ''} $disk.AltSerialNumber = $diskSerialNumber } } #Searching all VM disks and matching them with Windows disks by their SerialNumber / UUID $diskMaps = @() foreach ($vmDisk in $vmDisks) { $vmDiskUuid = $virtualDiskManager.queryvirtualdiskuuid($vmDisk.Filename, $vmDatacenterView.MoRef) | foreach {$_.replace(' ','').replace('-','')} $windowsDisk = $winDisk | where {$_.SerialNumber -eq $vmDiskUuid} if (-not $windowsDisk){$windowsDisk = $winDisk | where {$_.AltSerialNumber -eq $vmDisk.ScsiCanonicalName.substring(12,24)}} $curDiskMap = "" | select vmDiskDatastore, vmDiskVmdk, vmDiskName, windowsDiskIndex, vmDiskUuid, windowsDeviceID, drives, volumes $curDiskMap.vmDiskDatastore = $vmDisk.filename.split(']')[0].split('[')[1] $curDiskMap.vmDiskVmdk = $vmDisk.filename.split(']')[1].trim() $curDiskMap.vmDiskName = $vmDisk.Name $curDiskMap.windowsDiskIndex = if ($windowsDisk){$windowsDisk.Index}else{"FAILED TO MATCH"} $curDiskMap.vmDiskUuid = $vmDiskUuid $curDiskMap.windowsDeviceID = if ($windowsDisk){$windowsDisk.DeviceID}else{"FAILED TO MATCH"} $driveVolumes = $diskToDriveVolume | where {$_.Disk -eq $windowsDisk.DeviceID} $curDiskMap.drives = $driveVolumes.DriveLetter $curDiskMap.volumes = $driveVolumes.VolumeName $diskMaps += $curDiskMap } $diskMaps = $diskMaps | sort {[int]$_.vmDiskName.split(' ')[2]} $diskMaps | ft
The script also returns information about drive letters and volume labels in Windows.
Now you can easily find what Windows disk matches the given virtual vmdk disk.
11 comments
I was able to run this successfully against one VM. But every other VM I run this against I’m getting the following message:
You cannot call a method on a null-valued expression.
At line:49 char:59
+ … sk | where {$_.AltSerialNumber -eq $vmDisk.ScsiCanonicalName.substrin …
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (:) [], RuntimeException
+ FullyQualifiedErrorId : InvokeMethodOnNull
Ran into that myself until I realized I was missing the second instance of the name.
$vmName = “ber-man01”
$WinHostName = “ber-mansrv01.woshub.com”
Also, I changed those two lines to:
$vmName = Read-Host “VM Name”
$WinHostName = Read-Host “FQDN”
So it prompts me for the names rather than editing the script every time.
Hi,
thanks for sharing the knowledge
I am not able to get the SerialNumber of each disks remotely. Any idea if you have faced the issue?
The script you included at the bottom doesn’t work, as you are using Get-WmiObject, which was deprecated in PowerShell 3. I had to look at the date this was published… thought for sure it was going to be at least 4 years old.
The Get-WmiObject cmdlet works correctly in all versions up to PowerShell 5.1 (Windows 10/Windows Server 2016). If you are using PowerShell Core (6.0 or 7.1), you should use the Get-CimInstance command instead.
The script which has potential, but doesn’t consider mount points aka Junctions. There are Windows Servers where the best design requires > then the alphabet allows. Note the vmDiskName.
vmDiskDatastore : Datastore-03
vmDiskVmdk : /_13.vmdk
vmDiskName : Hard disk 46
windowsDiskIndex : 31
vmDiskUuid : 6000C297208225779df4c81af1a0ed68
windowsDeviceID : \\.\PHYSICALDRIVE31
drives :
volumes :
Hi Bob,
This works just fine for junctions. You just have to use the windowsDiskIndex column instead of drive letter. It will be the same as the Disk number in Disk Manager.
Below, windows disk 9 and 16 are junction mounted:
vmDiskDatastore vmDiskVmdk vmDiskName windowsDiskIndex vmDiskUuid windowsDeviceID drives volumes
————— ———- ———- —————- ———- ————— —— ——-
NPE-RDB-01 MYSQL/MYSQL_23.vmdk Hard disk 1 0 6000C29fd533b29c5fcdd110c1c2ef48 \\.\PHYSICALDRIVE0 C:
NPE-RDB-01 MYSQL/MYSQL_1.vmdk Hard disk 2 8 6000C292e0ac9adc4531644db101b606 \\.\PHYSICALDRIVE8 M: warehouse_instance
NPE-RDB-01 MYSQL/MYSQL_2.vmdk Hard disk 3 9 6000C293860223ce23e772ad631b047b \\.\PHYSICALDRIVE9
NPE-RDB-01 MYSQL/MYSQL_3.vmdk Hard disk 4 16 6000C29974c935e031d82e3eb18a0839 \\.\PHYSICALDRIVE16
I did find that if you connect to multiple vCenters in ELM, you need to disconnect from the ones not housing the VM in question, or specifically direct all queries to the proper vCenter. I also put in a test to see if the machine exists before launching into a screen of red.
Hi, how can i combine this 2 script in two 1. so i can see it in out-gridview:
$compName = ‘vmname’
# Get Disk Capacity/Free Space of remote computer
Get-CimInstance -Class Win32_LogicalDisk -ComputerName $compName | Where-Object DriveType -EQ ‘3’ |
Select-Object @{Name=”Size(GB)”;Expression={$_.size/1gb}},
@{Name=”Free Space(GB)”;Expression={$_.freespace/1gb}},
@{Name=”Free (%)”;Expression={“{0,6:P0}” -f(($_.freespace/1gb) / ($_.size/1gb))}}, DeviceID, SystemName| Out-GridView
# Lists Windows Drive letter and VM Disk
foreach($disk in (Get-CimInstance -ComputerName $compName -ClassName Win32_DiskDrive))
{foreach($partition in (Get-CimAssociatedInstance -InputObject $disk -ResultClassName Win32_DiskPartition))
{Get-CimAssociatedInstance -InputObject $partition -ResultClassName Win32_LogicalDisk | %{
Write-Output “$($disk.SCSIPort-2):$($disk.SCSITargetId) $($_.DeviceID) $($_.VolumeName)”}}}
HI
good script – THX
I try to understand what this line – $WinHostName ?
because I get an error message
mmmm this is the line where I run the Powercli command ?
thanks Again
This is the FQDN name of the Windows guest operating system.
I am trying to include VMname in the output, how to include in $curDiskMap.?
$curDiskMap.vmDiskDatastore = $vmDisk.filename.split(‘]’)[0].split(‘[‘)[1]