Hey Guys,
So, I'm not that great at scripting put I'm pretty resourceful in finding the appropriate scripts for things I need. I am having an issue though. So I found a script that will Pull the Disk/UUID Information from VMware and from a Windows machine and then match the VMware Disk configuration to the Windows Disk Configuration.
Here is the script I am using for that:
Running this produces a result like this:
I was then asked to also include the VMware SCSI information. I found another script that does that.
$vm="VMNAME"
$vmview = Get-View -ViewType VirtualMachine -Filter @{"Name" = $vm}
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})) {
Write-Host SCSI" ("$($VirtualSCSIController.BusNumber):$($VirtualDiskDevice.UnitNumber)")" $VirtualDiskDevice.DeviceInfo.Label
}
}
That produces the following result:
So I have two questions really.
1. Can someone help me combine both scripts into one?
2. Can someone also help me figure out how to have this script run against a list of machines and export it to a CSV File? The output can either be one file or separate files per VM. I'm okay with either.
I was thinking maybe the script can reference a .txt or csv file that I populate with all of the machines but I'll defer to whatever you guys recommend. The only issue I see there is since you have to define the VM Name and the DNS Name (Windows WMI Portion of the script) I'm not sure how I can do both with one txt input file.
I have about 50 machines I have to run this against and just looking for the easiest way to accomplish this. At a minimum if someone can help be combine both scripts into one that would be awesome. Thanks guys, I appreciate the help!
Since all your VMs are meeting the specific requirements, you could try something like this
# User with permission to query the WMI on the guest
$user = 'domain\user'
$pswd = 'VMware1!'
$cred = New-Object -TypeName PSCredential -Argumentlist $user,(ConvertTo-SecureString -String $pswd -AsPlainText -Force)
# CSV file with a column named vmName
Import-Csv -Path .\vmnames.csv -UseCulture -PipelineVariable row |
ForEach-Object -Process {
$vm = Get-VM -Name $row.vmName
# If the script can't determine the FQDN of the Guest OS, the VM is skipped
$winHostName = ''
if($vm.Guest.State -eq 'Running'){
if($vm.Guest.HostName -ne ''){
$WinHostName = $vm.Guest.HostName
}
elseif($vm.Guest.IPAddress){
$resolve = Resolve-DnsName -Name $vm.guest.IPAddress[0]
if($resolve){
$WinHostName = $resolve.NameHost
}
}
}
if($winHostName){
$vmDisks = Get-HardDisk -VM $vm | Sort-Object -Property Name
$vmDatacenterView = Get-VM -Name $vmName | Get-Datacenter | Get-View
$virtualDiskManager = Get-View -Id VirtualDiskManager-virtualDiskManager
# 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
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)}
}
$ctrl = $vm.ExtensionData.Config.Hardware.Device | where{$_.Key -eq $vmDisk.ExtensionData.ControllerKey}
$curDiskMap = "" | select vmDiskDatastore, vmDiskVmdk, SCSIAddress, vmDiskName, windowsDiskIndex, vmDiskUuid, windowsDeviceID, drives, volumes
$driveVolumes = $diskToDriveVolume | where {$_.Disk -eq $windowsDisk.DeviceID}
New-Object -TypeName PSObject -Property ([ordered]@{
VM = $vm.Name
vmDiskDatastore = $vmDisk.filename.split(']')[0].split('[')[1]
vmDiskVmdk = $vmDisk.filename.split(']')[1].trim()
SCSIAddress = "SCSI($($vmDisk.ExtensionData.UnitNumber):$($ctrl.BusNumber))"
vmDiskName = $vmDisk.Name
windowsDiskIndex = if ($windowsDisk){$windowsDisk.Index}else{"FAILED TO MATCH"}
vmDiskUuid = $vmDiskUuid
windowsDeviceID = if ($windowsDisk){$windowsDisk.DeviceID}else{"FAILED TO MATCH"}
drives = $driveVolumes.DriveLetter
volumes = $driveVolumes.VolumeName
})
}
}
else{
Write-Host "Could not determine FQDN for VM ($vm.Name)"
}
} | Export-Csv -Path .\report.csv -NoTypeInformation -UseCulture
Blog: lucd.info Twitter: @LucD22 Co-author PowerCLI Reference
First, let me start with a word of warning that I always give when I see a question to map Guest OS disks to VM's VMDK files.
There is no fool-proof way to make that mapping.
There are so many exceptions that there is no general rule that works.
And I didn't even start about the mapping in Linux Guest OS.
On the other hand, when certain conditions are met, you might be able to pull it off.
If there is only one Guest OS partition/disk per VMDK.
If there hasn't been any removal/addition of Guest OS partitions and/or VMDK since the creation of the VM.
And a few other prerequisites.
In your case (environment) all the conditions seem to be met.
Then it would be a matter of mapping the SCSI controller to the VMDK and the VMDK to the Guest OS drive/partition.
Is my assumption correct?
Blog: lucd.info Twitter: @LucD22 Co-author PowerCLI Reference
Yes, your assumptions are correct. There is only one Windows partition per VMDK. Also, the number of Guest OS partitions/VMDKs have been the same since the creation of the VM.
These servers are used for a specific purpose and that configuration shouldn't have changed since the creation of the VM. So far in all of my tests when I manually went to match up the result it has been accurate thus far. There should be a single standard across all of them.
The only reason to run this script is to confirm that they all conform to the same standard. Anything that produces a different/odd result will require us to investigate it anyway. Thanks again for your help.
Since all your VMs are meeting the specific requirements, you could try something like this
# User with permission to query the WMI on the guest
$user = 'domain\user'
$pswd = 'VMware1!'
$cred = New-Object -TypeName PSCredential -Argumentlist $user,(ConvertTo-SecureString -String $pswd -AsPlainText -Force)
# CSV file with a column named vmName
Import-Csv -Path .\vmnames.csv -UseCulture -PipelineVariable row |
ForEach-Object -Process {
$vm = Get-VM -Name $row.vmName
# If the script can't determine the FQDN of the Guest OS, the VM is skipped
$winHostName = ''
if($vm.Guest.State -eq 'Running'){
if($vm.Guest.HostName -ne ''){
$WinHostName = $vm.Guest.HostName
}
elseif($vm.Guest.IPAddress){
$resolve = Resolve-DnsName -Name $vm.guest.IPAddress[0]
if($resolve){
$WinHostName = $resolve.NameHost
}
}
}
if($winHostName){
$vmDisks = Get-HardDisk -VM $vm | Sort-Object -Property Name
$vmDatacenterView = Get-VM -Name $vmName | Get-Datacenter | Get-View
$virtualDiskManager = Get-View -Id VirtualDiskManager-virtualDiskManager
# 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
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)}
}
$ctrl = $vm.ExtensionData.Config.Hardware.Device | where{$_.Key -eq $vmDisk.ExtensionData.ControllerKey}
$curDiskMap = "" | select vmDiskDatastore, vmDiskVmdk, SCSIAddress, vmDiskName, windowsDiskIndex, vmDiskUuid, windowsDeviceID, drives, volumes
$driveVolumes = $diskToDriveVolume | where {$_.Disk -eq $windowsDisk.DeviceID}
New-Object -TypeName PSObject -Property ([ordered]@{
VM = $vm.Name
vmDiskDatastore = $vmDisk.filename.split(']')[0].split('[')[1]
vmDiskVmdk = $vmDisk.filename.split(']')[1].trim()
SCSIAddress = "SCSI($($vmDisk.ExtensionData.UnitNumber):$($ctrl.BusNumber))"
vmDiskName = $vmDisk.Name
windowsDiskIndex = if ($windowsDisk){$windowsDisk.Index}else{"FAILED TO MATCH"}
vmDiskUuid = $vmDiskUuid
windowsDeviceID = if ($windowsDisk){$windowsDisk.DeviceID}else{"FAILED TO MATCH"}
drives = $driveVolumes.DriveLetter
volumes = $driveVolumes.VolumeName
})
}
}
else{
Write-Host "Could not determine FQDN for VM ($vm.Name)"
}
} | Export-Csv -Path .\report.csv -NoTypeInformation -UseCulture
Blog: lucd.info Twitter: @LucD22 Co-author PowerCLI Reference
So I modified the $user and $pswd values with the appropriate credentials. I created a vmware.csv file in the same directory that this script resides. I created a column "vmName" and I put one of the servers I want to run it against in that column.
I received the following errors from PowerShell:
Get-VM : 11/24/2020 9:49:34 AM Get-VM VM with name 'vEng' was not found using the specified filter(s).
At line:27 char:29
+ $vmDatacenterView = Get-VM -Name $vmName | Get-Datacenter | G ...
+ ~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : ObjectNotFound: (:) [Get-VM], VimException
+ FullyQualifiedErrorId : Core_OutputHelper_WriteNotFoundError,VMware.VimAutomation.ViCore.Cmdlets.Commands.GetVM
Exception calling "QueryVirtualDiskUuid" with "2" argument(s): "A specified parameter was not correct: dc"
At line:65 char:13
+ $vmDiskUuid = $virtualDiskManager.queryvirtualdiskuuid($v ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [], MethodInvocationException
+ FullyQualifiedErrorId : VimException
Exception calling "QueryVirtualDiskUuid" with "2" argument(s): "A specified parameter was not correct: dc"
At line:65 char:13
+ $vmDiskUuid = $virtualDiskManager.queryvirtualdiskuuid($v ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [], MethodInvocationException
+ FullyQualifiedErrorId : VimException
Exception calling "QueryVirtualDiskUuid" with "2" argument(s): "A specified parameter was not correct: dc"
At line:65 char:13
+ $vmDiskUuid = $virtualDiskManager.queryvirtualdiskuuid($v ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [], MethodInvocationException
+ FullyQualifiedErrorId : VimException
Exception calling "QueryVirtualDiskUuid" with "2" argument(s): "A specified parameter was not correct: dc"
At line:65 char:13
+ $vmDiskUuid = $virtualDiskManager.queryvirtualdiskuuid($v ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [], MethodInvocationException
+ FullyQualifiedErrorId : VimException
Exception calling "QueryVirtualDiskUuid" with "2" argument(s): "A specified parameter was not correct: dc"
At line:65 char:13
+ $vmDiskUuid = $virtualDiskManager.queryvirtualdiskuuid($v ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [], MethodInvocationException
+ FullyQualifiedErrorId : VimException
Exception calling "QueryVirtualDiskUuid" with "2" argument(s): "A specified parameter was not correct: dc"
At line:65 char:13
+ $vmDiskUuid = $virtualDiskManager.queryvirtualdiskuuid($v ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [], MethodInvocationException
+ FullyQualifiedErrorId : VimException
Exception calling "QueryVirtualDiskUuid" with "2" argument(s): "A specified parameter was not correct: dc"
At line:65 char:13
+ $vmDiskUuid = $virtualDiskManager.queryvirtualdiskuuid($v ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [], MethodInvocationException
+ FullyQualifiedErrorId : VimException
I looked and found the vEng value in the script but not sure if I'm supposed to change this value.
Also, I'm not sure if the errors caused this but the output mapped all of the Windows Drive letters to the same Uuid.
However, when I run the original script I used without the SCSI controller information it maps correctly.
What am I missing?
I forgot to remove a test line from the script.
Hence the error you got.
I updated the code
Blog: lucd.info Twitter: @LucD22 Co-author PowerCLI Reference
That fixed it. Thanks so much!!!
@LucD So I ran it against a few machines and manually checked the results. I found that while it lists the SCSI controller addresses on the VM they are not properly matching to the VM Hard Disks. All other information is correct.
Here is the export from the script I got from you:
In the export is shows Hard Disk 2 as using SCSI (1:0) but when I manually checked in VMware Settings is shows that Hard Disk 2 is actually using SCSI (0:1)
I even made sure that the export was showing the same VMDK disk as in settings and it is.
If I run the two original scripts I had where I do the Mapping and Lists the SCSI controller addresses separately it does match up to what is in VMware settings.
Do you think I'm trying to do too many things at the same time with one script and should keep the two queries separate or do you think there's an easy fix for this? If you think I should separate them into two scripts how can I get the desired effect of having an input file and export file for each separate script?
@LucD so I ran the script against a few VMs and I'm noticing that the SCSI Controller Address isn't matching up.
For example, If you look at Hard Disk 2 the export says it's using SCSI (1:0) but it's actually using SCSI (0:1)
I even checked the VMDK to make sure that was matching correctly on the Export and it is.
All of the other information on the export is matching up properly. I even ran the original two scripts I mentioned and it too gave me the correct results of 0:1 for Hard Disk 2.
Is there an easy fix for this or do you think I'm trying to do too much with one script? If I need to run them as two separate scripts how can I have the same desired effect of having an input file with the list of servers and an export with the Server Name and the SCSI addresses?
My bad, I just noticed that I swapped the controller and the disk in the SCSI address.
It should be
SCSIAddress = "SCSI($($ctrl.BusNumber):$($vmDisk.ExtensionData.UnitNumber))"
Blog: lucd.info Twitter: @LucD22 Co-author PowerCLI Reference
@LucD Yup. That was it. Awesome. Last question and I'll stop bugging you. If I wanted to add the Hard Disk Sizes to a column on the export as well what would I need to change in the script?
I assume I have to add the values here but I'm not 100% sure what the values that need to be added and how the formatting needs to be changed.
$curDiskMap = "" | select vmDiskDatastore, vmDiskVmdk, SCSIAddress, vmDiskName, windowsDiskIndex, vmDiskUuid, windowsDeviceID, drives, volumes
$driveVolumes = $diskToDriveVolume | where {$_.Disk -eq $windowsDisk.DeviceID}
New-Object -TypeName PSObject -Property ([ordered]@{
VM = $vm.Name
vmDiskDatastore = $vmDisk.filename.split(']')[0].split('[')[1]
vmDiskVmdk = $vmDisk.filename.split(']')[1].trim()
SCSIAddress = "SCSI($($ctrl.BusNumber):$($vmDisk.ExtensionData.UnitNumber))"
vmDiskName = $vmDisk.Name
windowsDiskIndex = if ($windowsDisk){$windowsDisk.Index}else{"FAILED TO MATCH"}
vmDiskUuid = $vmDiskUuid
windowsDeviceID = if ($windowsDisk){$windowsDisk.DeviceID}else{"FAILED TO MATCH"}
drives = $driveVolumes.DriveLetter
volumes = $driveVolumes.VolumeName
No problem, add this line
vmDiskSizeGB = $vmDisk.CapacityGB
Blog: lucd.info Twitter: @LucD22 Co-author PowerCLI Reference
Great. Everything works perfectly now. I noticed there was another leftover from your original testing when you had the $vmName value.
if($winHostName){
$vmDisks = Get-HardDisk -VM $vm | Sort-Object -Property Name
$vmDatacenterView = Get-VM -Name $vmName | Get-Datacenter | Get-View
$virtualDiskManager = Get-View -Id VirtualDiskManager-virtualDiskManager
If I'm not mistaken $vmName should be changed to $vm as well. I was getting errors on that until I changed it to $vm. Is that correct?
if($winHostName){
$vmDisks = Get-HardDisk -VM $vm | Sort-Object -Property Name
$vmDatacenterView = Get-VM -Name $vm | Get-Datacenter | Get-View
$virtualDiskManager = Get-View -Id VirtualDiskManager-virtualDiskManager
Finally, I changed this portion from putting the static credentials inside the powershell script:
# User with permission to query the WMI on the guest
$user = 'Username'
$pswd = 'Password'
$cred = New-Object -TypeName PSCredential -Argumentlist $user,(ConvertTo-SecureString -String $pswd -AsPlainText -Force)
To this so it just prompts for the credentials so I can share it will my colleagues:
# User with permission to query the WMI on the guest
$cred = if ($cred){$cred}else{Get-Credential}
Thanks again for all your help!
Indeed
Blog: lucd.info Twitter: @LucD22 Co-author PowerCLI Reference
Or even better, just
$vmDatacenterView = Get-Datacenter -VM $vm | Get-View
Blog: lucd.info Twitter: @LucD22 Co-author PowerCLI Reference
Here is my final version of the script in case anybody wants to reference it:
# Load PowerCLI Plugin and connect to VCenter Server (Put in your FQDN where it says YOURVCENTERSERVERNAME)
Import-Module VMware.VimAutomation.Core -ErrorAction SilentlyContinue
connect-viserver YOURVCENTERSERVERNAME
# User with permission to query the WMI on the guest
$cred = if ($cred){$cred}else{Get-Credential}
# CSV file with a column named vmName
Import-Csv -Path .\vmnames.csv -UseCulture -PipelineVariable row |
ForEach-Object -Process {
$vm = Get-VM -Name $row.vmName
# If the script can't determine the FQDN of the Guest OS, the VM is skipped
$winHostName = ''
if($vm.Guest.State -eq 'Running'){
if($vm.Guest.HostName -ne ''){
$WinHostName = $vm.Guest.HostName
}
elseif($vm.Guest.IPAddress){
$resolve = Resolve-DnsName -Name $vm.guest.IPAddress[0]
if($resolve){
$WinHostName = $resolve.NameHost
}
}
}
if($winHostName){
$vmDisks = Get-HardDisk -VM $vm | Sort-Object -Property Name
$vmDatacenterView = Get-Datacenter -VM $vm | Get-View
$virtualDiskManager = Get-View -Id VirtualDiskManager-virtualDiskManager
# 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
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)}
}
$ctrl = $vm.ExtensionData.Config.Hardware.Device | where{$_.Key -eq $vmDisk.ExtensionData.ControllerKey}
$curDiskMap = "" | select vmDiskDatastore, vmDiskVmdk, SCSIAddress, vmDiskName, windowsDiskIndex, vmDiskUuid, windowsDeviceID, drives, volumes
$driveVolumes = $diskToDriveVolume | where {$_.Disk -eq $windowsDisk.DeviceID}
New-Object -TypeName PSObject -Property ([ordered]@{
VM = $vm.Name
vmDiskDatastore = $vmDisk.filename.split(']')[0].split('[')[1]
vmDiskVmdk = $vmDisk.filename.split(']')[1].trim()
SCSIAddress = "SCSI($($ctrl.BusNumber):$($vmDisk.ExtensionData.UnitNumber))"
vmDiskName = $vmDisk.Name
vmDiskSizeGB = $vmDisk.CapacityGB
windowsDiskIndex = if ($windowsDisk){$windowsDisk.Index}else{"FAILED TO MATCH"}
vmDiskUuid = $vmDiskUuid
windowsDeviceID = if ($windowsDisk){$windowsDisk.DeviceID}else{"FAILED TO MATCH"}
drives = $driveVolumes.DriveLetter
volumes = $driveVolumes.VolumeName
})
}
}
else{
Write-Host "Could not determine FQDN for VM ($vm.Name)"
}
} | Export-Csv -Path .\report.csv -NoTypeInformation -UseCulture
Make sure when you're running the PowerShell script that you are in the working directory first where the vmnames.csv file resides. Otherwise it'll complain it can't find the vmnames.csv file. All of the VMs you want to run this against need to go in this file. This will also be the same directory that the report.csv exports to.
You can do a search and export the list of VMs from the Virtual Machines view in vCenter so you don't have to type them all out by hand if you have a lot.
Another issue I had was some VMs were affected by a vSphere 6.5 bug where the "disk.EnableUUID = "TRUE" value was missing from multiple vmx configuration files. I'll have to go back and fix that on those VMs. Without that value the Uuid is not exposed to the Guest OS. Therefore, it can't match the drives up.
You can reference that KB article here.
https://kb.vmware.com/s/article/52815
Finally, As @LucD mentioned. This is not a fool proof method and only works under the right conditions. Your mileage may vary.
HUGE THANKS to @LucD. I wouldn't have even known where to begin (or end) without his help!
Here is my final version of the script in case anybody wants to reference it:
# Load PowerCLI Plugin and connect to VCenter Server (Put in your FQDN where it says YOURVCENTERSERVERNAME)
Import-Module VMware.VimAutomation.Core -ErrorAction SilentlyContinue
connect-viserver YOURVCENTERSERVERNAME
# User with permission to query the WMI on the guest
$cred = if ($cred){$cred}else{Get-Credential}
# CSV file with a column named vmName
Import-Csv -Path .\vmnames.csv -UseCulture -PipelineVariable row |
ForEach-Object -Process {
$vm = Get-VM -Name $row.vmName
# If the script can't determine the FQDN of the Guest OS, the VM is skipped
$winHostName = ''
if($vm.Guest.State -eq 'Running'){
if($vm.Guest.HostName -ne ''){
$WinHostName = $vm.Guest.HostName
}
elseif($vm.Guest.IPAddress){
$resolve = Resolve-DnsName -Name $vm.guest.IPAddress[0]
if($resolve){
$WinHostName = $resolve.NameHost
}
}
}
if($winHostName){
$vmDisks = Get-HardDisk -VM $vm | Sort-Object -Property Name
$vmDatacenterView = Get-Datacenter -VM $vm | Get-View
$virtualDiskManager = Get-View -Id VirtualDiskManager-virtualDiskManager
# 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
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)}
}
$ctrl = $vm.ExtensionData.Config.Hardware.Device | where{$_.Key -eq $vmDisk.ExtensionData.ControllerKey}
$curDiskMap = "" | select vmDiskDatastore, vmDiskVmdk, SCSIAddress, vmDiskName, windowsDiskIndex, vmDiskUuid, windowsDeviceID, drives, volumes
$driveVolumes = $diskToDriveVolume | where {$_.Disk -eq $windowsDisk.DeviceID}
New-Object -TypeName PSObject -Property ([ordered]@{
VM = $vm.Name
vmDiskDatastore = $vmDisk.filename.split(']')[0].split('[')[1]
vmDiskVmdk = $vmDisk.filename.split(']')[1].trim()
SCSIAddress = "SCSI($($ctrl.BusNumber):$($vmDisk.ExtensionData.UnitNumber))"
vmDiskName = $vmDisk.Name
vmDiskSizeGB = $vmDisk.CapacityGB
windowsDiskIndex = if ($windowsDisk){$windowsDisk.Index}else{"FAILED TO MATCH"}
vmDiskUuid = $vmDiskUuid
windowsDeviceID = if ($windowsDisk){$windowsDisk.DeviceID}else{"FAILED TO MATCH"}
drives = $driveVolumes.DriveLetter
volumes = $driveVolumes.VolumeName
})
}
}
else{
Write-Host "Could not determine FQDN for VM ($vm.Name)"
}
} | Export-Csv -Path .\report.csv -NoTypeInformation -UseCulture
Make sure when you're running the PowerShell script that you are in the working directory first where the vmnames.csv file resides. Otherwise it'll complain it can't find the vmnames.csv file. All of the VMs you want to run this against need to go in this file. This will also be the same directory that the report.csv exports to.
You can do a search and export the list of VMs from the Virtual Machines view in vCenter so you don't have to type them all out by hand if you have a lot.
Another issue I had was some VMs were affected by a vSphere 6.5 bug where the "disk.EnableUUID = "TRUE" value was missing from multiple vmx configuration files. I'll have to go back and fix that on those VMs. Without that value the Uuid is not exposed to the Guest OS. Therefore, it can't match the drives up.
You can reference that KB article here.
https://kb.vmware.com/s/article/52815
Finally, As @LucD mentioned. This is not a fool proof method and only works under the right conditions. Your mileage may vary.
HUGE THANKS to @LucD. I wouldn't have even known where to begin (or end) without his help!
Here is my final version of the script in case anybody wants to reference it:
# Load PowerCLI Plugin and connect to VCenter Server (Put in your FQDN where it says YOURVCENTERSERVERNAME)
Import-Module VMware.VimAutomation.Core -ErrorAction SilentlyContinue
connect-viserver YOURVCENTERSERVERNAME
# User with permission to query the WMI on the guest
$cred = if ($cred){$cred}else{Get-Credential}
# CSV file with a column named vmName
Import-Csv -Path .\vmnames.csv -UseCulture -PipelineVariable row |
ForEach-Object -Process {
$vm = Get-VM -Name $row.vmName
# If the script can't determine the FQDN of the Guest OS, the VM is skipped
$winHostName = ''
if($vm.Guest.State -eq 'Running'){
if($vm.Guest.HostName -ne ''){
$WinHostName = $vm.Guest.HostName
}
elseif($vm.Guest.IPAddress){
$resolve = Resolve-DnsName -Name $vm.guest.IPAddress[0]
if($resolve){
$WinHostName = $resolve.NameHost
}
}
}
if($winHostName){
$vmDisks = Get-HardDisk -VM $vm | Sort-Object -Property Name
$vmDatacenterView = Get-Datacenter -VM $vm | Get-View
$virtualDiskManager = Get-View -Id VirtualDiskManager-virtualDiskManager
# 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
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)}
}
$ctrl = $vm.ExtensionData.Config.Hardware.Device | where{$_.Key -eq $vmDisk.ExtensionData.ControllerKey}
$curDiskMap = "" | select vmDiskDatastore, vmDiskVmdk, SCSIAddress, vmDiskName, windowsDiskIndex, vmDiskUuid, windowsDeviceID, drives, volumes
$driveVolumes = $diskToDriveVolume | where {$_.Disk -eq $windowsDisk.DeviceID}
New-Object -TypeName PSObject -Property ([ordered]@{
VM = $vm.Name
vmDiskDatastore = $vmDisk.filename.split(']')[0].split('[')[1]
vmDiskVmdk = $vmDisk.filename.split(']')[1].trim()
SCSIAddress = "SCSI($($ctrl.BusNumber):$($vmDisk.ExtensionData.UnitNumber))"
vmDiskName = $vmDisk.Name
vmDiskSizeGB = $vmDisk.CapacityGB
windowsDiskIndex = if ($windowsDisk){$windowsDisk.Index}else{"FAILED TO MATCH"}
vmDiskUuid = $vmDiskUuid
windowsDeviceID = if ($windowsDisk){$windowsDisk.DeviceID}else{"FAILED TO MATCH"}
drives = $driveVolumes.DriveLetter
volumes = $driveVolumes.VolumeName
})
}
}
else{
Write-Host "Could not determine FQDN for VM ($vm.Name)"
}
} | Export-Csv -Path .\report.csv -NoTypeInformation -UseCulture
Make sure when you're running the PowerShell script that you are in the working directory first where the vmnames.csv file resides. Otherwise it'll complain it can't find the vmnames.csv file. All of the VMs you want to run this against need to go in this file. This will also be the same directory that the report.csv exports to.
You can do a search and export the list of VMs from the Virtual Machines view in vCenter so you don't have to type them all out by hand if you have a lot.
Another issue I had was some VMs were affected by a vSphere 6.5 bug where the "disk.EnableUUID = "TRUE" value was missing from multiple vmx configuration files. I'll have to go back and fix that on those VMs. Without that value the Uuid is not exposed to the Guest OS. Therefore, it can't match the drives up.
You can reference that KB article here.
https://kb.vmware.com/s/article/52815
Finally, As @LucD mentioned. This is not a fool proof method and only works under the right conditions. Your mileage may vary.
HUGE THANKS to @LucD. I wouldn't have even known where to begin (or end) without his help!
Here is my final version of the script in case anybody wants to reference it:
# Load PowerCLI Plugin and connect to VCenter Server (Put in your FQDN where it says YOURVCENTERSERVERNAME)
Import-Module VMware.VimAutomation.Core -ErrorAction SilentlyContinue
connect-viserver YOURVCENTERSERVERNAME
# User with permission to query the WMI on the guest
$cred = if ($cred){$cred}else{Get-Credential}
# CSV file with a column named vmName
Import-Csv -Path .\vmnames.csv -UseCulture -PipelineVariable row |
ForEach-Object -Process {
$vm = Get-VM -Name $row.vmName
# If the script can't determine the FQDN of the Guest OS, the VM is skipped
$winHostName = ''
if($vm.Guest.State -eq 'Running'){
if($vm.Guest.HostName -ne ''){
$WinHostName = $vm.Guest.HostName
}
elseif($vm.Guest.IPAddress){
$resolve = Resolve-DnsName -Name $vm.guest.IPAddress[0]
if($resolve){
$WinHostName = $resolve.NameHost
}
}
}
if($winHostName){
$vmDisks = Get-HardDisk -VM $vm | Sort-Object -Property Name
$vmDatacenterView = Get-Datacenter -VM $vm | Get-View
$virtualDiskManager = Get-View -Id VirtualDiskManager-virtualDiskManager
# 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
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)}
}
$ctrl = $vm.ExtensionData.Config.Hardware.Device | where{$_.Key -eq $vmDisk.ExtensionData.ControllerKey}
$curDiskMap = "" | select vmDiskDatastore, vmDiskVmdk, SCSIAddress, vmDiskName, windowsDiskIndex, vmDiskUuid, windowsDeviceID, drives, volumes
$driveVolumes = $diskToDriveVolume | where {$_.Disk -eq $windowsDisk.DeviceID}
New-Object -TypeName PSObject -Property ([ordered]@{
VM = $vm.Name
vmDiskDatastore = $vmDisk.filename.split(']')[0].split('[')[1]
vmDiskVmdk = $vmDisk.filename.split(']')[1].trim()
SCSIAddress = "SCSI($($ctrl.BusNumber):$($vmDisk.ExtensionData.UnitNumber))"
vmDiskName = $vmDisk.Name
vmDiskSizeGB = $vmDisk.CapacityGB
windowsDiskIndex = if ($windowsDisk){$windowsDisk.Index}else{"FAILED TO MATCH"}
vmDiskUuid = $vmDiskUuid
windowsDeviceID = if ($windowsDisk){$windowsDisk.DeviceID}else{"FAILED TO MATCH"}
drives = $driveVolumes.DriveLetter
volumes = $driveVolumes.VolumeName
})
}
}
else{
Write-Host "Could not determine FQDN for VM ($vm.Name)"
}
} | Export-Csv -Path .\report.csv -NoTypeInformation -UseCulture
Make sure when you're running the PowerShell script that you are in the working directory first where the vmnames.csv file resides. Otherwise it'll complain it can't find the vmnames.csv file. All of the VMs you want to run this against need to go in this file. This will also be the same directory that the report.csv exports to.
You can do a search and export the list of VMs from the Virtual Machines view in vCenter so you don't have to type them all out by hand if you have a lot.
Another issue I had was some VMs were affected by a vSphere 6.5 bug where the "disk.EnableUUID = "TRUE" value was missing from multiple vmx configuration files. I'll have to go back and fix that on those VMs. Without that value the Uuid is not exposed to the Guest OS. Therefore, it can't match the drives up.
You can reference that KB article here.
https://kb.vmware.com/s/article/52815
Finally, As @LucD mentioned. This is not a fool proof method and only works under the right conditions. Your mileage may vary.
HUGE THANKS to @LucD. I wouldn't have even known where to begin (or end) without his help!
Hi
When I try to run the script, getting below errors:
You cannot call a method on a null-valued expression.
At C:\Users\XODXCAKUppu\Desktop\vdisk.ps1:69 char:50
+ ... sk | where {$_.SerialNumber -eq $vmDisk.ScsiCanonicalName.substring(1 ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (:) [], RuntimeException
+ FullyQualifiedErrorId : InvokeMethodOnNull
Kindly assist to fix. thanks,
Reach me @ k_anandhan@hotmail.com or skanandhan@gmail.com