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
I have updated my script (and the post on my website) with your code. Thanks for sharing it!
Hugo
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
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
David
-EDIT- Sorry, you can't get the physical serial number from Windows 2003 WMI, so even that is a non starter
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.
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
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
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
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
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
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.
I might try to use your code in my script if I find the time.
Hugo
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.
I have updated my script (and the post on my website) with your code. Thanks for sharing it!
Hugo
Great! Thanks Hugo!
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
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 ?
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!
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.