Est. 1981 Beginner

DOS Batch

The command-line scripting language of MS-DOS and Windows — simple, ubiquitous, and still powering Windows automation more than four decades after its introduction.

Created by Microsoft (COMMAND.COM shipped with MS-DOS/PC-DOS 1.0)

Paradigm Procedural, Scripting
Typing Dynamic, Weak (effectively untyped strings)
First Appeared 1981
Latest Version Ships as part of Windows (no independent version; cmd.exe tracks the Windows build)

DOS Batch is the scripting language of the MS-DOS and Windows command processors — a small, quirky language defined less by formal design than by what Microsoft’s COMMAND.COM (and later cmd.exe) happened to do when handed a file with a .BAT or .CMD extension. Despite never being “designed” as a language in the usual sense, batch has survived for more than four decades, shipping on every version of MS-DOS since 1981 and every version of Windows ever released. Its peculiarities — parse-time variable expansion, string-only “typing,” baroque escape rules — are the natural consequence of a syntax that grew by accretion rather than by plan, but its ubiquity on Windows means that batch remains a language every serious Windows administrator eventually learns.

History & Origins

MS-DOS 1.0 and COMMAND.COM

When the IBM PC launched on August 12, 1981, it shipped with PC-DOS 1.0 — Microsoft’s rebadged version of MS-DOS 1.0. The command processor, COMMAND.COM, handled both interactive command entry and the execution of files with the .BAT extension. In this first release the language was bare-bones: COMMAND.COM recognized only a handful of internal commands (including REM and PAUSE), and a .BAT file was essentially a sequence of commands played back one at a time.

The underlying operating system itself had a convoluted origin. Tim Paterson at Seattle Computer Products wrote QDOS (“Quick and Dirty Operating System”) starting in April 1980 as a 16-bit OS for the 8086, modeled on Digital Research’s CP/M. Microsoft licensed QDOS (by then renamed 86-DOS) in December 1980 and bought it outright in July 1981. Because CP/M was the target of imitation, MS-DOS inherited many CP/M conventions — including the concept of a file of commands to execute, which CP/M had called SUBMIT files with a .SUB extension.

CP/M’s SUBMIT: The Spiritual Ancestor

CP/M, the dominant 8-bit business OS of the late 1970s, shipped a utility called SUBMIT that read a .SUB file line by line and fed each line to the CCP (CP/M’s command interpreter) as if the user had typed it. This is functionally what a DOS batch file does, though DOS integrated the feature directly into COMMAND.COM rather than requiring a separate SUBMIT program. CP/M’s SUBMIT had no conditionals, no branching, and no real control flow — and neither did DOS batch in its earliest form.

MS-DOS 2.0: Batch Becomes a Real Language

The release of MS-DOS 2.0 in March 1983, timed with the IBM PC XT and its hard disk, expanded batch into something resembling a proper scripting language. MS-DOS 2.0 introduced:

  • ECHO — print a line, toggle command echoing on or off
  • IF — conditional execution (IF EXIST, IF ERRORLEVEL, IF "%var%"=="value")
  • GOTO — jump to a :label
  • FOR — iterate over a set of values
  • SHIFT — rotate positional parameters (%1, %2, …)
  • SET — environment variables, referenced in scripts with %varname%

With these pieces, batch could express structured logic — loops, branches, and parameterized subroutines (via labels and GOTO) — though the syntax remained idiosyncratic and the language semantics never received a formal specification.

MS-DOS 3.3: The CALL Command

Until MS-DOS 3.3 (1987), invoking one batch file from another was problematic: the child batch file replaced the parent in execution, so control never returned to the caller. A common workaround was to spawn a new COMMAND.COM instance. MS-DOS 3.3 added CALL, which invokes another batch file (or, later, a subroutine label within the same file) and returns control when it finishes — making batch genuinely modular for the first time.

OS/2 and the .CMD Extension

IBM and Microsoft jointly developed OS/2 in the mid-1980s as the intended successor to DOS. OS/2 shipped its own command interpreter, initially called CMD.EXE, and introduced the .CMD file extension to distinguish OS/2-era batch files from DOS-era .BAT files. The OS/2 CMD interpreter was more capable than COMMAND.COM — supporting 32-bit arithmetic, more sophisticated error handling, and cleaner quoting.

Windows NT: cmd.exe Inherits the Throne

When Microsoft developed Windows NT separately from the DOS-based consumer Windows line, the NT team brought the CMD lineage forward. Windows NT 3.1, released on July 27, 1993, shipped cmd.exe as the default NT command processor (with 16-bit COMMAND.COM available for backward compatibility). Relative to COMMAND.COM, NT’s cmd.exe added:

  • Native 32-bit execution (not constrained by real-mode DOS limits)
  • Long filename support
  • True concurrent pipelines (COMMAND.COM implemented pipes by writing the first command’s output to a temp file, then running the second)
  • Command-line history and editing
  • Path completion

Windows NT 4.0 (1996) enabled “Command Extensions” by default, bringing enhanced FOR loops (FOR /R, FOR /L, FOR /F), SET /A for integer arithmetic, and the SETLOCAL / ENDLOCAL pair for scoped environment changes. Windows 2000 then added delayed variable expansion via SETLOCAL ENABLEDELAYEDEXPANSION and the !var! syntax — a fix for the long-standing problem that variables inside a FOR or IF block were expanded when the block was parsed, not when each iteration ran.

The Unification

The consumer Windows line (Windows 95, 98, Me) continued to use COMMAND.COM into 2000. With Windows XP (2001), Microsoft unified the codebase on the NT kernel, and cmd.exe became the standard command interpreter on every Windows edition — a status it retains today.

PowerShell and the Succession

In November 2006, Microsoft released Windows PowerShell 1.0 as the strategic successor to cmd.exe for Windows automation. PowerShell’s object pipeline, .NET integration, and rich scripting semantics addressed essentially every shortcoming of batch. Microsoft’s official position for more than a decade has been that cmd.exe and batch files are maintained for compatibility but will not receive significant new features; new automation work on Windows should use PowerShell. Even so, batch remains universally available, often simpler for basic tasks, and is unlikely to disappear from Windows any time soon.

Design Philosophy (or Lack Thereof)

Batch is less a designed language than an accreted one. It grew one command at a time, typically to solve a specific immediate problem, without any overarching syntactic or semantic vision. This produces characteristic quirks that both baffle newcomers and endear batch to long-time Windows administrators:

  • Everything is a string. There are no types, no declarations, and no distinction between numbers and text except when commands choose to interpret a string as a number.
  • Lines are commands, period. A batch file is a sequence of command-prompt lines; there is no block syntax, only labels and GOTO.
  • Variables are expanded at parse time by default. %VAR% inside a FOR or IF block is substituted when the whole block is read, not on each iteration — leading to the infamous “why doesn’t my loop variable update?” problem that !VAR! (delayed expansion) eventually solved.
  • Parentheses are blocks, but fragile ones. (...) groups commands for a single execution context, but placement of redirects, quoting of special characters, and whitespace all interact in surprising ways.
  • Error handling is by convention. The ERRORLEVEL pseudo-variable holds the last program’s exit code, which scripts test with IF ERRORLEVEL n (which checks for greater than or equal to n, another classic gotcha).

The language’s lack of design is also its greatest strength: batch has remained largely backward-compatible across four decades. A simple batch script written for MS-DOS 3.3 in 1987 — using ECHO, IF, GOTO, FOR, and SET — will generally still run on Windows 11 in 2026 when invoked from cmd.exe, though AUTOEXEC.BAT itself is no longer auto-executed at boot on NT-based Windows.

Key Features

Variables and Parameters

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
@echo off
set NAME=World
echo Hello, %NAME%!

REM Positional arguments: %0 is the script name, %1..%9 are arguments
echo First argument: %1
echo Script name: %0

REM %* expands to all arguments
echo All arguments: %*

REM Default values using substitution
set "GREETING=%1"
if "%GREETING%"=="" set "GREETING=Hello"
echo %GREETING%, world

The @echo off Idiom

Every batch file of any size begins with @echo off. echo off tells COMMAND.COM to stop echoing each command to the screen before executing it; the leading @ suppresses the echo of the echo off command itself. The result is clean output from echo statements only, not a transcript of every line in the file.

Conditionals

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
REM File existence
if exist "config.ini" (
    echo Found config
) else (
    echo Missing config
    exit /b 1
)

REM String equality (quote both sides to survive empty values)
if "%USERNAME%"=="Administrator" (
    echo Admin user
)

REM Exit code of the previous command
some_command.exe
if errorlevel 1 (
    echo Command failed
    exit /b %errorlevel%
)

REM Numeric comparisons (NT cmd.exe only, with Command Extensions)
if %count% GEQ 10 echo Ten or more

Loops

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
REM Iterate over a static list
for %%f in (*.txt) do echo Found: %%f

REM Iterate over numbers
for /L %%i in (1, 1, 10) do echo %%i

REM Walk a directory tree
for /R "C:\logs" %%f in (*.log) do echo %%f

REM Parse a file line by line (/F)
for /F "tokens=1,3 delims=," %%a in (data.csv) do (
    echo Column 1: %%a, Column 3: %%b
)

Note the double %% in batch files — positional substitution uses a single % at the interactive prompt, but batch files require the doubled form so the % itself survives parsing.

Delayed Expansion

1
2
3
4
5
6
7
8
9
@echo off
setlocal enabledelayedexpansion

set COUNT=0
for %%f in (*.txt) do (
    set /A COUNT+=1
    echo File !COUNT!: %%f
)
echo Total: !COUNT!

Without enabledelayedexpansion, %COUNT% inside the loop would expand once — to 0 — before the loop ever ran. The !COUNT! syntax defers expansion until each iteration executes.

Arithmetic with SET /A

1
2
3
4
5
set /A x=5+3*2
set /A y=%x% / 2
set /A z=1 << 8         REM bit shift
set /A remainder=17 %% 5
echo x=%x% y=%y% z=%z% remainder=%remainder%

SET /A supports 32-bit signed integer arithmetic with C-style operators. The % operator has to be doubled in batch files because % is the variable expansion character.

Subroutines with CALL

1
2
3
4
5
6
7
8
@echo off
call :greet "Alice"
call :greet "Bob"
exit /b 0

:greet
echo Hello, %~1!
exit /b 0

CALL :label invokes a label in the same file as a subroutine; EXIT /B returns. The %~1 syntax strips surrounding quotes from the argument.

DOS Batch vs. Other Shells

FeatureDOS Batch (cmd.exe)PowerShellBash
First appeared198120061989
Data modelStrings only.NET objectsStrings only
ArithmeticSET /A (32-bit int)Full .NET numerics$(( )) / bc
ArraysNone (use delimited strings)Native arrays, hashtablesIndexed and associative
FunctionsLabels + CALLFirst-class with parametersFirst-class
PipelinesText onlyObject-basedText only
Error handling%ERRORLEVEL%Exceptions, $?, -ErrorActionExit codes, set -e
Current Microsoft stanceLegacy (maintained)StrategicN/A (Unix)

Common Quirks and Gotchas

IF ERRORLEVEL n is “Greater Than or Equal To”

The classic form IF ERRORLEVEL 1 means “if the exit code is 1 or greater.” To test for exact values, either use the Command Extensions syntax IF %ERRORLEVEL% EQU 1 or test descending: check high values first.

Special Characters Need Escaping

&, |, <, >, ^, %, and ! all have special meanings. The escape character is ^ — except inside quoted strings, except when the next character is itself special, except inside FOR /F tokens, and with further exceptions for delayed expansion. Mastering quoting in batch is a rite of passage.

No Local Variables (Sort Of)

There is no local keyword. The closest equivalent is to wrap code in SETLOCAL / ENDLOCAL, which saves the entire environment on entry and restores it on exit — all-or-nothing scoping rather than per-variable locals.

Return Values from Functions

Since CALL :label doesn’t return a value, the idiomatic pattern is either to rely on ERRORLEVEL (via EXIT /B n), or to have the “function” set a well-known variable name that the caller reads.

Current Relevance

Despite Microsoft’s clear direction toward PowerShell, DOS Batch remains deeply embedded in Windows-based workflows in 2026:

  • Ubiquity: cmd.exe ships with every Windows installation and runs batch files with no interpreter to install or configure.
  • Legacy tooling: Decades of existing .bat and .cmd scripts across enterprises, installers, and utilities continue to run unchanged.
  • Launcher scripts: Tools shipped by Apache, Oracle, IBM, and others still include .cmd launchers alongside their Unix shell equivalents.
  • Quick tasks: For trivial jobs — renaming files, launching an executable with a specific environment — batch is often shorter and faster to write than the PowerShell equivalent.
  • Bootstrap scripts: Batch remains a common choice for the outermost wrapper that sets up PATH, activates a Python venv or a Visual Studio command environment, and then invokes the real tool.

At the same time, new Windows automation work increasingly happens in PowerShell, Python, or (since WSL) Bash. Batch’s role has narrowed from “the scripting language of Windows” to “the compatibility layer and quick-task glue of Windows” — but that role is secure.

Why DOS Batch Matters

DOS Batch is not a well-designed language, and no serious analysis pretends otherwise. Its quirks are many, its syntax is fragile, and its semantics are defined by whatever COMMAND.COM (and later cmd.exe) happens to do. And yet:

Batch is the scripting language of the personal computer era. For roughly twenty-five years, from 1981 until PowerShell’s release in 2006, it was the primary scripting language shipped with Microsoft’s desktop operating systems — and it came with every IBM PC, every PC clone, and every copy of Windows on every desk in countless offices. More programmers learned to automate tasks by editing AUTOEXEC.BAT than through any formal course. For an entire generation, “writing a script” meant writing a batch file.

Its endurance is a testament to the power of being there. Bash is more expressive, PowerShell is more modern, Python is more general. But on a Windows machine, at a command prompt, the simplest path from “I have a task” to “it’s automated” still runs through a .bat file — and has for more than four decades.

Timeline

1981
MS-DOS 1.0 / PC-DOS 1.0 released on August 12, 1981 alongside the original IBM PC; COMMAND.COM provided the first .BAT file support, with internal commands including REM and PAUSE
1983
MS-DOS 2.0 released in March 1983, adding ECHO, IF, GOTO, FOR, SHIFT, and SET — the release that transformed batch files from simple command lists into a genuine (if minimal) scripting language
1987
MS-DOS 3.3 introduced the CALL command, allowing one batch file to invoke another and return, without having to spawn a new COMMAND.COM process
1987
OS/2 1.0 introduced the .CMD extension and a more capable command interpreter, establishing the lineage that would later become Windows NT's cmd.exe
1993
Windows NT 3.1 shipped cmd.exe (32-bit) alongside the legacy 16-bit COMMAND.COM; cmd.exe supported long filenames, true concurrent pipelines, and command history
1996
Windows NT 4.0 shipped cmd.exe with 'Command Extensions' enabled by default, including enhanced FOR loops, SET /A integer arithmetic, and expanded SETLOCAL semantics
2000
Windows 2000 added delayed variable expansion (the !var! syntax enabled via SETLOCAL ENABLEDELAYEDEXPANSION), removing a long-standing limitation where variables inside blocks were expanded at parse time
2000
Windows Me shipped with the final consumer version of COMMAND.COM; later Windows consumer releases (XP and beyond) unified on the NT kernel and cmd.exe
2006
Microsoft released Windows PowerShell 1.0 on November 14, 2006 as the strategic successor to cmd.exe for Windows automation; cmd.exe and batch files remain supported but receive minimal new functionality
2016
Windows Subsystem for Linux (WSL) introduced, giving Windows developers Bash alongside cmd.exe and PowerShell — further narrowing the niche where batch is the natural choice, though it remains ubiquitous in existing Windows tooling

Notable Uses & Legacy

AUTOEXEC.BAT

The canonical DOS batch file: AUTOEXEC.BAT was executed automatically at boot by COMMAND.COM on MS-DOS and early Windows systems, setting the PATH, loading TSRs, and configuring the environment. For more than a decade it was the first program most PC users ever (indirectly) wrote.

Windows System Administration

Login scripts in Active Directory domains, scheduled task commands, and system maintenance routines on Windows Server and Windows desktop have long been written in batch. Many shops still prefer .bat/.cmd for simple tasks where PowerShell would be overkill.

Application Launchers and Wrappers

A very common pattern on Windows: a .bat or .cmd file that sets environment variables and invokes a Java, Python, or native executable. Tools shipped by Apache (Tomcat, Maven, Ant), Oracle, IBM, and many others include .cmd launcher scripts alongside their Unix shell equivalents.

Build and CI Scripts on Windows

Before PowerShell's rise, Windows build systems routinely used batch for compilation, packaging, and deployment glue. Visual Studio's vcvarsall.bat, which configures the command-line build environment for MSVC, remains a load-bearing batch file shipped with every Visual Studio install.

Software Installers

Many commercial and open-source installers on Windows use .bat/.cmd wrappers around MSI packages, Chocolatey invocations, or custom install logic — especially for enterprise deployment via SCCM, Intune, or Group Policy.

Game Modding and Utility Scripts

The PC gaming and retro-computing communities have produced an enormous volume of batch scripts for launching games with specific options, managing mods, configuring DOSBox, and automating tasks where a full installer would be excessive.

Language Influence

Influenced By

CP/M SUBMIT Unix shell (conceptually)

Influenced

OS/2 CMD Windows NT cmd.exe 4DOS / 4NT / TCC

Running Today

Run examples using the official Docker image:

docker pull
Last updated: