Skip to content

How to Compile a .NET Project from a Batch Script (MSBuild)

Learn how to automate .NET project compilation using Batch Script with MSBuild for legacy .NET Framework projects or the dotnet CLI for modern .NET 6+ applications, including build configurations, error handling, and CI/CD integration.

Automating .NET project compilation through Batch Script is fundamental to build pipelines, continuous integration, and repeatable release processes. Whether you are building a legacy .NET Framework project with MSBuild or a modern .NET 6+ application with the dotnet CLI, a Batch Script can orchestrate the entire build workflow including restoration, compilation, testing, and packaging.

In this guide, we will explore how to compile .NET projects from a Batch Script using both MSBuild and the dotnet CLI, covering build configurations, output management, and error handling.

Understanding the Two Build Systems

Tool Projects Usage
MSBuild (msbuild.exe) .NET Framework (.csproj, .sln) Older projects, WPF, WinForms, ASP.NET
dotnet CLI (dotnet build) .NET Core / .NET 5+ (.csproj, .sln) Modern cross-platform projects

Both tools ultimately use MSBuild under the hood, but the dotnet CLI provides a simpler, more modern interface.

Method 1: Building with MSBuild (.NET Framework)

@echo off
setlocal

set "solution=MyApp.sln"
set "config=Release"
set "logfile=build.log"

:: Verify solution file exists
if not exist "%solution%" (
    echo [ERROR] Solution file not found: %solution%
    pause
    exit /b 1
)

:: Find MSBuild via vswhere
set "msbuild="
set "vswhere=%ProgramFiles(x86)%\Microsoft Visual Studio\Installer\vswhere.exe"

if exist "%vswhere%" (
    for /f "usebackq delims=" %%M in (`"%vswhere%" -latest -requires Microsoft.Component.MSBuild -find MSBuild\**\Bin\MSBuild.exe 2^>nul`) do set "msbuild=%%M"
)

if not defined msbuild (
    echo [ERROR] MSBuild not found. Install Visual Studio or Build Tools.
    pause
    exit /b 1
)

echo =============================================
echo   .NET BUILD
echo   Solution: %solution%
echo   Config:   %config%
echo   MSBuild:  %msbuild%
echo =============================================
echo.

:: Clean
echo [1/2] Cleaning...
"%msbuild%" "%solution%" /t:Clean /p:Configuration=%config% /v:minimal /nologo >nul
echo       Done.

:: Build
echo [2/2] Building...
"%msbuild%" "%solution%" /t:Build /p:Configuration=%config% /v:minimal /nologo /fl /flp:logfile=%logfile%

if %errorlevel%==0 (
    echo.
    echo [SUCCESS] Build succeeded.
) else (
    echo.
    echo [ERROR] Build failed. See %logfile% for details.
    exit /b 1
)

pause

Finding MSBuild with vswhere

The vswhere.exe tool (included with Visual Studio) locates the correct MSBuild version. This is the recommended approach because MSBuild's path changes with each Visual Studio version.

Method 2: Building with dotnet CLI (.NET Core / .NET 5+)

@echo off
setlocal

set "project=MyWebApp"
set "config=Release"
set "publish_dir=publish"

:: Verify dotnet CLI is available
where dotnet >nul 2>&1
if %errorlevel% neq 0 (
    echo [ERROR] dotnet CLI not found. Install the .NET SDK.
    pause
    exit /b 1
)

echo =============================================
echo   DOTNET BUILD
echo   Project: %project%
echo   Config:  %config%
echo =============================================
echo.

:: Restore NuGet packages
echo [1/3] Restoring packages...
dotnet restore "%project%" --verbosity quiet
if %errorlevel% neq 0 (
    echo [ERROR] Package restore failed.
    pause
    exit /b 1
)
echo       Packages restored.

:: Build
echo [2/3] Building...
dotnet build "%project%" -c %config% --no-restore --verbosity minimal
if %errorlevel% neq 0 (
    echo [ERROR] Build failed.
    pause
    exit /b 1
)
echo       Build succeeded.

:: Publish (create deployable output)
echo [3/3] Publishing...
dotnet publish "%project%" -c %config% --no-build -o "%publish_dir%"
if %errorlevel% neq 0 (
    echo [ERROR] Publish failed.
    pause
    exit /b 1
)

echo.
echo [SUCCESS] Output: %publish_dir%\

pause

Method 3: Complete Build Pipeline

A full build script with versioning, build numbering, and artifact packaging:

@echo off
setlocal enabledelayedexpansion

:: Configuration
set "solution=MyApp.sln"
set "config=Release"
set "publish_dir=artifacts\publish"
set "package_dir=artifacts\packages"

:: Verify solution exists
if not exist "%solution%" (
    echo [ERROR] Solution file not found: %solution%
    exit /b 1
)

echo =============================================
echo   BUILD PIPELINE
echo =============================================
echo.

:: Version
set "version=1.0.0"
if exist VERSION (
    set /p "version=" < VERSION
)
set "build=0"
if exist BUILD_NUMBER (
    set /p "build=" < BUILD_NUMBER
)
set /a build+=1
>BUILD_NUMBER echo !build!

set "full_version=!version!.!build!"
echo   Version: !full_version!
echo.

:: Stage 1: Clean
echo [1/5] Cleaning...
if exist "%publish_dir%" rmdir /s /q "%publish_dir%"
if exist "%package_dir%" rmdir /s /q "%package_dir%"
mkdir "%publish_dir%"
mkdir "%package_dir%"
dotnet clean "%solution%" -c %config% --verbosity quiet >nul 2>&1
echo       Done.

:: Stage 2: Restore
echo [2/5] Restoring dependencies...
dotnet restore "%solution%" --verbosity quiet
if !errorlevel! neq 0 (
    echo [FAIL] Restore failed.
    exit /b 1
)
echo       Done.

:: Stage 3: Build
echo [3/5] Building...
dotnet build "%solution%" -c %config% --no-restore /p:Version=!full_version! --verbosity minimal
if !errorlevel! neq 0 (
    echo [FAIL] Build failed.
    exit /b 1
)
echo       Done.

:: Stage 4: Test
echo [4/5] Running tests...
dotnet test "%solution%" -c %config% --no-build --verbosity minimal --logger "trx;LogFileName=results.trx"
if !errorlevel! neq 0 (
    echo [FAIL] Tests failed.
    exit /b 1
)
echo       Done.

:: Stage 5: Publish and Package
echo [5/5] Publishing and packaging...
dotnet publish "%solution%" -c %config% --no-build -o "%publish_dir%"
if !errorlevel! neq 0 (
    echo [FAIL] Publish failed.
    exit /b 1
)

set "zip_file=%package_dir%\MyApp_v!full_version!.zip"
powershell -NoProfile -Command ^
    "try { Compress-Archive -Path '%publish_dir%\*' -DestinationPath '!zip_file!' -Force -ErrorAction Stop; exit 0 }" ^
    "catch { Write-Host ('[ERROR] ' + $_.Exception.Message); exit 1 }"

if !errorlevel! neq 0 (
    echo [FAIL] Packaging failed.
    exit /b 1
)
echo       Done.

echo.
echo =============================================
echo   BUILD COMPLETE: v!full_version!
echo   Package: !zip_file!
echo =============================================

pause

Method 4: Multi-Project Solution Build

Build specific projects in a solution with different configurations:

@echo off
setlocal enabledelayedexpansion

set "config=Release"

:: Define projects as numbered entries
set "proj_count=3"
set "proj[0]=src\API\API.csproj|API"
set "proj[1]=src\Web\Web.csproj|Web Frontend"
set "proj[2]=src\Worker\Worker.csproj|Worker Service"

echo Building solution projects (%config%^)...
echo.

set "failed=0"
set /a last=proj_count - 1

for /L %%i in (0,1,!last!) do (
    for /f "tokens=1,2 delims=|" %%A in ("!proj[%%i]!") do (
        set /a step=%%i + 1
        echo [!step!/%proj_count%] %%B...

        if not exist "%%A" (
            echo         FAIL: project file not found
            set /a failed+=1
        ) else (
            dotnet build "%%A" -c %config% --verbosity quiet
            if !errorlevel! neq 0 (
                echo         FAIL
                set /a failed+=1
            ) else (
                echo         OK
            )
        )
    )
)

echo.
if !failed! gtr 0 (
    echo [ERROR] !failed! project(s^) failed to build.
    exit /b 1
) else (
    echo [SUCCESS] All projects built.
)

pause

Method 5: Using Visual Studio Developer Command Prompt

For projects that require the full Visual Studio build environment:

@echo off
setlocal enabledelayedexpansion

set "solution=MyApp.sln"
set "config=Release"

:: Try to find VsDevCmd.bat across common editions
set "vs_year=2022"
set "vs_found="

for %%E in (Community Professional Enterprise BuildTools) do (
    set "try_path=%ProgramFiles%\Microsoft Visual Studio\%vs_year%\%%E\Common7\Tools\VsDevCmd.bat"
    if exist "!try_path!" (
        set "vs_found=!try_path!"
    )
)

if not defined vs_found (
    echo [ERROR] Visual Studio %vs_year% not found (checked Community, Professional, Enterprise, BuildTools^).
    pause
    exit /b 1
)

:: Initialize VS Developer environment
echo Initializing Visual Studio environment...
call "!vs_found!" >nul 2>&1
echo   Using: !vs_found!
echo.

:: Now MSBuild and other tools are on PATH
msbuild "%solution%" /t:Rebuild /p:Configuration=%config% /v:minimal /nologo

if %errorlevel%==0 (
    echo.
    echo [SUCCESS] Build complete.
) else (
    echo.
    echo [ERROR] Build failed.
    exit /b 1
)

pause

Common Mistakes

The Wrong Way: Hardcoding MSBuild Path

:: WRONG - Path changes with every VS version
"C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\MSBuild\Current\Bin\MSBuild.exe" MyApp.sln

Output Concern: MSBuild's installation path changes with each Visual Studio edition and version. Hardcoding the path makes the script break on other machines. Use vswhere.exe to dynamically locate MSBuild, or use the dotnet CLI for .NET Core+ projects.

The Wrong Way: Not Restoring Packages First

:: WRONG - Build may fail due to missing NuGet packages
dotnet build MyApp.sln -c Release
:: error NU1... Package 'X' was not found

While dotnet build performs an implicit restore by default, explicit restore with dotnet restore before building ensures dependencies are resolved, especially in CI environments with separate restore and build steps. Use --no-restore on the build step only after a confirmed successful restore.

Best Practices

  1. Use vswhere to find MSBuild: Never hardcode MSBuild paths.
  2. Separate restore/build/test/publish steps: Each stage should be independently verifiable.
  3. Pass version numbers to the build: Use /p:Version=x.y.z to embed version info in assemblies.
  4. Log build output: Use /fl (file logger) to capture detailed build logs.
  5. Fail fast: Check %errorlevel% after each step and abort the pipeline on first failure.

Conclusion

Compiling .NET projects from a Batch Script is accomplished through either msbuild.exe for .NET Framework projects or dotnet build for modern .NET applications. By structuring the build as a multi-stage pipeline (clean, restore, build, test, publish, package), each step is independently verified and the pipeline fails fast on the first error. Dynamic MSBuild discovery via vswhere, version embedding through build properties, and artifact packaging complete the automation, producing consistent, reproducible builds suitable for any CI/CD environment.

Navigation