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 type | Graph object created | PowerShell cmdlet | Works for managed identity at runtime? |
|---|---|---|---|
| Application (app role) | appRoleAssignment | New-MgServicePrincipalAppRoleAssignment | ✅ Yes |
| Delegated (scope) | oauth2PermissionGrant | New-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:
| Permission | Application | Delegated | Purpose |
|---|---|---|---|
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.Allis the highest service message permission available. ServiceMessageViewpoint.Writeis 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:
AppRolesfor application permissions,Oauth2PermissionScopesfor delegated ones.
3. Prerequisites
| Item | Requirement |
|---|---|
| Role | Global Administrator or Privileged Role Administrator |
| Modules | Microsoft.Graph.Applications, Microsoft.Graph.Identity.SignIns |
| Identity | Managed 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
| Symptom | Cause |
|---|---|
| Step 5 returns nothing | The permission isn't in $graphSp.AppRoles because it's delegated-only |
$role.Id is empty | Graph receives appRoleId: "" |
Edm.Guid 400 error | An 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"
| Action | Endpoint |
|---|---|
| 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:
| Activity | Meaning |
|---|---|
Add app role assignment to service principal | An application permission was assigned |
Add delegated permission grant | A 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 |
|---|---|
| 1 | The portal can't add Graph permissions to a managed identity. Use PowerShell, the CLI or Graph Explorer |
| 2 | Application permission → New-MgServicePrincipalAppRoleAssignment; delegated → New-MgOauth2PermissionGrant |
| 3 | Look up permission IDs from AppRoles (application) or Oauth2PermissionScopes (delegated) |
| 4 | Cannot convert the literal '' to 'Edm.Guid' usually means the permission isn't an application permission |
| 5 | ServiceMessageViewpoint.Write is delegated only, so a managed identity can't use it at runtime |
| 6 | Managed identity tokens are cached, so allow time before a new permission takes effect in a running job |