name: vs2022
variables:
- name: vs_edition
value: "Community"
required: false
- name: vs_iso_path
value: ""
required: false
- name: vs_workloads
value: "Microsoft.VisualStudio.Workload.ManagedDesktop;Microsoft.VisualStudio.Workload.NetWeb"
required: false
- name: vs_components
value: ""
required: false
- name: vs_languages
value: "en-US"
required: false
- name: vs_install_path
value: "C:\\Program Files\\Microsoft Visual Studio\\2022\\Community"
required: false
- name: include_recommended
value: "true"
required: false
- name: include_optional
value: "false"
required: false
- name: dev_drive_letter
value: ""
required: false
- name: vs_cache_path
value: ""
required: false
fodder:
- name: install-vs-2022
type: shellscript
filename: install-vs.ps1
content: |
Write-Host "Starting Visual Studio {{ vs_edition }} 2022 installation process..."
function Test-CommandExists {
param($Command)
try {
if (Get-Command $Command -ErrorAction Stop) {
return $true
}
} catch {
return $false
}
return $false
}
function Find-VSInstallerOnISO {
param([string]$Edition)
Write-Host "Searching for Visual Studio $Edition installer on mounted drives..."
$drivesToCheck = @(Get-Volume | Where-Object { $_.DriveType -eq 'CD-ROM' -and $_.DriveLetter } |
ForEach-Object { "$($_.DriveLetter):" })
foreach ($drive in $drivesToCheck) {
if (Test-Path $drive) {
Write-Host "Checking drive $drive..."
$installerPaths = @(
"$drive\vs_$($Edition.ToLower()).exe",
"$drive\vs_community.exe",
"$drive\vs_professional.exe",
"$drive\vs_enterprise.exe",
"$drive\vs_setup.exe",
"$drive\setup.exe",
"$drive\vs_installer\vs_$($Edition.ToLower()).exe"
)
foreach ($path in $installerPaths) {
if (Test-Path $path) {
Write-Host "Found VS installer at: $path"
return $path
}
}
}
}
return $null
}
function Mount-VsISO {
param($IsoPath)
if ([string]::IsNullOrWhiteSpace($IsoPath)) {
return $null
}
if (!(Test-Path $IsoPath)) {
Write-Host "WARNING: Provided ISO path does not exist: $IsoPath"
return $null
}
Write-Host "Mounting ISO from: $IsoPath"
try {
$mount = Mount-DiskImage -ImagePath $IsoPath -PassThru
$driveLetter = ($mount | Get-Volume).DriveLetter
if ($driveLetter) {
$installerPath = "${driveLetter}:\vs_$($Edition.ToLower()).exe"
if (!(Test-Path $installerPath)) {
$installerPath = "${driveLetter}:\vs_setup.exe"
}
if (!(Test-Path $installerPath)) {
$installerPath = "${driveLetter}:\setup.exe"
}
if (Test-Path $installerPath) {
Write-Host "Found installer at: $installerPath"
return $installerPath
} else {
Write-Host "No VS installer found on mounted ISO"
Dismount-DiskImage -ImagePath $IsoPath
return $null
}
}
} catch {
Write-Host "Error mounting ISO: $_"
return $null
}
return $null
}
function Download-VSBootstrapper {
param([string]$Edition)
Write-Host "Downloading Visual Studio $Edition 2022 bootstrapper..."
$editionUrls = @{
"Community" = "https://aka.ms/vs/17/release/vs_community.exe"
"Professional" = "https://aka.ms/vs/17/release/vs_professional.exe"
"Enterprise" = "https://aka.ms/vs/17/release/vs_enterprise.exe"
}
if (-not $editionUrls.ContainsKey($Edition)) {
Write-Host "ERROR: Unknown edition '$Edition'. Valid editions: Community, Professional, Enterprise"
return $null
}
$bootstrapperUrl = $editionUrls[$Edition]
$bootstrapperPath = "$env:TEMP\vs_$($Edition.ToLower()).exe"
try {
Invoke-WebRequest -Uri $bootstrapperUrl -OutFile $bootstrapperPath -UseBasicParsing
if (Test-Path $bootstrapperPath) {
Write-Host "Bootstrapper downloaded to: $bootstrapperPath"
return $bootstrapperPath
}
} catch {
Write-Host "Error downloading bootstrapper: $_"
return $null
}
return $null
}
function Build-InstallArguments {
param(
[string]$InstallPath,
[string]$Workloads,
[string]$Components,
[string]$Languages,
[bool]$IncludeRecommended,
[bool]$IncludeOptional,
[string]$CachePath
)
$args = @(
"--quiet",
"--wait",
"--norestart",
"--installPath", "`"$InstallPath`""
)
if (![string]::IsNullOrWhiteSpace($Workloads)) {
$workloadList = $Workloads -split ';'
foreach ($workload in $workloadList) {
$workload = $workload.Trim()
if (![string]::IsNullOrWhiteSpace($workload)) {
$args += "--add", $workload
}
}
}
if (![string]::IsNullOrWhiteSpace($Components)) {
$componentList = $Components -split ';'
foreach ($component in $componentList) {
$component = $component.Trim()
if (![string]::IsNullOrWhiteSpace($component)) {
$args += "--add", $component
}
}
}
if (![string]::IsNullOrWhiteSpace($Languages)) {
$languageList = $Languages -split ';'
foreach ($language in $languageList) {
$language = $language.Trim()
if (![string]::IsNullOrWhiteSpace($language)) {
$args += "--addProductLang", $language
}
}
}
if ($IncludeRecommended) {
$args += "--includeRecommended"
}
if ($IncludeOptional) {
$args += "--includeOptional"
}
if (![string]::IsNullOrWhiteSpace($CachePath)) {
$args += "--path", "cache=`"$CachePath`""
}
return $args
}
$installed = $false
$installerPath = $null
$edition = "{{ vs_edition }}"
$isoPath = "{{ vs_iso_path }}"
$workloads = "{{ vs_workloads }}"
$components = "{{ vs_components }}"
$languages = "{{ vs_languages }}"
$installPath = "{{ vs_install_path }}"
$includeRecommended = [bool]::Parse("{{ include_recommended }}")
$includeOptional = [bool]::Parse("{{ include_optional }}")
if ($installPath -like "*\Community") {
$installPath = $installPath -replace "Community", $edition
}
if (Test-Path "$installPath\Common7\IDE\devenv.exe") {
Write-Host "Visual Studio is already installed at: $installPath"
$installed = $true
}
if (!$installed) {
if (![string]::IsNullOrWhiteSpace($isoPath)) {
Write-Host "Attempting to use provided ISO path..."
$installerPath = Mount-VsISO -IsoPath $isoPath
}
if (!$installerPath) {
Write-Host "Checking for auto-mounted ISO..."
$installerPath = Find-VSInstallerOnISO -Edition $edition
}
if (!$installerPath) {
Write-Host "No ISO found, downloading from internet..."
$installerPath = Download-VSBootstrapper -Edition $edition
}
if (!$installerPath) {
Write-Host "ERROR: Could not obtain Visual Studio installer"
exit 1
}
$cachePath = "{{ vs_cache_path }}".Trim()
if ($cachePath) {
$cacheRoot = [System.IO.Path]::GetPathRoot($cachePath)
if (-not $cacheRoot -or -not (Test-Path $cacheRoot)) {
Write-Host "WARNING: drive of vs_cache_path '$cachePath' not found, using the default package cache"
$cachePath = ""
}
}
$installArgs = Build-InstallArguments -InstallPath $installPath -Workloads $workloads -Components $components -Languages $languages -IncludeRecommended $includeRecommended -IncludeOptional $includeOptional -CachePath $cachePath
Write-Host "Starting Visual Studio installation..."
Write-Host "Installer: $installerPath"
Write-Host "Arguments: $($installArgs -join ' ')"
$logDir = "$env:TEMP\VSInstallLogs"
New-Item -ItemType Directory -Path $logDir -Force | Out-Null
try {
$process = Start-Process -FilePath $installerPath -ArgumentList $installArgs -Wait -PassThru -NoNewWindow
$exitCode = $process.ExitCode
if ($exitCode -eq 0) {
if (Test-Path "$installPath\Common7\IDE\devenv.exe") {
Write-Host "Visual Studio installation completed successfully"
$installed = $true
} else {
Write-Host "ERROR: the installer returned 0, but $installPath\Common7\IDE\devenv.exe does not exist"
}
} elseif ($exitCode -eq 3010) {
Write-Host "Visual Studio installation completed successfully (reboot required)"
$installed = $true
Write-Host "VS installation completed successfully but requires reboot."
Write-Host "Using exit code 1003 to reboot and continue cloud-init processing."
exit 1003
} elseif ($exitCode -eq 1641) {
Write-Host "Visual Studio installer initiated a restart"
exit 1003
} else {
Write-Host "Visual Studio installation failed with exit code: $exitCode"
}
if (-not $installed) {
$logs = Get-ChildItem -Path $env:TEMP -Filter "dd_*.log" -ErrorAction SilentlyContinue |
Sort-Object LastWriteTime -Descending | Select-Object -First 3
foreach ($log in $logs) {
Write-Host "----- $($log.FullName) (last 40 lines)"
Get-Content $log.FullName -Tail 40 | Write-Host
}
}
} catch {
Write-Host "Error during installation: $_"
}
if ($installerPath -like "*\Temp\*") {
Remove-Item $installerPath -Force -ErrorAction SilentlyContinue
}
if (![string]::IsNullOrWhiteSpace($isoPath) -and (Test-Path $isoPath)) {
try {
Dismount-DiskImage -ImagePath $isoPath -ErrorAction SilentlyContinue
} catch {
}
}
}
if ($installed) {
Write-Host "Visual Studio $edition 2022 installation completed!"
$vsPath = "$installPath\Common7\IDE"
if (Test-Path $vsPath) {
$currentPath = [System.Environment]::GetEnvironmentVariable("Path", "Machine")
if ($currentPath -notlike "*$vsPath*") {
[System.Environment]::SetEnvironmentVariable("Path", "$currentPath;$vsPath", "Machine")
Write-Host "Added Visual Studio to system PATH"
}
}
Write-Host "`nInstalled with workloads:"
if (![string]::IsNullOrWhiteSpace($workloads)) {
$workloads -split ';' | ForEach-Object { Write-Host " - $_" }
} else {
Write-Host " - Default workloads"
}
Write-Host "`n=== Configuring Dev Drive for Visual Studio ==="
$providedDevDrive = "{{ dev_drive_letter }}"
$devDriveLetter = $null
if (![string]::IsNullOrWhiteSpace($providedDevDrive)) {
Write-Host "Using explicitly provided Dev Drive letter: ${providedDevDrive}:"
$volume = Get-Volume -DriveLetter $providedDevDrive -ErrorAction SilentlyContinue
if ($volume) {
if ($volume.DriveType -eq 'Fixed') {
$devDriveLetter = $providedDevDrive
Write-Host " File System: $($volume.FileSystem)"
Write-Host " Label: $($volume.FileSystemLabel)"
Write-Host " Size: $([Math]::Round($volume.Size / 1GB, 2)) GB"
} else {
Write-Host "WARNING: Drive ${providedDevDrive}: is not a fixed drive. Skipping Dev Drive configuration."
}
} else {
Write-Host "WARNING: Drive ${providedDevDrive}: does not exist. Skipping Dev Drive configuration."
}
} else {
Write-Host "Auto-detecting Dev Drive..."
$volumes = Get-Volume | Where-Object { $_.DriveType -eq 'Fixed' -and $_.DriveLetter }
foreach ($volume in $volumes) {
$driveLetter = $volume.DriveLetter
$devDrvResult = cmd /c "fsutil devdrv query ${driveLetter}:" 2>&1
if ($devDrvResult -like "*trusted developer volume*") {
Write-Host "Found Dev Drive at ${driveLetter}:"
$devDriveLetter = $driveLetter
break
}
elseif ($volume.FileSystem -eq 'ReFS' -and $volume.FileSystemLabel -like '*Dev*') {
Write-Host "Found ReFS development drive at ${driveLetter}:"
$devDriveLetter = $driveLetter
break
}
}
}
if ($devDriveLetter) {
Write-Host "Configuring Visual Studio for Dev Drive at ${devDriveLetter}:"
$vsPaths = @(
"${devDriveLetter}:\source\repos",
"${devDriveLetter}:\source\projects",
"${devDriveLetter}:\source\templates\ProjectTemplates",
"${devDriveLetter}:\source\templates\ItemTemplates",
"${devDriveLetter}:\SymbolCache"
)
foreach ($path in $vsPaths) {
if (-not (Test-Path $path)) {
New-Item -ItemType Directory -Path $path -Force | Out-Null
Write-Host "Created: $path"
}
}
$configScript = @"
Write-Host 'Configuring Visual Studio 2022 for Dev Drive...'
Write-Host ''
Write-Host 'Please configure the following in Visual Studio:'
Write-Host '1. Go to Tools -> Options -> Projects and Solutions -> Locations'
Write-Host ' - Projects location: ${devDriveLetter}:\source\repos'
Write-Host ' - User project templates: ${devDriveLetter}:\source\templates\ProjectTemplates'
Write-Host ' - User item templates: ${devDriveLetter}:\source\templates\ItemTemplates'
Write-Host ''
Write-Host '2. Go to Tools -> Options -> Debugging -> Symbols'
Write-Host ' - Symbol cache directory: ${devDriveLetter}:\SymbolCache'
Write-Host ''
Write-Host 'Dev Drive provides ~25% better performance for builds and file operations.'
"@
$configScript | Out-File -FilePath "C:\ProgramData\configure-vs-devdrive.ps1" -Encoding UTF8 -Force
Write-Host "Configuration script saved to: C:\ProgramData\configure-vs-devdrive.ps1"
Write-Host "Run this script for each user to configure their VS settings."
} else {
Write-Host "No Dev Drive detected. Visual Studio will use default locations."
Write-Host "Consider creating a Dev Drive for better performance."
}
} else {
Write-Host "ERROR: Failed to install Visual Studio $edition 2022"
exit 1
}