Skip to main content

How to Get the Total and Used Space for All Drives in Batch Script

A comprehensive storage report is the cornerstone of good system administration. Rather than clicking on each drive in Explorer to check Properties, a Batch script can generate a full report of total size, used space, free space, and usage percentage for every drive on the computer in one pass. This information is critical for capacity planning, alerting when drives are near full, and generating daily health reports.

This guide will explain how to build a complete storage report.

Method 1: Formatted Storage Dashboard

This method displays a clean, human-readable table of all fixed local drives with sizes in GB, usage percentage, and status indicators.

Implementation

@echo off
setlocal

echo [INFO] Storage capacity 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] ' }" ^
" };" ^
" [PSCustomObject]@{" ^
" Drive = $_.DeviceID;" ^
" Label = if ($_.VolumeName) { $_.VolumeName } else { '(none)' };" ^
" 'Total GB' = $totalGB;" ^
" 'Used GB' = $usedGB;" ^
" 'Free GB' = $freeGB;" ^
" 'Used %%' = $pct;" ^
" Status = $status" ^
" }" ^
"} | Format-Table -AutoSize;" ^
"$totalSpace = [math]::Round(($disks | Measure-Object Size -Sum).Sum / 1GB, 1);" ^
"$totalFree = [math]::Round(($disks | Measure-Object FreeSpace -Sum).Sum / 1GB, 1);" ^
"$totalUsed = $totalSpace - $totalFree;" ^
"Write-Host \" Totals: $totalSpace GB capacity, $totalUsed GB used, $totalFree GB free\""

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]

Totals: 1874.2 GB capacity, 1182.7 GB used, 691.5 GB free

Why Get-CimInstance instead of Get-WmiObject:

  • Get-WmiObject is deprecated in PowerShell 5.1+ and removed in PowerShell 7+.
  • Get-CimInstance uses the modern CIM session protocol and returns the same Win32_LogicalDisk data.
  • DriveType=3 filters for fixed local disks, excluding CD-ROMs (5), network drives (4), and removable media (2).

Why the Size -gt 0 filter:

Some system volumes (EFI System Partition, Recovery) may appear in Win32_LogicalDisk with zero size. Filtering prevents division-by-zero errors and false alarms from system partitions.

Method 2: Visual Dashboard with Usage Bars

For at-a-glance monitoring, useful for NOC displays or quick server health checks.

@echo off
setlocal

echo ============================================================
echo STORAGE DASHBOARD - %COMPUTERNAME% %date% %time%
echo ============================================================
echo.

powershell -NoProfile -Command ^
"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);" ^
" $warn = if ($pct -ge 90) { '!!!' } elseif ($pct -ge 80) { '! ' } else { ' ' };" ^
" $label = if ($_.VolumeName) { $_.VolumeName.PadRight(12).Substring(0,12) } else { '(none) ' };" ^
" Write-Host (' {0} {1} [{2}] {3,3}%% {4,8} GB free {5}' -f $_.DeviceID, $label, $bar, $pct, $freeGB, $warn)" ^
" }"

echo.
echo ============================================================
echo( !!! = Critical (90+%%) ! = Warning (80+%%)
echo ============================================================
echo.

endlocal
exit /b 0

Sample display:

============================================================
STORAGE DASHBOARD - SERVER-DB01 Fri 05/10/2024 14:32:05
============================================================

C: Windows [########## ] 51% 234.1 GB free
D: Data [###################.] 95% 45.3 GB free !!!
E: Backups [## ] 12% 412.1 GB free
L: Logs [###### ] 32% 638.7 GB free

============================================================
!!! = Critical (90%+) ! = Warning (80%+)
============================================================
Refreshing Dashboard

To create a continuously updating display (like top on Linux), wrap this in a loop:

:Loop
cls
call storage_dashboard.bat
timeout /t 30 >nul
goto :Loop

Method 3: Daily Report with Timestamped Export

Generates a timestamped report file for tracking storage trends over days and weeks.

@echo off
setlocal

set "LogDir=%~dp0StorageReports"

:: Generate locale-independent timestamp
for /f "delims=" %%t in (
'powershell -NoProfile -Command "Get-Date -Format ''yyyyMMdd''"'
) do set "DateStamp=%%t"

set "ReportFile=%LogDir%\storage_%COMPUTERNAME%_%DateStamp%.txt"

if not exist "%LogDir%\" mkdir "%LogDir%"

echo [INFO] Generating daily storage report...

:: Report header
(
echo ==================================================
echo DAILY STORAGE REPORT
echo ==================================================
) > "%ReportFile%"

for /f "delims=" %%t in (
'powershell -NoProfile -Command "Get-Date -Format ''yyyy-MM-dd HH:mm:ss''"'
) do echo Generated: %%t >> "%ReportFile%"

echo Computer: %COMPUTERNAME% >> "%ReportFile%"
echo. >> "%ReportFile%"

:: Storage data
powershell -NoProfile -Command ^
"$disks = Get-CimInstance Win32_LogicalDisk -Filter 'DriveType=3' |" ^
" Where-Object { $_.Size -gt 0 };" ^
"$disks | ForEach-Object {" ^
" $totalGB = [math]::Round($_.Size / 1GB, 1);" ^
" $freeGB = [math]::Round($_.FreeSpace / 1GB, 1);" ^
" $usedGB = $totalGB - $freeGB;" ^
" $pct = [math]::Round(($_.Size - $_.FreeSpace) / $_.Size * 100, 1);" ^
" $status = if ($pct -ge 90) { 'CRITICAL' } elseif ($pct -ge 80) { 'WARNING' } else { 'OK' };" ^
" Write-Output ('{0,-5} {1,-15} Total: {2,8} GB Used: {3,8} GB Free: {4,8} GB [{5}%%] {6}' -f" ^
" $_.DeviceID," ^
" $(if ($_.VolumeName) { $_.VolumeName } else { '(none)' })," ^
" $totalGB, $usedGB, $freeGB, $pct, $status)" ^
"};" ^
"Write-Output '';" ^
"$totalCap = [math]::Round(($disks | Measure-Object Size -Sum).Sum / 1GB, 1);" ^
"$totalFree = [math]::Round(($disks | Measure-Object FreeSpace -Sum).Sum / 1GB, 1);" ^
"Write-Output \"Total capacity: $totalCap GB Total free: $totalFree GB\"" >> "%ReportFile%"

echo. >> "%ReportFile%"
echo ================================================== >> "%ReportFile%"

echo [OK] Report saved to: %ReportFile%

:: Optional: clean up reports older than 90 days
forfiles /P "%LogDir%" /M "storage_*.txt" /D -90 /C "cmd /c del @path" 2>nul

endlocal
exit /b 0

Scheduling for trend analysis:

Run this as a daily scheduled task (e.g., 7:00 AM). Over weeks, the reports show:

  • Which drives are growing fastest (capacity planning)
  • Whether cleanup automation is working (space should stabilize or decrease periodically)
  • Seasonal patterns (end-of-month processing, quarterly reporting, annual archiving)

Why locale-independent timestamps:

Using %date:~-4%%date:~3,2%%date:~0,2% assumes a specific locale date format. On non-US systems, the substring positions extract wrong characters, producing garbled filenames. PowerShell's Get-Date -Format 'yyyyMMdd' produces a consistent format regardless of locale.

Method 4: Fleet-Wide Storage CSV

For monitoring storage across multiple machines, export one row per drive per machine to a shared CSV.

@echo off
setlocal

set "CSVFile=\\Server\Audit\storage_fleet.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] Storage data exported for %COMPUTERNAME%.

endlocal
exit /b 0

What to look for in the fleet CSV:

  • Sort by UsedPct descending: Drives closest to full need attention first.
  • Filter by Status = Critical: These drives need immediate cleanup or expansion.
  • Compare weekly snapshots: Growth rate tells you when drives will hit capacity at the current pace.
  • Drives at 80%+ without cleanup automation: Good candidates for automated log rotation, temp cleanup, or archive compression.

How to Avoid Common Errors

Wrong Way: Using Batch Arithmetic for Disk Sizes

:: BROKEN: overflows at ~2 GB (2,147,483,647 bytes)
set /a "totalGB=%Size% / 1073741824"

Modern drives have sizes in the hundreds of billions or trillions of bytes. Batch's 32-bit set /a overflows silently, producing wildly incorrect results.

Correct Way: Use PowerShell for all size calculations. It handles 64-bit integers natively.

Wrong Way: Using wmic logicaldisk for Reports

:: DEPRECATED:wmic output contains \r characters and raw byte values
wmic logicaldisk where drivetype=3 get name, size, freespace

wmic outputs raw byte values (e.g., 511999942656 for ~477 GB) that require manual conversion, and the output contains \r characters that corrupt any automated parsing.

Correct Way: Use Get-CimInstance Win32_LogicalDisk with PowerShell calculations for human-readable GB values.

Wrong Way: Using Get-WmiObject

# DEPRECATED - removed in PowerShell 7+
Get-WmiObject Win32_LogicalDisk

Correct Way: Use Get-CimInstance Win32_LogicalDisk that is the modern replacement with identical functionality.

Problem: Network and Removable Drives in the Report

Without filtering, the report includes network-mapped drives (which show misleading or zero sizes) and removable media (which may not be present consistently).

Solution: All methods in this guide filter with DriveType=3 (fixed local disks only). To include other types, adjust the filter or remove it, but be aware of the implications.

Including Other Drive Types

If you need to report on removable drives (type 2) or network drives (type 4) in addition to fixed disks, modify the filter:

# Fixed + Removable
Get-CimInstance Win32_LogicalDisk -Filter 'DriveType=3 OR DriveType=2'

# All drives with valid sizes
Get-CimInstance Win32_LogicalDisk | Where-Object { $_.Size -gt 0 }

Network drives (type 4) may report zero or incorrect sizes depending on the server configuration.

Best Practices and Rules

1. Include Computer Name in All Reports

For multi-machine environments, always include %COMPUTERNAME% in the output. Without it, a report emailed from a server is indistinguishable from one sent by a workstation.

2. Show Totals

Individual drive statistics are useful, but the total capacity and total free space across all drives (shown in Method 1) provides the overall storage health at a glance.

3. Use Status Indicators

Raw numbers require mental comparison. Status indicators ([OK], [WARNING], [CRITICAL]) or visual bars (Method 2) make problems immediately visible without reading every number.

4. Schedule Daily Reports

Run Method 3 as a daily scheduled task. Over time, these reports reveal growth trends that single-point checks cannot. A drive growing 2 GB per day will be full in 30 days, daily reports catch this.

5. Automate Cleanup Alongside Monitoring

Detection without action is incomplete. When monitoring identifies drives at 80%+, automated cleanup (log rotation, temp file deletion, archive compression) should already be in place. See our guides on rotating log files and compressing old logs.

6. Clean Up Old Reports

Method 3 includes a forfiles command that deletes reports older than 90 days. Without cleanup, daily reports accumulate and eventually consume significant space themselves.

Conclusions

Getting the total and used space for all drives provides the visibility foundation for every aspect of storage management. By generating formatted dashboards with status indicators, visual usage bars, timestamped daily reports, and fleet-wide CSV exports, you ensure that capacity issues are detected early and that your infrastructure never runs out of space unexpectedly. This is one of the most fundamental and frequently used monitoring scripts in any administrator's toolkit.