Grant an App Permission to a Service Principal or Managed Identity with Microsoft Graph PowerShell

Learn how to assign Microsoft Graph application permissions to a Managed Identity or Service Principal in Microsoft Entra ID using Microsoft Graph PowerShell.

# Validated on Microsoft.Graph PowerShell SDK v2.29.1
$ErrorActionPreference = 'stop'
$requiredScopes = @('Application.Read.All', 'AppRoleAssignment.ReadWrite.All')

$ctx = Get-MgContext
if (-not $ctx -or ($requiredScopes | Where-Object { $ctx.Scopes -notcontains $_ })) {
    Connect-MgGraph -Scopes $requiredScopes -NoWelcome
}

function Grant-AppRolesToServicePrincipal {
    [CmdletBinding(SupportsShouldProcess = $true, DefaultParameterSetName = 'PName_RName')]
    param(
        [Parameter(Mandatory, ParameterSetName = 'PName_RName')]
        [Parameter(Mandatory, ParameterSetName = 'PName_RApp')]
        [string]$PrincipalDisplayName,

        [Parameter(Mandatory, ParameterSetName = 'PId_RName')]
        [Parameter(Mandatory, ParameterSetName = 'PId_RApp')]
        [string]$PrincipalObjectId,

        [Parameter(Mandatory, ParameterSetName = 'PName_RApp')]
        [Parameter(Mandatory, ParameterSetName = 'PId_RApp')]
        [string]$ResourceAppId,

        [Parameter(Mandatory, ParameterSetName = 'PName_RName')]
        [Parameter(Mandatory, ParameterSetName = 'PId_RName')]
        [string]$ResourceDisplayName,

        [Parameter(Mandatory)]
        [string[]]$RoleValues
    )

    if ($PSCmdlet.ParameterSetName -like 'PId_*') {
        $principal = Get-MgServicePrincipal -ServicePrincipalId $PrincipalObjectId -Select 'id,displayName'
        if (-not $principal) { throw "Service principal with objectId '$PrincipalObjectId' not found." }
    }
    else {
        $escaped = $PrincipalDisplayName.Replace("'", "''")
        $candidates = Get-MgServicePrincipal -Filter "startswith(displayName,'$escaped')" -All -Select 'id,displayName'

        $exact = $candidates | Where-Object { $_.DisplayName -eq $PrincipalDisplayName }
        if ($exact) { $candidates = $exact }

        $foundMatches = @($candidates)
        if ($foundMatches.Count -eq 0) {
            throw "No service principal matched '$PrincipalDisplayName'. Use -PrincipalObjectId for certainty."
        }
        if ($foundMatches.Count -gt 1) {
            throw "Multiple service principals matched '$PrincipalDisplayName': $($foundMatches.DisplayName -join ', '). Use -PrincipalObjectId."
        }
        $principal = $foundMatches[0]
    }
    if ([string]::IsNullOrWhiteSpace($principal.Id)) { throw "Resolved principal has no Id." }

    if ($PSCmdlet.ParameterSetName -like '*_RApp') {
        $resourceSp = Get-MgServicePrincipal -Filter "appId eq '$ResourceAppId'" -Select 'id,displayName,appId,appRoles'
        if (-not $resourceSp) { throw "Resource service principal with appId '$ResourceAppId' not found." }
    }
    else {
        $escapedRes = $ResourceDisplayName.Replace("'", "''")
        $resources = Get-MgServicePrincipal -Filter "startswith(displayName,'$escapedRes')" -All -Select 'id,displayName,appId,appRoles'
        $exactRes = $resources | Where-Object { $_.DisplayName -eq $ResourceDisplayName }
        if ($exactRes) { $resources = $exactRes }

        $resMatches = @($resources)
        if ($resMatches.Count -eq 0) {
            throw "Resource service principal '$ResourceDisplayName' not found. Prefer -ResourceAppId."
        }
        if ($resMatches.Count -gt 1) {
            throw "Multiple resources named '$ResourceDisplayName': $($resMatches.DisplayName -join ', '). Use -ResourceAppId."
        }
        $resourceSp = $resMatches[0]
    }
    if ([string]::IsNullOrWhiteSpace($resourceSp.Id)) { throw "Resolved resource SP has no Id." }

    $existing = Get-MgServicePrincipalAppRoleAssignment -ServicePrincipalId $principal.Id -All |
    Where-Object { $_.ResourceId -eq $resourceSp.Id }

    $results = @()

    foreach ($val in $RoleValues) {
        $appRole = $resourceSp.AppRoles | Where-Object {
            $_.IsEnabled -and
            $_.Value -eq $val -and
            ($_.AllowedMemberTypes -contains 'Application')
        } | Select-Object -First 1

        if (-not $appRole) {
            Write-Warning "Role '$val' not found on '$($resourceSp.DisplayName)' (or not an Application role)."
            $results += [pscustomobject]@{ Principal = $principal.DisplayName; Resource = $resourceSp.DisplayName; RoleValue = $val; Status = 'MissingRole' }
            continue
        }

        if ($existing | Where-Object { $_.AppRoleId -eq $appRole.Id }) {
            Write-Host "Already assigned: $val to '$($principal.DisplayName)' on '$($resourceSp.DisplayName)'." -ForegroundColor Yellow
            $results += [pscustomobject]@{ Principal = $principal.DisplayName; Resource = $resourceSp.DisplayName; RoleValue = $val; Status = 'AlreadyAssigned' }
            continue
        }

        if ($PSCmdlet.ShouldProcess($principal.DisplayName, "Grant $val on $($resourceSp.DisplayName)")) {
            $appRoleAssignmentParams = @{
                ServicePrincipalId = $principal.Id
                BodyParameter      = @{
                    principalId = $principal.Id
                    resourceId  = $resourceSp.Id
                    appRoleId   = $appRole.Id
                }
            }
            New-MgServicePrincipalAppRoleAssignment @appRoleAssignmentParams | Out-Null

            Write-Host "Assigned $val to '$($principal.DisplayName)' on '$($resourceSp.DisplayName)'." -ForegroundColor Green
            $results += [pscustomobject]@{ Principal = $principal.DisplayName; Resource = $resourceSp.DisplayName; RoleValue = $val; Status = 'Assigned' }
        }
    }

    return $results
}

<#

Usage:

$params = @{
    PrincipalDisplayName = 'AAA-001'
    ResourceDisplayName  = 'Microsoft Graph'
    RoleValues           = @('AuditLog.Read.All', 'User.Read.All')
}
Grant-AppRolesToServicePrincipal @params

OR

$params = @{
    PrincipalObjectId = '1654793e-8817-4050-b7c0-1f14c331d8d7'
    ResourceAppId     = '00000003-0000-0000-c000-000000000000'
    RoleValues        = @('AuditLog.Read.All', 'User.Read.All')
}
Grant-AppRolesToServicePrincipal @params

#>
Loading...