How to Set Up a Build Server with MSBuild on Windows Server 2012 R2
MSBuild (Microsoft Build Engine) is the foundational build system for .NET and C++ projects on the Windows platform. Setting up a dedicated MSBuild build server on Windows Server 2012 R2 enables your team to perform automated, reproducible builds of Visual Studio solutions and projects without requiring Visual Studio to be installed on the build machine. This is critical for CI/CD pipelines where build reproducibility, isolation, and speed are paramount. This guide covers installing the build tools, configuring the build environment, writing build scripts, integrating with source control, and structuring a complete automated build workflow.
Prerequisites
- Windows Server 2012 R2 with 4 GB+ RAM (8 GB recommended for large solutions)
- PowerShell 4.0
- At least 20 GB free disk space for build tools, NuGet packages, and build artifacts
- Network access to your source control server (Git, TFS, or SVN)
- NuGet.exe for package restoration
Step 1: Install .NET Framework Build Tools
The Microsoft Build Tools package installs MSBuild, the C# and VB compilers, and supporting targets without the full Visual Studio IDE. Download the build tools from Microsoft:
Invoke-WebRequest -Uri "https://aka.ms/vs/15/release/vs_buildtools.exe" -OutFile "C:Tempvs_buildtools.exe"
Install with the workloads needed for .NET Framework development (MSBuild, .NET Framework build tools, and NuGet targets):
Start-Process -FilePath "C:Tempvs_buildtools.exe" -ArgumentList `
"--quiet --wait --norestart --nocache",
"--add Microsoft.VisualStudio.Workload.MSBuildTools",
"--add Microsoft.VisualStudio.Workload.NetFrameworkBuildTools",
"--add Microsoft.Component.MSBuild",
"--add Microsoft.Net.Component.4.7.2.SDK",
"--add Microsoft.Net.Component.4.6.2.TargetingPack",
"--add Microsoft.Net.Component.4.5.2.TargetingPack" `
-Wait -PassThru
Alternatively, if building against .NET 4.5.2 (native to WS2012 R2), MSBuild is already available at the .NET Framework path:
$env:MSBuildPath = "C:WindowsMicrosoft.NETFramework64v4.0.30319MSBuild.exe"
& $env:MSBuildPath /version
Step 2: Set MSBuild Environment Variables
Create consistent environment variables so build scripts work regardless of installed version location:
# Find the most recent MSBuild installed
$msbuildPaths = @(
"C:Program Files (x86)Microsoft Visual Studio2017BuildToolsMSBuild15.0BinMSBuild.exe",
"C:Program Files (x86)MSBuild14.0BinMSBuild.exe",
"C:WindowsMicrosoft.NETFramework64v4.0.30319MSBuild.exe"
)
$msbuildExe = $msbuildPaths | Where-Object { Test-Path $_ } | Select-Object -First 1
if ($msbuildExe) {
[System.Environment]::SetEnvironmentVariable("MSBUILD_EXE", $msbuildExe, "Machine")
Write-Host "MSBuild found at: $msbuildExe"
& $msbuildExe /version
} else {
Write-Error "MSBuild not found. Install .NET Framework or Build Tools."
}
Step 3: Install NuGet for Package Restoration
Modern .NET projects use NuGet packages that must be restored before building. Download the NuGet CLI:
New-Item -ItemType Directory -Path "C:ToolsNuGet" -Force
Invoke-WebRequest -Uri "https://dist.nuget.org/win-x86-commandline/latest/nuget.exe" -OutFile "C:ToolsNuGetnuget.exe"
$path = [System.Environment]::GetEnvironmentVariable("Path","Machine")
[System.Environment]::SetEnvironmentVariable("Path","$path;C:ToolsNuGet","Machine")
Verify NuGet:
nuget help | Select-Object -First 3
Step 4: Install Git for Source Checkout
Install Git for Windows to enable source code checkout as part of the build process:
choco install git -y --params '"/GitOnlyOnPath /NoGitLfs"'
Or manually install from a downloaded MSI. Configure Git with build service account credentials:
git config --global user.email "[email protected]"
git config --global user.name "Build Server"
git config --global credential.helper wincred
Step 5: Create the Build Script
Write a comprehensive PowerShell build script that handles checkout, NuGet restore, compilation, testing, and artifact packaging:
# Build.ps1 - MSBuild automated build script
param(
[string]$SolutionPath = "C:BuildsMySolutionMySolution.sln",
[string]$Configuration = "Release",
[string]$Platform = "Any CPU",
[string]$ArtifactDir = "C:BuildsArtifacts",
[string]$BuildNumber = (Get-Date -Format "yyyyMMdd.HHmmss")
)
$ErrorActionPreference = "Stop"
$MSBuild = $env:MSBUILD_EXE
function Write-Step { param([string]$Msg) Write-Host "`n=== $Msg ===" -ForegroundColor Cyan }
function Exit-OnError { if ($LASTEXITCODE -ne 0) { throw "Step failed with exit code $LASTEXITCODE" } }
Write-Step "Starting Build $BuildNumber"
Write-Host "Solution : $SolutionPath"
Write-Host "Config : $Configuration"
Write-Host "MSBuild : $MSBuild"
Write-Step "Restoring NuGet Packages"
nuget restore $SolutionPath -NonInteractive
Exit-OnError
Write-Step "Cleaning Previous Build Output"
& $MSBuild $SolutionPath `
/t:Clean `
/p:Configuration=$Configuration `
/p:Platform=$Platform `
/nologo /verbosity:minimal
Exit-OnError
Write-Step "Compiling Solution"
& $MSBuild $SolutionPath `
/t:Build `
/p:Configuration=$Configuration `
/p:Platform=$Platform `
/p:BuildNumber=$BuildNumber `
/nologo /verbosity:normal `
/maxcpucount `
/fl /flp:"logfile=C:BuildsLogsbuild_$BuildNumber.log;verbosity=detailed"
Exit-OnError
Write-Step "Collecting Artifacts"
New-Item -ItemType Directory -Path "$ArtifactDir$BuildNumber" -Force
Get-ChildItem -Path (Split-Path $SolutionPath) -Recurse -Include "*.exe","*.dll" |
Where-Object { $_.FullName -like "*bin$Configuration*" -and $_.FullName -notlike "*Test*" } |
Copy-Item -Destination "$ArtifactDir$BuildNumber" -Force
Write-Step "Build $BuildNumber Complete"
Write-Host "Artifacts at: $ArtifactDir$BuildNumber"
Step 6: Create a Scheduled Build Task
For nightly builds, create a scheduled task that runs the build script automatically:
$action = New-ScheduledTaskAction `
-Execute "powershell.exe" `
-Argument "-NonInteractive -ExecutionPolicy Bypass -File C:BuildScriptsBuild.ps1 -Configuration Release"
$trigger = New-ScheduledTaskTrigger -Daily -At "01:00AM"
$settings = New-ScheduledTaskSettingsSet `
-ExecutionTimeLimit (New-TimeSpan -Hours 2) `
-RestartCount 1 `
-RestartInterval (New-TimeSpan -Minutes 5)
Register-ScheduledTask `
-TaskName "NightlyBuild" `
-Action $action `
-Trigger $trigger `
-Settings $settings `
-RunLevel Highest `
-User "DOMAINbuild_svc" `
-Password "ServicePassword"
Step 7: Configure Build Notifications
Send email notifications on build completion or failure using PowerShell’s Send-MailMessage:
function Send-BuildNotification {
param([string]$BuildNumber, [bool]$Success, [string]$LogPath)
$status = if ($Success) { "SUCCESS" } else { "FAILED" }
$body = "Build $BuildNumber $status`n`nLog: $LogPath`n`nServer: $env:COMPUTERNAME"
Send-MailMessage `
-To "[email protected]" `
-From "[email protected]" `
-Subject "[$status] Build $BuildNumber" `
-Body $body `
-SmtpServer "smtp.yourdomain.com"
}
Step 8: Verify the Build Environment
# Verify all required tools are accessible
Write-Host "MSBuild: $(& $env:MSBUILD_EXE /version 2>&1 | Select-Object -First 1)"
Write-Host "NuGet : $(nuget help 2>&1 | Select-Object -First 1)"
Write-Host "Git : $(git --version)"
Write-Host ".NET : $([System.Runtime.InteropServices.RuntimeEnvironment]::GetSystemVersion())"
Summary
Your Windows Server 2012 R2 machine is now configured as a dedicated MSBuild build server. It includes the .NET Framework build tools and MSBuild engine, NuGet for package restoration, Git for source checkout, a comprehensive parameterized build script, a scheduled nightly build task, and email notification on completion. This build server can compile any .NET Framework solution targeting versions 4.5.2 through 4.7.2, supporting the full range of enterprise WS2012 R2 application workloads. The build server integrates naturally with Jenkins, GitHub Actions runners, or any CI system that can invoke PowerShell scripts.