Src/Private/Get-AbrIntuneMicrosoftTunnel.ps1

function Get-AbrIntuneMicrosoftTunnel {
    <#
    .SYNOPSIS
        Documents Microsoft Tunnel VPN sites, server configurations, and server health.
    .DESCRIPTION
        Collects and reports on:
          - Tunnel Sites (beta/deviceManagement/microsoftTunnelSites)
          - Tunnel Configurations per site
          - Server health and version information
    .NOTES
        Version: 0.1.0
        Added: v0.2.51 — addresses CRITICAL gap from gap tracker
    #>

    [CmdletBinding()]
    param (
        [Parameter(Position = 0, Mandatory)]
        [string]$TenantId
    )

    begin {
        Write-PScriboMessage -Message "Collecting Microsoft Tunnel configuration for $TenantId."
        Show-AbrDebugExecutionTime -Start -TitleMessage 'Microsoft Tunnel'
    }

    process {
        try {
            Write-Host " - Retrieving Microsoft Tunnel sites..."
            $TunnelSitesResp = $null
            try {
                $TunnelSitesResp = Invoke-MgGraphRequest -Method GET `
                    -Uri "$($script:GraphEndpoint)/beta/deviceManagement/microsoftTunnelSites" `
                    -ErrorAction Stop
            } catch {
                Write-AbrDebugLog "Tunnel sites unavailable: $($_.Exception.Message)" 'WARN' 'Tunnel'
            }

            $TunnelSites = if ($TunnelSitesResp -and $TunnelSitesResp.value) { $TunnelSitesResp.value } else { $null }

            if ($TunnelSites -and @($TunnelSites).Count -gt 0) {
                Section -Style Heading2 'Microsoft Tunnel Sites' {
                    Paragraph "The following documents Microsoft Tunnel VPN infrastructure configured in tenant $TenantId."
                    BlankLine

                    $SiteObj = [System.Collections.ArrayList]::new()
                    foreach ($Site in ($TunnelSites | Sort-Object displayName)) {
                        # Fetch servers for this site
                        $serverCount = 0
                        $serverHealth = '--'
                        try {
                            $ServersResp = $null
                            try { $ServersResp = Invoke-MgGraphRequest -Method GET -Uri "$($script:GraphEndpoint)/beta/deviceManagement/microsoftTunnelSites/$($Site.id)/microsoftTunnelServers" -ErrorAction Stop } catch { }
                            if ($ServersResp -and $ServersResp.value) {
                                $serverCount = @($ServersResp.value).Count
                                $healthStates = $ServersResp.value | ForEach-Object { $_.tunnelServerHealthStatus }
                                if ($healthStates -contains 'unhealthy') { $serverHealth = 'Unhealthy' }
                                elseif ($healthStates -contains 'healthy')   { $serverHealth = 'Healthy' }
                                else { $serverHealth = ($healthStates | Select-Object -First 1) }
                            }
                        } catch { }

                        $SiteObj.Add([pscustomobject]([ordered]@{
                            'Site Name'       = $Site.displayName
                            'Description'     = if ($Site.description) { $Site.description } else { '--' }
                            'Public Address'  = if ($Site.publicAddress) { $Site.publicAddress } else { '--' }
                            'Servers'         = $serverCount
                            'Server Health'   = $serverHealth
                            'Upgrade Auto'    = if ($null -ne $Site.upgradeAutomatically) { if ($Site.upgradeAutomatically) { 'Yes' } else { 'No' } } else { '--' }
                        })) | Out-Null
                    }
                    $SiteParams = @{ Name = "Tunnel Sites - $TenantId"; ColumnWidths = 22, 24, 22, 8, 12, 12 }
                    if ($Report.ShowTableCaptions) { $SiteParams['Caption'] = "- $($SiteParams.Name)" }
                    $SiteObj | Table @SiteParams

                    # Tunnel Configurations
                    try {
                        $TunnelConfigResp = $null
                        try { $TunnelConfigResp = Invoke-MgGraphRequest -Method GET -Uri "$($script:GraphEndpoint)/beta/deviceManagement/microsoftTunnelConfigurations" -ErrorAction Stop } catch { Write-AbrDebugLog "Tunnel configs unavailable: $($_.Exception.Message)" 'WARN' 'Tunnel' }
                        if ($TunnelConfigResp -and $TunnelConfigResp.value -and @($TunnelConfigResp.value).Count -gt 0) {
                            BlankLine
                            Paragraph "Tunnel Server Configurations ($(@($TunnelConfigResp.value).Count)):"
                            BlankLine
                            $CfgObj = [System.Collections.ArrayList]::new()
                            foreach ($Cfg in ($TunnelConfigResp.value | Sort-Object displayName)) {
                                $CfgObj.Add([pscustomobject]([ordered]@{
                                    'Config Name'     = $Cfg.displayName
                                    'Network'         = if ($Cfg.network) { $Cfg.network } else { '--' }
                                    'DNS Servers'     = if ($Cfg.dnsServers) { $Cfg.dnsServers -join ', ' } else { '--' }
                                    'Split Tunneling' = if ($null -ne $Cfg.splitDNS) { if ($Cfg.splitDNS) { 'Yes' } else { 'No' } } else { '--' }
                                    'Port'            = if ($Cfg.listenPort) { "$($Cfg.listenPort)" } else { '--' }
                                })) | Out-Null
                            }
                            $CfgParams = @{ Name = "Tunnel Configurations - $TenantId"; ColumnWidths = 25, 20, 25, 15, 15 }
                            if ($Report.ShowTableCaptions) { $CfgParams['Caption'] = "- $($CfgParams.Name)" }
                            $CfgObj | Table @CfgParams
                        }
                    } catch { }
                }
            } else {
                Write-AbrDebugLog 'No Microsoft Tunnel sites found (feature may not be licensed).' 'INFO' 'Tunnel'
            }
        } catch {
            if (Test-AbrGraphForbidden -ErrorRecord $_) { Write-AbrPermissionError -Section 'Microsoft Tunnel' -RequiredRole 'Intune Service Administrator or Global Administrator' }
            else { Write-AbrSectionError -Section 'Microsoft Tunnel' -Message "$($_.Exception.Message)" }
        }
    }

    end { Show-AbrDebugExecutionTime -End -TitleMessage 'Microsoft Tunnel' }
}