Public/New-EntraIDAppPermission.ps1

<#
.SYNOPSIS
Assigns new permissions to a service principal

.DESCRIPTION
Assigns new permissions to a service principal, such as assigning the user.read.all Graph permission to an enterprise application.

.EXAMPLE
New-EntraIDAppPermission -Permission "User.Read.All","Group.Read.All" -ObjectId "your-enterprise-application-object-id"

.EXAMPLE
New-EntraIDAppPermission -Permission "User.Read.All","Group.Read.All" -ObjectId "your-enterprise-application-object-id" -ResourceApplicationId "2808f963-7bba-4e66-9eee-82d0b178f408"
#>

function New-EntraIDAppPermission {
    [CmdletBinding(SupportsShouldProcess = $true)]

    Param(
        [Parameter(Mandatory = $true)]
        [String[]] $Permission,

        [Parameter(Mandatory = $true)]
        [String] $ObjectId,

        [Parameter(Mandatory = $false)]
        [String] $ResourceApplicationId = "00000003-0000-0000-c000-000000000000", # Microsoft Graph

        [Parameter(Mandatory = $false)]
        [String] $AccessTokenProfile = "Default"
    )

    Process {
        if (!(Get-EntraIDAccessTokenProfile -Profile $AccessTokenProfile)) {
            Write-Output "No access token profile found. Starting interactive sign-in."
            Add-EntraIDInteractiveUserAccessTokenProfile -Name $AccessTokenProfile -Scope "https://graph.microsoft.com/application.read.all approleassignment.readwrite.all"
        }
        
        # Ensure permissions
        if (!(Get-EntraIDAccessToken -Profile $AccessTokenProfile | Get-EntraIDAccessTokenHasScopes -Scopes "application.readwrite.all", "application.read.all" -Any)) {
            throw "The access token profile '$AccessTokenProfile' does not have the required permissions to assign application roles. Please ensure it has 'Application.ReadWrite.All' or 'Application.Read.All' permissions."
        }

        # Get Microsoft Graph service principal
        $Resource = Invoke-RestMethod -Uri "https://graph.microsoft.com/v1.0/servicePrincipals(appId='$ResourceApplicationId')" -Headers (Get-EntraIDAccessTokenHeader -Profile $AccessTokenProfile)
        if(!$Resource) {
            throw "Could not find the resource application with application id $ResourceApplicationId"
        }
        Write-Output "Found resource service principal $($Resource.displayName) with objectid $($Resource.id)"

        # Build map of app roles
        $ResourceAppRoles = Invoke-RestMethod -Uri "https://graph.microsoft.com/v1.0/servicePrincipals(appId='$ResourceApplicationId')/appRoles?`$top=999" -Headers (Get-EntraIDAccessTokenHeader -Profile $AccessTokenProfile) |
        Select-Object -ExpandProperty value |
        Group-Object -AsHashTable -Property value
        
        Write-Output "Found a total of $($ResourceAppRoles.Count) available app roles for the resource"
        $ResourceAppRoles.Keys | ForEach-Object {
            Write-Verbose "Available app role: $_"
        }

        $Permission | ForEach-Object {
            if ($ResourceAppRoles.ContainsKey($_)) {
                $id = $ResourceAppRoles[$_].id
                $body = @{
                    principalId = $ObjectId
                    resourceId  = $Resource.id
                    appRoleId   = $id
                } | ConvertTo-Json
                Write-Output ""
                Write-Output "Permission $_"
                try {
                    if ($PSCmdlet.ShouldProcess("Permission $_ to object $ObjectId", "Assign")) {
                        Write-Debug "Sending request body to uri https://graph.microsoft.com/v1.0/servicePrincipals/$($Resource.id)/appRoleAssignments: $body"
                        $r = Invoke-RestMethod -Uri "https://graph.microsoft.com/v1.0/servicePrincipals/$($Resource.id)/appRoleAssignments" -Method POST -Body $body -Headers (Get-EntraIDAccessTokenHeader -Profile $AccessTokenProfile) -ContentType "application/json"
                        if ($r.id) {
                            Write-Output " ✅ New assignment was created"
                        }
                    }                    
                }
                catch {
                    if ($_.ErrorDetails.Message -like "*already exists*") {
                        Write-Output " ✅ Already exists"
                    }
                    else {
                        Write-Error "Unhandled exception while assigning permission: $_"
                    }
                }
            }
            else {
                Write-Warning "App role $($_) not found"
            }
        }
    }
}