I've written a powershell script that essentially goes through app registrations and removes all delegated permissions from them.
Currently I'm using a PATCH request to microsoft graph to set API permissions/requiredResourceAccess (as found here) to be an empty array if there are only delegated api permissions. However if there are application api permissions I'm using an Update-MgApplication command to replace the permissions with only the existing application api permissions.
This is because I was having errors trying to set the body of a PATCH request to be the remaining application api permissions.
I wanted to know if there was a more efficient way to write this script that made use of the correct calls whether it be Microsoft graph or MG powershell commands.
function main {
$TenantId = "XXX"
$Parameters = @{
TenantId = $TenantId
}
Remove-ManifestPermissions @Parameters
}
function Remove-ManifestPermissions
{
[CmdletBinding()]
param (
[Parameter(Mandatory)]
[Guid]
$TenantId
)
$requiredScopes = @(
"Application.ReadWrite.All",
"DelegatedPermissionGrant.ReadWrite.All"
)
$null = Connect-MgGraph -Scopes $requiredScopes -TenantId $TenantId
$apps = Get-MgApplication -All
foreach ($app in $apps)
{
Write-Host "Reviewing App: $( $app.DisplayName )"
# Get the service principal associated with the app
$servicePrincipal = Get-MgServicePrincipal -Filter "AppId eq '$($app.AppId)'"
$apiPermissions = Get-MgServicePrincipalOauth2PermissionGrant -ServicePrincipal $servicePrincipal.Id
foreach ($perm in $apiPermissions)
{
# Remove Admin Access Grant
Write-Host " - Revoking admin consent from API permissions..."
Remove-MgOauth2PermissionGrant -Oauth2PermissionGrantId $perm.Id
}
$appManifest = Get-MgApplication -ApplicationId $app.Id
if ($appManifest.RequiredResourceAccess[0].ResourceAccess) #$appManifest.RequiredResourceAccess.Count gt- 0
{
Write-Host "Processing ResourceAppId: $( $app.DisplayName ) with $( $appManifest.RequiredResourceAccess[0].ResourceAccess.Count ) API Permissions."
$appManifest.RequiredResourceAccess[0].ResourceAccess = $appManifest.RequiredResourceAccess[0].ResourceAccess | Where-Object { $_.Type -eq 'Role' }
if ($appManifest.RequiredResourceAccess[0].ResourceAccess.Count -gt 0)
{
Update-MgApplication -ApplicationId $app.Id -RequiredResourceAccess $appManifest.RequiredResourceAccess
Write-Host "$( $app.DisplayName ) has $( $appManifest.RequiredResourceAccess[0].ResourceAccess.Count ) API Permission(s) remaining."
}
# If all API Permissions are delegated permissions, remove the RequiredResourceAccess entry
elseif ($appManifest.RequiredResourceAccess[0].ResourceAccess.Count -eq 0)
{
$uri = "https://graph.microsoft.com/v1.0/applications/$($app.Id)"
$body = @{
requiredResourceAccess = @()
} | ConvertTo-Json -Depth 3
Invoke-MgGraphRequest -Method PATCH -Uri $uri -Body $body -ContentType "application/json"
}
# Update the manifest if there are role type API permissions
Write-Host "No Delegated Permissions remain for $( $app.DisplayName )"
}
else
{
Write-Host "No API Permissions found for $( $app.DisplayName )"
}
}
Disconnect-MgGraph | Out-Null
}
main
I agree with @Santiago Squarzon, Instead of querying the application again (Get-MgApplication -ApplicationId $app.Id
), we simply use $app.RequiredResourceAccess
directly within the loop. Since $app
already contains this data (from Get-MgApplication -All
), there's no need to retrieve it again:
For sample, I have API permissions like below:
function main {
$TenantId = "TenantID"
$Parameters = @{
TenantId = $TenantId
}
Remove-ManifestPermissions @Parameters
}
function Remove-ManifestPermissions
{
[CmdletBinding()]
param (
[Parameter(Mandatory)]
[Guid]
$TenantId
)
$requiredScopes = @(
"Application.ReadWrite.All",
"DelegatedPermissionGrant.ReadWrite.All"
)
# Connect to Microsoft Graph
$null = Connect-MgGraph -Scopes $requiredScopes -TenantId $TenantId
# Get all apps
$apps = Get-MgApplication -All
foreach ($app in $apps)
{
Write-Host "Reviewing App: $($app.DisplayName)"
# Get the service principal associated with the app
$servicePrincipal = Get-MgServicePrincipal -Filter "AppId eq '$($app.AppId)'"
$apiPermissions = Get-MgServicePrincipalOauth2PermissionGrant -ServicePrincipal $servicePrincipal.Id
foreach ($perm in $apiPermissions)
{
# Remove Admin Access Grant
Write-Host " - Revoking admin consent from API permissions..."
Remove-MgOauth2PermissionGrant -Oauth2PermissionGrantId $perm.Id
}
# Now directly use the app's RequiredResourceAccess property
if ($app.RequiredResourceAccess.Count -gt 0)
{
$resourceAccess = $app.RequiredResourceAccess[0].ResourceAccess
Write-Host "Processing ResourceAppId: $($app.DisplayName) with $($resourceAccess.Count) API Permissions."
# Only keep role-based permissions (remove delegated permissions)
$resourceAccess = $resourceAccess | Where-Object { $_.Type -eq 'Role' }
if ($resourceAccess.Count -gt 0)
{
# Update the app manifest with remaining application permissions
$app.RequiredResourceAccess[0].ResourceAccess = $resourceAccess
Update-MgApplication -ApplicationId $app.Id -RequiredResourceAccess $app.RequiredResourceAccess
Write-Host "$($app.DisplayName) has $($resourceAccess.Count) API Permission(s) remaining."
}
elseif ($resourceAccess.Count -eq 0)
{
# If no role-based permissions remain, remove the RequiredResourceAccess entry
$uri = "https://graph.microsoft.com/v1.0/applications/$($app.Id)"
$body = @{
requiredResourceAccess = @()
} | ConvertTo-Json -Depth 3
Invoke-MgGraphRequest -Method PATCH -Uri $uri -Body $body -ContentType "application/json"
Write-Host "All API permissions removed for $($app.DisplayName)."
}
}
else
{
Write-Host "No API Permissions found for $($app.DisplayName)"
}
}
# Disconnect from Microsoft Graph
Disconnect-MgGraph | Out-Null
}
main
The delegated API permissions revoked and removed successfully: