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 }