One way to build a custom DevTest Labs artifact to install Visual Studio 2019.
The public artifacts repo for DevTest Labs includes a Visual Studio artifact. But it only supports VS2015 and VS2017. The easiest way to work around this is to use the Chocolatey artifact with the “visualstudio2019enterprise” target. This only installs the core editor workload though and the Chocolatey artifact doesn’t allow specifying options.
For my team’s environment, we have a specific set of workloads and individual components that are generally needed. Specifying these all on the command line is a lot of text and it’s easy to make mistakes.
The solution I took was to make a custom artifact that uses Chocolatey and passes in a config file that specifies all the workloads and individual components needed. Here are the steps I took.
Export the desired Visual Studio 2019 configuration
Get VS2019 configured the way you want first. Then open the Visual Studio Installer.
Click More next to VS2019 and select Export Configuration. Save the resulting .vsconfig file to your new artifact folder.
Create the artifact file
Since my custom artifact doesn’t need customization, I used a base Artifactfile.json file.
|
|
I use the same iconUri as the public Visual Studio artifact. The command calls a PowerShell script to install the Chocolatey package.
Write the install script
I started by copying the install script from the public Chocolatey artifact. Then I removed the script parameters and changed the Install-Packages function.
|
|
The config file is passed to the Visual Studio installer via the --config
option. But this needs
to be wrapped in --package-parameters
so Chocolatey will send it to the installer.
The full file is in this Gist for convenience:
################################################################################################### | |
# | |
# PowerShell configurations | |
# | |
# NOTE: Because the $ErrorActionPreference is "Stop", this script will stop on first failure. | |
# This is necessary to ensure we capture errors inside the try-catch-finally block. | |
$ErrorActionPreference = 'Stop' | |
# Suppress progress bar output. | |
$ProgressPreference = 'SilentlyContinue' | |
# Ensure we force use of TLS 1.2 for all downloads. | |
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 | |
# Expected path of the choco.exe file. | |
$choco = "$Env:ProgramData/chocolatey/choco.exe" | |
################################################################################################### | |
# | |
# Handle all errors in this script. | |
# | |
trap | |
{ | |
# NOTE: This trap will handle all errors. There should be no need to use a catch below in this | |
# script, unless you want to ignore a specific error. | |
$message = $Error[0].Exception.Message | |
if ($message) | |
{ | |
Write-Host -Object "`nERROR: $message" -ForegroundColor Red | |
} | |
Write-Host "`nThe artifact failed to apply.`n" | |
# IMPORTANT NOTE: Throwing a terminating error (using $ErrorActionPreference = "Stop") still | |
# returns exit code zero from the PowerShell script when using -File. The workaround is to | |
# NOT use -File when calling this script and leverage the try-catch-finally block and return | |
# a non-zero exit code from the catch block. | |
exit -1 | |
} | |
################################################################################################### | |
# | |
# Functions used in this script. | |
# | |
function Ensure-Chocolatey | |
{ | |
[CmdletBinding()] | |
param( | |
[string] $ChocoExePath | |
) | |
if (-not (Test-Path "$ChocoExePath")) | |
{ | |
Set-ExecutionPolicy Bypass -Scope Process -Force; iex ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1')) | |
if ($LastExitCode -eq 3010) | |
{ | |
Write-Host 'The recent changes indicate a reboot is necessary. Please reboot at your earliest convenience.' | |
} | |
} | |
} | |
function Ensure-PowerShell | |
{ | |
[CmdletBinding()] | |
param( | |
[int] $Version | |
) | |
if ($PSVersionTable.PSVersion.Major -lt $Version) | |
{ | |
throw "The current version of PowerShell is $($PSVersionTable.PSVersion.Major). Prior to running this artifact, ensure you have PowerShell $Version or higher installed." | |
} | |
} | |
function Install-Packages | |
{ | |
[CmdletBinding()] | |
param( | |
[string] $ChocoExePath | |
) | |
$configFilePath = Get-ChildItem .\cloudplat.vsconfig | % { $_.FullName } | |
$expression = "$ChocoExePath install -y -f --acceptlicense --no-progress --stoponfirstfailure visualstudio2019enterprise --package-parameters=`"--config $configFilePath`"" | |
Invoke-ExpressionImpl -Expression $expression | |
} | |
function Invoke-ExpressionImpl | |
{ | |
[CmdletBinding()] | |
param( | |
$Expression | |
) | |
# This call will normally not throw. So, when setting -ErrorVariable it causes it to throw. | |
# The variable $expError contains whatever is sent to stderr. | |
iex $Expression -ErrorVariable expError | |
# This check allows us to capture cases where the command we execute exits with an error code. | |
# In that case, we do want to throw an exception with whatever is in stderr. Normally, when | |
# Invoke-Expression throws, the error will come the normal way (i.e. $Error) and pass via the | |
# catch below. | |
if ($LastExitCode -or $expError) | |
{ | |
if ($LastExitCode -eq 3010) | |
{ | |
# Expected condition. The recent changes indicate a reboot is necessary. Please reboot at your earliest convenience. | |
} | |
elseif ($expError[0]) | |
{ | |
throw $expError[0] | |
} | |
else | |
{ | |
throw "Installation failed ($LastExitCode). Please see the Chocolatey logs in %ALLUSERSPROFILE%\chocolatey\logs folder for details." | |
} | |
} | |
} | |
################################################################################################### | |
# | |
# Main execution block. | |
# | |
try | |
{ | |
pushd $PSScriptRoot | |
Write-Host 'Configuring PowerShell session.' | |
Ensure-PowerShell -Version $PSVersionRequired | |
Enable-PSRemoting -Force -SkipNetworkProfileCheck | Out-Null | |
Write-Host 'Ensuring latest Chocolatey version is installed.' | |
Ensure-Chocolatey -ChocoExePath "$choco" | |
Write-Host "Preparing to install Visual Studio 2019 Enterprise." | |
Install-Packages -ChocoExePath "$choco" | |
Write-Host "`nThe artifact was applied successfully.`n" | |
} | |
finally | |
{ | |
popd | |
} |