Windows administrators using Windows PowerShell 5.1 or PowerShell 7 on Windows. Get-Volume requires the Windows Storage module; remote CIM queries require authorised access and a working management connection.
Examples illustrate read-only capacity queries. Adapt host names and report locations to your environment; validate module availability, permissions and volume coverage before relying on the output.Choose the view that matches the question
I start by deciding whether I need the current shell’s filesystem drives, the host’s volumes or a repeatable report from several servers. Those are different inventories. A command can run successfully and still leave out the storage that matters to the service.
Get-PSDrive -PSProvider FileSystem is convenient for a quick local view, including filesystem drives visible in that session. It is not a complete server-volume inventory: mappings and context can differ between your interactive account and an automation identity. Use Get-Volume when a volume may have no drive letter.
For remote drive-letter reporting, Win32_LogicalDisk offers a small, consistent set of capacity properties. DriveType = 3 selects local fixed disks on each queried machine; it does not mean the query itself is local. This filter is not a replacement for checking mounted volumes, storage pools or a virtualisation datastore.
Which storage view do you need?
- Current session → Get-PSDrive
- Quick filesystem-drive view. Session mappings can differ; not a complete volume inventory.
- Host volumes → Get-Volume
- Volume capacity and identifiers, including volumes without drive letters. Requires Storage module.
- Remote drive letters → CIM
- Win32_LogicalDisk via authorised WSMan access. Reconcile unlettered volumes separately.
All three are snapshots. None proves backing-storage capacity or future workload headroom.
Check local volumes and calculate free space
The command below reads fixed volumes with a positive capacity. It keeps Path alongside the optional drive letter so an unlettered volume has an identifier. Size and SizeRemaining are byte counts; the calculated columns are easier to read while the raw values remain available for later checks.
In PowerShell, 1GB is the binary multiplier 1,073,741,824, so these columns are labelled GiB. Percentage free is remaining bytes divided by total bytes, multiplied by 100. Round the displayed result, not the input used for a threshold.
A null free-space value stays null. It is neither proof of zero free space nor a healthy result. The size filter deliberately omits zero-sized or unknown-sized entries from this capacity calculation; reconcile exclusions against your expected inventory rather than assuming they are irrelevant.
$volumes = @(Get-Volume -ErrorAction Stop |
Where-Object { $_.DriveType -eq 'Fixed' -and $_.Size -gt 0 })
$volumes | Select-Object DriveLetter, FileSystemLabel, Path,
Size, SizeRemaining,
@{Name='SizeGiB'; Expression={[math]::Round($_.Size / 1GB, 2)}},
@{Name='FreeGiB'; Expression={
if ($null -ne $_.SizeRemaining) {
[math]::Round($_.SizeRemaining / 1GB, 2)
}
}},
@{Name='FreePct'; Expression={
if ($null -ne $_.SizeRemaining) {
[math]::Round(100 * $_.SizeRemaining / $_.Size, 1)
}
}}Check C drive on one remote server
Replace server01.example.test with an approved host name. Get-CimInstance uses a temporary WSMan connection when ComputerName is supplied. The calling account needs the relevant remote-management and WMI permissions, and the target must be reachable through the approved management path.
Validate DNS, authentication and management connectivity before broadening the query. A failed remote query does not justify disabling the firewall or weakening authentication. Where repeated operations are needed, use your organisation’s approved CIM-session configuration instead of embedding a password in the script.
This query reads C: only. A successful response says nothing about D:, an unlettered database volume or the host’s backing datastore. If the filter returns nothing, check the intended device identifier before treating the server as covered.
Get-CimInstance -ClassName Win32_LogicalDisk `
-ComputerName 'server01.example.test' `
-Filter "DeviceID = 'C:'" -ErrorAction Stop |
Select-Object SystemName, DeviceID, VolumeName, Size, FreeSpaceBuild a multiple-server report without hiding failures
Use a reviewed list of authorised machines. The report below queries them sequentially, keeps one row per returned fixed disk and records a separate QueryFailed or NoFixedDisksReturned row when no usable inventory can be established. It also marks a disk with missing or invalid capacity values as MissingCapacity.
Collected means the query supplied internally plausible capacity values. It is not a health grade. The script does not decide whether 20 GiB is enough for a database, verify service health or prove that discovery covered all required mount points. Compare the returned identifiers with the workload inventory before signing off the report.
The timestamp identifies when each server query began. Queries run at different times, so this is a collection window rather than an atomic fleet snapshot. The 30-second operation timeout bounds the requested CIM operation; connection and authentication failures can have their own timing. Keep the approved list small for an initial run.
$computers = @('server01.example.test', 'server02.example.test')
$report = foreach ($computer in $computers) {
$observedAt = (Get-Date).ToUniversalTime().ToString('o')
try {
$disks = @(Get-CimInstance -ClassName Win32_LogicalDisk `
-ComputerName $computer -Filter 'DriveType = 3' `
-OperationTimeoutSec 30 -ErrorAction Stop)
if ($disks.Count -eq 0) {
[pscustomobject]@{
Computer=$computer; ObservedAtUtc=$observedAt
Drive=$null; SizeBytes=$null; FreeBytes=$null
FreeGiB=$null; FreePct=$null
CollectionStatus='NoFixedDisksReturned'; Error=$null
}
}
foreach ($disk in $disks) {
$valid = $null -ne $disk.Size -and $disk.Size -gt 0 -and
$null -ne $disk.FreeSpace -and $disk.FreeSpace -ge 0 -and
$disk.FreeSpace -le $disk.Size
[pscustomobject]@{
Computer=$computer; ObservedAtUtc=$observedAt
Drive=$disk.DeviceID; SizeBytes=$disk.Size
FreeBytes=$disk.FreeSpace
FreeGiB=$(if ($valid) {
[math]::Round($disk.FreeSpace / 1GB, 2)
} else { $null })
FreePct=$(if ($valid) {
[math]::Round(100 * $disk.FreeSpace / $disk.Size, 1)
} else { $null })
CollectionStatus=$(if ($valid) {
'Collected'
} else { 'MissingCapacity' })
Error=$null
}
}
} catch {
[pscustomobject]@{
Computer=$computer; ObservedAtUtc=$observedAt
Drive=$null; SizeBytes=$null; FreeBytes=$null
FreeGiB=$null; FreePct=$null
CollectionStatus='QueryFailed'; Error=$_.Exception.Message
}
}
}
$report | Format-Table Computer, Drive, FreeGiB, FreePct, CollectionStatusExport objects to CSV, then check the exceptions
Export the stored report objects directly. Do not put Format-Table before Export-Csv: formatting objects are presentation instructions, not your original disk records. The separate display command above does not change the objects saved in $report.
The timestamped filename below is written to your current directory. Choose an approved writable location and retain the report according to your organisation’s policy. NoClobber prevents an existing file with the same name from being overwritten. Error details and host names can reveal infrastructure information, so review them before sharing the CSV outside the team.
After export, review every status other than Collected and compare the set of computers with the input list. One server may contribute several rows. Counting report rows as successful servers exaggerates coverage. CSV export success proves that a file was written; it does not make failed queries successful.
$path = Join-Path (Get-Location).Path (
'disk-space-{0}.csv' -f (Get-Date -Format 'yyyyMMdd-HHmmss')
)
$report | Export-Csv -LiteralPath $path -NoTypeInformation `
-Encoding UTF8 -NoClobber -ErrorAction Stop
$report | Where-Object { $_.CollectionStatus -ne 'Collected' } |
Select-Object Computer, Drive, CollectionStatus, ErrorInterpret the numbers before choosing an action
Consider a fictional 500 GiB volume with 80 GiB free. Free space is 16%, and used space is 420 GiB. If the workload needs a 20 GiB operating reserve, only 60 GiB is available for additional growth. These are illustrative inputs, not results from a measured server.
Percentage is useful for context, but the required write matters. A planned 90 GiB import cannot fit in 80 GiB of free space even if a generic percentage alert has not fired. Quotas and storage-layer limits can further constrain what a workload may write.
To investigate low space, identify the service, owner and growth source first. Use the workload’s supported retention, log-management or expansion process. Do not delete backups, database files or snapshots just because they are large. For ongoing alerts, use history, a workload reserve, response lead time and a defined missing-data condition.
- Design useful disk-space alerts and response thresholds
- Plan storage capacity and expansion lead time
| Illustrative field | Calculation | Result |
|---|---|---|
| Capacity | 500 × 1,073,741,824 | 536,870,912,000 bytes |
| Free percentage | 80 ÷ 500 × 100 | 16% |
| Used space | 500 − 80 | 420 GiB |
| Headroom above reserve | 80 − 20 | 60 GiB |
Troubleshoot missing or misleading results
First establish whether you have a collection problem, an inventory gap or a real capacity problem. Those require different next steps. Preserve the original error and the query scope; changing several settings at once can hide the cause.
A guest filesystem report is only one layer of a virtualised service. It cannot certify spare capacity in the hypervisor datastore, thin-provisioned pool or backup repository. Assign separate checks to the layers that can independently fill.
| Symptom | Likely distinction | Next check |
|---|---|---|
| Get-Volume is unavailable | Shell/platform/module issue | Use a supported Windows host; inspect Get-Command Get-Volume and module availability |
| Access denied or connection failure | Remote collection failed | Check authorised identity, DNS, WSMan and approved firewall rules |
| Expected volume is absent | Wrong inventory or filter | Inspect Get-Volume and expected mount paths; do not rely only on drive letters |
| Blank free percentage | Missing/invalid capacity | Inspect raw Size/FreeSpace and CollectionStatus; never substitute zero |
| CSV contains formatting fields | Formatted output was exported | Export $report objects directly |
| Guest has space but writes fail | Another constraint may apply | Check quotas, workload limits and backing storage |
Turn the snapshot into a repeatable operational check
Keep the approved host list, script revision, collection time and exceptions together. Pilot the query against a representative server and verify the identifiers and byte counts against an independent view of that system. Include an unreachable host and denied-permission case in acceptance checks so the reporting failure path is visible.
If this becomes scheduled work, add failed-run detection, report retention and an accountable recipient before relying on it. A script that runs every hour but silently stops delivering results is not monitoring. When the team needs history and escalation across many systems, evaluate a monitoring platform against those requirements.
My recommendation is to begin with a correct inventory and an honest exception list. Then add thresholds that reflect the workload and the time needed to act. That sequence gives the next administrator a report they can interpret, rather than a green status built from missing data.