Public/Get-IOOverPrivilegedApps.ps1

function Get-IOOverPrivilegedApps {
    <#
    .SYNOPSIS
        Flags applications with high-privilege Microsoft Graph permissions.
    .EXAMPLE
        Get-IOOverPrivilegedApps
    .EXAMPLE
        Get-IOOverPrivilegedApps -ToCsv "over-privileged.csv"
    #>

    [CmdletBinding()]
    param(
        [string[]]$CustomHighPrivilegeRoles,

        [string]$ToCsv
    )

    $cmdName = $MyInvocation.MyCommand.Name
    Write-IOLog 'Scanning for apps with high-privilege API permissions...' -Level Info -Component $cmdName

    $dangerousRoles = if ($CustomHighPrivilegeRoles) { $CustomHighPrivilegeRoles } else { $script:HighPrivilegeRoles }
    $results = [System.Collections.Generic.List[PSCustomObject]]::new()

    # Resolve MS Graph SP to get role ID -> name mapping
    $graphSps = Invoke-IOGraphRequest -Uri "v1.0/servicePrincipals?`$filter=appId eq '$($script:MsGraphAppId)'&`$select=id,appRoles"
    if (-not $graphSps -or $graphSps.Count -eq 0) {
        throw "Microsoft Graph service principal not found in tenant."
    }
    $graphSp  = $graphSps[0]
    $roleMap  = @{}
    foreach ($role in $graphSp.appRoles) {
        $roleMap[$role.id] = $role.value
    }

    # Scan all service principals of type Application
    $sps = Invoke-IOGraphRequest -Uri "v1.0/servicePrincipals?`$filter=servicePrincipalType eq 'Application'&`$select=id,displayName,appId"
    $total   = ($sps | Measure-Object).Count
    $counter = 0

    if ($total -eq 0) {
        Export-IOResult -Data @() -ToCsv $ToCsv -CommandName $cmdName
        return
    }

    try {
    foreach ($sp in $sps) {
        $counter++
        if ($counter % 50 -eq 0) {
            Write-Progress -Activity 'Evaluating app permissions' -Status "$counter / $total" -PercentComplete (($counter / $total) * 100)
        }

        try {
            $assignments = Invoke-IOGraphRequest -Uri "v1.0/servicePrincipals/$($sp.id)/appRoleAssignments?`$select=appRoleId,resourceId,resourceDisplayName" -NoPagination -SkipConnectionCheck
        }
        catch {
            continue
        }

        foreach ($assignment in $assignments) {
            $roleName = $roleMap[$assignment.appRoleId]
            if (-not $roleName) { $roleName = $assignment.appRoleId }

            if ($roleName -in $dangerousRoles) {
                $results.Add([PSCustomObject]@{
                    ApplicationName = $sp.displayName
                    ApplicationId   = $sp.appId
                    ObjectId        = $sp.id
                    Permission      = $roleName
                    ResourceApp     = $assignment.resourceDisplayName
                    RiskLevel       = if ($roleName -match 'ReadWrite\.All|FullControl|RoleManagement') { 'Critical' } else { 'High' }
                })
            }
        }
    }
    }
    finally {
        Write-Progress -Activity 'Evaluating app permissions' -Completed
    }

    $sorted = $results | Sort-Object RiskLevel, ApplicationName
    Export-IOResult -Data $sorted -ToCsv $ToCsv -CommandName $cmdName
}