Skip to main content

How to Set and Read Build Version Numbers in Batch Script

Build version numbers are a critical part of software release management. They uniquely identify each build, communicate the scope of changes (major, minor, patch), and enable traceability from a deployed binary back to the exact source code that produced it. Managing version numbers through Batch Script allows you to automate version bumping, embed version info into builds, and integrate versioning into CI/CD pipelines.

In this guide, we will explore how to set, read, increment, and use build version numbers in Batch Script for various project types.

Understanding Version Number Formats

The most common format is Semantic Versioning (SemVer):

MAJOR.MINOR.PATCH
ComponentWhen to IncrementExample
MAJORBreaking changes, incompatible API changes1.0.0 to 2.0.0
MINORNew features, backward-compatible additions1.0.0 to 1.1.0
PATCHBug fixes, backward-compatible corrections1.0.0 to 1.0.1

Some projects add a build number as a fourth component: MAJOR.MINOR.PATCH.BUILD (e.g., 2.1.3.1547).

Method 1: Reading Version from a File

Store the version in a dedicated file and read it into variables:

@echo off
setlocal

set "version_file=VERSION"

:: Read the version
if not exist "%version_file%" (
echo [ERROR] Version file not found: %version_file%
echo Creating default version...
>"%version_file%" echo 1.0.0
)

set /p "version=" < "%version_file%"

echo Current version: %version%

:: Parse into components
for /f "tokens=1,2,3 delims=." %%A in ("%version%") do (
set "major=%%A"
set "minor=%%B"
set "patch=%%C"
)

echo Major: %major%
echo Minor: %minor%
echo Patch: %patch%

pause

The VERSION File

Create a simple text file named VERSION containing just the version string:

1.2.3

This single-line file is easy to read from any language or build tool.

Method 2: Auto-Incrementing the Version

@echo off
setlocal enabledelayedexpansion

set "version_file=VERSION"

:: Read current version
set /p "version=" < "%version_file%"

:: Parse components
for /f "tokens=1,2,3 delims=." %%A in ("%version%") do (
set "major=%%A"
set "minor=%%B"
set "patch=%%C"
)

:: Pre-compute next values for display
set /a "next_major=major+1"
set /a "next_minor=minor+1"
set /a "next_patch=patch+1"

echo Current version: %version%
echo.
echo Bump type:
echo [1] Major (%major%.%minor%.%patch% -^> !next_major!.0.0^)
echo [2] Minor (%major%.%minor%.%patch% -^> %major%.!next_minor!.0^)
echo [3] Patch (%major%.%minor%.%patch% -^> %major%.%minor%.!next_patch!)
echo [4] Cancel
set /p "bump=Select: "

set "bumped=0"
if "%bump%"=="1" (
set /a major+=1
set "minor=0"
set "patch=0"
set "bumped=1"
)
if "%bump%"=="2" (
set /a minor+=1
set "patch=0"
set "bumped=1"
)
if "%bump%"=="3" (
set /a patch+=1
set "bumped=1"
)
if "%bump%"=="4" exit /b 0

if "!bumped!"=="0" (
echo [ERROR] Invalid selection.
pause
exit /b 1
)

set "new_version=!major!.!minor!.!patch!"

echo.
echo Version bumped: %version% -^> !new_version!

:: Write new version
>"%version_file%" echo !new_version!

echo [OK] Version file updated.
pause

Method 3: Version with Build Number

Append an auto-incrementing build number:

@echo off
setlocal enabledelayedexpansion

set "version_file=VERSION"
set "build_file=BUILD_NUMBER"

:: Read version
set /p "version=" < "%version_file%"

:: Read and increment build number
if exist "%build_file%" (
set /p "build=" < "%build_file%"
) else (
set "build=0"
)
set /a build+=1

:: Write updated build number
>"%build_file%" echo !build!

set "full_version=%version%.!build!"

echo =============================================
echo BUILD VERSION: %full_version%
echo =============================================
echo Version: %version%
echo Build: !build!
echo.

:: Export as environment variable for downstream use
endlocal & set "BUILD_VERSION=%full_version%"
echo BUILD_VERSION=%BUILD_VERSION%

pause

Method 4: Reading Version from package.json (Node.js)

@echo off
setlocal enabledelayedexpansion

echo Reading version from package.json...

set "version="

if not exist "package.json" (
echo [ERROR] package.json not found.
goto :end
)

for /f "delims=" %%V in ('powershell -noprofile -command ^
"try { (Get-Content package.json | ConvertFrom-Json).version } catch { '' }"') do (
set "version=%%V"
)

if defined version (
echo Version: !version!
) else (
echo [ERROR] Could not read version from package.json.
)

:end
pause

Method 5: Reading and Setting Version in .csproj (.NET)

@echo off
setlocal enabledelayedexpansion

set "csproj=MyApp\MyApp.csproj"

echo Reading version from %csproj%...

set "version="

:: Check file exists
if not exist "%csproj%" (
echo [ERROR] File not found: %csproj%
goto :done
)

:: Read version safely
for /f "delims=" %%V in ('powershell -noprofile -command ^
"try { ([xml](Get-Content '%csproj%')).Project.PropertyGroup.Version ^| Select-Object -First 1 } catch { '' }"') do (
set "version=%%V"
)

if defined version (
echo Current version: !version!
) else (
echo [WARNING] No <Version> element found or unable to read it.
)

:: Prompt for new version
set "new_version="
set /p "new_version=Enter new version (or press Enter to keep): "

if "!new_version!"=="" goto :done

echo Updating version to !new_version!...

powershell -noprofile -command ^
"$path = (Resolve-Path '%csproj%').Path; ^
try { ^
$xml = [xml](Get-Content $path); ^
$node = $xml.SelectSingleNode('//Version'); ^
if ($node) { ^
$node.InnerText = '!new_version!'; ^
$xml.Save($path); ^
Write-Host '[OK] Version updated.' ^
} else { ^
Write-Host '[ERROR] No <Version> element found.' ^
} ^
} catch { ^
Write-Host '[ERROR] Failed to update version.' ^
}"

:done
pause

Embedding Version Into Build Output

Stamping a Version into an Executable Resource

@echo off
setlocal

set "version_file=VERSION"

if not exist "%version_file%" (
echo [ERROR] VERSION file not found.
exit /b 1
)

set /p "version=" < "%version_file%"

echo Building with version: %version%

:: Initialize Visual Studio environment (IMPORTANT)
call "%ProgramFiles%\Microsoft Visual Studio\2022\BuildTools\VC\Auxiliary\Build\vcvars64.bat" >nul 2>&1

where cl >nul 2>&1
if errorlevel 1 (
echo [ERROR] cl.exe not found. Run from Visual Studio Developer Command Prompt.
exit /b 1
)

cl /DVERSION_STRING=\"%version%\" main.c /Fe:app.exe
if errorlevel 1 (
echo [ERROR] Build failed.
exit /b 1
)

echo [OK] Built app.exe with version %version%
exit /b 0

Creating a Version Header File

@echo off
setlocal

set "version_file=VERSION"
set /p "version=" < "%version_file%"

for /f "tokens=1,2,3 delims=." %%A in ("%version%") do (
set "major=%%A"
set "minor=%%B"
set "patch=%%C"
)

:: Ensure output directory exists
if not exist "include" mkdir "include"

:: Generate a C header file
(
echo // Auto-generated by build script. Do not edit.
echo #define VERSION_MAJOR %major%
echo #define VERSION_MINOR %minor%
echo #define VERSION_PATCH %patch%
echo #define VERSION_STRING "%version%"
) > include\version.h

echo [OK] Generated include\version.h with version %version%.

Git Tag-Based Versioning

Derive the version from Git tags:

@echo off
setlocal

:: Get the latest tag
for /f "delims=" %%T in ('git describe --tags --abbrev=0 2^>nul') do set "tag=%%T"

if not defined tag (
echo [WARNING] No git tags found. Using default.
set "tag=v0.0.0"
set "tag_found=0"
) else (
set "tag_found=1"
)

:: Remove the 'v' prefix if present
set "version=%tag%"
if "%tag:~0,1%"=="v" set "version=%tag:~1%"

:: Get commits since tag
set "commits=0"
if "%tag_found%"=="1" (
for /f %%C in ('git rev-list "%tag%..HEAD" --count 2^>nul') do set "commits=%%C"
) else (
for /f %%C in ('git rev-list HEAD --count 2^>nul') do set "commits=%%C"
)

:: Get short commit hash
set "hash=unknown"
for /f %%H in ('git rev-parse --short HEAD 2^>nul') do set "hash=%%H"

if "%commits%"=="0" (
set "full_version=%version%"
) else (
set "full_version=%version%+%commits%.%hash%"
)

echo =============================================
echo GIT VERSION INFO
echo =============================================
echo Tag: %tag%
echo Version: %version%
echo Commits: %commits% since tag
echo Hash: %hash%
echo Full: %full_version%
echo =============================================

pause

Common Mistakes

The Wrong Way: Hardcoding Version in Multiple Files

:: WRONG - Version scattered across multiple files
:: build.bat says 1.2.3, package.json says 1.2.2, .csproj says 1.2.1

Output Concern: When the version number is duplicated across multiple files, they inevitably get out of sync. Maintain a single source of truth (a VERSION file, Git tag, or package.json) and derive all other version references from it.

The Wrong Way: Using Date as Version

:: PROBLEMATIC - Dates are not semantic
set "version=%date:~-4%.%date:~4,2%.%date:~7,2%"
:: Produces: 2024.01.15

Date-based versions do not communicate whether changes are breaking, features, or bug fixes. They make it impossible to determine compatibility between versions. Use semantic versioning and append dates or build numbers as metadata if needed.

Best Practices

  1. Single source of truth: Keep the version in one place and derive everything else from it.
  2. Use semantic versioning: MAJOR.MINOR.PATCH communicates the nature of changes.
  3. Automate version bumping: Use scripts to increment versions consistently.
  4. Tag releases in Git: Git tags provide a permanent, immutable record of each release.
  5. Include build numbers for CI: Append a build counter or commit hash for unique identification of every build.

Conclusion

Setting and reading build version numbers in Batch Script involves reading from a centralized version source (a VERSION file, package.json, .csproj, or Git tags), parsing the components, and using them throughout the build process. By automating version increments, generating version header files, and integrating with Git tags, developers maintain consistent, traceable versioning across their entire build pipeline. The key principle is maintaining a single source of truth for the version number and deriving all downstream references from it.