Azure DevOps Automation using Powershell and REST APIs

Sayan Roy
6 min readAug 30, 2021

We often use Azure DevOps every day for different clients, teams and projects where you need to setup access choosing and managing user licenses and managing user permissions for compliance, security and license management.

While the portal works, these tasks are manual and time consuming.

Automating these tasks can be very useful leveraging Azure DevOps REST APIs

Over the past weeks, I have worked on automation within Azure DevOps. In this blog post we will talk about how to change a user license and add a user to Organization and Project with Contributor role.

Note, I will use PowerShell to operate, but you can choose the language of your choice. I use API version 6.1. The documentation can be found here:

https://docs.microsoft.com/en-us/rest/api/azure/devops/?view=azure-devops-rest-6.1

First, we need a way to authenticate to an Azure DevOps organization. There is two way to authenticate to Azure DevOps, using Azure Active Directory or using a Personal Access Token. It depends on the situation and on what you will need to build. For Azure Active Directory access you will need a client library (for .NET and PowerShell) or you can use Personal Access Token (PAT).

To create a Personal Access Token, login to Azure DevOps in this organization. On the right top corner click on the user icon.

Select “Personal access tokens”

Then Click on “New Token”. You will be asked to provide a name for the token, the expiration date, Organization Access, and the scope you want to apply, either all scopes or specify access for Work items, code (git repository), Build, Release, test and packaging.

Defining scope is important for your application; it defines how the application associated with the token will interact with Azure DevOps Services. Unless you are testing the API, never choose full access, review your needs and select the appropriate scopes.

After pushing the “Create” button, the token is displayed. Make sure to save the token securely, there is no way to retrieve it later!

To access Azure DevOps Service Rest API, we need to send a basic authentication header with every http request to the service. The basic authentication HTTP header look like
Authorization: basic
The credential needs to be Base64 encoded. In PowerShell you can do it like this.

$base64AuthInfo = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes((“{0}:{1}” -f $AdminUser, $Token)))
$Header = @{
Authorization = (“Basic {0}” -f $base64AuthInfo)
}

Now that we know how to authenticate to Azure DevOps API, let’s see what we can do with the API.

Azure DevOps user licenses have the following options:
[1] Stakeholders: This license is free to use. The options are limited though. With the biggest restriction in my experience that you are not able to read code. But there are smaller limitations. You can for example read the boards, but you are not able to drag the work items to a different place on the board.[2] Basic and Basic + Test Plans: These licenses give you full options to use Azure DevOps, with the only difference between the two that the lather can create and manage test plans. You get 5 basic licenses for free.
[3] Visual studio Enterprise: If a user has Visual studio Enterprise licenses or benefits, they can possible make use of that for Azure DevOps.

Managing User Licenses:

Let’s consider our options to manage user licenses besides PowerShell and the Rest API.

Get all users licenses status:

The following snippet gets you all the users in your Azure DevOps organization and their license status.

#Create API for header
#First create all needed variables for your situation
$OrganizationName = “organizationname”
$AdminUser = “admin@exampleorganization.com”
$Token = “PATKey”

#The Header is created with the given information.
$base64AuthInfo = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes((“{0}:{1}” -f $AdminUser, $Token)))

$Header = @{
Authorization = (“Basic {0}” -f $base64AuthInfo)
}

# Splat the parameters in a hashtable for readability
$UsersParameters = @{
Method = “GET”
Headers = $Header
Uri = “
https://vsaex.dev.azure.com/$OrganizationName/_apis/userentitlements?api-version=6.1-preview.3"
}

# Collect all the users
$Users = (Invoke-RestMethod @UsersParameters).members

# Create a readable output
$Output = [System.Collections.ArrayList]@()
$Users | ForEach-Object {
$UserObject = [PSCustomObject]@{
UserName = $_.user.principalName
License = $_.accessLevel.licenseDisplayName
}
[void]$Output.Add($UserObject)
}

#Return the outputArray
$Output

Change a license:

To change license, you need to use the POST method. You can use this code to change the license for an existing user.

$OrganizationName = “organizationname”
$username = “admin@exampleorganization.com”
$PatToken = “PATKey”

$NewLicense = Read-Host “Please enter Userlicense to be updated (Available options Advanced/Express/StakeHolder)”

$EmailAddress = Read-Host “Please enter the Email address of user you want to change License Type”

#Create API for Header
$base64AuthInfo = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes((“{0}:{1}” -f $AdminUser, $Token)))
$Header = @{
Authorization = (“Basic {0}” -f $base64AuthInfo)
}

$UsersParameters = @{
Method = “GET”
Headers = $Header
Uri = “
https://vsaex.dev.azure.com/$OrganizationName/_apis/userentitlements?api-version=6.1-preview.3"
}

$User = (Invoke-RestMethod @UsersParameters).members | Where-Object { $_.user.mailaddress -eq $Emailaddress }

if ($null -eq $user){
Throw “A user with the emailaddress $EmailAddress was not found”
}
else {
# A body needs to be created to send to the Rest API
$body = @{
from = “”
op = “replace”
path = “/accessLevel”
value = @{
accountLicenseType = $NewLicense
licensingSource = “account”
}
}

#Splat the parameters to use with Invoke-RestMethod
$ChangeLicenseParameters = @{
Method = “PATCH”
Headers = $Header
Uri = “
https://vsaex.dev.azure.com/$OrganizationName/_apis/userentitlements/$($User.id)?api-version=6.1-preview.3"
body = “[$($body | ConvertTo-Json)]”
ContentType = “application/json-patch+json”
}

#Perform the action of setting the new license
$Output = Invoke-RestMethod @ChangeLicenseParameters
Write-Host “User $EmailAddress license changed: $($Output.isSuccess)”

Add user to Organization:

In order to add a user to an organization, we need to pass a request body to invoke the REST API to add user to organization. You can refer to the below sample code to input the parameters for user details, license and group type:

$OrganizationName = “organizationname”
$username = “admin@exampleorganization.com”
$PatToken = “PATKey”

$Emailaddress = Read-Host “Please enter your Email address: “

$Licence= Read-Host “Please enter License Type (Available options are stakeholder/express/advanced/earlyAdopter/none)”

$Role= Read-Host “Please enter Group Type (Available options are projectContributor/projectReader/projectAdministrator)”

#Create API for Header
$base64AuthInfo = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes((“{0}:{1}” -f $AdminUser, $Token)))
$Header = @{
Authorization = (“Basic {0}” -f $base64AuthInfo)
}

#Pass request body for POST method to add user to organization
$body=@{
accessLevel = @{
accountLicenseType = $Licence;
}
extensions = @{
id = “ms.feed”
}
user = @{
principalName= $Emailaddress;
subjectKind = “user”;
}
projectEntitlements = @{
group = @{
groupType = $Role;
}
}
}| ConvertTo-Json

#Add user to organization
$GroupParameters = @{
Method = “POST”
Headers = $Header
Uri = “
https://vsaex.dev.azure.com/$OrganizationName/_apis/userentitlements?api-version=6.0-preview.3"
body = $body
ContentType = “application/json”
}

$Output = ($(Invoke-RestMethod @GroupParameters).operationResult).isSuccess

Write-Host “User added:$Output”

Add User to Project as Contributor:

This sample code will seek inputs on the user details and the project name where you want to add the user with Contributor role

$OrganizationName = “organizationname”
$username = “admin@exampleorganization.com”
$PatToken = “PATKey”

$Emailaddress = Read-Host “Please enter your Email address”

$Project = Read-Host “Enter the project name”

#Create API for Header
$base64AuthInfo = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes((“{0}:{1}” -f $AdminUser, $Token)))
$Header = @{
Authorization = (“Basic {0}” -f $base64AuthInfo)
}

#Get Member ID of the user
$UsersParameters = @{
Method = “GET”
Headers = $Header
Uri = “
https://vsaex.dev.azure.com/$OrganizationName/_apis/userentitlements?api-version=6.1-preview.3"
}

$Users = (Invoke-RestMethod @UsersParameters).members

foreach($User in $Users)
{
if ($User.user.mailAddress -eq $Emailaddress)
{
$MembersID=$User.id
}
}
if ($null -eq $MembersID) {
Throw “A user with the emailaddress $EmailAddress was not found”
}

#Get Contributor GroupID of the Project
$ProjectGroup=”[$Project]\Contributors”
$GroupParameters = @{
Method = “GET”
Headers = $Header
Uri = “
https://vssps.dev.azure.com/$OrganizationName/_apis/graph/groups??api-version=6.1-preview.3"
ContentType = “application/json-patch+json”
}

# Collect all the users
$Groups = (Invoke-RestMethod @GroupParameters).value
foreach($Group in $Groups)
{
if ($Group.principalName -eq $ProjectGroup)
{
$newgroupID=$Group.originId
}
}

#Add User as Contributor to Project
$url=”https://vsaex.dev.azure.com/$OrganizationName/_apis/GroupEntitlements/$newgroupID/members/$MembersID"
$GroupParameters = @{
Method = “PUT”
Headers = $Header
Uri = $url+”?api-version=6.0-preview.1"
}

$Output= Invoke-RestMethod @GroupParameters
if ($Output -eq “ok”)
{
Write-Host “$Emailaddress is added as Contributor.”
}

So with this post I wanted to show you the options to automate Azure DevOps tasks with PowerShell and the Rest API. I hope these examples can help you get started.

--

--