I still write .bat files in 2026. I also refuse to build an identity around them. That distinction—legacy glue vs personality—is the retrospective I wish someone had handed me earlier.
Where .bat still appears (unironically)
- Vendor installers and unwrap scripts.
- Scheduled tasks authored a decade ago that still pay the bills.
- Tiny wrappers that set env vars and launch something else.
- Locked images where PowerShell scripts are political and CMD isn’t.
If your job touches Windows estates, you will read batch. Pretending otherwise is aesthetic, not operational.
What I no longer use batch for
- Nontrivial logic (nested delayed expansion temples).
- JSON / HTTP workflows.
- Anything that needs testable functions and clear error objects.
- Team-shared automation when PowerShell is available.
Writing clever batch to prove cleverness is a personality choice. Production doesn’t owe you applause.
Glue patterns that aged okay
Thin wrapper:
@echo off
setlocal
set APP_ENV=prod
"C:\Program Files\App\app.exe" %*
exit /b %ERRORLEVEL%
Explicit failure:
some-tool.exe || exit /b 1
Boring. Copyable. Reviewable.
Retrospective rules I keep
- Prefer PowerShell (or a real language) for new automation when the environment allows.
- Keep batch short when you must maintain it—delegate to something saner quickly.
-
Comment the why, especially quoting and
setlocalchoices. - Don’t teach batch as a first language for beginners beyond survival file ops and reading existing glue.
Teaching without contempt
Calling batch “ancient trash” shames juniors who just opened a vendor script. Calling it “beautiful” traps them in a cul-de-sac. Better: classify it as glue. You learn enough to untangle it, then you graduate.
Reading legacy batch without drowning
When you inherit a 400-line .bat:
- Find the entry points and
gotolabels. - Note every
setlocal/endlocal. - Track
ERRORLEVELafter external calls. - Extract the “real work” (usually one
.exewith flags). - Decide whether to wrap, rewrite, or quarantine.
Often the right 2026 move is a PowerShell rewrite plus a one-line .bat shim for callers that still expect a .bat path. Glue on the outside, sense on the inside.
Personality check
If you enjoy delayed-expansion puzzles more than shipping the installer’s outcome, notice that. Fascination is fine; production ownership is a different scoreboard.
Compatibility shim pattern
@echo off
pwsh -NoProfile -File "%~dp0tools\task.ps1" %*
exit /b %ERRORLEVEL%
Callers keep a .bat path; logic lives in something testable. That’s legacy glue with a 2026 brain—not a personality built out of goto.
When rewrite is the wrong first move
If a .bat is called from fifteen undocumented scheduled tasks, a clever rewrite without inventory is how you create an outage. Inventory callers, add logging, shim carefully, then rewrite the core. Legacy glue rewards patience more than contempt.
Documentation stub worth pasting above legacy scripts
REM Owner: platform-team
REM Purpose: thin launcher only — logic in tools\task.ps1
REM Last verified: 2026-09
Three lines prevent the next engineer from assuming the .bat is the product.
Glue is honorable when it’s thin, labeled, and temporary-by-intent—even if temporary lasts years.
That framing matches how I sequence Windows CLI practice in CMD Master: respect the artifact, don’t romanticize the dialect.
What I disabled in my own habits
- Clever
gotoforests. - Environment mutation without
setlocal. - Silent error ignoring.
- Using batch where a checked-in PowerShell task would be clearer for the team.
.bat in 2026 is a compatibility surface. Use it like duct tape: proud when it holds, eager to replace it when you can engineer a joint.
Top comments (0)