How to Get Disk Usage Percentage in Batch Script
Monitoring how full your drives are is one of the most fundamental system administration tasks. When a drive reaches 100%, applications crash, databases corrupt, and Windows itself can become unbootable. Instead of manually checking each drive in Explorer, a Batch script can calculate the usage percentage by comparing the total size of each volume against its free space. This allows you to build automated alerts that trigger a warning when any drive passes a critical threshold, like 80% or 90%, giving you time to clean up or expand before disaster strikes.
This guide will explain how to calculate and monitor drive usage percentages.
Method 1: Disk Usage Report for All Drives
This method calculates the usage percentage for every fixed local drive, displays a formatted report with status indicators, and provides a fleet-ready summary.
Implementation
@echo off
setlocal
echo [INFO] Disk usage report for %COMPUTERNAME%:
echo --------------------------------------------------
powershell -NoProfile -Command ^
"$disks = Get-CimInstance Win32_LogicalDisk -Filter 'DriveType=3' |" ^
" Where-Object { $_.Size -gt 0 };" ^
"if (-not $disks) {" ^
" Write-Host 'No fixed drives found.';" ^
" exit 0" ^
"};" ^
"$disks | ForEach-Object {" ^
" $totalGB = [math]::Round($_.Size / 1GB, 1);" ^
" $freeGB = [math]::Round($_.FreeSpace / 1GB, 1);" ^
" $usedGB = [math]::Round(($_.Size - $_.FreeSpace) / 1GB, 1);" ^
" $pct = [math]::Round(($_.Size - $_.FreeSpace) / $_.Size * 100, 1);" ^
" $status = switch ($true) {" ^
" ($pct -ge 95) { '[CRITICAL]' }" ^
" ($pct -ge 90) { '[WARNING] ' }" ^
" ($pct -ge 80) { '[CAUTION] ' }" ^
" default { '[OK] ' }" ^
" };" ^
" $label = if ($_.VolumeName) { $_.VolumeName } else { '(none)' };" ^
" [PSCustomObject]@{" ^
" Drive = $_.DeviceID;" ^
" Label = $label;" ^
" 'Total GB' = $totalGB;" ^
" 'Used GB' = $usedGB;" ^
" 'Free GB' = $freeGB;" ^
" 'Used %%' = $pct;" ^
" Status = $status" ^
" }" ^
"} | Format-Table -AutoSize"
echo --------------------------------------------------
endlocal
exit /b 0
Sample output:
Drive Label Total GB Used GB Free GB Used % Status
----- ----- -------- ------- ------- ------ ------
C: Windows 476.9 242.8 234.1 50.9 [OK]
D: Data 931.5 886.2 45.3 95.1 [CRITICAL]
E: Backups 465.8 53.7 412.1 11.5 [OK]
Why Get-CimInstance instead of Get-WmiObject:
Get-WmiObjectis deprecated in PowerShell 5.1+ and removed in PowerShell 7+.Get-CimInstanceis the modern replacement.- Both query the same WMI class (
Win32_LogicalDisk), butGet-CimInstanceuses the more efficient CIM session protocol. DriveType=3filters for fixed local disks, excluding CD-ROMs (type 5), network drives (type 4), and removable media (type 2).
Why the Size -gt 0 filter:
Some system volumes (like the EFI System Partition) may appear in Win32_LogicalDisk with a size of zero or with no drive letter. Filtering by Size -gt 0 excludes these to prevent division-by-zero errors and false alarms.
Method 2: Threshold Alert Monitor
Checks all drives against configurable thresholds and generates alerts for any drive exceeding the limit. Suitable for scheduled tasks and monitoring integration.
@echo off
setlocal
set "WarnPct=80"
set "CritPct=90"
set "LogFile=%~dp0disk_usage.log"
echo [INFO] Checking disk usage against thresholds...
for /f "tokens=1-4 delims=|" %%a in (
'powershell -NoProfile -Command ^
"$disks = Get-CimInstance Win32_LogicalDisk -Filter ''DriveType=3'' | Where-Object { $_.Size -gt 0 };" ^
"$total = @($disks).Count;" ^
"$warnings = 0; $criticals = 0; $alertDetails = @();" ^
"foreach ($d in $disks) {" ^
" $pct = [math]::Round(($d.Size - $d.FreeSpace) / $d.Size * 100, 1);" ^
" $freeGB = [math]::Round($d.FreeSpace / 1GB, 1);" ^
" if ($pct -ge %CritPct%) {" ^
" $criticals++;" ^
" $alertDetails += ('CRITICAL: ' + $d.DeviceID + ' at ' + $pct + '%% (' + $freeGB + ' GB free)');" ^
" } elseif ($pct -ge %WarnPct%) {" ^
" $warnings++;" ^
" $alertDetails += ('WARNING: ' + $d.DeviceID + ' at ' + $pct + '%% (' + $freeGB + ' GB free)');" ^
" }" ^
"};" ^
"$alertStr = $alertDetails -join '|';" ^
"Write-Output ($total.ToString() + '|' + $warnings.ToString() + '|' + $criticals.ToString() + '|' + $alertStr);"'
) do (
set "TotalDrives=%%a"
set "Warnings=%%b"
set "Criticals=%%c"
set "AlertDetails=%%d"
)
:: for /f does not propagate the inner command's exit code if it returns text.
:: We must calculate the status manually from the parsed variables.
set "PSResult=0"
if "%Warnings%"=="" set "Warnings=0"
if "%Criticals%"=="" set "Criticals=0"
if %Warnings% gtr 0 set "PSResult=1"
if %Criticals% gtr 0 set "PSResult=2"
:: Generate timestamp
for /f "delims=" %%d in ('powershell -NoProfile -Command "Get-Date -Format yyyy-MM-dd"') do set "_date=%%d"
for /f "delims=" %%t in ('powershell -NoProfile -Command "Get-Date -Format HH:mm:ss"') do set "_time=%%t"
set "Timestamp=%_date% %_time%"
echo [%Timestamp%] Checked %TotalDrives% drive(s): %Warnings% warning(s), %Criticals% critical
if %PSResult% equ 0 (
echo [OK] All drives are below %WarnPct%%% usage.
echo [%Timestamp%] OK: All %TotalDrives% drives below %WarnPct%%% on %COMPUTERNAME% >> "%LogFile%"
endlocal
exit /b 0
)
echo.
:: Display each alert
if defined AlertDetails (
powershell -NoProfile -Command "$('%AlertDetails%'.Split('|')) | ForEach-Object { Write-Host (' ' + $_) }"
)
echo.
echo [%Timestamp%] ALERT on %COMPUTERNAME%: %Warnings% warning(s), %Criticals% critical >> "%LogFile%"
:: Write to Event Log for monitoring tool visibility
if %PSResult% equ 2 (
eventcreate /T ERROR /ID 930 /L APPLICATION /SO "DiskUsageMonitor" /D "CRITICAL: %Criticals% drive(s) above %CritPct%%% usage on %COMPUTERNAME%" >nul 2>&1
) else (
eventcreate /T WARNING /ID 931 /L APPLICATION /SO "DiskUsageMonitor" /D "WARNING: %Warnings% drive(s) above %WarnPct%%% usage on %COMPUTERNAME%" >nul 2>&1
)
endlocal
exit /b %PSResult%
Exit codes for automation:
| Exit Code | Meaning | Recommended Action |
|---|---|---|
0 | All drives below warning threshold | No action needed |
1 | At least one drive above warning threshold | Plan cleanup or expansion |
2 | At least one drive above critical threshold | Immediate cleanup required |
Recommended threshold tiers:
| Usage Level | Threshold | Severity | Typical Response |
|---|---|---|---|
| 80% | Warning | Low | Review and plan cleanup |
| 90% | Warning | Medium | Begin active cleanup or expansion |
| 95% | Critical | High | Emergency cleanup; service disruption imminent |
| 98%+ | Critical | Emergency | Applications may already be failing |
The EFI System Partition (~100 MB) and Recovery Partition (~500 MB) are often near 100% usage by design. The DriveType=3 filter in these scripts excludes most hidden system partitions, but some configurations may include them. If you get false alarms from very small partitions, add a minimum size filter:
Where-Object { $_.Size -gt 1GB }
Method 3: Disk Usage Dashboard
A continuously updating display of all drive usage, useful for NOC screens, server monitoring sessions, or keeping an eye on drives during large file operations.
@echo off
setlocal
set "Interval=30"
set "WarnPct=80"
title Disk Usage Dashboard - %COMPUTERNAME%
echo [INFO] Dashboard refreshes every %Interval% seconds. Press Ctrl+C to stop.
:DashLoop
cls
echo ============================================================
echo DISK USAGE DASHBOARD - %COMPUTERNAME%
echo ============================================================
powershell -NoProfile -Command ^
"$ts = Get-Date -Format 'yyyy-MM-dd HH:mm:ss';" ^
"Write-Host \" Updated: $ts`n\";" ^
"Get-CimInstance Win32_LogicalDisk -Filter 'DriveType=3' |" ^
" Where-Object { $_.Size -gt 0 } |" ^
" ForEach-Object {" ^
" $pct = [math]::Round(($_.Size - $_.FreeSpace) / $_.Size * 100);" ^
" $freeGB = [math]::Round($_.FreeSpace / 1GB, 1);" ^
" $totalGB = [math]::Round($_.Size / 1GB, 1);" ^
" $barLen = [math]::Floor($pct / 5);" ^
" $bar = ('#' * $barLen).PadRight(20);" ^
" $status = if ($pct -ge 90) { '!!!' } elseif ($pct -ge %WarnPct%) { '! ' } else { ' ' };" ^
" Write-Host (' {0} [{1}] {2,3}%% {3,7} GB free {4}' -f $_.DeviceID, $bar, $pct, $freeGB, $status)" ^
" }"
echo.
echo ============================================================
echo Refresh: %Interval%s ^| !!! = Critical ! = Warning
echo ============================================================
timeout /t %Interval% >nul
goto :DashLoop
Sample display:
============================================================
DISK USAGE DASHBOARD - SERVER-DB01
============================================================
Updated: 2024-05-10 14:32:05
C: [########## ] 51% 234.1 GB free
D: [###################.] 95% 45.3 GB free !!!
E: [## ] 12% 412.1 GB free
L: [###### ] 32% 638.7 GB free
============================================================
Refresh: 30s | !!! = Critical ! = Warning
============================================================
Why a visual bar:
Numbers alone require mental comparison. A visual bar immediately shows relative usage, a drive at 95% has a nearly full bar, while a drive at 12% has a short one. Combined with the !!! warning indicator, critical drives are visible at a glance across a room.
Method 4: Fleet-Wide Usage CSV
For auditing disk usage across multiple machines, essential for identifying which servers need storage expansion before they run out.
@echo off
setlocal
set "CSVFile=\\Server\Audit\disk_usage.csv"
if not exist "%CSVFile%" (
echo "Timestamp","Computer","Drive","Label","TotalGB","UsedGB","FreeGB","UsedPct","Status" > "%CSVFile%" 2>nul
)
powershell -NoProfile -Command ^
"$ts = Get-Date -Format 'yyyy-MM-dd HH:mm:ss';" ^
"Get-CimInstance Win32_LogicalDisk -Filter 'DriveType=3' |" ^
" Where-Object { $_.Size -gt 0 } |" ^
" ForEach-Object {" ^
" $totalGB = [math]::Round($_.Size / 1GB, 1);" ^
" $freeGB = [math]::Round($_.FreeSpace / 1GB, 1);" ^
" $usedGB = [math]::Round(($_.Size - $_.FreeSpace) / 1GB, 1);" ^
" $pct = [math]::Round(($_.Size - $_.FreeSpace) / $_.Size * 100, 1);" ^
" $status = if ($pct -ge 90) { 'Critical' } elseif ($pct -ge 80) { 'Warning' } else { 'OK' };" ^
" $label = if ($_.VolumeName) { $_.VolumeName } else { 'none' };" ^
" Write-Output ('\"' + $ts + '\",\"' + $env:COMPUTERNAME + '\",\"' + $_.DeviceID + '\",\"' + $label + '\",\"' + $totalGB + '\",\"' + $usedGB + '\",\"' + $freeGB + '\",\"' + $pct + '\",\"' + $status + '\"')" ^
" }" >> "%CSVFile%" 2>nul
echo [OK] Disk usage data exported for %COMPUTERNAME%.
endlocal
exit /b 0
What to look for in the fleet CSV:
- Sort by
UsedPctdescending: The drives closest to full need attention first. - Drives consistently above 90%: Need immediate expansion or cleanup.
- Usage trending upward over weekly scans: Estimate when the drive will hit 100% at the current growth rate and plan accordingly.
- Drives at 80%+ on servers with no cleanup automation: Good candidates for automated log rotation, temp file cleanup, or archive compression.
How to Avoid Common Errors
Wrong Way: Checking Only the C: Drive
:: INCOMPLETE: misses data drives that may be even more critical
powershell -Command "(Get-CimInstance Win32_LogicalDisk -Filter 'DeviceID=''C:''').FreeSpace"
Many applications store data on secondary drives (D:, E:). Database servers, file servers, and backup targets often fill data drives faster than the OS drive.
Correct Way: Always scan ALL fixed drives using DriveType=3. All methods in this guide check every local fixed disk.
Wrong Way: Using Batch Arithmetic for Disk Space
:: BROKEN: set /a overflows at ~2 GB (2,147,483,647)
set /a "pct=(%used% * 100) / %total%"
Modern disks have sizes in the hundreds of billions of bytes. Batch's 32-bit integer arithmetic overflows silently, producing incorrect percentages.
Correct Way: Delegate all size calculations to PowerShell, which handles 64-bit integers natively.
Wrong Way: Using wmic for Disk Space Queries
:: DEPRECATED:wmic output contains \r characters that corrupt parsing
wmic logicaldisk where drivetype=3 get name, size, freespace
Correct Way: Use Get-CimInstance Win32_LogicalDisk in PowerShell for clean, typed data.
Problem: False Alarms from System Partitions
Small system partitions (EFI ~100 MB, Recovery ~500 MB) are often near 100% by design. Including them in threshold alerts generates false alarms.
Solution: Filter by minimum size ($_.Size -gt 1GB) or use DriveType=3 which typically excludes hidden system partitions. If false alarms persist, add the size filter explicitly.
Best Practices and Rules
1. Use Tiered Thresholds
A single threshold is too blunt. Use multiple tiers for graduated response:
- 80%: Informational - review and plan.
- 90%: Warning - begin active cleanup.
- 95%: Critical - immediate action required.
2. Monitor All Fixed Drives
Don't just check C:. Data drives, log volumes, and backup targets are often the first to fill up, and their failure impacts applications directly.
3. Schedule Daily Checks
Run Method 2 as a daily scheduled task. Drives can fill unexpectedly from log growth, failed cleanup jobs, or runaway temporary files. Daily checks catch problems before they become emergencies.
4. Track Trends, Not Just Snapshots
A single reading of "78% used" is informational. Three weekly readings showing 72% → 78% → 84% reveal a trend that will hit critical within weeks. Use Method 4's fleet CSV for trend analysis.
5. Write Critical Alerts to the Event Log
Text log files require someone to actively check them. Writing critical disk usage alerts to the Event Log (Method 2) makes them visible to enterprise monitoring tools that can trigger automated notifications.
6. Consider Percentage AND Absolute Free Space
A 10 TB drive at 95% still has 500 GB free, plenty for most workloads. A 100 GB drive at 85% has only 15 GB free, much more urgent. Consider alerting on both percentage AND minimum free space thresholds for the most accurate assessment.
For large drives, a percentage threshold alone can be misleading. Consider a dual-threshold approach:
# Alert if usage > 90% OR free space < 10 GB (whichever triggers first)
if ($pct -gt 90 -or $freeGB -lt 10) { "ALERT" }
This catches both large drives slowly filling up and small drives running critically low.
Conclusions
Calculating disk usage percentage transforms reactive storage management into a proactive discipline. By monitoring all fixed drives with tiered threshold alerts, visual dashboards, and fleet-wide CSV tracking, you gain the early warning needed to prevent service outages caused by full disks. This is one of the simplest yet most impactful monitoring scripts you can deploy, the difference between a planned cleanup and an emergency recovery at 3 AM.