Private/Scanning/Get-SubAssignmentsRecursive.ps1

function Get-SubAssignmentsRecursive {
    <#
    .SYNOPSIS
        Get policy assignments from all subscriptions in the tenant.
     
    .DESCRIPTION
        Scans all enabled subscriptions in the tenant and retrieves their policy
        assignments. Automatically filters for direct assignments (excluding inherited).
     
    .PARAMETER EnabledOnly
        If true (default), only processes enabled subscriptions. Set to false to include all states.
     
    .EXAMPLE
        $allSubAssignments = Get-SubAssignmentsRecursive
     
    .OUTPUTS
        Array of policy assignment objects from all subscriptions
    #>

    [CmdletBinding()]
    param(
        [bool]$EnabledOnly = $true
    )
    
    $assignments = @()
    
    try {
        Write-Host " ├─ Discovering ALL subscriptions in tenant..." -ForegroundColor DarkCyan
        
        $allSubs = Invoke-AzCommandWithRetry -Command {
            Get-AzSubscription -ErrorAction Stop
        } -OperationName "Subscription"
        
        # Track API type
        if ($script:ApiCallStats) {
            $script:ApiCallStats.SubscriptionCalls++
        }
        
        if ($EnabledOnly) {
            $allSubs = $allSubs | Where-Object { $_.State -eq 'Enabled' }
        }
        
        Write-Host (" │ ├─ Found {0} {1}subscriptions" -f $allSubs.Count, $(if ($EnabledOnly) { "enabled " } else { "" })) -ForegroundColor DarkGray
        
        $subCount = 0
        $subAssignmentCount = 0
        $individualPolicyCount = 0
        
        foreach ($sub in $allSubs) {
            $subCount++
            
            try {
                $directSubAssignments = Get-SubAssignments -SubscriptionId $sub.Id -DirectOnly
                
                if ($directSubAssignments.Count -gt 0) {
                    $directCount = ($directSubAssignments | Where-Object { 
                        $_.PolicyDefinitionId -like "*/policyDefinitions/*" -and 
                        $_.PolicyDefinitionId -notlike "*/policySetDefinitions/*"
                    }).Count
                    
                    Write-Host (" │ ├─ [{0}/{1}] Sub '{2}': {3} assignments ({4} individual policies)" `
                        -f $subCount, $allSubs.Count, $sub.Name, $directSubAssignments.Count, $directCount) `
                        -ForegroundColor DarkGray
                    
                    $assignments += $directSubAssignments
                    $subAssignmentCount += $directSubAssignments.Count
                    $individualPolicyCount += $directCount
                } else {
                    Write-Host (" │ ├─ [{0}/{1}] Sub '{2}': no direct assignments (all inherited)" `
                        -f $subCount, $allSubs.Count, $sub.Name) -ForegroundColor DarkGray
                }
            } catch {
                Write-Host (" │ ├─ [{0}/{1}] Sub '{2}': ERROR - {3}" `
                    -f $subCount, $allSubs.Count, $sub.Name, $_.Exception.Message) -ForegroundColor Red
            }
        }
        
        Write-Host ""
        Write-Host (" │ └─ Found {0} assignments across all subscriptions" -f $subAssignmentCount) -ForegroundColor Green
        Write-Host (" (Including {0} individual policies)" -f $individualPolicyCount) -ForegroundColor DarkGray
        
    } catch {
        Write-Warning "Cannot enumerate subscriptions: $($_.Exception.Message)"
    }
    
    return $assignments
}