-
Batch scripting, while not as modern or powerful as Bash or PowerShell, still serves as a go-to tool for many Windows automation tasks.
One of the most underrated and misunderstood aspects of batch scripting is modular programming using functions and subroutines.
-
In batch files, functions are often referred to as subroutines.
-
Unlike modern programming languages, batch scripting doesn't support true functions with return values or parameter scopes,
but it does support labels and the
CALLcommand, which together simulate the behavior of subroutines.A subroutine:
- Is defined using a
:label - Is invoked using
CALL :label(optionally with parameters) - Returns control back to the calling code using the
EXIT /Bcommand
- Is defined using a
CALL :SubroutineName arg1 arg2 ... ... :SubroutineName :: some code here EXIT /B
@ECHO OFF ECHO Calling the subroutine... CALL :MySubroutine ECHO Subroutine finished. GOTO :EOF :MySubroutine ECHO Inside the subroutine. EXIT /B
CALL :MySubroutine— This tells CMD to jump to:MySubroutineand come back after it's done.EXIT /B— Exits the subroutine and returns control to the caller. Always end withEXIT /Bto return control back to the caller.GOTO :EOF— Ends the script cleanly, preventing accidental execution of the subroutine.
-
-
You can pass parameters just like command-line arguments:
%1,%2, etc.@ECHO OFF CALL :GreetUser "Omkar" CALL :Add 10 20 GOTO :EOF :GreetUser ECHO Hello, %1! EXIT /B :Add SET /A result=%1 + %2 ECHO Sum: %result% EXIT /B
-
Since you can't directly return values, you typically use global variables or environment variables:
@ECHO OFF CALL :Multiply 6 7 ECHO Product is: %result% GOTO :EOF :Multiply SET /A result=%1 * %2 EXIT /B
🔁 Remember: All variables are global unless explicitly localized using
SETLOCAL. -
To prevent variable pollution and manage memory properly:
@ECHO OFF CALL :CalcSquare 5 ECHO Square is: %square% GOTO :EOF :CalcSquare SETLOCAL SET /A squareVal=%1 * %1 ENDLOCAL & SET square=%squareVal% EXIT /B
Here, we capture the local variable using the
ENDLOCAL & SET var=valuetrick.
-
Let's say you're checking disk space and writing logs. You can modularize this with functions.
@ECHO OFF CALL :LogMessage "Starting disk check..." CALL :CheckDisk C: CALL :LogMessage "Done." GOTO :EOF :CheckDisk ECHO Checking disk %1 CHKDSK %1 >NUL EXIT /B :LogMessage ECHO [%DATE% %TIME%] %1 >> script.log EXIT /B
-
You can pass error codes using
EXIT /B nand then access them via%ERRORLEVEL%.@ECHO OFF CALL :Divide 10 0 IF %ERRORLEVEL% NEQ 0 ( ECHO Error occurred in Divide subroutine! ) ELSE ( ECHO Division succeeded! ) GOTO :EOF :Divide IF %2==0 ( ECHO Cannot divide by zero! EXIT /B 1 ) SET /A quotient=%1 / %2 ECHO Quotient: %quotient% EXIT /B 0
-
You can call subroutines from within subroutines:
@ECHO OFF CALL :Main GOTO :EOF :Main CALL :Add 5 3 CALL :PrintResult "Sum" %result% EXIT /B :Add SET /A result=%1 + %2 EXIT /B :PrintResult ECHO %1: %2 EXIT /B
-
@ECHO OFF FOR %%X IN (2 4 6 8) DO CALL :Square %%X GOTO :EOF :Square SET /A sq=%1 * %1 ECHO Square of %1 = %sq% EXIT /B
- Always use
EXIT /Bto avoid falling through unintended labels. - Use
SETLOCAL/ENDLOCALfor variable isolation. - Quote your parameters when passing strings.
- Use
CALL :labelonly, avoidGOTO :labelunless absolutely necessary (as it doesn’t return). - Avoid deep nesting — batch isn’t optimized for recursion or complex logic.
- Prefix subroutine labels (
:fn_) - Avoid accidental label name clashes.
:Greet
ECHO Hello
:: Missing EXIT /B – will fall through to next code block:: Main logic
CALL :Process
:: Falls into :Process if GOTO :EOF is missing@ECHO OFF
CALL :Start
CALL :Middle
CALL :End
GOTO :EOF
:Start
ECHO Starting...
EXIT /B
:Middle
ECHO In the middle...
EXIT /B
:End
ECHO Ending...
EXIT /BYou can only use %1 to %9 — but with SHIFT, you can rotate through arguments:
@ECHO OFF
CALL :List %*
GOTO :EOF
:List
IF "%1"=="" GOTO :EOF
ECHO %1
SHIFT
GOTO List@ECHO OFF
CALL :DeployApp MyApp "C:\\Builds\\MyApp"
CALL :CleanUp
GOTO :EOF
:DeployApp
ECHO Deploying %1 from %2 ...
:: Simulated deployment logic
EXIT /B
:CleanUp
ECHO Cleaning up temp files...
EXIT /B