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}
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
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
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
Does this work when you have 2 or more Windows partitions on 1 vDisk?
Blog: lucd.info Twitter: @LucD22 Co-author PowerCLI Reference
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.
@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
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
Thanks @mk_ultra I'll try to use this for Windows servers
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:
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
