How to Generate an HTML Report from a Batch Script
Outputting script results to a plain text file or a CSV is fine for the administrator, but it's not ideal for managers or clients who need a readable summary of system health. By using simple redirection, a Batch script can generate an HTML file with colors (green for success, red for errors), tables, and formatting that make it easy to see the status of a backup, a system audit, or a software deployment at a glance.
This guide will explain how to build an HTML document line-by-line using Batch.
Method 1: Creating a Dynamic HTML Report
The core technique is straightforward: echo HTML tags into a file, inserting your Batch variables for the dynamic data. The first write uses > to create (or overwrite) the file, and subsequent writes use >> to append.
Implementation
@echo off
setlocal EnableDelayedExpansion
set "ReportFile=%~dp0summary.html"
:: =============================================
:: 1. HTML Header (structure, CSS, opening tags)
:: =============================================
:: Use > on the first line to create/overwrite the file
:: Use >> on all subsequent lines to append
(
echo ^<!DOCTYPE html^>
echo ^<html^>^<head^>
echo ^<meta charset="utf-8"^>
echo ^<title^>System Status Report^</title^>
echo ^<style^>
echo body { font-family: Segoe UI, sans-serif; background: #f4f4f4; padding: 20px; }
echo .card { background: white; padding: 20px; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1^); max-width: 800px; margin: auto; }
echo table { border-collapse: collapse; width: 100%%; margin-top: 15px; }
echo th, td { border: 1px solid #ddd; padding: 8px; text-align: left; }
echo th { background: #4a90d9; color: white; }
echo .ok { color: green; font-weight: bold; }
echo .err { color: red; font-weight: bold; }
echo .warn { color: orange; font-weight: bold; }
echo ^</style^>
echo ^</head^>^<body^>
echo ^<div class="card"^>
echo ^<h1^>System Status Report^</h1^>
echo ^<p^>Generated: ^<b^>%date% %time%^</b^> on ^<b^>%COMPUTERNAME%^</b^>^</p^>
) > "%ReportFile%"
:: =============================================
:: 2. Run checks and build the status table
:: =============================================
echo ^<table^> >> "%ReportFile%"
echo ^<tr^>^<th^>Check^</th^>^<th^>Result^</th^>^<th^>Status^</th^>^</tr^> >> "%ReportFile%"
:: Check 1: Admin privileges
net session >nul 2>&1
if not errorlevel 1 (
set "val=Administrator"
set "css=ok"
) else (
set "val=Standard User"
set "css=warn"
)
echo ^<tr^>^<td^>Permission Level^</td^>^<td^>!val!^</td^>^<td class="!css!"^>!css!^</td^>^</tr^> >> "%ReportFile%"
:: Check 2: Free disk space on the system drive
for /f "delims=" %%a in (
'powershell -NoProfile -Command ^
"$d = Get-PSDrive -Name '%SystemDrive:~0,1%' -ErrorAction SilentlyContinue;" ^
"if ($d) { [math]::Floor($d.Free / 1GB) } else { Write-Output 'N/A' }"'
) do set "FreeGB=%%a"
if "%FreeGB%"=="N/A" (
set "css=err"
) else (
set "css=ok"
if %FreeGB% LSS 10 set "css=err"
if %FreeGB% LSS 20 if not "!css!"=="err" set "css=warn"
)
echo ^<tr^>^<td^>%SystemDrive% Free Space^</td^>^<td^>%FreeGB% GB^</td^>^<td class="!css!"^>!css!^</td^>^</tr^> >> "%ReportFile%"
:: Check 3: Connectivity test
ping -n 1 -w 2000 8.8.8.8 >nul 2>&1
if not errorlevel 1 (
set "val=Reachable"
set "css=ok"
) else (
set "val=Unreachable"
set "css=err"
)
echo ^<tr^>^<td^>Internet Connectivity^</td^>^<td^>!val!^</td^>^<td class="!css!"^>!css!^</td^>^</tr^> >> "%ReportFile%"
:: Close the table
echo ^</table^> >> "%ReportFile%"
:: =============================================
:: 3. HTML Footer (close all tags)
:: =============================================
(
echo ^</div^>
echo ^</body^>^</html^>
) >> "%ReportFile%"
echo [OK] Report generated: %ReportFile%
start "" "%ReportFile%"
endlocal
exit /b 0
Key techniques:
Escaping HTML characters: The < and > characters are redirection operators in Batch. To output them as literal text, prefix each with a caret: ^<h1^> produces <h1> in the file.
Percent signs in CSS: The % character is a variable marker in Batch. To output a literal % (e.g., for width: 100%), double it: 100%% produces 100% in the file.
Parenthesized blocks for multi-line writes: Wrapping multiple echo lines in ( ... ) > file or ( ... ) >> file writes them all in a single file operation, which is faster and cleaner than appending line by line. Use > for the first block (to create/overwrite) and >> for subsequent blocks.
Delayed expansion for variables set in loops: Variables set inside if blocks (like val and css) need !var! syntax with EnableDelayedExpansion because %var% would be expanded at parse time, before the if block executes.
Method 2: Generating a Table from File System Data
You can use a FOR loop to automatically generate rows in an HTML table, for example, listing files in a directory with their sizes.
@echo off
setlocal
set "ReportFile=%~dp0file_inventory.html"
set "TargetDir=%~dp0"
:: HTML Header: redirect-first syntax at the top command level.
:: Paren blocks consume ^ during parsing, leaving bare < and > that
:: cmd treats as redirects. Redirect-first avoids this entirely.
:: First line uses > to create/overwrite; the rest use >>
> "%ReportFile%" echo ^<!DOCTYPE html^>
>> "%ReportFile%" echo ^<html^>
>> "%ReportFile%" echo ^<head^>^<meta charset="utf-8"^>^<title^>File Inventory^</title^>^</head^>
>> "%ReportFile%" echo ^<body^>
>> "%ReportFile%" echo ^<style^>
>> "%ReportFile%" echo body { font-family: Segoe UI, sans-serif; padding: 20px; }
>> "%ReportFile%" echo table { border-collapse: collapse; width: 100%%; max-width: 800px; }
>> "%ReportFile%" echo th, td { border: 1px solid #ddd; padding: 8px; text-align: left; }
>> "%ReportFile%" echo th { background: #4a90d9; color: white; }
>> "%ReportFile%" echo tr:nth-child(even) { background: #f9f9f9; }
>> "%ReportFile%" echo ^</style^>
>> "%ReportFile%" echo ^<h1^>File Inventory^</h1^>
>> "%ReportFile%" echo ^<p^>Directory: ^<code^>%TargetDir%^</code^>^</p^>
>> "%ReportFile%" echo ^<table^>
>> "%ReportFile%" echo ^<tr^>^<th^>Filename^</th^>^<th^>Size (bytes)^</th^>^<th^>Last Modified^</th^>^</tr^>
:: Enable delayed expansion only for the counter loop.
:: Inside a for body, ^< and ^> lose their escaping because ^ is consumed during
:: block parsing, leaving bare < and > that cmd treats as redirect operators.
setlocal EnableDelayedExpansion
set "FileCount=0"
for %%a in ("%TargetDir%*.txt") do (
set /a "FileCount+=1"
set "_name=%%~nxa"
set "_size=%%~za"
set "_date=%%~ta"
call :WriteRow
)
set "_fc=!FileCount!"
endlocal & set "FileCount=%_fc%"
:: HTML Footer, same redirect-first pattern
>> "%ReportFile%" echo ^</table^>
>> "%ReportFile%" echo ^<p^>Total files: ^<b^>%FileCount%^</b^>^</p^>
>> "%ReportFile%" echo ^</body^>^</html^>
echo [OK] Report generated: %ReportFile% (%FileCount% files listed)
start "" "%ReportFile%"
endlocal
exit /b 0
:WriteRow
:: Redirect-first at top command level, ^< and ^> work correctly here
>> "%ReportFile%" echo ^<tr^>^<td^>%_name%^</td^>^<td^>%_size%^</td^>^<td^>%_date%^</td^>^</tr^>
goto :eof
Why this approach scales:
Each for iteration appends one <tr> row. Whether the directory contains 5 files or 500, the pattern is identical. The %%~nxa (name + extension), %%~za (size in bytes), and %%~ta (timestamp) modifiers extract file metadata without needing external tools.
How to Avoid Common Errors
Problem: Forgetting to Escape < and >
:: BROKEN: Batch interprets < and > as redirection, not text
echo <h1>Title</h1> >> report.html
This creates a file called h1 and pipes nothing useful into it, or fails with a syntax error.
Correct Way: Escape every < and > with a caret: echo ^<h1^>Title^</h1^>.
Problem: Percent Signs Consumed as Variables
:: BROKEN: %% is needed, not %, for literal percent in echo
echo width: 100% >> report.html
Batch interprets % as a variable delimiter. A single % may be silently consumed or cause unpredictable behavior.
Correct Way: Double the percent sign: echo width: 100%%.
Problem: Parentheses in CSS or HTML Breaking Batch Blocks
The ) character inside a ( ... ) block can prematurely close the block. For example, rgba(0,0,0,0.1) contains a ) that Batch interprets as the end of the block.
Correct Way: Escape the closing parenthesis with a caret: rgba(0,0,0,0.1^). This is only necessary inside ( ... ) blocks, standalone echo lines handle ) without escaping.
Problem: Large Reports Are Slow with Line-by-Line echo
Each echo ... >> file statement opens, seeks to the end, writes, and closes the file. With hundreds of rows, this becomes noticeably slow.
Solution: Wrap multiple echo lines in a single ( ... ) >> file block where possible. For the loop body, a single echo per iteration is unavoidable but is usually fast enough for files under 1,000 rows. For very large reports, consider generating the data as CSV and converting to HTML with a PowerShell one-liner.
Problem: Browser Cache Showing Old Reports
If you always use the same filename (summary.html), users may see a cached version of a previous report.
Solution: Include a date stamp in the filename:
set "ReportFile=%~dp0report_%date:~-4%%date:~4,2%%date:~7,2%.html"
Note that %date% formatting varies by locale. Test on your target systems.
Best Practices and Rules
1. Overwrite, Then Append
Use > on the first write to ensure you start with a fresh file. Use >> for everything after. If you use >> for everything, repeated runs will append duplicate reports to the same file.
2. Always Close Your HTML Tags
An unclosed <table>, <div>, or <body> tag will cause the browser to render the page incorrectly. Structure your script so the footer (closing tags) is always written, even if the data section encounters an error. Consider writing the header and footer first, then inserting data between them.
3. Include Metadata
Always include the generation timestamp, the computer name (%COMPUTERNAME%), and the script name in the report header. This makes it clear when, where, and how the report was produced, essential when reports are emailed or saved to shared drives.
4. Keep CSS Inline for Portability
If the report will be emailed or moved to a different machine, an external styles.css file may not follow it. Inline CSS (in a <style> block within <head>) ensures the report renders correctly anywhere.
5. Validate the Output
Open the generated HTML file in a browser after writing your script to verify that all tags are properly closed, escaping is correct, and the layout looks as intended. A missing caret on a single < can break the entire file.
Conclusions
Generating HTML reports from Batch scripts transforms raw command-line output into clear, visually appealing documentation that can be shared with stakeholders who never open a terminal. By combining careful character escaping, delayed expansion for dynamic data, and basic CSS for presentation, you create value-added reporting that enhances the transparency and professionalism of your system administration.