Skip to main content

How to Create a Dashboard-Style Summary Screen in Batch Script

Standard Batch output is a scrolling stream of text that can be difficult to read. If you are running a server monitor, a backup manager, or a system audit, you need a dashboard, i.e. a static, organized summary screen that stays in place and updates its values periodically. By using cls for screen clearing, PowerShell for reliable data collection, and structured layout with consistent padding, you can transform a plain Batch window into a readable monitoring display that provides instant visibility into your system's vital signs.

This guide will explain how to design and build a refreshing dashboard in Batch.

Method 1: System Health Dashboard

This dashboard displays CPU load, memory status, disk space, and uptime in a structured layout that refreshes at a configurable interval. All data is collected via PowerShell to avoid the \r corruption and arithmetic limitations of wmic.

@echo off
setlocal EnableDelayedExpansion
title System Health Dashboard - %COMPUTERNAME%
mode con: cols=66 lines=24
color 0B

set "Interval=5"
set "Drive=C:"
set "PSScript=%TEMP%\dashboard_collect.ps1"
set "TempResult=%TEMP%\dashboard_result.tmp"

:: Create PowerShell collection script using a different method
echo $ErrorActionPreference = 'SilentlyContinue' > "%PSScript%"
echo $os = Get-CimInstance Win32_OperatingSystem >> "%PSScript%"
echo $cpu = Get-CimInstance Win32_Processor >> "%PSScript%"
echo $cpuLoad = $cpu.LoadPercentage >> "%PSScript%"
echo if ($null -eq $cpuLoad) { $cpuLoad = 0 } >> "%PSScript%"
echo $ramTotalMB = [math]::Round($os.TotalVisibleMemorySize / 1024) >> "%PSScript%"
echo $ramFreeMB = [math]::Round($os.FreePhysicalMemory / 1024) >> "%PSScript%"
echo $ramUsedMB = $ramTotalMB - $ramFreeMB >> "%PSScript%"
echo $ramPct = [math]::Round((1 - $os.FreePhysicalMemory / $os.TotalVisibleMemorySize) * 100) >> "%PSScript%"
echo $disk = Get-CimInstance Win32_LogicalDisk -Filter "DeviceID='%Drive%'" >> "%PSScript%"
echo $diskFreeGB = [math]::Round($disk.FreeSpace / 1GB, 1) >> "%PSScript%"
echo $diskTotalGB = [math]::Round($disk.Size / 1GB, 1) >> "%PSScript%"
echo $diskUsedGB = [math]::Round(($disk.Size - $disk.FreeSpace) / 1GB, 1) >> "%PSScript%"
echo $diskPct = [math]::Round((1 - $disk.FreeSpace / $disk.Size) * 100) >> "%PSScript%"
echo $uptime = (Get-Date) - $os.LastBootUpTime >> "%PSScript%"
echo $days = $uptime.Days >> "%PSScript%"
echo $hours = $uptime.Hours >> "%PSScript%"
echo $mins = $uptime.Minutes >> "%PSScript%"
echo $uptimeStr = "$days" + "d " + "$hours" + "h " + "$mins" + "m" >> "%PSScript%"
echo $ts = Get-Date -Format 'yyyy-MM-dd HH:mm:ss' >> "%PSScript%"
echo Write-Output "$ts|$cpuLoad|$ramUsedMB|$ramTotalMB|$ramPct|$diskUsedGB|$diskTotalGB|$diskPct|$uptimeStr|$diskFreeGB" >> "%PSScript%"

:DashboardLoop
:: =============================================
:: Gather all data via PowerShell script
:: =============================================
set "TS="
set "CPU=0"
set "RAMUsed=0"
set "RAMTotal=0"
set "RAMPct=0"
set "DiskUsed=0"
set "DiskTotal=0"
set "DiskPct=0"
set "DiskFree=0"
set "Uptime=N/A"

powershell -NoProfile -ExecutionPolicy Bypass -File "%PSScript%" > "%TempResult%" 2>&1

if exist "%TempResult%" (
for /f "usebackq tokens=1-10 delims=|" %%a in ("%TempResult%") do (
set "TS=%%a"
set "CPU=%%b"
set "RAMUsed=%%c"
set "RAMTotal=%%d"
set "RAMPct=%%e"
set "DiskUsed=%%f"
set "DiskTotal=%%g"
set "DiskPct=%%h"
set "Uptime=%%i"
set "DiskFree=%%j"
)
)

:: Handle data collection failure
if not defined TS set "TS=Data collection failed"

:: =============================================
:: Build progress bars (fixed version)
:: =============================================

:: Initialize bars
set "CPUBar=----------"
set "RAMBar=----------"
set "DiskBar=----------"

:: Get integer values safely
set "CPUInt=0"
set "RAMInt=0"
set "DiskInt=0"

:: Extract numbers before decimal point
if defined CPU for /f "tokens=1 delims=." %%a in ("!CPU!") do set "CPUInt=%%a"
if defined RAMPct for /f "tokens=1 delims=." %%a in ("!RAMPct!") do set "RAMInt=%%a"
if defined DiskPct for /f "tokens=1 delims=." %%a in ("!DiskPct!") do set "DiskInt=%%a"

:: Build CPU Bar using simple IF statements
set "CPUBar="
if !CPUInt! GEQ 10 (set "CPUBar=#") else (set "CPUBar=-")
if !CPUInt! GEQ 20 (set "CPUBar=!CPUBar!#") else (set "CPUBar=!CPUBar!-")
if !CPUInt! GEQ 30 (set "CPUBar=!CPUBar!#") else (set "CPUBar=!CPUBar!-")
if !CPUInt! GEQ 40 (set "CPUBar=!CPUBar!#") else (set "CPUBar=!CPUBar!-")
if !CPUInt! GEQ 50 (set "CPUBar=!CPUBar!#") else (set "CPUBar=!CPUBar!-")
if !CPUInt! GEQ 60 (set "CPUBar=!CPUBar!#") else (set "CPUBar=!CPUBar!-")
if !CPUInt! GEQ 70 (set "CPUBar=!CPUBar!#") else (set "CPUBar=!CPUBar!-")
if !CPUInt! GEQ 80 (set "CPUBar=!CPUBar!#") else (set "CPUBar=!CPUBar!-")
if !CPUInt! GEQ 90 (set "CPUBar=!CPUBar!#") else (set "CPUBar=!CPUBar!-")
if !CPUInt! GEQ 100 (set "CPUBar=!CPUBar!#") else (set "CPUBar=!CPUBar!-")

:: Build RAM Bar
set "RAMBar="
if !RAMInt! GEQ 10 (set "RAMBar=#") else (set "RAMBar=-")
if !RAMInt! GEQ 20 (set "RAMBar=!RAMBar!#") else (set "RAMBar=!RAMBar!-")
if !RAMInt! GEQ 30 (set "RAMBar=!RAMBar!#") else (set "RAMBar=!RAMBar!-")
if !RAMInt! GEQ 40 (set "RAMBar=!RAMBar!#") else (set "RAMBar=!RAMBar!-")
if !RAMInt! GEQ 50 (set "RAMBar=!RAMBar!#") else (set "RAMBar=!RAMBar!-")
if !RAMInt! GEQ 60 (set "RAMBar=!RAMBar!#") else (set "RAMBar=!RAMBar!-")
if !RAMInt! GEQ 70 (set "RAMBar=!RAMBar!#") else (set "RAMBar=!RAMBar!-")
if !RAMInt! GEQ 80 (set "RAMBar=!RAMBar!#") else (set "RAMBar=!RAMBar!-")
if !RAMInt! GEQ 90 (set "RAMBar=!RAMBar!#") else (set "RAMBar=!RAMBar!-")
if !RAMInt! GEQ 100 (set "RAMBar=!RAMBar!#") else (set "RAMBar=!RAMBar!-")

:: Build Disk Bar
set "DiskBar="
if !DiskInt! GEQ 10 (set "DiskBar=#") else (set "DiskBar=-")
if !DiskInt! GEQ 20 (set "DiskBar=!DiskBar!#") else (set "DiskBar=!DiskBar!-")
if !DiskInt! GEQ 30 (set "DiskBar=!DiskBar!#") else (set "DiskBar=!DiskBar!-")
if !DiskInt! GEQ 40 (set "DiskBar=!DiskBar!#") else (set "DiskBar=!DiskBar!-")
if !DiskInt! GEQ 50 (set "DiskBar=!DiskBar!#") else (set "DiskBar=!DiskBar!-")
if !DiskInt! GEQ 60 (set "DiskBar=!DiskBar!#") else (set "DiskBar=!DiskBar!-")
if !DiskInt! GEQ 70 (set "DiskBar=!DiskBar!#") else (set "DiskBar=!DiskBar!-")
if !DiskInt! GEQ 80 (set "DiskBar=!DiskBar!#") else (set "DiskBar=!DiskBar!-")
if !DiskInt! GEQ 90 (set "DiskBar=!DiskBar!#") else (set "DiskBar=!DiskBar!-")
if !DiskInt! GEQ 100 (set "DiskBar=!DiskBar!#") else (set "DiskBar=!DiskBar!-")

:: =============================================
:: Draw the screen
:: =============================================
cls
echo.
echo +============================================================+
echo ^| SYSTEM HEALTH DASHBOARD - %COMPUTERNAME%
echo ^| !TS!
echo +============================================================+
echo ^|
echo ^| Computer: %COMPUTERNAME%
echo ^| User: %USERNAME%
echo ^| Uptime: !Uptime!
echo ^|
echo +------------------------------------------------------------+
echo ^| CPU LOAD
echo ^| [!CPUBar!] !CPU!%%
echo +------------------------------------------------------------+
echo ^| MEMORY
echo ^| [!RAMBar!] !RAMPct!%% - !RAMUsed! / !RAMTotal! MB
echo +------------------------------------------------------------+
echo ^| DISK ^(%Drive%^)
echo ^| [!DiskBar!] !DiskPct!%% - !DiskUsed! / !DiskTotal! GB ^(Free: !DiskFree! GB^)
echo +------------------------------------------------------------+
echo ^|
echo ^| STATUS:

:: =============================================
:: Status alerts
:: =============================================
set "AlertShown=0"

if !CPU! GTR 90 (
echo ^| [!!] CPU load is critically high!
set "AlertShown=1"
)

if !RAMPct! GTR 85 (
echo ^| [!!] Memory usage is high!
set "AlertShown=1"
)

if !DiskPct! GTR 90 (
echo ^| [!!] Disk space is critically low!
set "AlertShown=1"
)

if "!AlertShown!"=="0" echo ^| [OK] All systems normal.

echo ^|
echo +============================================================+
echo ^| Refresh: %Interval%s Press Ctrl+C to stop
echo +============================================================+

:: =============================================
:: Wait and repeat
:: =============================================
timeout /t %Interval% >nul
goto :DashboardLoop

:: Cleanup on exit
del "%PSScript%" 2>nul
del "%TempResult%" 2>nul
endlocal
exit /b 0

Design principles:

  • Single PowerShell call for data: Each refresh launches one PowerShell process to collect all metrics. This minimizes the 1–3 second startup overhead that multiple calls would accumulate.
  • Fixed layout positions: Labels (CPU LOAD, MEMORY, DISK) stay in the same screen position on every refresh. Only the values change. This prevents visual jitter.
  • Box-drawing characters: Using +, -, and | creates visual sections that make the dashboard easier to scan at a glance.
  • Timestamp in header: Confirms the dashboard is live and shows exactly when the last data refresh occurred.

Method 2: Multi-Drive Dashboard

For file servers with multiple drives, this dashboard shows the space status of every fixed drive.

@echo off
setlocal EnableDelayedExpansion
title Disk Space Dashboard - %COMPUTERNAME%
mode con: cols=70 lines=20

:DiskLoop
cls
echo ====================================================================
echo DISK SPACE DASHBOARD %date% %time%
echo ====================================================================
echo.
echo Drive Total GB Free GB Used %% Status
echo ----- -------- ------- ------ ------

powershell -NoProfile -Command ^
"Get-CimInstance Win32_LogicalDisk -Filter 'DriveType=3' |" ^
"ForEach-Object {" ^
" $total = [math]::Round($_.Size / 1GB, 1);" ^
" $free = [math]::Round($_.FreeSpace / 1GB, 1);" ^
" $pct = [math]::Round((1 - $_.FreeSpace / $_.Size) * 100);" ^
" $status = if ($pct -gt 90) { '[CRITICAL]' }" ^
" elseif ($pct -gt 75) { '[WARNING] ' }" ^
" else { '[OK] ' };" ^
" $line = ' {0,-8} {1,8} {2,7} {3,5}%% {4}' -f" ^
" $_.DeviceID, $total, $free, $pct, $status;" ^
" Write-Host $line" ^
"}"

echo.
echo ====================================================================
echo Auto-refresh every 10 seconds. Press Ctrl+C to stop.
echo ====================================================================

timeout /t 10 >nul
goto :DiskLoop

Sample display:

====================================================================
DISK SPACE DASHBOARD 2024-05-10 14:32:05
====================================================================

Drive Total GB Free GB Used % Status
----- -------- ------- ------ ------
C: 476.9 234.1 50% [OK]
D: 931.5 45.3 95% [CRITICAL]
E: 1863.0 812.7 56% [OK]

====================================================================
Auto-refresh every 10 seconds. Press Ctrl+C to stop.
====================================================================

Method 3: Network Connectivity Dashboard

A dashboard that monitors connectivity to multiple targets, useful for NOC (Network Operations Center) displays or verifying that key services are reachable.

@echo off
setlocal EnableDelayedExpansion
title Network Status Dashboard
mode con: cols=60 lines=18

set "Target[0]=8.8.8.8|Google DNS"
set "Target[1]=192.168.1.1|Default Gateway"
set "Target[2]=dc01.corp.local|Domain Controller"
set "Target[3]=fileserver|File Server"
set "TargetCount=4"

:NetLoop
cls
echo ===================================================================
echo NETWORK CONNECTIVITY DASHBOARD %date% %time%
echo ===================================================================
echo.
echo Target Status Response
echo ------ ------ --------

for /L %%i in (0, 1, 3) do (
for /f "tokens=1-2 delims=|" %%a in ("!Target[%%i]!") do (
set "Host=%%a"
set "Label=%%b "
set "Label=!Label:~0,25!"
set "Status=OFFLINE"
set "RTT=N/A"

for /f "tokens=6 delims== " %%r in (
'ping -n 1 -w 2000 %%a 2^>nul ^| findstr /i "time="'
) do (
set "Status=ONLINE "
set "RTT=%%r"
)

set "RTT=!RTT:ms=! ms"
echo !Label!!Status! !RTT!
)
)

echo.
echo ===================================================================
echo Refresh: 10s ^| Press Ctrl+C to stop
echo ===================================================================

timeout /t 10 >nul
goto :NetLoop

Note on alignment:

The spacing in the echo statements is intentionally padded to keep columns aligned. In a production dashboard, you might generate the display lines in PowerShell using format strings ('{0,-25} {1,-12} {2}' -f $label, $status, $rtt) for precise column control.

How to Avoid Common Errors

Problem: Screen Flicker from Rapid Refresh

A refresh interval below 3 seconds causes constant screen flicker from cls, making the dashboard physically unpleasant to watch and impossible to read.

Solution: Use a 5–10 second refresh interval. This provides sufficient update frequency for monitoring while remaining visually stable. The PowerShell data collection itself takes 1–3 seconds, so a 5-second interval produces updates roughly every 6–8 seconds in practice.

Problem: mode con Fails on Some Terminals

The mode con: cols=X lines=Y command may fail in certain terminal emulators (Windows Terminal custom profiles, VS Code integrated terminal, remote sessions) where the window size is controlled by the host application.

Solution: Set sensible defaults (60–80 columns, 20–25 lines) that fit most displays. Consider wrapping the mode command in an error-suppressing pattern:

mode con: cols=64 lines=22 2>nul

Problem: Data Collection Failure Leaves Variables Empty

If the PowerShell call fails (e.g., WMI service unavailable, PowerShell restricted), the variables from the previous iteration remain, showing stale data.

Solution: Initialize or clear critical variables before each data collection pass, and check for empty results:

set "CPU="
set "RAMPct="
:: ... (data collection) ...
if not defined CPU set "CPU=ERR"

Problem: Misaligned Columns

When displayed values have different digit counts (e.g., 5% vs. 95%), columns shift unless padding is applied.

Solution: For simple dashboards, add manual spaces to keep rough alignment. For precise control, generate the display lines in PowerShell using format strings with fixed-width fields:

'{0,-20} {1,6}%% {2,8} MB' -f 'Memory Used', $pct, $free

Wrong Way: Using wmic for Dashboard Data

:: BROKEN: wmic output contains \r characters that corrupt displayed values
for /f "skip=1" %%a in ('wmic cpu get loadpercentage') do set "cpu=%%a"

The \r characters appear as invisible garbage in the dashboard display, causing values to overwrite parts of the layout or display incorrectly.

Correct Way: Use Get-CimInstance in PowerShell, which returns clean typed data.

Best Practices and Rules

1. Minimize PowerShell Invocations

Each PowerShell process launch takes 1–3 seconds. Collect all data in a single PowerShell call (as shown in Method 1) rather than making separate calls for CPU, RAM, and disk. This keeps the refresh fast.

2. Keep Labels Fixed, Change Only Values

Labels (CPU LOAD, MEMORY, DISK) should be static text that never changes position. Only the numeric values should update. This prevents the visual "jumping" that makes dashboards hard to read.

3. Include a Timestamp

Always show the last refresh time in the header. Without it, the user cannot tell whether the dashboard is live or frozen.

4. Add Threshold Alerts

A dashboard that shows numbers without context requires the user to remember what's "normal." Add color-coded or text-labeled status indicators ([OK], [WARNING], [CRITICAL]) based on configurable thresholds.

5. Set the Window Title

Use title Dashboard - %COMPUTERNAME% so the dashboard is identifiable from the taskbar when running alongside other terminal windows.

6. Use color Sparingly

The color command changes the entire window's foreground and background. You cannot color individual lines in standard Batch without VT100 escape sequences (Windows 10+). Changing the whole window to red for a critical alert is effective; constantly changing colors between sections is distracting.

Conclusions

Building a dashboard-style summary screen transforms raw monitoring data into an organized, at-a-glance display. By combining cls for clean refreshes, PowerShell for reliable data collection, structured layouts for readability, and threshold-based alerting for actionable visibility, you create a practical monitoring tool from a simple Batch script. While not a replacement for professional monitoring software, a Batch dashboard provides immediate value for quick health checks, NOC displays, and lightweight server monitoring.