Reporting Who Made Azure AD License Assignments

Who Performed an Azure AD License Assignment?

After writing about how to detect underused (and expensive) licenses assigned to Azure AD accounts, I was asked if it was possible to report who assigned a license to accounts. It’s a good question that stumped me for a moment. There’s no obvious off-the-shelf indication of who assigned licenses to accounts in any Microsoft 365 administrative interface.

Azure AD Audit Data

License assignment is an Azure AD activity. It’s therefore possible to find information about these actions in the Azure AD audit log by searching for “Change user license” events. Unfortunately, these events only note that some sort of license assignment occurred. It doesn’t tell you what happened to licenses in terms of additions, removals, or disabling service plans in licenses. For that information, you need to find a matching “Update user” event where the license assignment detail is captured in the Modified Properties tab (Figure 1).

Azure AD license assignment details in the audit log
Figure 1: Azure AD license assignment details in the audit log

Unfortunately, the Get-MgAuditLogDirectoryAudit cmdlet doesn’t report the same level of detail about license assignments, so the Azure AD audit log isn’t a good source for reporting.

Azure AD License Assignment in the Office 365 Audit Log

Azure AD is a source for the Office 365 (unified) audit log and the information ingested into the Office 365 audit log is more comprehensive albeit formatted in such a way that the data isn’t easy to fetch. However, we can find enough data to write a PowerShell script to create a basic report that contains enough information to at least give administrators some insight into who assigns licenses.

To create the report, the script:

  • Ran the Search-UnifiedAuditLog cmdlet to retrieve audit records for the Change user license and Update User actions.
  • Create separate arrays for both types of event.
  • For each Change user license event, see if there’s a matching Update user record. If one is found, extract the license assignment information from the record.
  • Report what’s been found.

Here’s the script to prove that the concept works:

# Azure AD license assignment script
$StartDate = (Get-Date).AddDays(-90)
$EndDate = (Get-Date).AddDays(1)
Write-Host "Searching for license assignment audit records"
[array]$Records = Search-UnifiedAuditLog -StartDate $StartDate -EndDate $EndDate -Formatted -ResultSize 5000 -Operations "Change user license", "Update User"
If (!($Records)) { Write-Host "No audit records found... exiting... " ; break}

Write-Host ("Processing {0} records" -f $Records.count)
[array]$LicenseUpdates = $Records | Where-Object {$_.Operations -eq "Change user license."}
[array]$UserUpdates = $Records | Where-Object {$_.Operations -eq "Update user."}

$Report = [System.Collections.Generic.List[Object]]::new()

ForEach ($L in $LicenseUpdates) {
  $NewLicenses = $Null; $OldLicenses = $Null
  $AuditData = $L.AuditData | ConvertFrom-Json
  $CreationDate = Get-Date($L.CreationDate) -format s
  [array]$Detail = $UserUpdates | Where-Object {$_.CreationDate -eq $CreationDate -and $_.UserIds -eq $L.UserIds}
  If ($Detail) { # Found a user update record
     $LicenseData = $Detail[0].AuditData | ConvertFrom-Json
     $NewLicenses = $LicenseData.ModifiedProperties | Where {$_.Name -eq 'AssignedLicense'} | Select-Object -ExpandProperty NewValue | Convertfrom-Json
     $OldLicenses = $LicenseData.ModifiedProperties | Where {$_.Name -eq 'AssignedLicense'} | Select-Object -ExpandProperty OldValue | Convertfrom-Json
  } # end if
  $ReportLine   = [PSCustomObject] @{ 
     Operation      = $AuditData.Operation
     Timestamp      = Get-Date($AuditData.CreationTime) -format g
     'Assigned by'  = $AuditData.UserId
     'Assigned to'  = $AuditData.ObjectId 
     'New licenses' = $NewLicenses
     'Old licenses' = $OldLicenses
  }
  $Report.Add($ReportLine)
}

$Report = $Report | Sort-Object {$_.TimeStamp -as [datetime]} 
$Report | Out-GridView

The output is sparse (Figure 2) but I reckon it is sufficient to understand what happens when a license assignment occurred. Events without any license detail appear to be when an administrator removes a license from an account or a service plan from a license.

Azure AD license assignment data extracted from the Office 365 audit log
Figure 2: License assignment data extracted from the Office 365 audit log

I didn’t bother attempting to parse out the license detail. The information returned by Azure AD includes all the licenses assigned to an account, so you’d end up with something like this for an account with three licenses. Splitting the individual licenses and disabled service plans out from this information is an exercise for the reader.

$NewLicenses.Split(',')
[SkuName=POWER_BI_STANDARD
 AccountId=a662313f-14fc-43a2-9a7a-d2e27f4f3478
 SkuId=a403ebcc-fae0-4ca2-8c8c-7a907fd6c235
 DisabledPlans=[]]
[SkuName=ENTERPRISEPACK
 AccountId=a662313f-14fc-43a2-9a7a-d2e27f4f3478
 SkuId=6fd2c87f-b296-42f0-b197-1e91e994b900
 DisabledPlans=[]]
[SkuName=TOPIC_EXPERIENCES
 AccountId=a662313f-14fc-43a2-9a7a-d2e27f4f3478
 SkuId=4016f256-b063-4864-816e-d818aad600c9
 DisabledPlans=[]]

Principal Proved

In any case, the answer to the question is that it’s possible to track and report Azure AD license assignments by using the audit log to extract events relating to these actions and parsing the information in the events. The resulting output might not be pretty (but could be cleaned up), but it’s enough to prove the principal.


Learn how to exploit the data available to Microsoft 365 tenant administrators through the Office 365 for IT Pros eBook. We love figuring out how things work.

One Reply to “Reporting Who Made Azure AD License Assignments”

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.