Disclaimer: This issue was identified and written by Claude Code (model: claude-opus-4-6-1m) during an automated code review, and has had a cursory review by a human before submission.
Summary
check_AppRegistrations.psm1 contains two patterns that perform full scans of large collections for every app registration processed. In large tenants (thousands of app registrations, enterprise apps, and role assignments), these become significant bottlenecks.
Affected file
modules/check_AppRegistrations.psm1
Issue 1: Enterprise App matching — O(N×M)
Evidence (line 606)
For every app registration, the entire $EnterpriseApps hashtable is iterated to find the matching service principal by AppId:
# Line 606-610
$EnterpriseApps.GetEnumerator() | Where-Object { $_.Value.AppId -eq $item.AppId } |
Select-Object -First 1 | ForEach-Object {
$ImpactScore += $_.Value.Impact
$SPObjectID = $_.Name
$ApiDelegatedCount = $_.Value.ApiDelegated
}
If there are 2,000 app registrations and 5,000 enterprise apps, this performs 10,000,000 property comparisons.
Suggested fix
Build a lookup hashtable before the main loop:
# Before the main processing loop:
$SPByAppId = @{}
$EnterpriseApps.GetEnumerator() | ForEach-Object {
$SPByAppId[$_.Value.AppId] = $_
}
# Inside the loop (replaces lines 606-610):
$matchingSP = $SPByAppId[$item.AppId]
if ($matchingSP) {
$ImpactScore += $matchingSP.Value.Impact
$SPObjectID = $matchingSP.Name
$ApiDelegatedCount = $matchingSP.Value.ApiDelegated
}
This reduces the operation from O(N×M) to O(N+M).
Issue 2: Per-app role assignment scanning — O(N×R)
Evidence (lines 493-510)
For each app registration, the code scans ALL tenant role assignments to find scoped Cloud Application Administrators and Application Administrators:
# Line 493 (inside the per-app-registration loop)
$CloudAppAdminCurrentApp = $TenantRoleAssignments.Values |
ForEach-Object { $_ |
Where-Object {
$_.RoleDefinitionId -eq "158c047a-c907-4556-b7ef-446551a6b5f7" -and
$_.DirectoryScopeId -eq "/$($item.Id)"
}
} | Select-Object PrincipalId, AssignmentType
The same pattern repeats for Application Administrator at line 507. With 2,000 app registrations and 10,000 total role assignments, this is 40,000,000 comparisons (2 roles × 2,000 apps × 10,000 assignments).
Suggested fix
Pre-index scoped role assignments by DirectoryScopeId before the main loop:
# Before the main loop:
$ScopedCloudAppAdmins = @{}
$ScopedAppAdmins = @{}
$TenantRoleAssignments.Values | ForEach-Object {
$_ | Where-Object { $_.RoleDefinitionId -eq "158c047a-c907-4556-b7ef-446551a6b5f7" } |
ForEach-Object { $ScopedCloudAppAdmins[$_.DirectoryScopeId] += @($_) }
$_ | Where-Object { $_.RoleDefinitionId -eq "9b895d92-2cd3-44c7-9d02-a6ac2d5ea5c3" } |
ForEach-Object { $ScopedAppAdmins[$_.DirectoryScopeId] += @($_) }
}
# Inside the loop:
$CloudAppAdminCurrentApp = $ScopedCloudAppAdmins["/$($item.Id)"]
$AppAdminCurrentApp = $ScopedAppAdmins["/$($item.Id)"]
Note
The tenant-wide admin lookups at lines 187-208 run once (not per-app) and are not a performance concern. Only the per-app scoped lookups at lines 493-510 are quadratic.
Version
V20260316
Summary
check_AppRegistrations.psm1contains two patterns that perform full scans of large collections for every app registration processed. In large tenants (thousands of app registrations, enterprise apps, and role assignments), these become significant bottlenecks.Affected file
modules/check_AppRegistrations.psm1Issue 1: Enterprise App matching — O(N×M)
Evidence (line 606)
For every app registration, the entire
$EnterpriseAppshashtable is iterated to find the matching service principal byAppId:If there are 2,000 app registrations and 5,000 enterprise apps, this performs 10,000,000 property comparisons.
Suggested fix
Build a lookup hashtable before the main loop:
This reduces the operation from O(N×M) to O(N+M).
Issue 2: Per-app role assignment scanning — O(N×R)
Evidence (lines 493-510)
For each app registration, the code scans ALL tenant role assignments to find scoped Cloud Application Administrators and Application Administrators:
The same pattern repeats for Application Administrator at line 507. With 2,000 app registrations and 10,000 total role assignments, this is 40,000,000 comparisons (2 roles × 2,000 apps × 10,000 assignments).
Suggested fix
Pre-index scoped role assignments by
DirectoryScopeIdbefore the main loop:Note
The tenant-wide admin lookups at lines 187-208 run once (not per-app) and are not a performance concern. Only the per-app scoped lookups at lines 493-510 are quadratic.
Version
V20260316