Friday, September 25, 2026

Assigning Microsoft Graph Permissions to a Managed Identity: Application vs Delegated

Assigning Microsoft Graph Permissions to a Managed Identity: Application vs Delegated (with ServiceMessage Examples)

You've enabled a system-assigned managed identity on an Azure Automation Account, and the runbook needs to read Microsoft 365 Message Center posts through Microsoft Graph. You open Entra ID → Enterprise applications → your identity → Permissions and there's no Add a permission button, and Grant admin consent is greyed out.

This article explains why, how to assign the permission with PowerShell, and why one permission people commonly ask for (ServiceMessageViewpoint.Write) fails with a confusing Edm.Guid error.


1. Why the portal can't do it

A managed identity is a service principal with no app registration behind it. In the portal, the API permissions blade (where you click Add a permission → Microsoft Graph) exists only on app registrations. The enterprise app Permissions blade just shows what has already been granted.

To give a managed identity Graph permissions, you create the grant directly on its service principal:

Permission typeGraph object createdPowerShell cmdletWorks for managed identity at runtime?
Application (app role)appRoleAssignmentNew-MgServicePrincipalAppRoleAssignment✅ Yes
Delegated (scope)oauth2PermissionGrantNew-MgOauth2PermissionGrant❌ No, a managed identity has no signed-in user

You can do this with PowerShell, the Azure CLI (az rest), or Graph Explorer. All three call the same Graph API.


2. Know your permission before you assign it

The service message permissions are a good example of how application and delegated permissions differ. From the Microsoft Graph permissions reference:

PermissionApplicationDelegatedPurpose
ServiceMessage.Read.All✅ 1b620472-6534-4fe6-9df2-4680e8aa28ec✅Read Message Center posts
ServiceHealth.Read.All✅ 79c261e0-fe76-4144-aad5-bdc68fbe4037✅Read service health and incidents
ServiceMessageViewpoint.Write❌ none✅Mark posts read, archived or favourite for the signed-in user

Two things to note:

  • There is no ServiceMessage.ReadWrite.All. For app-only automation, ServiceMessage.Read.All is the highest service message permission available.
  • ServiceMessageViewpoint.Write is delegated only. Read, archived and favourite status is stored per user, so Graph doesn't offer it for app-only access.

Tip: Permission names are the same in both lists, but their GUIDs differ. Always look up the ID from the correct collection: AppRoles for application permissions, Oauth2PermissionScopes for delegated ones.


3. Prerequisites

ItemRequirement
RoleGlobal Administrator or Privileged Role Administrator
ModulesMicrosoft.Graph.Applications, Microsoft.Graph.Identity.SignIns
IdentityManaged identity enabled on the resource (e.g. Automation Account → Identity → System assigned → On)

4. Assign an application permission (the one your runbook can use)

Run these blocks one at a time. Replace aa-automation-demo with your resource name.

# 1. Install and import the module (first time only)
Install-Module Microsoft.Graph.Applications -Scope CurrentUser -Force -AllowClobber
Import-Module Microsoft.Graph.Applications

# 2. Sign in
Connect-MgGraph -Scopes "Application.Read.All","AppRoleAssignment.ReadWrite.All" -NoWelcome

# 3. Get the managed identity's service principal
$miName = "aa-automation-demo"
$miSp = Get-MgServicePrincipal -Filter "displayName eq '$miName'"
$miSp | Select-Object DisplayName, Id, ServicePrincipalType     # expect: ManagedIdentity

# 4. Get the Microsoft Graph service principal
$graphSp = Get-MgServicePrincipal -Filter "appId eq '00000003-0000-0000-c000-000000000000'"

# 5. Find the application permission (app role)
$role = $graphSp.AppRoles | Where-Object { $_.Value -eq "ServiceMessage.Read.All" -and $_.AllowedMemberTypes -contains "Application" }
$role | Select-Object Value, Id                                  # must return a row

# 6. Check whether it's already assigned
$existing = Get-MgServicePrincipalAppRoleAssignment -ServicePrincipalId $miSp.Id -All |
    Where-Object { $_.ResourceId -eq $graphSp.Id -and $_.AppRoleId -eq $role.Id }

# 7. Assign it
if (-not $role) {
    Write-Warning "Not an application permission. Check the name, or use the delegated section."
} elseif ($existing) {
    Write-Host "Already assigned" -ForegroundColor Yellow
} else {
    New-MgServicePrincipalAppRoleAssignment -ServicePrincipalId $miSp.Id -PrincipalId $miSp.Id -ResourceId $graphSp.Id -AppRoleId $role.Id
    Write-Host "Assigned $($role.Value)" -ForegroundColor Green
}

# 8. Check the result
Get-MgServicePrincipalAppRoleAssignment -ServicePrincipalId $miSp.Id -All |
    Where-Object { $_.ResourceId -eq $graphSp.Id } |
    ForEach-Object { $rid = $_.AppRoleId; ($graphSp.AppRoles | Where-Object { $_.Id -eq $rid }).Value }

Disconnect-MgGraph

Refresh Enterprise applications → your identity → Permissions in the portal. The permission shows with type Application.


5. The Edm.Guid error explained

If you put ServiceMessageViewpoint.Write into step 5 above, you'll get:

New-MgServicePrincipalAppRoleAssignment : Cannot convert the literal '' to the expected type 'Edm.Guid'.
Status: 400 (BadRequest)
ErrorCode: Request_BadRequest
SymptomCause
Step 5 returns nothingThe permission isn't in $graphSp.AppRoles because it's delegated-only
$role.Id is emptyGraph receives appRoleId: ""
Edm.Guid 400 errorAn empty string can't be converted to a GUID

To confirm which list a permission belongs to:

# Application permissions
$graphSp.AppRoles | Where-Object { $_.Value -like "ServiceMessage*" } | Select-Object Value, Id

# Delegated permissions
$graphSp.Oauth2PermissionScopes | Where-Object { $_.Value -like "ServiceMessage*" } | Select-Object Value, Id

6. Granting a delegated permission to a managed identity (and why it won't help)

You can grant a delegated scope to a managed identity's service principal with an admin-consent (AllPrincipals) oauth2PermissionGrant. It will then appear on the Permissions blade with type Delegated:

Import-Module Microsoft.Graph.Identity.SignIns
Connect-MgGraph -Scopes "Application.Read.All","DelegatedPermissionGrant.ReadWrite.All" -NoWelcome

$miSp    = Get-MgServicePrincipal -Filter "displayName eq 'aa-automation-demo'"
$graphSp = Get-MgServicePrincipal -Filter "appId eq '00000003-0000-0000-c000-000000000000'"
$scopeName = "ServiceMessageViewpoint.Write"

$grant = Get-MgOauth2PermissionGrant -All |
    Where-Object { $_.ClientId -eq $miSp.Id -and $_.ResourceId -eq $graphSp.Id -and $_.ConsentType -eq "AllPrincipals" }

if (-not $grant) {
    New-MgOauth2PermissionGrant -BodyParameter @{
        clientId    = $miSp.Id
        consentType = "AllPrincipals"
        resourceId  = $graphSp.Id
        scope       = $scopeName
    }
} elseif (($grant.Scope -split " ") -contains $scopeName) {
    Write-Host "Already granted" -ForegroundColor Yellow
} else {
    Update-MgOauth2PermissionGrant -OAuth2PermissionGrantId $grant.Id -Scope (($grant.Scope.Trim() + " " + $scopeName).Trim())
}

Disconnect-MgGraph

But the runbook still can't use it. A managed identity always gets an app-only token, and app-only tokens contain roles (application permissions), never scp (delegated scopes). Calls to markRead or archive from the runbook will still return 403.


7. Marking Message Center posts read or archived (the delegated way)

If you need viewpoint actions, run them as a signed-in admin:

Connect-MgGraph -Scopes "ServiceMessage.Read.All","ServiceMessageViewpoint.Write" -NoWelcome

$body = @{ messageIds = @("MC000001","MC000002") } | ConvertTo-Json
$base = "https://graph.microsoft.com/v1.0/admin/serviceAnnouncement/messages"

Invoke-MgGraphRequest -Method POST -Uri "$base/markRead" -Body $body -ContentType "application/json"
Invoke-MgGraphRequest -Method POST -Uri "$base/archive"  -Body $body -ContentType "application/json"
ActionEndpoint
Mark read / unread/markRead, /markUnread
Archive / unarchive/archive, /unarchive
Favourite / unfavourite/favorite, /unfavorite

Status changes apply only to the signed-in user. Other admins still see the posts unchanged.


8. Auditing: who added a permission, and how?

Every assignment is logged. Go to Enterprise applications → your identity → Audit logs and look for:

ActivityMeaning
Add app role assignment to service principalAn application permission was assigned
Add delegated permission grantA delegated scope was granted

The User-Agent field shows the tool that was used. For example, AZURECLI/… cloud-shell means Azure CLI in Cloud Shell, and a Graph PowerShell SDK agent string means Microsoft.Graph PowerShell.


9. Removing a permission

# Application permission
$assignment = Get-MgServicePrincipalAppRoleAssignment -ServicePrincipalId $miSp.Id -All |
    Where-Object { $_.AppRoleId -eq $role.Id }
Remove-MgServicePrincipalAppRoleAssignment -ServicePrincipalId $miSp.Id -AppRoleAssignmentId $assignment.Id

# Delegated grant (entire grant)
Remove-MgOauth2PermissionGrant -OAuth2PermissionGrantId $grant.Id

You can also use Review permissions on the enterprise app's Permissions blade.


10. Key takeaways

#Takeaway
1The portal can't add Graph permissions to a managed identity. Use PowerShell, the CLI or Graph Explorer
2Application permission → New-MgServicePrincipalAppRoleAssignment; delegated → New-MgOauth2PermissionGrant
3Look up permission IDs from AppRoles (application) or Oauth2PermissionScopes (delegated)
4Cannot convert the literal '' to 'Edm.Guid' usually means the permission isn't an application permission
5ServiceMessageViewpoint.Write is delegated only, so a managed identity can't use it at runtime
6Managed identity tokens are cached, so allow time before a new permission takes effect in a running job

References

No comments:

Post a Comment

Featured Post

Assigning Microsoft Graph Permissions to a Managed Identity: Application vs Delegated

Assigning Microsoft Graph Permissions to a Managed Identity: Application vs Delegated (with ServiceMessage Examples) You've enabled a sy...

Popular posts