List everyone who has an Application Role for applications in Azure

Some time ago I had to validate who or what has access to the applications we created in our Azure environment.
There were hundreds of different applications with each their own specific Application Roles. Both users and service principals had roles assigned to the applications to perform the required operations.

It is possible to click through every application in Entra ID and validate the assigned roles. However, this takes quite a bit of time.
So I figured “Is it possible to iterate through all my applications in Entra ID and see who or what has an application role assigned?”
Needless to say, the answer is “Yes!”.

By using the Azure CLI and the Graph API I was able to accomplish what is required.

#########################################################
# Login if running local and you haven't done so already
#########################################################
Function Login {
    # Subscription used for logging in. Necessary to get a context to work in.
    $loggedInSubscription = "The subscription name"
    $tenantId = "73fefcf4-c062-45ef-9607-e19aba33f82d"
    az login --tenant $tenantId
    # # Log in to a subscription which resides in the tentant we want to add & configure MG's to.
    az account set --subscription $loggedInSubscription
}
Function Get-AppRoleAssignments {
    [CmdletBinding()]Param()
    Write-Verbose -Message "Starting to retrieve all Enterprise Applications matching the naming convention."
    $roleAssignmentsForAllApplications = $null
    $enterpriseAppRegistrationCollection =
        az ad sp list --filter "startswith(displayname, 'jv-sample-app1') or startswith(displayname, 'jv-sample-app2')"
        | ConvertFrom-Json

    Write-Verbose -Message "Found $($enterpriseAppRegistrationCollection.Length) enterprise applications."
    foreach ($enterpriseAppRegistration in $enterpriseAppRegistrationCollection) {
        $servicePrincipalAppRoles = $null
        $assignedAppRolesForServicePrincipal = $null

        # The service principal from 'az ad sp list' already has all the data we need
        $servicePrincipalAppRoles = $enterpriseAppRegistration.appRoles

        Write-Verbose -Message "Found $($servicePrincipalAppRoles.Length) appRoles for $($enterpriseAppRegistration.id)."
        $assignedAppRolesForServicePrincipal =
            az rest `
            --method get `
            --uri https://graph.microsoft.com/v1.0/servicePrincipals/$($enterpriseAppRegistration.id)/appRoleAssignedTo
            | ConvertFrom-Json
            | Select-Object -expand value

            Write-Verbose -Message "Found $($assignedAppRolesForServicePrincipal.Length) assigned identities for $($enterpriseAppRegistration.id)."
        $roleAssignmentsForAllApplications += foreach($assignedAppRole in $assignedAppRolesForServicePrincipal) {
            $role = $servicePrincipalAppRoles | Where-Object{ $_.id -eq $assignedAppRole.appRoleId}
            Write-Verbose -Message "Adding $($role.displayName) to collection for $($assignedAppRole.principalDisplayName) on application $($enterpriseAppRegistration.displayName)."
            New-Object PsObject -Property @{
                AppRoleId = $assignedAppRole.appRoleId
                AppRoleDisplayName = $role.displayName
                PrincipalObjectId = $assignedAppRole.principalId
                PrincipalDisplayName = $assignedAppRole.principalDisplayName
                ApplicationDisplayName = $enterpriseAppRegistration.displayName
                ApplicationApplicationId = $enterpriseAppRegistration.appId
                ApplicationObjectId = $enterpriseAppRegistration.id
            }
        }
    }
    return $roleAssignmentsForAllApplications
}
#Login
Get-AppRoleAssignments -Verbose | Sort-Object -Property ApplicationDisplayName | Format-Table -Property ApplicationDisplayName, ApplicationApplicationId, ApplicationObjectId, PrincipalObjectId, PrincipalDisplayName, AppRoleId, AppRoleDisplayName | Out-File "AppRoleAssignments.md"

The output looks pretty much like this


ApplicationDisplayName ApplicationApplicationId             ApplicationObjectId                  PrincipalObjectId                    PrincipalDisplayName AppRoleId
---------------------- ------------------------             -------------------                  -----------------                    -------------------- ---------
jv-sample-app1         768e6a21-dc24-4824-80c1-b288802b912b 34990cc8-7834-4b1c-a921-39b1e18f396f 1bb1840c-97db-4e87-bdee-00b20bce29bf Jan de Vries1        17604bce-7031-4b3c-…
jv-sample-app2         f18edfa7-1ab5-4f64-9c46-7413c214a8db b53f783d-81e0-43c9-b874-8576855301ae b9bc3ac9-9a35-45e4-961a-2929158bb73e Jan de Vries2        17604bce-7031-4b3c-…

Nice, right?
I know this is just a small script to share, which could have been a Gist, but having a post makes it more discoverable for me.