Private/Scanning/Get-MgAssignmentsRecursive.ps1

function Get-MgAssignmentsRecursive {
    <#
    .SYNOPSIS
        Get policy assignments from all Management Groups in the tenant.
     
    .DESCRIPTION
        Recursively scans all Management Groups in the tenant and retrieves their
        policy assignments. Useful for comprehensive tenant-wide analysis.
     
    .PARAMETER ExcludeRootMg
        Optional Management Group ID to exclude from scanning (typically the root MG
        already scanned by Get-MgAssignments).
     
    .EXAMPLE
        $allMgAssignments = Get-MgAssignmentsRecursive -ExcludeRootMg "MyRootMG"
     
    .OUTPUTS
        Array of policy assignment objects from all MGs
    #>

    [CmdletBinding()]
    param(
        [string]$ExcludeRootMg
    )
    
    $assignments = @()
    
    try {
        Write-Host " ├─ Discovering all Management Groups in tenant..." -ForegroundColor DarkCyan
        
        $allMgs = Invoke-AzCommandWithRetry -Command {
            Get-AzManagementGroup -ErrorAction Stop
        } -OperationName "ManagementGroup"
        
        # Track API type
        if ($script:ApiCallStats) {
            $script:ApiCallStats.ManagementGroupCalls++
        }
        
        Write-Host (" │ ├─ Total MGs found: {0}" -f $allMgs.Count) -ForegroundColor DarkGray
        
        $mgCount = 0
        $mgAssignmentCount = 0
        
        foreach ($childMg in $allMgs) {
            # Skip excluded MG (typically root already scanned)
            if ($ExcludeRootMg -and $childMg.Name -eq $ExcludeRootMg) {
                continue
            }
            
            $mgCount++
            
            # Progress indicator every 5 MGs
            if ($mgCount % 5 -eq 0 -or $mgCount -eq $allMgs.Count) {
                Write-Host ("`r │ ├─ [{0}/{1}] Scanning MGs..." -f $mgCount, $allMgs.Count) -NoNewline -ForegroundColor DarkGray
            }
            
            try {
                $childMgScope = "/providers/Microsoft.Management/managementGroups/$($childMg.Name)"
                
                $childMgAssignments = Invoke-AzCommandWithRetry -Command {
                    Get-AzPolicyAssignment -Scope $childMgScope -ErrorAction SilentlyContinue
                } -OperationName "PolicyAssignment"
                
                # Track API type
                if ($script:ApiCallStats) {
                    $script:ApiCallStats.PolicyAssignmentCalls++
                }
                
                if ($childMgAssignments) {
                    $assignments += $childMgAssignments
                    $mgAssignmentCount += $childMgAssignments.Count
                }
            } catch {
                Write-Debug "Cannot scan MG $($childMg.Name): $($_.Exception.Message)"
            }
        }
        
        Write-Host ""
        Write-Host (" │ └─ Found {0} assignments across all MGs" -f $mgAssignmentCount) -ForegroundColor Green
        
    } catch {
        Write-Warning "Cannot enumerate Management Groups: $($_.Exception.Message)"
    }
    
    return $assignments
}