How to deploy Azure Pipelines agent?

Piotr Rogala
6 min readApr 24, 2022

Azure DevOps allows you to deploy solutions called “Microsoft-hosted” agents for 1,800 minutes (30 hours) per month. When this time is exceeded, we cannot perform deployments. The only option is to buy unlimited access to the agent for $ 40 per month a minute or use your server or laptop after installing the agent — this deployment option is called “Self-Hosted” in the documentation. Let me just add that using your own laptop for deployment is not a production solution 🙃.

Azure DevOps pricing page:

I hear the question very often, why do I need my own server to deploy?

Classic answer: it depends … When doing a university diploma thesis or a PoC project for a friend, you can choose to have an agent on your laptop. If you work in a group of several people, it would be best to use your own server. If you are a company and provide solutions, you should definitely consider your own server or buy “Microsoft-hosted” for $ 40 — if the implementation time exceeds 1800 minutes per month. But there is one more situation when you choose your own server with agents. Many companies use private, controlled internal networks with limited external access. The implementation of your own server in your network will allow you to easily communicate with the implemented solutions on the Microsoft Azure platform, but you will also increase the security of the processed data. Remember that the “Microsoft-hosted” agent is a “public” agent isolated from your network. Self-Hosted can be deployed just like an isolated agent, but you can also connect it or deploy it on your own network.

Below, I refer you to the documentation where you can find out in detail what Microsoft offers in terms of Azure Pipelines agent.

MS Docs — Azure Pipelines agents

Let’s start implementing our own server with an agent.

I prepared illustrations related to the entire project for the implementation of Azure Pipelines agent based on this post and the necessary materials for its implementation.

Links — using these links you have access to my example implementation


Parameters 1

Variables description

  • adminUsername [User name]
  • adminPassword [Password for user]
  • dnsLabelPrefix (default set: generated based on ResourceGroup.Id) [Public DNS for connection RDP]
  • vmName [VM Name for resource]
  • AccessIPNSG [IP will be added to NSG rule for RDP access]
  • tag [default set: “Project: AzureDevOpsAgent”]
  • DiskForVM [StorageAccount or Managed — choose disk for deployment VM]

“parameters”: {
“adminUsername”: {
“value”: “test-user”
“adminPassword”: {
“value”: “test-P@ssw0rd12345”
“vmName”: {
“value”: “ado-agents”
“AccessIPNSG”: {
“value”: “”
“tag”: {
“value”: “AzureDevOpsAgent”
“DiskForVM”: {
“value”: “StorageAccount”

Parameters 2

createresourcegroup-adoagent.param.json — those parameters define a resource group.

“parameters”: {
“ResourceGroupLocation”: {
“value”: “westeurope”
“ResourceGroupName”: {
“value”: “vm-azure-devops-self-hosted-agents”
“tag”: {
“value”: {
“key1”: “Project”,
“value1”: “AzureDevOpsAgent”

Parameters 3

script-post-configuration.ps1 — the script is responsible for installing the agent and preparing the necessary packages. If you need to install additional packages, add them in the section: # Install Packages — the easiest way is to use packages choco:

Variables description

  • urlProjectADO [URL for your ADO project]
  • auth (default set: PAT) [Authentication method for your ADO]
  • token [Security token for your ADO]
  • pool (default set: default) [Pool name for agent in ADO]
  • agentname [default is same as VM name]
  • numberagents [default is 1 — you can deploy from 1 to 20 agents on one server]
  • instalAddtionalPackages [default is $true and is installing all what is needed for simple deployment to Azure — if you need .NET etc. please add manually]

[Parameter(Mandatory = $true)][string] $urlProjectADO,
[Parameter(Mandatory = $false)][string] $auth = “pat”,
[Parameter(Mandatory = $true)][string] $token,
[Parameter(Mandatory = $false)][string] $pool,
[Parameter(Mandatory = $false)][string] $agentname,
[Parameter(Mandatory = $false)][ValidateRange(1, 20)][int] $numberagents = 1,
[Parameter(Mandatory = $false)][bool] $instalAddtionalPackages = $true
begin {
process {
try {
if (!(Test-Path “c:\temp”)) { $null = mkdir “c:\temp” }
$log | % {
# Install Additional Packages
if ($instalAddtionalPackages) {
try {

# Install Packages
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
Install-PackageProvider -Name “Nuget” -Force
# FIX: WARNING: Not setting tab completion: Profile file does not exist at ‘C:\Users\USER\Documents\WindowsPowerShell\Microsoft.PowerShellISE_profile.ps1’.
if (!(Test-Path -Path $PROFILE)) {
New-Item -ItemType File -Path $PROFILE -Force
Set-ExecutionPolicy Bypass -Scope Process -Force; [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072; iex ((New-Object System.Net.WebClient).DownloadString(‘’))
choco feature enable -n allowGlobalConfirmation
choco install -y powershell-core
Start-Sleep 3
choco install -y azure-cli
Start-Sleep 3
choco install -y az.powershell
Start-Sleep 3
choco install -y azcopy
Start-Sleep 3
choco install -y iiscrypto
Start-Sleep 3
#Install AzureRM in Powershell 5.1
C:\Windows\system32\WindowsPowerShell\v1.0\powershell.exe -Command “Install-Module -Name AzureRM -AllowClobber -Force -Confirm:`$false”
# END Install Packages

catch {
throw $_
# Get latest version install agent
$getLatestVersion = Invoke-WebRequest -UseBasicParsing
$tag = ($getLatestVersion | ConvertFrom-Json)[0].tag_name
$tag = $tag.Substring(1)
$downloadInstallURL = “$tag/vsts-agent-win-x64-$"

# Creating Pool in Azure DevOps
$encodedPat = [System.Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes( “:$token”))
$body = “{name:`”$pool`”, autoProvision: `”true`”}”
if (!($pool -match “default” -or $pool -match “Azure Pipelines” -or $((Invoke-WebRequest -Method GET -Uri “$urlProjectADO/_apis/distributedtask/pools?api-version=5.1” -UseBasicParsing -Headers @{Authorization = “Basic $encodedPat” }).content | ? { $_ -like “*$pool*” }))) {
$tmp = $(Invoke-WebRequest -Method POST -Uri “$urlProjectADO/_apis/distributedtask/pools?api-version=5.0-preview.1” -UseBasicParsing -Headers @{Authorization = “Basic $encodedPat” } -Body $body -ContentType “application/json”) 2>$null

# Install agents
$filename = $urlProjectADO.Split(‘/’)[-1]
if (!(Test-Path “c:\temp\$filename”)) { Invoke-WebRequest -Uri $downloadInstallURL -OutFile “c:\temp\$filename” }

for ($i = 1; $i -le $numberagents; $i++) {
if (!(Test-Path “c:\agent-$i”)) { mkdir “c:\agent-$i” }else { rm c:\agent-$i -recurse -force; mkdir “c:\agent-$i” }
Add-Type -AssemblyName System.IO.Compression.FileSystem ; [System.IO.Compression.ZipFile]::ExtractToDirectory(“c:\temp\$filename”, “c:\agent-$i”)
.”c:\agent-$i\config.cmd” — unattended — url $urlProjectADO — auth $auth — token $token — pool $pool — agent “$agentname-$i” — runAsService
} | Out-File “c:\temp\ADOagent-$((Get-Date).ToString(“s”).Replace(“:”,”-”)).log”
Remove-Item -Recurse -Force c:\temp -Exclude *.log
catch {
throw $_
Write-Verbose “Successfully installed Azure DevOps Job Agents”


vm-azure-devops-self-hosted-agents-ci.yml — in this file define precisely the data needed to install the agents — the address of your ADO project, the name of the pool, the number of agents. Group means the Library group in Azure DevOps where we securely transfer confidential data.
- name: SPNName
value: ‘Subscription-JustCloudPublic’
- name: location
value: ‘westeurope’
- name: urlProjectADO
value: ‘'
- name: pool
value: ‘Default’
- name: numberagents
value: 3
- group: ‘justcloudpublickeyvault’

Requirements — you need before you start implementing

  1. Azure Subscription
  2. PAT — Personal Access Token —
  3. Key Vault —

After deployment:

Azure DevOps agents

Azure Portal

Article available on LinkedIn and Blog:

Feel free to leave a comment!