Build your own Hosted VSTS Agent Cloud: Part 2 – Deploy

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.



   $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,



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

A Variable Group contains all values I need for building and releasing my Agents

I’ve added a switch to the script to install the prerequisites needed for building the Packer image. I first install chocolatey and then use that to install Packer and Git. I also install the AzureRM PowerShell commands. Make sure to run this with an agent that is an administrator otherwise the chocolatey install fails:
if ($InstallPrerequisites) {

   "Installing prerequisites"

   Set-ExecutionPolicy Bypass -Scope Process -Force

   Invoke-Expression ((New-Object System.Net.WebClient).DownloadString(''))

   "Install Packer"

   choco install packer -y

   "Install Git"

   choco install git -y

   "Install AzureRM PowerShell commands"

   Install-PackageProvider -Name NuGet -MinimumVersion -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 ErrorActionErrorAction 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.




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 `


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.

To create a VMSS you need some networking resources in place. Create a Virtual Machine Scale Set with Azure PowerShell shows all the commands you need to create your network and initialize your VMSS. These are all standard Azure actions, so I won’t discuss those here. One important step that I do want to highlight is getting a reference to the image that you’ve build and setting it as the image for your VMSS:

$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 $

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://$$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.


de Kort, W. (2018). Build your own Hosted VSTS Agent Cloud: Part 2 –Deploy. Available at: [Accessed 3 January 2019].

Share this on...

Rate this Post: