Skip to content

Instantly share code, notes, and snippets.

@ababushk
Last active October 8, 2025 17:12
Show Gist options
  • Save ababushk/dfedfc1927c9245c67835ad1642e9504 to your computer and use it in GitHub Desktop.
Save ababushk/dfedfc1927c9245c67835ad1642e9504 to your computer and use it in GitHub Desktop.
Minimal VS Build Tools installation for container images
{
"version": "1.0",
"components": [
"Microsoft.Component.MSBuild",
"Microsoft.VisualStudio.Component.CoreBuildTools",
"Microsoft.VisualStudio.Workload.MSBuildTools",
"Microsoft.VisualStudio.Component.VC.Tools.x86.x64",
"Microsoft.VisualStudio.Component.VC.Redist.14.Latest",
"Microsoft.VisualStudio.Component.Windows11SDK.22000",
"Microsoft.VisualStudio.Component.VC.14.42.17.12.x86.x64",
"Microsoft.VisualStudio.Component.VC.14.42.17.12.x86.x64.Spectre",
"Microsoft.VisualStudio.Component.VC.14.42.17.12.ATL",
"Microsoft.VisualStudio.Component.VC.14.42.17.12.ATL.Spectre",
"Microsoft.VisualStudio.Component.VC.14.42.17.12.CLI.Support"
],
"extensions": []
}

The script is intended to be used for Windows container images for GitHub Actions builds. It installs Build Tools with the components specified in .vsconfig file, and removes everything that is not required at runtime, except files needed for vswhere to work (need for ilammy/msvc-dev-cmd GitHub action)

Usage

  1. Edit .vsconfig accordingly. You can also export it from existing Visual Tools installation, or use this list https://learn.microsoft.com/en-us/visualstudio/install/workload-component-id-vs-build-tools . MSBuild, CoreBuildTools, and RoslynCompiler will be installed anyway.
  2. Run the script:
Install-BuildTools.ps1 -InstallPath C:\BuildTools `
  -VsConfigPath C:\TEMP\.vsconfig `
  -LogsPath C:\vslogs.zip `
  -BuildToolsUri https://aka.ms/vs/17/release/vs_BuildTools.exe
FROM mcr.microsoft.com/windows/servercore:ltsc2022
# Install Visual Studio Build Tools
# Inspired by https://github.com/microsoft/vs-dockerfiles/tree/main/native-desktop and
# https://learn.microsoft.com/en-us/visualstudio/install/advanced-build-tools-container?view=vs-2022
# Pin Build Tools version to avoid surprises after image re-builds.
# Links to specific versions of Build Tools are available here: https://learn.microsoft.com/en-us/visualstudio/releases/2022/release-history
ENV BUILDTOOLS_URI="https://download.visualstudio.microsoft.com/download/pr/286c67ca-51f4-409d-ade8-3036a5184667/a8a9a3b82f278f504156a940dcfd5619e9f214eb7e9071c5f5571a0f8baa94f3/vs_BuildTools.exe"
# .vsconfig can be exported from your Windows laptop's Build Tools installation
# (there's an option in the GUI).
COPY .vsconfig C:\TEMP\.vsconfig
# If image build fails on BuildTools installation, do:
# docker cp <intermediate_container_id>:C:\vslogs.zip .\vslogs.zip
# to study the logs
COPY Install-BuildTools.ps1 C:\TEMP\Install-BuildTools.ps1
RUN pwsh -Command "Set-MpPreference -DisableRealtimeMonitoring $true"; `
C:\TEMP\Install-BuildTools.ps1 -InstallPath C:\BuildTools `
-VsConfigPath C:\TEMP\.vsconfig `
-LogsPath C:\vslogs.zip `
-BuildToolsUri $Env:BUILDTOOLS_URI
param(
[Parameter(Mandatory=$true)]
[string]$VsConfigPath = "C:\TEMP\.vsconfig",
[Parameter(Mandatory=$false)]
[string]$LogsPath = "C:\vslogs.zip",
[Parameter(Mandatory=$true)]
[string]$InstallPath = "C:\BuildTools",
[Parameter(Mandatory=$true)]
[string]$BuildToolsURI
)
$ErrorActionPreference = 'Stop'
Write-Host "Installing Visual Studio Build Tools..." -ForegroundColor Green
$InstallerPath = "C:\TEMP\vs_buildtools.exe"
$vsCachePath = "C:\vs_cache"
# We _have to_ remove the cache (it takes too much space in the image),
# so the commands below are supposed to fail on errors like "file is being used by another process".
# BUT we have to leave SOME directories, otherwise `vswhere` won't be able
# to find the BuildTools installation, see https://developercommunity.visualstudio.com/t/bug-remove-download-cache-after-installed/362186
# So it's technically not just a cache, but Microsoft be Microsoft...
function Clean-Cache {
Write-Host "Cleaning up Visual Studio cache at $vsCachePath..." -ForegroundColor Yellow
try {
# Remove everything except the '_Instances' directory
Get-ChildItem -Path $vsCachePath | Where-Object { $_.Name -ne '_Instances' } | ForEach-Object {
Remove-Item $_.FullName -Recurse -Force -ErrorAction Stop
}
Write-Host "Cache cleaned up successfully." -ForegroundColor Green
} catch {
Write-Warning "Failed to clean up cache: $_"
}
}
function Install-VsWhere {
$vsWhereUrl = "https://github.com/microsoft/vswhere/releases/download/3.1.7/vswhere.exe"
$vsWherePath = "$InstallPath\vswhere.exe"
Write-Host "Downloading vswhere from $vsWhereUrl..." -ForegroundColor Yellow
Invoke-WebRequest -Uri $vsWhereUrl -OutFile $vsWherePath -UseBasicParsing
Write-Host "vswhere downloaded to $vsWherePath" -ForegroundColor Green
[Environment]::SetEnvironmentVariable('PATH', "$installPath;" + `
[Environment]::GetEnvironmentVariable('PATH', 'Machine'), 'Machine')
}
try {
# Download Build Tools installer
Write-Host "Downloading Visual Studio Build Tools from $BuildToolsURI..." -ForegroundColor Yellow
Invoke-WebRequest -Uri $BuildToolsURI -OutFile $InstallerPath -UseBasicParsing
Write-Host "Download completed." -ForegroundColor Green
# Run Visual Studio Build Tools installer
Write-Host "Running Visual Studio Build Tools installer..." -ForegroundColor Yellow
# Don't use `--nocache` - installation takes too long
$process = Start-Process -FilePath $InstallerPath -ArgumentList @(
"--quiet",
"--wait",
"--norestart",
"--path", "cache=$vsCachePath",
"--installPath", $InstallPath,
"--config", $VsConfigPath
) -Wait -PassThru -NoNewWindow
$exitCode = $process.ExitCode
# 3010 means "restart required" - treat as success
if ($exitCode -eq 3010 -or $exitCode -eq 0) {
Write-Host "Build Tools installed successfully" -ForegroundColor Green
# vswhere.exe is usually installed in "C:\Program Files (x86)\Microsoft Visual Studio\Installer",
# but we don't want to keep the whole installed in the image, so install it separately
Install-VsWhere
# Cleanup
Remove-Item $InstallerPath -Force -ErrorAction Stop
Remove-Item "C:\Program Files (x86)\Microsoft Visual Studio\Installer" -Recurse -Force -ErrorAction Stop
Clean-Cache
exit 0
} else {
# Installation failed, and we don't care about the image size - it should not be used
Write-Warning "Build Tools installation failed with exit code: $exitCode"
# Try to collect diagnostic logs
Write-Host "Downloading collect.exe for diagnostic logs..." -ForegroundColor Yellow
try {
Invoke-WebRequest -Uri "https://aka.ms/vscollect.exe" -OutFile "C:\TEMP\collect.exe" -UseBasicParsing
Write-Host "Running collect.exe..." -ForegroundColor Yellow
Start-Process -FilePath "C:\TEMP\collect.exe" -ArgumentList @("-zip:$LogsPath") -Wait
Write-Host "Installation logs saved to $LogsPath" -ForegroundColor Yellow
} catch {
Write-Warning "Failed to download or run collect.exe: $_"
}
throw "Installation failed with exit code: $exitCode"
}
} catch {
Write-Error "Failed to run Build Tools installer: $_"
exit 1
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment