Collectors/LoadBalancers.ps1

function Get-AerLoadBalancers {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory)] [string[]] $SubscriptionIds,
        [Parameter(Mandatory)]            $SubscriptionMap
    )

    $subLookup = @{}
    if ($SubscriptionMap -is [hashtable]) { $subLookup = $SubscriptionMap }
    elseif ($SubscriptionMap) { $SubscriptionMap.PSObject.Properties | ForEach-Object { $subLookup[$_.Name] = $_.Value } }

    function Resolve-SubName($sid) { if ($sid) { $subLookup[$sid.ToLowerInvariant()] ?? $sid } else { '' } }
    function Leaf($id) { if ($id) { ($id -split '/')[-1] } else { '' } }
    function ById($arr) { $m = @{}; foreach ($x in @($arr)) { if ($x.id) { $m[$x.id.ToLowerInvariant()] = $x } }; $m }
    function Get-ByRef($map, $ref) { if ($ref -and $ref.id) { $map[$ref.id.ToLowerInvariant()] } else { $null } }
    $arg = { param($q) try { Invoke-AerArgQuery -SubscriptionIds $SubscriptionIds -Query $q } catch { Write-Warning "[LoadBalancers] $($_.Exception.Message)"; @() } }
    function RestGet($path) {
        $out = [System.Collections.Generic.List[object]]::new()
        try {
            while (-not [string]::IsNullOrWhiteSpace($path)) {
                $resp = Invoke-AzRestMethod -Method GET -Path $path -ErrorAction Stop
                if ($resp.StatusCode -ne 200) { break }
                $b = $resp.Content | ConvertFrom-Json
                foreach ($v in @($b.value)) { $out.Add($v) }
                $path = if ($b.nextLink) { ([uri]$b.nextLink).PathAndQuery } else { $null }
            }
        } catch { Write-Warning "[LoadBalancers.rest] $($_.Exception.Message)" }
        @($out)
    }

    $balancers = [System.Collections.Generic.List[object]]::new()
    function Add-Lb($type, $r, $sku, $endpoints) {
        $balancers.Add([pscustomobject]@{
            Type = $type; Name = $r.name; Id = $r.id
            SubscriptionId = $r.subscriptionId; SubscriptionName = (Resolve-SubName $r.subscriptionId)
            ResourceGroup = $r.resourceGroup; Location = $r.location; Sku = $sku; Tags = $r.tags
            Endpoints = @($endpoints)
        })
    }

    # ── Azure Load Balancer (ALB) ────────────────────────────────────────────
    foreach ($r in (& $arg "resources | where type =~ 'microsoft.network/loadbalancers' | project id, name, subscriptionId, resourceGroup, location, sku = tostring(sku.name), rules = properties.loadBalancingRules, pools = properties.backendAddressPools, probes = properties.probes, tags")) {
        $poolMap = ById $r.pools; $probeMap = ById $r.probes
        $eps = foreach ($rule in @($r.rules)) {
            $pool = Get-ByRef $poolMap $rule.properties.backendAddressPool
            $servers = @()
            if ($pool) {
                foreach ($a in @($pool.properties.loadBalancerBackendAddresses)) { if ($a.properties.ipAddress) { $servers += $a.properties.ipAddress } }
                $nic = @($pool.properties.backendIPConfigurations).Count
                if (-not @($servers).Count -and $nic) { $servers += "$nic backend NIC(s)" }
            }
            $probe = Get-ByRef $probeMap $rule.properties.probe
            [pscustomobject]@{
                Name = $rule.name; Group = (Leaf $rule.properties.backendAddressPool.id)
                Protocol = "$($rule.properties.protocol)"
                FrontendPort = "$($rule.properties.frontendPort)"; BackendPort = "$($rule.properties.backendPort)"
                Health = if ($probe) { "$($probe.properties.protocol):$($probe.properties.port) probe" } else { '' }
                Servers = @(@($servers) | Select-Object -First 12)
            }
        }
        Add-Lb 'Azure Load Balancer' $r $r.sku @($eps)
    }

    # ── Application Gateway (AGW) ─────────────────────────────────────────────
    foreach ($r in (& $arg "resources | where type =~ 'microsoft.network/applicationgateways' | project id, name, subscriptionId, resourceGroup, location, sku = tostring(sku.name), listeners = properties.httpListeners, fports = properties.frontendPorts, pools = properties.backendAddressPools, settings = properties.backendHttpSettingsCollection, rules = properties.requestRoutingRules, probes = properties.probes, tags")) {
        $lMap = ById $r.listeners; $fpMap = ById $r.fports; $poolMap = ById $r.pools; $setMap = ById $r.settings
        $eps = foreach ($rule in @($r.rules)) {
            $listener = Get-ByRef $lMap $rule.properties.httpListener
            $fport = if ($listener) { Get-ByRef $fpMap $listener.properties.frontendPort } else { $null }
            $pool = Get-ByRef $poolMap $rule.properties.backendAddressPool
            $set = Get-ByRef $setMap $rule.properties.backendHttpSettings
            $servers = @()
            if ($pool) { foreach ($a in @($pool.properties.backendAddresses)) { $servers += ($a.fqdn ?? $a.ipAddress) } }
            $proto = if ($listener) { "$($listener.properties.protocol)" } else { '' }
            $lhost = if ($listener -and $listener.properties.hostName) { $listener.properties.hostName } else { '' }
            [pscustomobject]@{
                Name = $rule.name; Group = (Leaf $rule.properties.backendAddressPool.id) + $(if ($lhost) { " · $lhost" } else { '' })
                Protocol = $proto
                FrontendPort = if ($fport) { "$($fport.properties.port)" } else { '' }
                BackendPort = if ($set) { "$($set.properties.port)" } else { '' }
                Health = if ($set -and $set.properties.probe) { 'custom probe' } else { 'default probe' }
                Servers = @(@($servers | Where-Object { $_ }) | Select-Object -First 12)
            }
        }
        Add-Lb 'Application Gateway' $r $r.sku @($eps)
    }

    # ── Azure Front Door (classic) ───────────────────────────────────────────
    foreach ($r in (& $arg "resources | where type =~ 'microsoft.network/frontdoors' | project id, name, subscriptionId, resourceGroup, location, backendPools = properties.backendPools, tags")) {
        $eps = foreach ($pool in @($r.backendPools)) {
            $servers = foreach ($b in @($pool.properties.backends)) { "$($b.address):$($b.httpsPort)" }
            [pscustomobject]@{
                Name = $pool.name; Group = $pool.name; Protocol = 'HTTP/HTTPS'
                FrontendPort = '80/443'; BackendPort = ''
                Health = ''
                Servers = @(@($servers) | Select-Object -First 12)
            }
        }
        Add-Lb 'Azure Front Door (classic)' $r 'Classic' @($eps)
    }

    # ── Azure Front Door (Standard/Premium) — origins via REST ───────────────
    foreach ($p in (& $arg "resources | where type =~ 'microsoft.cdn/profiles' and sku.name in~ ('Standard_AzureFrontDoor','Premium_AzureFrontDoor') | project id, name, subscriptionId, resourceGroup, location, sku = tostring(sku.name), tags")) {
        $base = "/subscriptions/$($p.subscriptionId)/resourceGroups/$($p.resourceGroup)/providers/Microsoft.Cdn/profiles/$($p.name)"
        $eps = foreach ($og in (RestGet "$base/originGroups?api-version=2023-05-01")) {
            $servers = foreach ($o in (RestGet "$base/originGroups/$($og.name)/origins?api-version=2023-05-01")) { "$($o.properties.hostName):$($o.properties.httpsPort)" }
            $hp = $og.properties.healthProbeSettings
            [pscustomobject]@{
                Name = $og.name; Group = $og.name; Protocol = 'HTTP/HTTPS'
                FrontendPort = '80/443'; BackendPort = ''
                Health = if ($hp) { "$($hp.probeProtocol) $($hp.probePath)" } else { '' }
                Servers = @(@($servers) | Select-Object -First 12)
            }
        }
        Add-Lb 'Azure Front Door (Std/Premium)' $p $p.sku @($eps)
    }

    # ── Traffic Manager (ATM) ────────────────────────────────────────────────
    foreach ($r in (& $arg "resources | where type =~ 'microsoft.network/trafficmanagerprofiles' | project id, name, subscriptionId, resourceGroup, location, method = tostring(properties.trafficRoutingMethod), monitor = properties.monitorConfig, endpoints = properties.endpoints, tags")) {
        $mport = "$($r.monitor.port)"; $mproto = "$($r.monitor.protocol)"
        $eps = foreach ($ep in @($r.endpoints)) {
            [pscustomobject]@{
                Name = $ep.name; Group = "$($r.method) routing"; Protocol = $mproto
                FrontendPort = ''; BackendPort = $mport
                Health = "$($ep.properties.endpointMonitorStatus)"
                Servers = @("$($ep.properties.target)")
            }
        }
        Add-Lb 'Traffic Manager' $r "$($r.method)" @($eps)
    }

    $all = @($balancers)
    $cnt = { param($t) @($all | Where-Object { $_.Type -eq $t }).Count }
    return [pscustomobject]@{
        Total  = $all.Count
        Counts = [pscustomobject]@{
            LoadBalancer   = (& $cnt 'Azure Load Balancer')
            AppGateway     = (& $cnt 'Application Gateway')
            FrontDoor      = (& $cnt 'Azure Front Door (classic)') + (& $cnt 'Azure Front Door (Std/Premium)')
            TrafficManager = (& $cnt 'Traffic Manager')
        }
        Balancers = $all
    }
}