Azure Pipelines are a powerful tool for automating CI/CD workflows, and the AzureCLI task allows you to execute scripts using the Azure CLI. When working with service principals, it’s essential to have their client ID, secret, and object ID at hand. While obtaining the client ID and secret is straightforward using the addSpnToEnvironment property, retrieving the object ID can be a bit more challenging. In this blog post, we’ll explore a workaround to easily fetch the object ID for your service principal, even if it doesn’t have direct read access to Azure AD.

There are a few ways to lookup the object ID. The easiest is to issue an az ad principal command:

1
az ad principal --name MyServicePrincipal --query objectId

However, for this command to work, it requires read access to Azure AD, which is often not granted to service principals. There is a workaround for this. The Azure Pipeline principal often has been assigned the Contributor role in a subscription. Thus, you can look up that role assignment and pull the object ID from there:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
- task: AzureCLI@2
  displayName: 'Create pipeline principal variables'
  inputs:
    azureSubscription: 'my-azure-subscription' # needs to be authorized with a service principal with Contributor access
    scriptType: pscore
    addSpnToEnvironment: true
    scriptLocation: inlineScript
    inlineScript: |
      # The addSpnToEnvironment variable adds $env:servicePrincipalId and $env:servicePrincipalKey variables
      $clientId = $env:servicePrincipalId
      $clientSecret = $env:servicePrincipalKey
      $objectId = ""

      # Lookup role assignments for the Contributor role for the current subscription
      $roleAssignments = (az role assignment list --role 'Contributor' | ConvertFrom-Json -NoEnumerate)
      
      # Lookup the role assignment for the client id of the current principal
      foreach ($roleAssignment in $roleAssignments) {
          if ($roleAssignment.principalName -eq $clientId) {
              $objectId = $roleAssignment.principalId
              break
          }
      }

      # Optional: create pipeline variables for the client id, client secret, and client object id
      Write-Host "##vso[task.setvariable variable=principalUserClientId;]$clientId"
      Write-Host "##vso[task.setvariable variable=principalUserSecret;issecret=true]$clientSecret"
      Write-Host "##vso[task.setvariable variable=principalUserObjectId;]$objectId"      

Cheers,

Lucas