<# .SYNOPSIS Install Visual Studio's 'v141_xp' XP toolkit on Github. .DESCRIPTION Update the existing Github Visual Studio installation in-place. This script will iterate through all VS installations identified by the `vswhere` utility. This script is Grey Magic. The `vs_installer` installer utility exits almost immediately if you run it from the command line. `vs_installer`'s subprocesses are reasonably well known, which allows this script to query the processes and wait for them to terminate. GitHub Actions does something strange with stdout and stderr, so you won't get any indication of failure or output that shows you that something is happening. The waiting code was adapted from the Chocolatey VS installer scripts (Wait-VSIstallerProcesses). Ref: https://github.com/jberezanski/ChocolateyPackages/blob/master/chocolatey-visualstudio.extension/extensions/Wait-VSInstallerProcesses.ps1 #> $exitcode = $null $vswhere = "${env:ProgramFiles} (x86)\Microsoft Visual Studio\Installer\vswhere" $vsinstaller = "${env:ProgramFiles} (x86)\Microsoft Visual Studio\Installer\vs_installer.exe" $vsInstallOut = "$env:TEMP\vsinstall-out.txt" $vsInstallErr = "$env:TEMP\vsinstall-err.txt" & ${vswhere} -property installationPath | Foreach-Object -process { # Save the installlation path for the current iteration $installPath = $_ # Make sure that previously running installers have all exited. Write-Debug ('Looking for previously running VS installer processes') $lazyQuitterProcessNames = @('vs_installershell', 'vs_installerservice') do { $lazyQuitterProcesses = Get-Process -Name $lazyQuitterProcessNames -ErrorAction SilentlyContinue | ` Where-Object { $null -ne $_ -and -not $_.HasExited } $lazyQuitterProcessCount = ($lazyQuitterProcesses | Measure-Object).Count if ($lazyQuitterProcessCount -gt 0) { try { $lazyQuitterProcesses | Sort-Object -Property Name, Id | ForEach-Object { '[{0}] {1}' -f $_.Id, $_.Name } | Write-Debug $lazyQuitterProcesses | Wait-Process -Timeout 10 -ErrorAction SilentlyContinue } finally { $lazyQuitterProcesses | ForEach-Object { $_.Dispose() } $lazyQuitterProcesses = $null } } } while ($lazyQuitterProcessCount -gt 0) ## Now kick off our install: ## ## --quiet: Don't open/show UI ## --force: Terminate any VS instances forcibly ## --norestart: Delay reboot after install, if needed ## --installWhileDownloading: Self-explanitory. ## ## Note: "--wait" doesn't. $vsinstallParams = @( "modify", "--installPath", "$installPath", "--add", "Microsoft.VisualStudio.Component.VC.v141.x86.x64", "--add", "Microsoft.VisualStudio.Component.WinXP", "--quiet", "--force", "--norestart", "--installWhileDownloading" ) & $vsInstaller $vsinstallParams 2> $vsInstallErr > $vsInstallOut ## VS installer processes for which we want to wait because installation isn't complete. Yet. $vsinstallerProcesses = @( 'vs_bootstrapper', 'vs_setup_bootstrapper', 'vs_installer', 'vs_installershell', ` 'vs_installerservice', 'setup') $vsInstallerProcessFilter = { $_.Name -ne 'setup' -or $_.Path -like '*\Microsoft Visual Studio\Installer*\setup.exe' } do { Write-Debug ('Looking for running VS installer processes') $vsInstallerRemaining = Get-Process -Name $vsinstallerProcesses -ErrorAction SilentlyContinue | ` Where-Object { $null -ne $_ -and -not $_.HasExited } | ` Where-Object $vsInstallerProcessFilter $vsInstallerProcessCount = ($vsInstallerRemaining | Measure-Object).Count if ($vsInstallerProcessCount -gt 0) { try { Write-Debug "Found $vsInstallerProcessCount running Visual Studio installer processes:" $vsInstallerRemaining | Sort-Object -Property Name, Id | ForEach-Object { '[{0}] {1}' -f $_.Id, $_.Name } | Write-Debug foreach ($p in $vsInstallerProcesses) { [void] $p.Handle # make sure we get the exit code http://stackoverflow.com/a/23797762/266876 } Write-Debug ('Waiting 60 seconds for process(es) to exit...') $vsInstallerRemaining | Wait-Process -Timeout 60 -ErrorAction SilentlyContinue foreach ($proc in $vsInstallerProcesses) { if (-not $proc.HasExited) { continue } if ($null -eq $exitCode) { $exitCode = $proc.ExitCode } if ($proc.ExitCode -ne 0 -and $null -ne $proc.ExitCode) { Write-Debug "$($proc.Name) process $($proc.Id) exited with code $($proc.ExitCode)" if ($exitCode -eq 0) { $exitCode = $proc.ExitCode } } } } finally { $vsInstallerRemaining | ForEach-Object { $_.Dispose() } $vsInstallerRemaining = $null } } } while (($vsInstallerStartup -and $vsInstallerStartCount -gt 0) -or $vsInstallerProcessCount -gt 0) } if ((Test-Path $vsInstallOut -PathType Leaf) -and (Get-Item $vsInstallOut).length -gt 0kb) { Write-Output "-- vsinstaller output:" Get-Content $vsInstallOut } else { Write-Debug "-- No vsinstaller output." } if ((Test-Path $vsInstallErr -PathType Leaf) -and (Get-Item $vsInstallErr).length -gt 0kb) { Write-Output "-- vsinstaller error output:" Get-Content $vsInstallErr } else { Write-Debug "-- No vsinstaller error/diag output." } if ($null -ne $exitcode -and $exitcode -ne 0) { throw "VS installer exited with non-zero exit status: $exitcode" }