azurepowershellmicrosoft-graph-apiazure-app-registration

Calls for powershell script to remove Delegated API Permissions from app registrations


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

Solution

  • 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:

    enter image description here

    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
    

    enter image description here

    The delegated API permissions revoked and removed successfully:

    enter image description here