Private/Resolve-METDnsName.ps1

function Resolve-METDnsName {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory)] [string] $Name,
        [Parameter(Mandatory)] [ValidateSet('TXT')] [string] $Type
    )

    # On Windows, delegate to the native Resolve-DnsName cmdlet (DnsClient module).
    if ($IsWindows -ne $false) {
        try {
            return Resolve-DnsName -Name $Name -Type $Type -DnsOnly -ErrorAction Stop
        }
        catch {
            # Resolve-DnsName throws for an authoritative NXDOMAIN (9003) and for a name with
            # no records of the type (9501). Both are answers, not lookup failures - the dig
            # and DNS-over-HTTPS tiers return no records for them, and so must this one.
            if ($_.Exception.NativeErrorCode -in 9003, 9501 -or
                $_.FullyQualifiedErrorId -match '^(DNS_ERROR_RCODE_NAME_ERROR|DNS_INFO_NO_RECORDS),') {
                return
            }
            throw
        }
    }

    # Non-Windows: build compatible result objects using dig (preferred) or nslookup.
    $records = [System.Collections.Generic.List[PSCustomObject]]::new()

    if (Get-Command -Name dig -CommandType Application -ErrorAction SilentlyContinue) {
        $raw = & dig +short $Type $Name 2>&1

        foreach ($line in ($raw | Where-Object { $_ -match '\S' })) {
            $text = ($line -replace '"', '').Trim()
            if (-not $text) { continue }

            $records.Add([PSCustomObject]@{
                Name    = $Name
                Type    = $Type
                TTL     = 0
                Strings = @($text)
            })
        }
    }
    elseif (Get-Command -Name nslookup -CommandType Application -ErrorAction SilentlyContinue) {
        $raw = & nslookup "-type=$Type" $Name 2>&1

        foreach ($line in $raw) {
            # TXT records appear as: text = "v=spf1 ..." or "v=spf1 ..."
            if ($line -match '(?:text\s*=\s*)?"([^"]+)"') {
                $records.Add([PSCustomObject]@{
                    Name    = $Name
                    Type    = $Type
                    TTL     = 0
                    Strings = @($Matches[1].Trim())
                })
            }
        }
    }
    else {
        # Minimal Linux containers (including GitHub Codespaces) often omit both
        # bind-utils and dnsutils. Use DNS-over-HTTPS rather than treating that
        # missing local tooling as proof that a DNS record does not exist.
        $resolver = $env:MET_DOH_RESOLVER
        if ($resolver) { $resolver = $resolver.Trim() }
        if ($resolver -and $resolver -eq 'none') {
            throw "DNS lookup for '$Name' requires the DNS-over-HTTPS fallback because neither Resolve-DnsName, dig nor nslookup is available on this host, but MET_DOH_RESOLVER is set to 'none'. Install dnsutils/bind-utils, or unset MET_DOH_RESOLVER to allow the fallback."
        }
        if (-not $resolver) { $resolver = 'https://dns.google/resolve' }

        if (-not $script:METDohWarned) {
            Write-Warning "No local DNS resolver is available, so MET is resolving '$Name' over DNS-over-HTTPS via $resolver. The domain names of the tenant being assessed leave this host and are sent to that third-party resolver. Set MET_DOH_RESOLVER to another endpoint, or to 'none' to disable this fallback."
            $script:METDohWarned = $true
        }

        $escapedName = [uri]::EscapeDataString($Name)
        $uri = "${resolver}?name=$escapedName&type=$Type"

        try {
            $response = Invoke-RestMethod -Uri $uri -Method Get -Headers @{ Accept = 'application/dns-json' } -TimeoutSec 15 -ErrorAction Stop
        }
        catch {
            throw "DNS lookup for '$Name' failed using the DNS-over-HTTPS fallback: $($_.Exception.Message)"
        }

        # Status 3 is an authoritative NXDOMAIN response: the lookup succeeded,
        # but the requested name does not exist. Other non-zero statuses are DNS
        # failures and must not be reported as an absent policy record.
        if ([int]$response.Status -eq 3) {
            return @()
        }
        if ([int]$response.Status -ne 0) {
            throw "DNS-over-HTTPS lookup for '$Name' returned status $($response.Status)."
        }

        foreach ($answer in @($response.Answer | Where-Object { [int]$_.type -eq 16 })) {
            # DNS JSON represents a TXT RR as one or more quoted character
            # strings. Join adjacent strings to match Resolve-DnsName's shape.
            $text = [string]$answer.data
            $text = [regex]::Replace($text, '"\s+"', '')
            $text = $text.Trim('"')
            if (-not $text) { continue }

            $records.Add([PSCustomObject]@{
                Name    = $Name
                Type    = $Type
                TTL     = [int]$answer.TTL
                Strings = @($text)
            })
        }
    }

    return $records.ToArray()
}