Why Does My Batch File Exit When I Call a Function?
The subroutine runs before anything calls it, and its goto :eof ends the whole script, because cmd.exe reads straight down through a label.

On this page
TL;DR
A batch label does not create a function body that cmd.exe skips. Execution
runs straight through the label unless you redirect it.
Put the main work first, end it with exit /b, and place subroutines below it:
@echo off
setlocal EnableExtensions
call :timestamp
echo [%stamp%] Work starts here
exit /b 0
:timestamp
set "stamp=%DATE% %TIME%"
goto :eofcall :timestamp creates a return point. goto :eof returns from that call.
Reached in the main batch context, it ends the batch instead.
You add a timestamp helper to a working batch file. The helper looks reasonable. The next run prints almost nothing, and the real work never starts.
It’s tempting to debug the timestamp expression. First check whether the script ever reaches the line that calls it.
Minimal reproduction: the call never happens
Save this as fall-through.cmd:
@echo off
echo Starting script
:timestamp
echo Inside timestamp
set "stamp=%DATE% %TIME%"
goto :eof
call :timestamp
echo [%stamp%] Doing the workRun it from an existing Command Prompt so the output stays visible:
Expected output
C:\scripts> cmd /d /e:on /c fall-through.cmd
Starting script
Inside timestamp
C:\scripts>That output is the clue: the helper ran even though the call below it was
never reached. Execution arrived there by reading downward.
A label marks a location. It doesn’t surround or hide the commands beneath it.
The goto :eof then ends the current batch context, so both remaining lines
are unreachable on this path. Microsoft’s goto reference (opens in a new tab)
documents the special end-of-file target; you don’t need to define an :eof label.
End the main path with exit /b before the subroutines
Here is the complete repaired example:
@echo off
setlocal EnableExtensions
echo Starting script
call :timestamp
echo [%stamp%] Doing the work
exit /b 0
:timestamp
echo Inside timestamp
set "stamp=%DATE% %TIME%"
goto :eofExpected output, with the timestamp varying by locale and clock:
Starting script
Inside timestamp
[<local date> <local time>] Doing the workThe main path calls the helper, resumes after the call, does its work, and exits before reaching the helper a second time. The helper is now reachable only through an intentional call.
Moving a subroutine to the bottom without the main-path exit is incomplete. The call returns correctly, but after the main work finishes, execution falls into the helper again.
What call, goto and exit /b each do
call :label starts a new batch context within the same file. When that context
ends, execution resumes at the statement after the call. goto label only jumps;
it doesn’t arrange a return. See Microsoft’s call reference (opens in a new tab).
| Command | Effect |
|---|---|
call :timestamp | Enter the subroutine with a return point |
goto timestamp | Jump to the label without a return point |
goto :eof | End the current batch context |
exit /b 0 | End the current batch context and set a success status |
exit | Exit the command interpreter |
Warning
Plain exit inside a batch file exits the command interpreter, not only the
script. Run it from a Command Prompt you were using and that window closes,
taking any unsaved session state with it. exit /b is almost always what you
want.
The /b matters. exit /b ends the current batch context instead of cmd.exe,
as Microsoft’s exit reference (opens in a new tab)
says. A call :label runs in its own context, so inside a subroutine exit /b
returns to the caller, and at the top level it ends the file. Use an explicit
code when the caller needs a result.
These label-call and :eof examples assume command extensions are enabled, as
they normally are. The repaired file makes that requirement explicit with
setlocal EnableExtensions.
Helpers can also go above main
There’s no rule that subroutines must live at the bottom. If you prefer helpers first, jump over them on entry:
@echo off
setlocal EnableExtensions
goto main
:timestamp
set "stamp=%DATE% %TIME%"
goto :eof
:main
call :timestamp
echo [%stamp%] Doing the work
exit /b 0Placement is a readability choice. An unambiguous execution path is the fix.
Common failures
The window disappears immediately. Run the file from an existing Command Prompt. A window opened only to run a batch file can close when that file ends; the disappearance alone doesn’t tell you which command caused it.
The helper runs twice. Look for a missing exit /b or goto :eof between
the end of the main work and the first helper label.
The main work never resumes. Check for goto timestamp where you intended
call :timestamp, or plain exit inside the helper.
I only needed a timestamp in a log. A separate subroutine may be unnecessary:
echo [%DATE% %TIME%] Backup started>>"backup.log"This is a display timestamp, not a locale-independent filename format. Keep this simple example outside a parenthesized block, where percent-variable expansion introduces a separate timing issue.
When a batch file stops early, follow the lines it actually executes. A label that looks like a function declaration is still on that path.
Tested with
- OS
- Windows Server 2019 (10.0.17763)
- Shell
- cmd.exe
- Date
- October 2026