In Part 1 you’ve seen how to use Packer to build a custom image based on a Packer configuration file with an Azure builder and create a new VM from the portal. In this blog post I’ll show you how to script a build and deployment based on your custom image and how to deploy the VSTS agent to it.
Scripting your Packer build
On GitHub you can find the complete build script that I use. Here I just want to discuss how the script works.
First, I define all the input values I need to run packer build.
[CmdletBinding()] Param( $Location = $env:Location, $PackerFile = $env:Packerfile, $ClientId = $env:ClientId, $ClientSecret = $env:ClientSecret, $TenantId = $env:TenantId, $SubscriptionId = $env:SubscriptionId, $ObjectId = $env:ObjectId, $ManagedImageResourceGroupName = $env:ManagedImageResourceGroupName, $ManagedImageName = $env:ManagedImageName, [switch]$InstallPrerequisites )
In VSTS I created a Variable Group that contains all values I need to build and release my Hosted Agent. This makes it easy to share the variables between different build and release definitions. I can choose to use the variables from the environment variables created by VSTS or I can directly pass them to the script.

A Variable Group contains all values I need for building and releasing my Agents
if ($InstallPrerequisites) { "Installing prerequisites" Set-ExecutionPolicy Bypass -Scope Process -Force Invoke-Expression ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1')) "Install Packer" choco install packer -y "Install Git" choco install git -y "Install AzureRM PowerShell commands" Install-PackageProvider -Name NuGet -MinimumVersion 2.8.5.201 -Force Install-Module AzureRM -AllowClobber -Force Import-Module AzureRM }
The next step is removing the previous image to make room for the new one. I just check if the resource group where the image is stored exists and if it does I remove it. For this I use a handy PowerShell trick where I set both ErrorVariable and ErrorAction. ErrorAction SilentlyContinue makes sure that if Get-AzureRmResourceGroup throws an error because it can’t find the resource group, my script continues without showing an error. Instead it sets notPresent to the error text. I can then check if the notPresent variable is empty meaning that there was no error, the resource group exists, and I can remove it.
Get-AzureRmResourceGroup -Name $ManagedImageResourceGroupName -ErrorVariable notPresent -ErrorAction SilentlyContinue if ( -Not $notPresent) { "Cleaning up previous image versions" Remove-AzureRmImage -ResourceGroupName $ManagedImageResourceGroupName -ImageName $ManagedImageName -Force }
The following snippet is to make sure that after installing chocolatey I’m in the correct directory where I want to build my image. If $env:BUILD_REPOSITORY_LOCALPATH is set, I’m running a build through VSTS and I want to set my current working folder to it.
if ($env:BUILD_REPOSITORY_LOCALPATH) { Set-Location $env:BUILD_REPOSITORY_LOCALPATH }
The VSTS Packer build creates a folder on the VM where it stores the commit id in a text file. This helps you easily check which version of the agent software a VM is running:
$commitId = $(git log --pretty=format:'%H' -n 1) "CommitId: $commitId"
And then we’re finally ready to call packer build. I pass in all the arguments on the command line instead of using a configuration file. This allows me to store the configuration values in VSTS and makes the whole script more flexible.
packer build ` -var "commit_id=$commitId" ` -var "client_id=$ClientId" ` -var "client_secret=$ClientSecret" ` -var "tenant_id=$TenantId" ` -var "subscription_id=$SubscriptionId" ` -var "object_id=$ObjectId" ` -var "location=$Location" ` -var "managed_image_resource_group_name=$ManagedImageResourceGroupName" ` -var "managed_image_name=$ManagedImageName" ` -on-error=abort ` $PackerFile
And that’s it. After running this script, you have your image.
Deploying your custom image
I’ve created a Release PowerShell script that you can find on GitHub. The script consists of two parts: VM deployment and VSTS Agent installation.
I’ve chosen to deploy my Virtual Machines to a Virtual Machine Scale Set. A VMSS helps with automatically scaling the number of Agents I want. By default, the script only deploys one Agent but with a simple command you can scale the agents up and down.
$vmssConfig = New-AzureRmVmssConfig ` -Location $Location ` -SkuCapacity 1 ` -SkuName "Standard_DS4_v2" ` -UpgradePolicyMode Automatic $image = Get-AzureRMImage -ImageName $imageName -ResourceGroupName $ManagedImageResourceGroupName Set-AzureRmVmssStorageProfile $vmssConfig ` -OsDiskCreateOption FromImage ` -ManagedDisk StandardLRS ` -OsDiskCaching "None" ` -OsDiskOsType Windows ` -ImageReferenceId $image.id
After creating the VMSS you have a Scale Set with a single VM based on the custom image you’ve build with Packer. The next step is to install the VSTS Agent on the VMs and connect them to your VSTS account.
Installing the VSTS Agent
I’ve created a PowerShell script AddAgentToVM that takes your VSTS account and a Personal Access Token as parameters. It also asks for the user and credentials under which it should run the Agent. I’ve mapped these to the administrator account that’s also used to install all software on the VM.
To run the script on the newly created VM I use Add-AzureRmVmssExtension. This command lets you specify a script that’s stored in a Storage Account on Azure and that you want to run on your VMs. Uploading the AddAgentToVM to an Azure Storage Account is easy. I can then run it on the VM:
$publicSettings = @{ "fileUris" = @("https://$StorageAccountName.blob.core.windows.net/$ContainerName/$FileName"); "commandToExecute" = "PowerShell -ExecutionPolicy Unrestricted .\$FileName -VSTSToken $VSTSToken -VSTSUrl $VSTSUrl -windowsLogonAccount $VMUser -windowsLogonPassword $VMUserPassword"; }; Add-AzureRmVmssExtension -VirtualMachineScaleSet $vmss ` -Name "VSTS_Agent_Install" ` -Publisher "Microsoft.Compute" ` -Type "CustomScriptExtension" ` -TypeHandlerVersion 1.8 ` -ErrorAction Stop ` -Setting $publicSettings
What’s next
And that’s it. You now have a build script that creates your custom image and a release script that creates a VM Scale Set and installs the VSTS agent on it. In the next blog post I’ll show you how to setup a Git repository and Build and Release definitions that fully automate building, deploying, scaling and managing your pool of Hosted Agents.
About the Author:
My name is Wouter de Kort. I live in Groningen, the Netherlands with my wife and our rabbits Donald and Katrien. I became interested in computers when my dad came home with an old 286 monochrome laptop when I was 6. After finding my way around MS-DOS, Windows and playing my first game of Solitaire I became interested in software development. My first programming language was Quick Basic and I managed to write a program that helped you practice the multiplication tables. All with ASCII art of course!
Now this is all a couple of years behind me. In the meantime, I’ve learned other things like Visual Basic, C++, JavaScript and C#. I work for Sogeti here in the Netherlands as a Principal Consultant. I focus on Application Lifecycle Management, Agile and DevOps using products such as Team Foundation Server and Visual Studio Team Services.
I think I’m a fairly good developer. I love to learn new things and share that with others. My focus is on Agile, DevOps and Application Lifecycle Management techniques. I’m one of the Microsoft ALM Rangers, a Microsoft MVP Visual Studio and Development Technologies and the author of DevOps on the Microsoft Stack. I also wrote some other books and I try to speak regularly at all kinds of conferences. If you want to get in touch, just for a chat or because I can help you or your company with something, you can contact me through Twitter.
Reference:
de Kort, W. (2018). Build your own Hosted VSTS Agent Cloud: Part 2 –Deploy. Available at: https://wouterdekort.com/2018/02/27/build-your-own-hosted-vsts-agent-cloud-part-2-deploy/ [Accessed 3 January 2019].