- Reading Azure VM name, IP address, and hostname with PowerShell - Fri, Jul 28 2017
- Automating WSUS with PowerShell - Thu, Jul 13 2017
- Disable SSL and TLS 1.0/1.1 on IIS with PowerShell - Tue, Jun 27 2017
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:
But when you looking on the drives from Windows, you'll notice the problem.
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.
Subscribe to 4sysops newsletter!
$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.
Yes, it works great.
Thank you for writing such a great script Alex !
You’re welcome!
Great Script! Quick question how can I add volume labels (c: etc) to the script output.
Thanks Again!
Norm
Well, this is not a real quick question. The short answer there is no straightforward way to do that.
You have to firstly get the information about relationship between physical disks and partitions and then the same for logical disks. The first one could be extracted from Win32_DiskDriveToDiskPartition wmi object, the second one from Win32_LogicalDiskToPartition.
Alex ,
This is script is not working I am using on VMware 5.5 and Powercli 5.5
Get-WmiObject : Cannot validate argument on parameter ‘ComputerName’. The argum
ent is null or empty. Supply an argument that is not null or empty and then try
the command again.
At D:\Script\VMDK.ps1:23 char:60
+ $Disks = Get-WmiObject -Class Win32_DiskDrive -ComputerName <<<< $VM.Name
+ CategoryInfo : InvalidData: (:) [Get-WmiObject], ParameterBindi
ngValidationException
+ FullyQualifiedErrorId : ParameterArgumentValidationError,Microsoft.Power
Shell.Commands.GetWmiObjectCommand
Sorry for late response, I didn’t have much time to look into it recently.
As you can see the error comes from the get-wmiobject cmdlet and since it is using $VM.Name as a computer name argument, I’d suggest you to look if your VM name corresponds to your windows host name. This construction
assumes that this is true, but if your hostname is different, you have to use your hostname instead of VM.Name in the Get–WmiObject.
When I ran this script getting this Error
Just look on the comment above. The reason it’s failing is that it can’t reach the remote machine by name. So either your VM name doesn’t correspond guest host name or it’s unreachable by network.
This works great except that it is listing the VMWare and Windows in numerical order and not matching up the lines. For example:
Hard disk 7 500 PHYSICALDRIVE6 300
Obviously these are different disks but listed on the same line because of numerical order.
Any ideas?
Alex this is exactly what I’ve been looking for, so wish it worked for me! Just spent over an hour with VMware support still with no good solution to match windows disk to VMware disk. The first few drives in the .csv file are accurate, but then it isn’t. There are windows disks that are matched to VMware disks of smaller size. For example a 100GB windows disk matched to a 61GB vmwaredisk. Would really appreciate it if you could help get this working for us, Thanks!
Thank you Alex for a great script! I have four controllers with many disks attached because these systems are huge mail servers using Exchange Database Availability Groups and many drives are the same initial size and then grow from 1024 GB. The problem I am having is only the scsi ids 0,1,2 are mapped the scsi id 3 drives are not mapped. I am very happy getting the 10/17 drives mapped quickly, it would be ideal if they could all get mapped. 🙂
Details:
#TYPE Selected.System.String
VMWareDisk VMWareDiskSize WindowsDeviceID WindowsDiskSize
Hard disk 1 60 PHYSICALDRIVE0 60
Hard disk 2 60 PHYSICALDRIVE1 60
Hard disk 6 1024 PHYSICALDRIVE2 1024
Hard disk 3 1055 PHYSICALDRIVE3 1055
Hard disk 5 1054 PHYSICALDRIVE5 1054
Hard disk 16 1024 PHYSICALDRIVE4 1024
Hard disk 4 1024 PHYSICALDRIVE6 1024
Hard disk 13 800 PHYSICALDRIVE14 800
Hard disk 14 800 PHYSICALDRIVE15 800
Hard disk 15 1024 PHYSICALDRIVE7 1024
Disk on fourth controller
VMWare SCSI (3:0) Hard disk 7
Virtual Guest Disk8 Location 193 (Bus Number 0, Target Id 0, LUN 0)
I decided that getting the VMWare Disk and SCSI Bus and SCSI ID was really all I needed and I could put it together from there to match the host.
Here is the code I used to get the details:
Connect-VIServer -Server MYVCENTER
Get-VM MYVMGUEST | Get-HardDisk |
Select @{N=’VM’;E={$_.Parent.Name}},
Name,
@{N=’SCSIid’;E={
$hd = $_
$ctrl = $hd.Parent.Extensiondata.Config.Hardware.Device | where{$_.Key -eq $hd.ExtensionData.ControllerKey}
“$($ctrl.BusNumber):$($_.ExtensionData.UnitNumber)”
}} | export-csv -path “C:\Output\MYVMGUEST_drive_table.csv”
This is great script 🙂
Just wanted to know how can we extend the drive using this script ?
Like i want to extend D drive from both VM and os level.
Appreciate the help
Not sure how often or if this thread is monitored, but in regards to Curtis Redkey’s post above, this script will not report on any drives for a Windows 2016 Server other than its first/default SCSI controller.
For instance, we have a server with 4 SCSI Paravirtual Adapters, with 2-3 drives on each adapter, for a total of 8 drives. Drive 0 is on the first SCSI adapter (adapter 0). So when I run the script it only returns that one drive, not the other 7.
I have tested this across all versions of Windows going back to 2003, and 2016 consistently has this issue. This is an EXTREMELY valuable script for us. Can anyone lend a hand to make it work? Could it have to do with the way it calls the WMI Object?
Thanks in advance to anyone who can help, much appreciated!
Just to add a little info.
The SCSIPort does not increment with the actual controller-ID of VMware. It increments every time you ADD a controller.
So if you have your first disk on (0:0) and windows says SCSIport:2 SCSITargetID:0
If you add just one disk on (2:1) in VMware, windows will tell you SCSIPort:3 SCSITargetID:1
It does not add 2 to the first number in VMware. It cares about the order you add them in.
Hi Alex,
The script works perfectly. There’s one suggestion. If the VM contains 2 SCSI controller (currently SCSI Controller 0, SCSI Controller 1), it works ok.
But if there’s additional 2 more SCSI Controller added (SCSI Controller 2 and SCSI Controller 3), it doesn’t map the drives.
Hope you can be able to enlighten us.