VMware Cloud Community
adavidm
Contributor
Contributor
Jump to solution

Reconciling Windows Disks with vmdk files

Hi,

First of all, sorry if this question has been answered before (or the solution is so obvious no-one has bothered to ask it)!

I am looking at building a automated documentation system for our ESX environment using PowerShell and Office integration. Most of the work is fairly easy, if tedious, but there is one area where I appear to be stuck.

Part of the documentation is a description of each VM (CPUs, Memory, Disks, etc...). Really simple stuff, and basically a one-liner. For various reasons, however, it would be useful to be able to tell at-a-glance which Datastore a given VM's C:\ (System) drive and P:\ (Page) drive are stored on (we have a bunch of OS LUNS and PAGE LUNS, with Datastores holding up to 12 machines on each).

At the moment, I am just assuming that "Hard Disk 1" in VMWare is C:\ and "Hard Disk 2" is P:\. This breaks for a couple of VMs as disks have been added or removed. I was wondering if anyone had a reliable way of matching Windows drive letters to a particular VMDK file? I thought that the "Guest" object of the VM (Which I assume gets data from Tools) might be a way forward, but there doesn't appear to be enough info there (can't match on size because all the disks have been 64kb aligned and this screws the sizes up slightly).

Because we are just starting to build the system, we have manual system in place for documentation, but we all know how they have a habit of getting out of date very quickly. We are aiming at running between 800 - 1000 VMs across two physical datacentres, so I am keen to automate as much as possible!

Has anyone got any ideas?

Thanks in advance.

David

1 Solution

Accepted Solutions
hugopeeters
Hot Shot
Hot Shot
Jump to solution

I have updated my script (and the post on my website) with your code. Thanks for sharing it!

Hugo

http://www.peetersonline.nl

View solution in original post

17 Replies
LucD
Leadership
Leadership
Jump to solution

This kind of functionality should be available with the new VIX APIs that should come shortly.

In the mean time you could use the Get-WMIObject cmdlet to access that kind of information for your Windows guests.

Similar to what I did for the network info. See .

But it is not valid for all guest types!


Blog: lucd.info  Twitter: @LucD22  Co-author PowerCLI Reference

Reply
0 Kudos
adavidm
Contributor
Contributor
Jump to solution

Thanks for your reply.

I took a look at using WMI, but the fundamental problem is the same. I can get the drive letter and sizes from either VMWare Tools or WMI. WMI also lets me grab things like the Hard disk serial number. I still don't see how I can map that information to show which vmdk file / datastore a particular drive letter resides on (Unless you know how to work out what serial number vmware assigns to a virtual hard disk, that would solve the problem completely)?

again, thanks for your reply (and i've already nicked bits of the script you posted Smiley Wink

David

-EDIT- Sorry, you can't get the physical serial number from Windows 2003 WMI, so even that is a non starter Smiley Sad

Reply
0 Kudos
hugopeeters
Hot Shot
Hot Shot
Jump to solution

The VI Client reports a SCSI Device Node (eg ) for each Virtual Harddisk. I'm sure you can get the link betwwen this and the driveletter through WMI. Windows Disk Mgmt shows this kind of stuff also.

adavidm
Contributor
Contributor
Jump to solution

Sorry, i've been away for the last few weeks.

SCSI ID is what I am working on now. In case anyone else attempts this, you should be aware that Windows SCSI ports appear to be 1-based, whereas VMWare is 0-based.

I am just looking for a way to tie SCSI ID to windows volumes via WMI now. It is not a direct route as the volume has no direct relationship to the physical disk.

thanks for the pointer though.

David

Reply
0 Kudos
adavidm
Contributor
Contributor
Jump to solution

In case it is useful, here is my first stab at mapping the Windows SCSI to Windows LogicalVolume stuff. Forgive the horrible coding form, I butchered some MS VBScript samples to get this together and haven't bothered to sort it yet.

$strComputer = ""

$WmiDisks = Get-WMIObject Win32_DiskDrive -computerName $strComputer -ErrorAction SilentlyContinue

$output = New-Object psobject

Foreach ($WmiDisk in $WmiDisks)

{

$WmiQuery = "ASSOCIATORS OF {Win32_DiskDrive.DeviceID='" + $WmiDisk.DeviceID + "'} WHERE AssocClass = Win32_DiskDriveToDiskPartition"

$WmiSearcher = \[wmiSearcher\]$WmiQuery

$WmiSearcher.Scope = "
$strComputer\root\cimv2"

$WmiDiskPartitions = $WmiSearcher.Get()

Foreach ($WmiDiskPartition in $WmiDiskPartitions)

{

$WmiQuery = "ASSOCIATORS OF {Win32_DiskPartition.DeviceID='" + $WmiDiskPartition.DeviceID + "'} WHERE AssocClass = Win32_LogicalDiskToPartition"

$WmiSearcher = \[wmiSearcher\]$WmiQuery

$WmiSearcher.Scope = "
$strComputer\root\cimv2"

$WmiLogicalDisks= $WmiSearcher.Get()

Foreach ($WmiLogicalDisk in $WmiLogicalDisks)

{

$SCSIName = "Disk " + $WmiDisk.Index + " SCSI Bus"

$VolumeName = "Disk " + $WmiDisk.Index + " Volume Name"

$LetterName = "Disk " + $WmiDisk.Index + " Drive Letter"

$output | Add-Member NoteProperty $SCSIName (($WmiDisk.SCSIPort-1).tostring() + ":" + $WmiDisk.SCSITargetId.tostring())

$output | Add-Member NoteProperty $VolumeName $WmiLogicaldisk.VolumeName

$output | Add-Member NoteProperty $LetterName $WmiLogicaldisk.DeviceID

}

}

}

Write-Output $output

Reply
0 Kudos
hugopeeters
Hot Shot
Hot Shot
Jump to solution

I am currently working on a very similar script. I am using the wmi classes Win32_DiskDrive, Win32_LogicalDisk, Win32_DiskPartition, Win32_DiskDriveToDiskPartition to map the logical drive to the partition to the diskdrive to the vmdk file to the datastore. It still requires two steps comparing sizes. But it's working ok. Expect the full script on my website very soon.

Hugo

http://www.peetersonline.nl

Reply
0 Kudos
adavidm
Contributor
Contributor
Jump to solution

Great, I'll look forward to that.

In the meantime, I have now got the script working and reconciling the disks using ScsiPort and ScsiTargetID as a key between VMware and WMI. There are a couple of hoops to jump through to get VMWare to give up its SCSI-VMDK mappings. I used something like this:

Function PreScanSCSIBus(\[string\]$vmName)

{

$hashTable = @{}

$ViView = get-vm -Name $vmName | % { get-view $_.id }

$ViScsiControllers = $ViView.Config.Hardware.Device | ? { $_.DeviceInfo.Label -match "SCSI Controller" }

Foreach ($ViScsiController in $ViScsiControllers)

{

$ViDisks = $ViView.Config.Hardware.Device | ? { $_.ControllerKey -eq $ViScsiController.Key }

ForEach ($ViDisk in $ViDisks)

{

$strPath = $ViDisk.Backing.Filename

$strSCSI = $ViScsiController.BusNumber.tostring() + ":" + $ViDisk.UnitNumber.tostring()

$hashTable\[$strSCSI\] = $strPath

}

}

return $hashtable

}

If there is a better way, please let me know. Once again, please forgive the style, I am no good with PS.

David

Reply
0 Kudos
adavidm
Contributor
Contributor
Jump to solution

Cool, I'll give it a go later.

I quickly looked through it, and it appears you are using the disk sizes to match the two halves (WMI and VMWare). If that is the case then I don't think it will work in our current environment, as we have several servers hosting multiple disks of the same size (for things like departmental shares, etc). There will be occasions where those disks reside on the same datastore. Have I understood the script correctly, as otherwise it looks miles better than my current implementation?

The SCSI Match is guaranteed to be unique, as far as I can tell, so I think I am going to have to stick with that, unless I am worrying about nothing!

David

Reply
0 Kudos
hugopeeters
Hot Shot
Hot Shot
Jump to solution

In fact, my script tries matching based on size twice. Here's the full chain of logic:

1. Get datastore

2. Get vmdk files

3. Find VM (and virtual disk) by matching virtual disk path to vmdk file path

4. Get virtual disk size

5. Match virtual disk size to physical disk size (Win32_DiskDrive)

6. Find partitions on physical disk (Win32_DiskDriveToDiskPartition)

7. Get partition size (Win32_DiskPartition)

8. Match logical drives size to partition size (Win32_LogicalDrive)

9. Get disk usage information from logical drive

I sort of forgot about my advice to you to use scsi id's and used the same size matching idea you proposed earlier. Smiley Happy

I might try to use your code in my script if I find the time.

Hugo

http://www.peetersonline.nl

Reply
0 Kudos
adavidm
Contributor
Contributor
Jump to solution

I'd forgotten i'd tried using sizes earlier!

I will plough on with it and (if the code isn't too embarrassing) share the finished version here.

Reply
0 Kudos
hugopeeters
Hot Shot
Hot Shot
Jump to solution

I have updated my script (and the post on my website) with your code. Thanks for sharing it!

Hugo

http://www.peetersonline.nl

adavidm
Contributor
Contributor
Jump to solution

Great! Thanks Hugo!

Reply
0 Kudos
edox77
Contributor
Contributor
Jump to solution

in a very simple manner if you want to find what .vmdk is matched with e Windows virtual disk you must see following informations:

From VC for every disks of a VM you can see SCSI (0:X), from Windows VM in disk management in disk's properties you can see "... Target ID "X"

X = X

Bye

Reply
0 Kudos
AlbertWT
Virtuoso
Virtuoso
Jump to solution

hugopeeters‌, is there any updated script ?

When I tried to run your script, http://www.peetersonline.nl/2008/12/get-vmware-disk-usage-with-powershell/ I couldn't specify one particular VM Hard Drive letter and the associated VMDK ?

/* Please feel free to provide any comments or input you may have. */
Reply
0 Kudos
ambaker70
Contributor
Contributor
Jump to solution

We found that the posted scripts would, more often than not, not work for our environment.  We have some VMs that have names in VMware that do not match the Windows server name (this occurrence is not best practice but exists in our environment).  We have a few systems with SATA controllers rather than SCSI.  We have many systems that have mounted volumes with no drive letter.

As such, we needed a script that wouldn't crash or error when these instances occur.  Pasted at the bottom is what we have found works for us.

Caveats:  This script works on Windows Server 2012 R2 today with PowerCLI 6 and the following output of $PSVersionTable:

PS C:\temp> $PSVersionTable

 

Name Value 

---- ----- 

PSVersion 4.0 

WSManStackVersion 3.0 

SerializationVersion 1.1.0.1 

CLRVersion 4.0.30319.34014 

BuildVersion 6.3.9600.16394 

PSCompatibleVersions {1.0, 2.0, 3.0, 4.0} 

PSRemotingProtocolVersion 2.2 

We have added the Storage Services Role on this server.  I believe this role is required to have the Get-Disk, Get-Partition, and Get-Volume commandlets available.

This script version is stripped of most comments and is our current working version.  Your mileage may vary.

Script start:

<#
.SYNOPSIS
Gets disk mapping information in Windows for VMWare virtual machines to match disks and help with space additions

.DESCRIPTION
This script gets SCSI and SATA information from both vCenter and PowerShell and collates them together.

.PARAMETER vCenter
The address of the vCenter server. This can either be the server name or the FQDN.

.PARAMETER VMfile
A list of server names (one per line) of the virtual machines you want disk information for.

.EXAMPLE
.\WinToVMDiskMatch.ps1 -vCenter <vcenter server name> -VMfile <server list>
Get disk information for list of servers from vCenter server

.NOTES
You will need PowerCLI and the appropriate access in vCenter. You will also need access to the PowerShell storage module.
#>
Param(
    [Parameter(Mandatory=$True, Position=1)]
    [String]$vCenter,
    [Parameter(Mandatory=$True, Position=2)]
    [String]$VMfile
)
#Connect to vCenter with current credentials
try { Connect-VIServer -Server $vCenter -Force -ErrorAction Stop | Out-Null }
catch {
    Write-Error $error[0].exception
    exit
}
$VMs = Get-Content -Path $VMfile
ForEach ($VM in $VMs){
if ($VM) {
$Windisks = get-disk -CimSession $VM | Select-Object -Property FriendlyName,Number,Size,UniqueId,SerialNumber,ObjectId,Location,IsSystem,IsBoot,IsOffline,Guid,Path
$WinMatch=@()
$WinMatch = foreach ($row in $Windisks) {
    if ($row.FriendlyName -match "SCSI") {
        $match = $row.SerialNumber.substring($row.SerialNumber.length-12,12)
    }
    elseif ($row.FriendlyName -match "SATA") {
        $match = $row.SerialNumber.substring(0,2)
    }
    else {
        $match = "No Matches"
    }
    $DN = $row.Number.ToString()
    $MountPath=""
    foreach ($part in (get-partition -CimSession $VM -DiskNumber $DN | where { $_.Type -ne "Reserved" })){
        $winpath = ""
        If (($part.AccessPaths -match ":\\")[0] -ne $null) { $winpath = ($part.AccessPaths -match ":\\")[0].ToString() }
        $MountPath += $winpath + ","
    }
    $MountPath=$MountPath.TrimEnd(",")
    $MountPath=$MountPath.TrimStart(",")
    [pscustomobject][Ordered]@{
        MatchID = $match
        Server = $VM
        MountPath = $MountPath
        DiskNumber = $DN
        SizeInGB = $row.Size/1GB
        IsSystem = $row.IsSystem
        IsBoot = $row.IsBoot
        IsOffline = $row.IsOffline
        UID = $row.UniqueId
        Serial = $row.SerialNumber
        DeviceName = $row.FriendlyName
        ObjectID = $row.ObjectID
        Location = $row.Location
        GUID = $row.GUID
        Path = $row.Path
    }
}
$VMdisks = @()
$VMView = Get-View -ViewType VirtualMachine -Filter @{"Name" = $VM}
foreach ($scsicontroller in ($VMView.config.hardware.device | ?{$_.deviceinfo.label -match "SCSI controller" -Or $_.deviceinfo.label -match "SATA controller"})) {
    foreach ($disk in ($vmview.config.hardware.device | ?{$_.controllerkey -eq $scsicontroller.key -and $_.deviceinfo.label -notmatch "CD/DVD"})) {
        $egn=$VMView.Name
        $ec = $scsicontroller.deviceinfo.label
        $eid = $disk.DiskObjectId
        $edu = $disk.backing.uuid
        if ($ec -match "SCSI") {
            $edm = $edu.substring($edu.length-12,12)
        }
        elseif ($ec -match "SATA") {
            $edm = $eid.substring($eid.length-2,2)
        }
        else {
            $edm = "No Matches"
        }
        $esi = "$($scsicontroller.busnumber) : $($disk.unitnumber)"
        $edr = $disk.CapacityInBytes
        $eds = $edr/1GB
        $edn = $disk.deviceinfo.label
        $edf = $disk.backing.filename
        $edk = $disk.controllerkey
        $edv = $disk.backing.datastore
        $VMdisks += [PSCustomObject][Ordered]@{
            MatchID=$edm;
            VMGuest=$egn;
            vSCSIController=$ec;
            vDiskName=$edn;
            vSCSIID=$esi;
            DiskSizeInBytes=$edr;
            DiskSizeInGB=$eds;
            Datastore=$edv;
            ControllerKey=$edk;
            vDiskID=$eid;
            VMDiskFile=$edf;
            DiskUUID=$edu;
        }
    }
}
}
$WinMatch = $WinMatch | Sort-Object -property MatchID
$VMdisks = $VMdisks | Sort-Object -property MatchID
$f1=($WinMatch | ConvertTo-Csv -Delimiter ";" | convertfrom-csv -Delimiter ";" -header MatchID,Server,MountPath,DiskNumber,SizeInGB,IsSystem,IsBoot,IsOffline,UID,Serial,DeviceName,ObjectID,Location,GUID,Path,VMGuest,vSCSIController,vDiskName,vSCSIID,DiskSizeInBytes,DiskSizeInGB,Datastore,ControllerKey,vDiskID,VMDiskFile,DiskUUID)[1..$WinMatch.Count]
$f2=($VMdisks | ConvertTo-Csv -Delimiter ";" | convertfrom-csv -Delimiter ";" -header MatchID,VMGuest,vSCSIController,vDiskName,vSCSIID,DiskSizeInBytes,DiskSizeInGB,Datastore,ControllerKey,vDiskID,VMDiskFile,DiskUUID)[1..$VMdisks.Count]
$f1|
   %{
      $matchid=$_.MatchID
      $m=$f2|?{$_.MatchID -eq $matchid}
      $_.VMGuest=$m.VMGuest
      $_.vSCSIController=$m.vSCSIController
      $_.vDiskName=$m.vDiskName
      $_.vSCSIID=$m.vSCSIID
      $_.DiskSizeInBytes=$m.DiskSizeInBytes
      $_.DiskSizeInGB=$m.DiskSizeInGB
      $_.Datastore=$m.Datastore
      $_.ControllerKey=$m.ControllerKey
      $_.vDiskID=$m.vDiskID
      $_.VMDiskFile=$m.VMDiskFile
      $_.DiskUUID=$m.DiskUUID
    }
$f1 | Out-GridView
$f1 | Export-Csv -Path "$VM.csv" -Delimiter ";" -NoClobber -NoTypeInformation
}
Disconnect-VIServer -Server $vCenter -Confirm:$false

Script End

There are a number of constructs in here that may be greatly improved upon...at least you won't find any 'Write-host...' commands... 🙂

Good luck!

ambaker70
Contributor
Contributor
Jump to solution

PS - further testing shows good results on Server 2012 but failures on anything older.  As most of our anomalies are on Server 2012, we're okay, but if you need results for older, the previous scripts are likely to yield better results.

Reply
0 Kudos