How to Create a Reusable Library of Subroutines in Batch Script
If you find yourself copy-pasting the same code for error logging, date calculation, or file validation into every new script you write, it's time to build a "Library." In modern programming, this is called a Module or Package. In Batch, we achieve this by creating a single .bat file that contains all your common subroutines. Your main scripts can "Call" into this library, ensuring that if you fix a bug in the logging logic, every script that uses that library is updated instantly.
This guide will explain how to create, structure, and invoke a reusable Batch library.
Step 1: Creating the Library File (mylib.bat)
Your library file should contain labeled blocks of code. The very first line after @echo off should be a goto that jumps past the subroutines when the file is called without a label: this prevents accidental execution if someone double-clicks it, while still allowing call mylib.bat :Label to reach the subroutines.
@echo off
:: MyLib.bat - Reusable subroutine library
:: Version 1.0 - 2024-05-10
:: Guard: skip all subroutines when called without a label
goto :eof
:: =========================================
:: FUNCTION: Logger
:: Input: %~1 = Message string
:: Output: None (prints to stdout)
:: =========================================
:Lib_Log
setlocal
echo [%date% %time%] %~1
endlocal
exit /b 0
:: =========================================
:: FUNCTION: CheckAdmin
:: Input: None
:: Output: exit /b 0 if admin, exit /b 1 if not
:: =========================================
:Lib_CheckAdmin
setlocal
net session >nul 2>&1
if %errorlevel% neq 0 (
endlocal
exit /b 1
)
endlocal
exit /b 0
:: =========================================
:: FUNCTION: GetSize
:: Input: %~1 = File path, %~2 = Return variable name
:: Output: Sets caller's variable to file size in bytes
:: =========================================
:Lib_GetSize
setlocal
set "filesize=%~z1"
endlocal & set "%~2=%filesize%"
exit /b 0
Key design points:
goto :eofguard: Prevents the subroutines from running if the file is called or double-clicked without specifying a label.setlocal/endlocal: Every function protects the caller's variable environment.- Caller-defined return variable names:
:Lib_GetSizeaccepts the return variable name as%~2, so the caller controls naming and avoids collisions. - Namespaced labels: All labels use a
Lib_prefix to avoid collisions with labels in the calling script.
Step 2: Calling the Library from a Main Script
To use functions from your library, you use call with the filename and then the :Label. The Batch interpreter opens the target file and jumps directly to the specified label.
@echo off
setlocal
set "LIB=%~dp0mylib.bat"
:: Verify the library exists before using it
if not exist "%LIB%" (
echo [FATAL] Library not found: %LIB% >&2
exit /b 1
)
:: 1. Use the Logger function
call "%LIB%" :Lib_Log "Starting Backup Process..."
:: 2. Use the Admin Check function
call "%LIB%" :Lib_CheckAdmin
if errorlevel 1 (
call "%LIB%" :Lib_Log "ERROR: Admin rights required!"
endlocal
pause
exit /b 1
)
:: 3. Use the GetSize function, caller chooses variable name
call "%LIB%" :Lib_GetSize "C:\Windows\explorer.exe" ExplorerSize
echo Explorer size: %ExplorerSize% bytes
call "%LIB%" :Lib_Log "All tasks completed successfully."
endlocal
exit /b 0
Important notes:
- Always use
%~dp0: This resolves to the drive and path of the calling script, so the library is found regardless of the current working directory. - Check that the library exists: If the library file is missing or misplaced, every
callto it will silently fail. Validate up front. - Use
if errorlevel 1: This is safer thanif %errorlevel% neq 0when testing return values fromcall, because%errorlevel%can sometimes reflect a stale value if it was not reset by the previous command. Theif errorlevel 1form tests the actual processor state.
Step 3: The "Internal Library" Pattern
If you don't want to manage multiple files, you can place reusable subroutines at the bottom of your main script, after the main logic's exit /b. While less portable than a separate file, it keeps the code organized and is useful for smaller projects.
@echo off
setlocal
:: === MAIN LOGIC ===
call :Lib_Log "Step 1: Initializing..."
call :Lib_Log "Step 2: Processing..."
echo Main script complete.
endlocal
exit /b 0
:: === INTERNAL LIBRARY SECTION ===
:: (Nothing below this line runs unless explicitly called)
:Lib_Log
setlocal
echo [LOG %date% %time%] %~1
endlocal
exit /b 0
Why this works:
The exit /b 0 at the end of the main logic stops execution. The subroutines below it are only reachable via call :Label, so they never run accidentally. This is the same pattern used in the external library file's goto :eof guard.
How to Avoid Common Errors
Wrong Way: Expecting an "Include" Mechanism
Batch does not have an #include or import command. You cannot "load" a library once and then use its functions globally. You must call the library file every single time you want to execute a subroutine within it.
Wrong Way: Library Functions Without setlocal
If your library function sets a variable like set "temp_file=data.tmp" without setlocal, that variable leaks into the calling script's environment. If the caller also has a variable called temp_file, it gets silently overwritten.
Correct Way: Every library function should use setlocal and return values via the endlocal & set export pattern, as shown in :Lib_GetSize above.
Problem: Label Collisions
If your main script has a label called :Log and your library also has a label called :Log, the Batch interpreter may jump to the wrong one: it searches the current file first.
Solution: Namespace all library labels with a consistent prefix like Lib_, Util_, or the library's name. This eliminates ambiguity.
Best Practices and Rules
1. Document Your Functions
At the top of each function in your library, include a comment block explaining the Inputs (what each %~n parameter means) and the Outputs (what variables are set, what exit codes are returned). This serves as the function's API contract.
2. Version Your Library
If you use your library across many machines, include a version number and date at the top of the file. This makes it easy to verify that all machines are running the same version.
3. Validate the Library at Startup
Your main script should check that the library file exists before making any calls into it. A missing library causes silent failures that are extremely difficult to debug.
4. Use Caller-Defined Return Names
Instead of hardcoding output variable names like RET_SIZE, accept the return variable name as a parameter (e.g., %~2). This lets the caller choose names that make sense in their context and prevents collisions when a subroutine is called multiple times.
Conclusions
Building a reusable library is the turning point where you stop writing "Scripts" and start building "Internal Tools." By centralizing your logic, you reduce code duplication, minimize bugs, and make your entire automation ecosystem significantly easier to maintain. This modular approach is the gold standard for professional Windows system administration and large-scale task automation.