Get-VMGuestDisk: guest disk free space with PowerCLI

Get-VMGuestDisk answers a question that no other PowerCLI cmdlet can answer: how much free space is left on the volumes inside a virtual machine. Broadcom’s reference describes it as retrieving “storage volumes as seen by the virtual machines’ guest operating systems”, and that phrase is the whole point. The number comes from VMware Tools, not from the hypervisor.

This matters because the two disk numbers most admins already have are the wrong ones. A datastore with 2 TB free tells you nothing about whether E:\ on the SQL server is full, and a 512 GB VMDK reports 512 GB of capacity whether the guest has written one byte to it or filled it to the last block. The hypervisor cannot see the partitions and filesystems the guest laid down on that disk, so it cannot report on them.

What follows separates those three numbers properly, lists the six properties the cmdlet actually returns (there is no percentage and no VM name among them), and shows the trap that silently breaks most scripts copied from older estates. It builds on the Get-VM inventory reporting guide, which covers the VM-level report this one completes.


Quick answer

Connect, then pull every guest volume in the estate and keep only the ones running out of room. The CapacityGB -gt 0 test is not decoration, and the Hidden gems section below explains exactly what it prevents.

# PowerCLI keeps no ambient session - a new console must connect first
Connect-VIServer -Server SRV-VC-01

# Every guest volume on one VM, as the guest OS reports it
Get-VMGuestDisk -VM SRV-APP-01

# Estate-wide: only volumes under 10 percent free
Get-VM | Get-VMGuestDisk |
  Where-Object { $_.CapacityGB -gt 0 -and ($_.FreeSpaceGB / $_.CapacityGB) -lt 0.10 } |
  Select-Object @{N='VM';E={$_.VMGuest.VmName}}, DiskPath,
    @{N='Free%';E={[math]::Round(($_.FreeSpaceGB / $_.CapacityGB) * 100, 1)}}
Note: $_.VMGuest.VmName is how you get the machine name onto the row. The VMGuestDisk object has no VM name property of its own, only a link to the guest it came from.

Three different disk numbers in vSphere

Most capacity arguments in a vSphere estate come down to two people quoting different numbers and both being right. There are three separate measurements, produced by three separate cmdlets, and each one answers a different question.

QuestionCmdletWhat the number describesWhere it comes from
Can I place another VM here?Get-DatastoreFree space on the VMFS or NFS volumeThe ESXi host
How large is the virtual disk?Get-HardDiskCapacity provisioned to the VMDKvCenter configuration
Is C:\ about to fill up?Get-VMGuestDiskFree space in the guest filesystemVMware Tools, inside the guest

The gap between the second and third rows is the one that causes outages. Get-HardDisk reports what the hypervisor handed to the VM. What the guest then did with it, one partition or five, NTFS or ext4, formatted or left raw, is invisible at that layer. Only the guest knows, and only VMware Tools can be asked.

Warning: A 512 GB VMDK sitting on a datastore with 2 TB free can still have a guest volume at 99 percent. Datastore headroom and guest headroom are unrelated numbers, and a capacity report that quotes only the first one is not a capacity report.

What Get-VMGuestDisk returns

The cmdlet has three parameter sets. They differ only in what you use to select the volumes: a virtual machine, a guest object, or a virtual disk.

Get-VMGuestDisk [-DiskPath <String[]>] [-VM <VirtualMachine[]>] [-Server <VIServer[]>] [<CommonParameters>]

Get-VMGuestDisk [-DiskPath <String[]>] [-VMGuest <VMGuest[]>] [-Server <VIServer[]>] [<CommonParameters>]

Get-VMGuestDisk [-DiskPath <String[]>] -HardDisk <HardDisk[]> [<CommonParameters>]

Five distinct parameters appear across those three sets. Two details are easy to miss: -HardDisk is the only required parameter anywhere in the cmdlet, and the set that uses it is the one set that does not accept -Server.

ParameterTypeNotes
-DiskPathString[]Filters on the mount location, for example C:\ or /var. Supports wildcards. Available in all three sets
-VMVirtualMachine[]Accepts pipeline input, so Get-VM | Get-VMGuestDisk works
-VMGuestVMGuest[]Accepts pipeline input from Get-VMGuest
-HardDiskHardDisk[]Required in its own set. Returns the guest volumes backed by a given VMDK
-ServerVIServer[]Targets a specific connection. Absent from the -HardDisk set

Each object that comes back carries six documented properties. Read this list before writing any report against it, because what is missing shapes the script more than what is present.

PropertyTypeWhat it holds
DiskPathStringThe mount point: C:\, D:\, /, /var
CapacityGBDecimalSize of the guest volume in GB
FreeSpaceGBDecimalFree space in the guest volume in GB
FileSystemTypeStringNTFS, ReFS, ext4 and so on, as the guest reports it
UidStringPowerCLI identifier for the volume
VMGuestVMGuestThe guest object, which carries VmName and VM

There is no used-space property, no percentage, and no VM name. All three are things you calculate or reach through VMGuest, and every example below does exactly that. FileSystemType is the property nobody uses and the one that lets a single mixed-estate report tell an NTFS volume from an ext4 mount without guessing from the path.


Before the first example

Two things have to be true or every command on this page returns nothing at all. The first is a connection. PowerCLI holds no ambient session, so a freshly opened console is connected to nothing.

# Against vCenter, which is what estate-wide reports need
Connect-VIServer -Server SRV-VC-01

# A standalone ESXi host works the same way, with the host's own credentials
Connect-VIServer -Server esx01.lab.local

If Get-VMGuestDisk is not recognised as a cmdlet, the module is the place to look rather than the connection. This prints what is installed.

# Lists every installed version of the core module side by side
Get-Module VMware.VimAutomation.Core -ListAvailable | Select-Object Name, Version

The second requirement is VMware Tools. The volume data is collected by Tools inside the guest and passed up, so a powered-off VM, or a running VM with Tools stopped, contributes no rows. Broadcom’s reference adds one more condition that surprises people building templates: the guest operating system has to have been run at least once before the volume information exists.

# Check Tools is actually running before blaming the cmdlet for empty output
Get-VM SRV-APP-01 | Select-Object Name, PowerState,
  @{N='Tools';E={$_.ExtensionData.Guest.ToolsRunningStatus}}
Note: ToolsRunningStatus answers “is Tools running right now”, which is the condition that matters here. It is a different question from whether the installed Tools version is current, and the Get-VM inventory reporting guide covers that distinction and the property that reports it correctly.

Practical examples

1. A user reports the application server is out of space

The problem: An application on SRV-APP-01 is failing to write temp files. The datastore has plenty of room and the VM has two disks, so nobody can say which volume is the one that filled.

The solution: Ask the guest directly. One cmdlet, no remote session, no credentials for the guest OS.

# Returns one row per mounted volume in the guest, not per VMDK
Get-VMGuestDisk -VM SRV-APP-01

To narrow it to one volume, use -DiskPath. It accepts wildcards, which is the practical way to catch every mount under a tree on a Linux guest.

# Just the system volume - the trailing backslash is part of the path
Get-VMGuestDisk -VM SRV-APP-01 -DiskPath 'C:\'

# Every mount under /var on a Linux guest
Get-VMGuestDisk -VM SRV-WEB-03 -DiskPath '/var*'

2. Which volumes across the estate are nearly full

The problem: Monitoring covers the datastores but not the guests, so the first warning anyone gets about a full drive is an application failing.

The solution: Build the percentage the cmdlet does not give you, and sort by it. This is the report worth scheduling.

$report = Get-VM | Get-VMGuestDisk |
  # Skip zero-capacity volumes: dividing a Decimal by zero errors and blanks the cell
  Where-Object { $_.CapacityGB -gt 0 } |
  Select-Object @{N='VM';E={$_.VMGuest.VmName}}, DiskPath, FileSystemType,
    @{N='CapacityGB';E={[math]::Round($_.CapacityGB, 1)}},
    @{N='FreeGB';E={[math]::Round($_.FreeSpaceGB, 1)}},
    @{N='Free%';E={[math]::Round(($_.FreeSpaceGB / $_.CapacityGB) * 100, 1)}}

$report | Sort-Object 'Free%' | Format-Table -AutoSize

Against a small estate of three VMs the table comes out like this. Note that PowerShell pads the decimal columns to a common width, so a value of 9.4 prints as 9.40 alongside 121.80.

VM         DiskPath FileSystemType CapacityGB FreeGB Free%
--         -------- -------------- ---------- ------ -----
SRV-WEB-03 /var     ext4                20.00   1.30  6.70
SRV-SQL-02 E:\      NTFS               512.00  37.00  7.20
SRV-APP-01 C:\      NTFS                80.00   9.40 11.80
SRV-SQL-02 C:\      NTFS               100.00  41.60 41.60
SRV-APP-01 D:\      NTFS               250.00 121.80 48.70
SRV-WEB-03 /        ext4                40.00  22.10 55.20
SRV-WEB-03 /boot    ext4                 1.00   0.60 62.00
SRV-SQL-02 L:\      ReFS               128.00  96.20 75.20

Adding a threshold turns the same pipeline into an alert. Filtering on the ratio rather than on the rounded percentage avoids arguing with the rounding rule described in Hidden gems.

# Compare the raw ratio, not the rounded column, so 9.96 percent is not read as 10
Get-VM | Get-VMGuestDisk |
  Where-Object { $_.CapacityGB -gt 0 -and ($_.FreeSpaceGB / $_.CapacityGB) -lt 0.10 } |
  Select-Object @{N='VM';E={$_.VMGuest.VmName}}, DiskPath,
    @{N='Free%';E={[math]::Round(($_.FreeSpaceGB / $_.CapacityGB) * 100, 1)}} |
  Sort-Object 'Free%'
Result: Against the sample estate above that filter returns two rows, /var on SRV-WEB-03 at 6.7 percent and E:\ on SRV-SQL-02 at 7.2 percent. Every other volume is above the threshold and drops out.

3. Which VMDK do I actually grow

The problem: E:\ on SRV-SQL-02 needs more space. The VM has several hard disks and none of them is labelled with a drive letter, because the hypervisor does not know the drive letters.

The solution: Pipe the guest volume into Get-HardDisk. Its -VMGuestDisk parameter set exists for exactly this, and it accepts pipeline input, so the mapping is one line.

# From a guest drive letter back to the VMDK that backs it
Get-VMGuestDisk -VM SRV-SQL-02 -DiskPath 'E:\' | Get-HardDisk |
  Select-Object Name, CapacityGB, StorageFormat, Filename

The same relationship runs the other way. Starting from a virtual disk, this lists the guest volumes carved out of it, which is how you check whether a VMDK carries one filesystem or several before resizing anything.

# One VMDK can back several guest volumes - confirm before growing it
Get-HardDisk -VM SRV-SQL-02 | ForEach-Object {
  $vmdk = $_
  Get-VMGuestDisk -HardDisk $vmdk |
    Select-Object @{N='VMDK';E={$vmdk.Name}}, DiskPath, CapacityGB
}
Warning: The mapping between a guest volume and a VMDK is not always resolvable. Administrators on the Broadcom PowerCLI community forum report the -HardDisk parameter set returning a “guest mapping is not available” message for some VMs, most often powered-off ones. Have the -VM form ready as a fallback so a single unmappable machine does not empty the whole report.

4. A capacity review that someone outside the team can read

The problem: The quarterly capacity review needs guest-level numbers in a spreadsheet, not a console window, and the storage totals have to add up.

The solution: Export the same objects to CSV, then total them separately so the summary line is calculated rather than typed. Accumulate the totals in a [decimal] variable rather than reaching for Measure-Object, for the reason set out directly below.

# -UseCulture writes the separator Excel expects on this machine's locale
$report | Export-Csv -Path 'C:\perf\guest-disks.csv' -NoTypeInformation -UseCulture

# Decimal in, Decimal out - no floating point anywhere in the total
$cap = [decimal]0; $free = [decimal]0
foreach ($row in $report) { $cap += $row.CapacityGB; $free += $row.FreeGB }

"Guest capacity {0} GB, free {1} GB, used {2} GB" -f $cap, $free, ($cap - $free)
Guest capacity 1131 GB, free 330.0 GB, used 801.0 GB
Common mistake: Measure-Object -Sum returns a System.Double even when every value fed into it is a Decimal. Summing the same eight rows that way produced 330.00000000000006 instead of 330.0. Worse, the faulty value hides: printed with string interpolation it reads 330, and only the -f format operator shows the full figure. A total that looks clean on screen can still land in the spreadsheet with fourteen decimal places.
Note: These totals are guest-visible capacity, which is not the same as provisioned or consumed storage on the datastore. Thin provisioning, snapshots and swap files all sit outside this number. Quote it as “space the operating systems can see”, and pull the datastore side from Get-Datastore separately.

Hidden gems

The property is DiskPath, and the old one was Path

Before this cmdlet existed, guest volumes were read from (Get-VMGuest $vm).Disks. That collection still exists and still works, but it returns a different type with a different property list. Scripts get copied between estates; the property names do not survive the trip.

PropertyDiskInfo from .DisksVMGuestDisk from the cmdlet
Mount pointPathDiskPath
Size in GBCapacityGBCapacityGB
Free in GBFreeSpaceGBFreeSpaceGB
Raw byte countsCapacity, FreeSpacenot present
Filesystemnot presentFileSystemType
Identity and ownernot presentUid, VMGuest
Total properties56

Only two names are shared. The dangerous one is the mount point, because PowerShell does not complain about a property that is not there. Run a filter written for the old object against the new one and it returns nothing, quietly.

# Copied from an older script - matches nothing, raises no error
$disks | Where-Object { $_.Path     -like 'C:*' }   # 0 rows

# The same intent against VMGuestDisk
$disks | Where-Object { $_.DiskPath -like 'C:*' }   # 2 rows
Common mistake: Tested against a set of eight sample volumes, the $_.Path filter returned 0 rows and raised 0 errors, while $_.DiskPath returned the expected 2. An empty report looks identical to a healthy estate. If a guest-disk report suddenly has no rows, check the property name before checking the VMs.

CapacityGB is a Decimal, so dividing by zero is an error, not Infinity

Both size properties are documented as Decimal, not Double, and the two types behave differently when the denominator is zero. A double gives you Infinity and carries on. A decimal throws.

[double]5.0 / 0.0      # Infinity
[decimal]5   / 0       # Attempted to divide by zero.

A guest can absolutely report a zero-capacity volume: a mounted ISO, an empty card reader, a drive letter assigned to nothing. Inside a calculated property the failure is not loud. The pipeline keeps going, the row still appears, and only that one cell is blank.

DiskPath FreePct
-------- -------
C:\        11.80
Z:\
D:\        48.70

Three rows out, one error raised, one empty cell. In a console you notice the red text. In a scheduled job writing to CSV you get a blank field in a spreadsheet somebody later sorts. The guard is one clause.

# Drop zero-capacity volumes before any percentage is calculated
Get-VM | Get-VMGuestDisk | Where-Object { $_.CapacityGB -gt 0 }

[math]::Round rounds halves to even

.NET rounds a midpoint to the nearest even number by default, which is not what most people expect from a percentage column.

[math]::Round(2.5)        # 2, not 3
[math]::Round(3.5)        # 4
[math]::Round(12.45, 1)   # 12.4, not 12.5
[math]::Round(10.05, 1)   # 10.0, not 10.1

# Force the familiar behaviour when a threshold depends on it
[math]::Round([decimal]10.05, 1, [MidpointRounding]::AwayFromZero)   # 10.1
Warning: Rounding is also why the threshold examples above compare the raw ratio and not the rounded Free% column. Ordinary rounding is enough to break a threshold on its own: 9.96 percent free rounds to 10.0, so a -lt 10 test applied after rounding drops the volume that is closest to failing. The midpoint rule then adds a second way to lose it, because 10.05 rounds down to 10.0 rather than up to 10.1.

The relationship with Get-HardDisk runs both ways

Get-VMGuestDisk takes -HardDisk and Get-HardDisk takes -VMGuestDisk, both with pipeline support. That is a deliberate pair, and it is the documented route between a drive letter and a VMDK file name, rather than guessing from disk ordering.

# Drive letter to VMDK
Get-VMGuestDisk -VM SRV-SQL-02 -DiskPath 'E:\' | Get-HardDisk

# VMDK to the guest volumes it carries
Get-HardDisk -VM SRV-SQL-02 -Name 'Hard disk 2' | Get-VMGuestDisk

Doing the same thing from inside the guest

Everything above runs against vCenter and needs no credentials for the guest operating system, which is the main reason to use it. The alternatives all require getting inside the machine.

On a Windows guest, Get-Volume is the native equivalent. It reports the same volumes with different column names, and unlike the PowerCLI route it includes health status.

Get-Volume -DriveLetter C
DriveLetter         FileSystemLabel     FileSystem          HealthStatus              SizeRemaining                Size
-----------         ---------------     ----------          ------------              -------------                ----
C                                       NTFS                Healthy                        23.61 GB           465.42 GB

On a Linux guest the equivalent is df, where -h prints human-readable sizes and -T adds the filesystem type that FileSystemType gives you from the PowerCLI side.

df -hT /var

PowerCLI can also run a command inside the guest with Invoke-VMScript, which goes through VMware Tools rather than the network. It is far heavier than Get-VMGuestDisk: Broadcom’s reference requires the VM powered on with Tools running, guest or host credentials supplied, a Console Interaction privilege, guest operation privileges, and network connectivity to the ESXi host running the VM.

ApproachRuns whereNeeds guest credentialsWorks at estate scale
Get-VMGuestDiskvCenter or ESXiNoYes, one pipeline
Get-Volume / dfInside the guestYesOnly with remoting set up
Invoke-VMScriptGuest, via ToolsYesSlow, per VM
Note: Use Invoke-VMScript when you need something the volume list cannot tell you, such as which folder is consuming the space. For “how full is it”, the read-only cmdlet with no guest credentials is the better tool.

Where this matters

  • Monitoring gaps. Estates that monitor datastores but not guests find out about a full drive when an application stops writing, and this is the cheapest way to close that gap without an agent.
  • Pre-patch checks. Windows updates fail on a system volume with no headroom, so a single pass over C:\ across the estate before a patch window saves a night of investigating individual failures.
  • Database log drives. A transaction log filling its own volume is invisible at the datastore layer and unmistakable in a guest volume report.
  • Right-sizing before a disk grows. Checking guest free space before adding capacity often shows the VMDK is fine and one folder is the real problem.
  • Handover and audits. An inherited estate with no documentation gives up its drive letters, mount points and filesystems in one pipeline, with no need to log into a single machine.

Tips and limitations

  • The cmdlet is read-only. Nothing on this page changes a VM, so all of it is safe to run during business hours.
  • No VMware Tools, no data. A powered-off VM, a VM with Tools stopped, and a template all return nothing rather than an error, so empty output is a state to investigate, not a failure.
  • Broadcom’s reference notes the guest operating system must have been run at least once for the volume information to exist, which is worth remembering when a freshly deployed template reports no disks.
  • The figures are as fresh as the last Tools update, not live. Treat them as a few minutes old rather than to the second.
  • One row per mounted volume, not one per virtual disk. A VM with two VMDKs can easily return five rows, and on Linux guests it returns every mount point.
  • Get-VM | Get-VMGuestDisk walks the whole inventory. On a large estate, scope it with Get-VM -Location against a cluster or folder rather than pulling everything.
  • If the cmdlet is not recognised, check the installed module version rather than the connection. Older PowerCLI releases predate it.

Official documentation


Related tools


Related guides