VMware Cloud Community
vishpk
Contributor
Contributor
Jump to solution

How to get the HardDisk details for the OS Partition

I am trying to match the Virtual Hard disk with the OS partition details and noticed that Get-VMGuest provides disk details Key Mappings which is same as the Key returned by get-Harddisk. But I am unable to get the value for some of the VMs and it is blank.

(get-vmguest $vmname).extensiondata.disk

DiskPath       : C:\
Capacity       : 214220926976
FreeSpace      : 69026787328
FilesystemType : NTFS
Mappings       : <Disk Key 200x is missing here>

It is working for some VMs, but is missing for many VMs. Executing the same command as above on another VM provides following output

DiskPath       : C:\
Capacity       : 214220926976
FreeSpace      : 30915121152
FilesystemType : NTFS
Mappings       : {2000}
1 Solution

Accepted Solutions
LucD
Leadership
Leadership
Jump to solution

There are some prerequisites for the Get-VMGuest cmdlet to show all the info.
See the VM Guest Disk Management section in New Release – PowerCLI 12

So it is perfectly normal that some fields are missing values.


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

View solution in original post

Reply
0 Kudos
9 Replies
LucD
Leadership
Leadership
Jump to solution

There are some prerequisites for the Get-VMGuest cmdlet to show all the info.
See the VM Guest Disk Management section in New Release – PowerCLI 12

So it is perfectly normal that some fields are missing values.


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

Reply
0 Kudos
mk_ultra
Contributor
Contributor
Jump to solution

I recently wrote a script which uses two functions I found online that can be modified to do what you want. My example matches a Windows volume name with the VM disk, and then deletes the VM disk. See below

 

 

<###############################################################################################

________         .__          __           ____   _________    ________  .__        __    
\______ \   ____ |  |   _____/  |_  ____   \   \ /   /     \   \______ \ |__| _____|  | __
 |    |  \_/ __ \|  | _/ __ \   __\/ __ \   \   Y   /  \ /  \   |    |  \|  |/  ___/  |/ /
 |    `   \  ___/|  |_\  ___/|  | \  ___/    \     /    Y    \  |    `   \  |\___ \|    < 
/_______  /\___  >____/\___  >__|  \___  >    \___/\____|__  / /_______  /__/____  >__|_ \
        \/     \/          \/          \/                  \/          \/        \/     \/

###############################################################################################>

<#

Script Name: Delete_VM_Disk
Author:
    VMTN Communities: mk_ultra
    Reddit: /u/powershellnovice3

Version:
    # v1.0 - 2-8-23

Description:
    # Finds a Windows volume by name ($VolumeName), identifies the SCSI ID of the Windows volume, then deletes the corresponding VM disk

Notes:
    # Change the 3 "$VolumeName" variables to delete the VM disk of a different volume name
    # Script is currently configured to run against the 999 server on a cluster

#>

# Create log file directory if it doesn't exist
New-Item -Path "C:\scripts\logs" -ItemType "directory" -ErrorAction SilentlyContinue

# Set variables
$ScriptName = "Delete_VM_Disk.ps1"
$Author = "mk_ultra"
$Version = "1.0"
$LastUpdated = "2-9-23"
$LogFile = "C:\scripts\logs\Delete_VM_Disk.log"
$Description = "-Finds a Windows volume by name, identifies the SCSI ID of the Windows disk, then deletes the corresponding VM disk"
$VolumeName = "Data"

# Import VMware modules
Write-Host "Importing VMWare modules." -ForegroundColor Cyan -BackgroundColor Black
Import-Module VMware.VumAutomation
Import-Module VMware.VimAutomation.Core
Import-Module VMware.VimAutomation.Storage
Import-Module VMware.VimAutomation.Common
Import-Module VMware.VimAutomation.License

# Print script information
Write-Host "`nScript Name: $ScriptName" -ForegroundColor Yellow
Write-Host "Author: $Author" -ForegroundColor Yellow
Write-Host "Version: $Version" -ForegroundColor Yellow
Write-Host "Last Updated: $LastUpdated" -ForegroundColor Yellow
Write-Host "Log File: $LogFile" -ForegroundColor Yellow
Write-Host "Description: `n$Description" -ForegroundColor Yellow

# Prompt for vCenter to connect to
$vCenter = (Read-Host "`nEnter FQDN of vCenter to connect to")
Connect-VIServer "$vCenter"

try {

    # Show contents of input file, allow user to make changes until 'y' is input
    do {
        Write-Host "`nPlease confirm that the text file located at 'C:\scripts\input.txt' contains a full list of the cluster numbers you wish to target, one per line.`nCurrent list contents are displayed below:" -ForegroundColor Cyan -BackgroundColor Black
        Get-Content "C:\scripts\input.txt"
        $Answer = Read-Host "`nIf the above list is correct, enter 'y' to continue. Otherwise, update the file and press 'Enter' to show file contents again."
    } until ($Answer -eq "y")

    $InputFile = Get-Content "C:\scripts\input.txt"

    # Print script start time
    Write-Host "$(Get-Date) - $ScriptName started." -ForegroundColor Cyan -BackgroundColor Black
    "$(Get-Date) - $ScriptName started." >> $LogFile
    $StartTime = $(Get-Date)

    foreach ($I in $InputFile) {

        $ClusterName = "$I"+"_Cluster"
        $Server = Get-Cluster $ClusterName | Get-VM | Where {$_.Name -like "*999*"}

        # Check if cluster/server exists
        if ($Server) {

            # Obtain SCSI Bus and SCSI Target ID variables with two different PSSessions
            $Sesh1 = New-PSSession -computerName $Server
            $Result1 = Invoke-Command -Session $Sesh1 -Scriptblock {

                $ClusterName = $args[0]
                $Server = $args[0]
                $VolumeName = "Data"

                # Import Functions
                Function Get-ScsiDisks {
                <#
                .SYNOPSIS
                    Retrieves disk details for VMWare Guests with corresponding SCSI disk details like SCSI ID
                    and SCSI Bus.

                .DESCRIPTION
                    Retrieves a concatenated object consisting of Win32_DiskDrive, Win32_LogicalDisk and
                    Win32_DiskDriveToDiskPartition using WMI. For WinRM you can use Invoke-Command and inject the script.

                .PARAMETER ComputerName
                    A single Computer or an array of computer names. The default is localhost ($env:COMPUTERNAME).

                .PARAMETER Credentials
                    Commit Credentials for a different domain.

                .PARAMETER Verbose
                    Run in Verbose Mode.

                .EXAMPLE
                    PS C:> Get-ScsiDisks

                    ComputerName    Disk               DriveLetter VolumeName  Size FreeSpace DiskModel
                    ------------    ----               ----------- ----------  ---- --------- ---------
                    SERVER          \.PHYSICALDRIVE1           Data         767       767 VMware Virtual di...
                    SERVER          \.PHYSICALDRIVE0 C:          OS            59        39 VMware Virtual di...

                .EXAMPLE
                	PS C:> Get-ScsiDisks | Out-GridView

                .EXAMPLE
                	PS C:> Get-ScsiDisks | ft -a

                .EXAMPLE
                	PS C:> Get-ScsiDisks -ComputerName (gc 'C:VMs.txt') -Credentials Get-Credential

                .LINK

                Home

                .NOTES
                    Author:  Sebastian Gräf
                    Email:   ps@graef.io
                    Date:    September 12, 2017
                    PSVer:   3.0/4.0/5.0
                #>

                	[Cmdletbinding()]
                	Param (
                		[Parameter(ValueFromPipelineByPropertyName = $true, ValueFromPipeline = $true)]
                		$ComputerName = $Env:COMPUTERNAME,
                		[Parameter(ValueFromPipelineByPropertyName = $true, ValueFromPipeline = $true)]
                		[ValidateNotNull()]
                		[System.Management.Automation.PSCredential][System.Management.Automation.Credential()]
                		$Credentials = [System.Management.Automation.PSCredential]::Empty
                	)

                	Begin
                	{
                		Write-Verbose " [$($MyInvocation.InvocationName)] :: Start Process"
                		$result=@()
                		$ProgressCounter = 0
                	}


                	Process
                	{
                		foreach ($Computer in $ComputerName)
                		{
                			$ProgressCounter++
                			Write-Progress -activity "Running on $Computer" -status "Please wait ..." -PercentComplete (($ProgressCounter / $ComputerName.length) * 100)
                			if (Test-Connection $Computer -Count 1 -Quiet)
                			{
                				Write-Verbose " [$($MyInvocation.InvocationName)] :: Processing $Computer"
                				try
                				{
                					Get-WmiObject -Class Win32_DiskDrive -ComputerName $Computer -Credential $Credentials | % {
                						$disk = $_
                						$partitions = "ASSOCIATORS OF " +
                						"{Win32_DiskDrive.DeviceID='$($disk.DeviceID)'} " +
                						"WHERE AssocClass = Win32_DiskDriveToDiskPartition"
                						Get-WmiObject -Query $partitions -ComputerName $Computer -Credential $Credentials | % {
                							$partition = $_
                							$drives = "ASSOCIATORS OF " +
                							"{Win32_DiskPartition.DeviceID='$($partition.DeviceID)'} " +
                							"WHERE AssocClass = Win32_LogicalDiskToPartition"
                							Get-WmiObject -Query $drives -ComputerName $Computer -Credential $Credentials | % {
                								$obj = New-Object -Type PSCustomObject -Property @{
                									ComputerName = $Computer
                									Disk = $disk.DeviceID
                									DiskSize = [math]::Truncate($disk.Size / 1GB);
                									DiskModel = $disk.Model
                									Partition = $partition.Name
                									DriveLetter = $_.DeviceID
                									VolumeName = $_.VolumeName
                									Size = [math]::Truncate($_.Size / 1GB)
                									FreeSpace = [math]::Truncate($_.FreeSpace / 1GB)
                									SCSIBus = $disk.SCSIBus
                									SCSITargetId = $disk.SCSITargetId
                								}
                								$result += $obj
                							}
                						}
                					}
                				}
                				catch
                				{
                					Write-Verbose " Host [$Computer] Failed with Error: $($Error[0])"
                				}
                			}
                			else
                			{
                				Write-Verbose " Host [$Computer] Failed Connectivity Test"
                			}
                		}
                        $result | select ComputerName,Disk,DriveLetter,VolumeName,Size,FreeSpace,DiskModel,Partition,SCSIBus,SCSITargetId
                	}
                	End
                	{
                		Write-Progress -activity "Running on $Computer" -Status "Completed." -Completed
                		Write-Verbose " [$($MyInvocation.InvocationName)] :: End Process"
                	}
                }

                # Get SCSI ID of disk which has the volume name defined in the Variables section
                $VolumeDisk = Get-ScsiDisks | Where {$_.VolumeName -like "$VolumeName"}
                $SCSIBus = $VolumeDisk.SCSIBus
                $SCSIBus

            }

            Remove-PSSession $Sesh1

            $Sesh2 = New-PSSession -computerName $Server
            $Result2 = Invoke-Command -Session $Sesh2 -Scriptblock {

                $ClusterName = $args[0]
                $Server = $args[0]
                $VolumeName = "Data"

                # Import Functions
                Function Get-ScsiDisks {
                <#
                .SYNOPSIS
                    Retrieves disk details for VMWare Guests with corresponding SCSI disk details like SCSI ID
                    and SCSI Bus.

                .DESCRIPTION
                    Retrieves a concatenated object consisting of Win32_DiskDrive, Win32_LogicalDisk and
                    Win32_DiskDriveToDiskPartition using WMI. For WinRM you can use Invoke-Command and inject the script.

                .PARAMETER ComputerName
                    A single Computer or an array of computer names. The default is localhost ($env:COMPUTERNAME).

                .PARAMETER Credentials
                    Commit Credentials for a different domain.

                .PARAMETER Verbose
                    Run in Verbose Mode.

                .EXAMPLE
                    PS C:> Get-ScsiDisks

                    ComputerName    Disk               DriveLetter VolumeName  Size FreeSpace DiskModel
                    ------------    ----               ----------- ----------  ---- --------- ---------
                    SERVER          \.PHYSICALDRIVE1           Data         767       767 VMware Virtual di...
                    SERVER          \.PHYSICALDRIVE0 C:          OS            59        39 VMware Virtual di...

                .EXAMPLE
                	PS C:> Get-ScsiDisks | Out-GridView

                .EXAMPLE
                	PS C:> Get-ScsiDisks | ft -a

                .EXAMPLE
                	PS C:> Get-ScsiDisks -ComputerName (gc 'C:VMs.txt') -Credentials Get-Credential

                .LINK

                Home

                .NOTES
                    Author:  Sebastian Gräf
                    Email:   ps@graef.io
                    Date:    September 12, 2017
                    PSVer:   3.0/4.0/5.0
                #>

                	[Cmdletbinding()]
                	Param (
                		[Parameter(ValueFromPipelineByPropertyName = $true, ValueFromPipeline = $true)]
                		$ComputerName = $Env:COMPUTERNAME,
                		[Parameter(ValueFromPipelineByPropertyName = $true, ValueFromPipeline = $true)]
                		[ValidateNotNull()]
                		[System.Management.Automation.PSCredential][System.Management.Automation.Credential()]
                		$Credentials = [System.Management.Automation.PSCredential]::Empty
                	)

                	Begin
                	{
                		Write-Verbose " [$($MyInvocation.InvocationName)] :: Start Process"
                		$result=@()
                		$ProgressCounter = 0
                	}


                	Process
                	{
                		foreach ($Computer in $ComputerName)
                		{
                			$ProgressCounter++
                			Write-Progress -activity "Running on $Computer" -status "Please wait ..." -PercentComplete (($ProgressCounter / $ComputerName.length) * 100)
                			if (Test-Connection $Computer -Count 1 -Quiet)
                			{
                				Write-Verbose " [$($MyInvocation.InvocationName)] :: Processing $Computer"
                				try
                				{
                					Get-WmiObject -Class Win32_DiskDrive -ComputerName $Computer -Credential $Credentials | % {
                						$disk = $_
                						$partitions = "ASSOCIATORS OF " +
                						"{Win32_DiskDrive.DeviceID='$($disk.DeviceID)'} " +
                						"WHERE AssocClass = Win32_DiskDriveToDiskPartition"
                						Get-WmiObject -Query $partitions -ComputerName $Computer -Credential $Credentials | % {
                							$partition = $_
                							$drives = "ASSOCIATORS OF " +
                							"{Win32_DiskPartition.DeviceID='$($partition.DeviceID)'} " +
                							"WHERE AssocClass = Win32_LogicalDiskToPartition"
                							Get-WmiObject -Query $drives -ComputerName $Computer -Credential $Credentials | % {
                								$obj = New-Object -Type PSCustomObject -Property @{
                									ComputerName = $Computer
                									Disk = $disk.DeviceID
                									DiskSize = [math]::Truncate($disk.Size / 1GB);
                									DiskModel = $disk.Model
                									Partition = $partition.Name
                									DriveLetter = $_.DeviceID
                									VolumeName = $_.VolumeName
                									Size = [math]::Truncate($_.Size / 1GB)
                									FreeSpace = [math]::Truncate($_.FreeSpace / 1GB)
                									SCSIBus = $disk.SCSIBus
                									SCSITargetId = $disk.SCSITargetId
                								}
                								$result += $obj
                							}
                						}
                					}
                				}
                				catch
                				{
                					Write-Verbose " Host [$Computer] Failed with Error: $($Error[0])"
                				}
                			}
                			else
                			{
                				Write-Verbose " Host [$Computer] Failed Connectivity Test"
                			}
                		}
                        $result | select ComputerName,Disk,DriveLetter,VolumeName,Size,FreeSpace,DiskModel,Partition,SCSIBus,SCSITargetId
                	}
                	End
                	{
                		Write-Progress -activity "Running on $Computer" -Status "Completed." -Completed
                		Write-Verbose " [$($MyInvocation.InvocationName)] :: End Process"
                	}
                }

                # Get SCSI ID of disk which has the volume name defined in the Variables section
                $VolumeDisk = Get-ScsiDisks | Where {$_.VolumeName -like "$VolumeName"}
                $SCSITargetID = $VolumeDisk.SCSITargetID
                $SCSITargetID

            }

            Remove-PSSession $Sesh2

            # Retrieve VM disk properties
            $VMView = Get-View -ViewType VirtualMachine -Filter @{'Name' = "$Server"}
            $VMDisks = 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})) {
                    [pscustomobject]@{
                        VM = $VM.Name
                        HostName = $VMView.Guest.HostName
                        PowerState = $VM.PowerState
                        DiskFile = $VirtualDiskDevice.Backing.FileName
                        DiskName = $VirtualDiskDevice.DeviceInfo.Label
                        DiskSize = $VirtualDiskDevice.CapacityInKB * 1KB
                        SCSIController = $VirtualSCSIController.BusNumber
                        SCSITarget = $VirtualDiskDevice.UnitNumber
                        DeviceID = $null
                    }
                }
            }

            # Match VM disk with SCSI ID that matches Windows disk SCSI ID
            $MatchingDisk = $VMDisks | where {$_.SCSIController -like "$Result1" -and $_.SCSITarget -like "$Result2"}

            # Select the VM disk object
            $GetVMDiskMatch = Get-HardDisk -VM $Server | where {$_.Name -like $MatchingDisk.DiskName}

            # Check for multiple matches, or no matches
            if ($GetVMDiskMatch.count -eq 1){

                # Example write to log file
                Write-Host "Deleting the VM disk for a Windows volume named $VolumeName on $Server." -ForegroundColor Green -BackgroundColor Black
                "$(Get-Date) - Deleting the VM disk for a Windows volume named $VolumeName on $Server." >> $LogFile

                # Delete the VM disk
                Remove-HardDisk $GetVMDiskMatch -DeletePermanently -Confirm:$false

            } else {

                Write-Host "No single VM disk match was found for a Windows volume named $VolumeName on $Server." -ForegroundColor Red -BackgroundColor Black
                "$(Get-Date) - No single VM disk match was found for a Windows volume named $VolumeName on $Server." >> $LogFile

            }

        } else {

            Write-Host "No 370 server found for $ClusterName." -ForegroundColor Red -BackgroundColor Black
            "$(Get-Date) - No 370 server found for $ClusterName." >> $LogFile

        }

    }

}

catch {

    $_

}

# Print script end time
$ElapsedTime = $(Get-Date) - $StartTime
$TotalTime = "{0:HH:mm:ss}" -f ([datetime]$ElapsedTime.Ticks)
Write-Host "$(Get-Date) - $ScriptName completed. Total script run time: $TotalTime`nLog file located at $LogFile" -ForegroundColor Cyan -BackgroundColor Black
"$(Get-Date) - $ScriptName completed. Total script run time: $TotalTime" >> $LogFile

 

Reply
0 Kudos
LucD
Leadership
Leadership
Jump to solution

Does this work when you have 2 or more Windows partitions on 1 vDisk?


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

mk_ultra
Contributor
Contributor
Jump to solution

I'm not sure; I have only tested it with single partition volumes. You have me curious now, but I'm assuming it would delete the whole disk of any partition that had a volume which matched the name.

I may test that out and write in some considerations for it.

Reply
0 Kudos
vishpk
Contributor
Contributor
Jump to solution

@LucD Thanks for sharing the link!  I get the error as below.

I have PowerCLI version is 12.6.0 and vSphere version is 7.0.3 and I am running the script on Windows VM with tools version 12.0.5. I think it matches all criteria mentioned. Am I missing something?

 

CapacityGB Persistence Filename
---------- ----------- --------
100.000 Persistent ...disk1.vmdk
100.000 Persistent ...disk2.vmdk



Get-HardDisk -VMGuestDisk $guestdisks
Get-HardDisk : 10/6/2023 4:00:20 PM Get-HardDisk Guest disk mapping information is unavailable for VM
+ Get-HardDisk -VMGuestDisk $guestdisks
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : ResourceUnavailable: (/VIServer=..vm-282853/:String) [Get-HardDisk], VimException
+ FullyQualifiedErrorId : ViCore_VmGuestHardwareHelper_ReportNoDiskMappingError,VMware.VimAutomation.ViCore.Cmdlets.Commands.VirtualDevice.Ge
tHardDisk
Reply
0 Kudos
LucD
Leadership
Leadership
Jump to solution

No, you are not missing anything.
Like I said before, this cmdlet does not always return the information (as the error message confirms).


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

Reply
0 Kudos
vishpk
Contributor
Contributor
Jump to solution

Thanks @mk_ultra  I'll try to use this for Windows servers

Reply
0 Kudos
mk_ultra
Contributor
Contributor
Jump to solution

Hey LucD I have a question related to getting my script to handle disks with multiple partitions. I just want it to throw a warning if a vDisk has 2 or more volumes on it.

So for example, $Result contains 5 objects from the Get-SCSIDisks function:

ComputerName   : [Obfuscated]
Disk           : \\.\PHYSICALDRIVE2
DriveLetter    : :anguished_face:
VolumeName     : [Obfuscated]
Size           : 99
FreeSpace      : 99
DiskModel      : VMware Virtual disk SCSI Disk Device
Partition      : Disk #2, Partition #0
SCSIBus        : 0
SCSITargetId   : 2
PSComputerName : [Obfuscated]

ComputerName   : [Obfuscated]
Disk           : \\.\PHYSICALDRIVE0
DriveLetter    : C:
VolumeName     :
Size           : 99
FreeSpace      : 65
DiskModel      : VMware Virtual disk SCSI Disk Device
Partition      : Disk #0, Partition #1
SCSIBus        : 0
SCSITargetId   : 0
PSComputerName : [Obfuscated]

ComputerName   : [Obfuscated]
Disk           : \\.\PHYSICALDRIVE3
DriveLetter    : G:
VolumeName     : [Obfuscated]
Size           : 299
FreeSpace      : 193
DiskModel      : VMware Virtual disk SCSI Disk Device
Partition      : Disk #3, Partition #0
SCSIBus        : 0
SCSITargetId   : 3
PSComputerName : [Obfuscated]

ComputerName   : [Obfuscated]
Disk           : \\.\PHYSICALDRIVE1
DriveLetter    : E:
VolumeName     : New1
Size           : 19
FreeSpace      : 19
DiskModel      : VMware Virtual disk SCSI Disk Device
Partition      : Disk #1, Partition #0
SCSIBus        : 0
SCSITargetId   : 1
PSComputerName : [Obfuscated]

ComputerName   : [Obfuscated]
Disk           : \\.\PHYSICALDRIVE1
DriveLetter    : F:
VolumeName     : New2
Size           : 20
FreeSpace      : 20
DiskModel      : VMware Virtual disk SCSI Disk Device
Partition      : Disk #1, Partition #1
SCSIBus        : 0
SCSITargetId   : 1
PSComputerName : [Obfuscated]
 
 
So volumes E: and F: exist on the same SCSIBus/SCSITargetID
 
I've been struggling with how to make this comparison. I think I need to combine the SCSIBus and SCSITargetID into one variable for each of the objects, then check those variables to see which are not unique.
Reply
0 Kudos
LucD
Leadership
Leadership
Jump to solution

You could use the Group-Object cmdlet and use the SCSIBus and SCSITargetId properties.
If the resulting Group property has more than 1 entry, you have a disk with more than 1 partition.
Something like this for example

$Result | Group-Object -Property SCSIBus,SCSITargetId |
Foreach-Object -Process {
  if($_.Group.Count -gt 1){
    Write-Host "Disk with more than 1 partition!"
    Write-Host "Bus: $($_.Name.Replace(' ','').Split(',')[0])  TargetId: $($_.Name.Replace(' ','').Split(',')[1])
  }
}


 


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