Workloads/Get-DNSData.ps1

# Get-DNSData.ps1
# Checks SPF, DKIM, DMARC, and MX records for all tenant domains.
# Logs findings for missing or weak records.
# Part of the M365-QuickAssess module -- not exported.

function Get-DNSData
{
    param
    (
        $Assessment
    )

    Write-Log "Collecting DNS data"

    # -------------------------------------------------------------------
    # Helper: Resolve-DNSRecord
    # Wraps Resolve-DnsName with error suppression
    # -------------------------------------------------------------------
    function Resolve-DNSRecord
    {
        param
        (
            [string]$Name,
            [string]$Type
        )

        try
        {
            Resolve-DnsName -Name $Name -Type $Type -ErrorAction Stop
        }
        catch
        {
            $null
        }
    }

    # -------------------------------------------------------------------
    # Get all tenant domains excluding mail.onmicrosoft.com routing domains
    # -------------------------------------------------------------------
    $domains = @()

    try
    {
        $domains = Get-MgDomain -All -ErrorAction Stop |
            Where-Object { $_.Id -notlike "*.mail.onmicrosoft.com" } |
            Select-Object -ExpandProperty Id

        Write-Log "DNS check domains: $( $domains -join ', ' )"
    }
    catch
    {
        Write-Log "Failed to retrieve tenant domains: $( $_.Exception.Message )" "ERROR"
        return
    }

    if ( $domains.Count -eq 0 )
    {
        Write-Log "No domains found to check" "WARN"
        return
    }

    # -------------------------------------------------------------------
    # Per-domain DNS checks
    # -------------------------------------------------------------------
    foreach ( $domain in $domains )
    {
        Write-Log "Checking DNS records for $domain"

        # -------------------------------------------------------------------
        # MX Record
        # -------------------------------------------------------------------
        try
        {
            $mx = Resolve-DNSRecord -Name $domain -Type MX

            if ( $mx )
            {
                $mxHosts = ( $mx | Where-Object { $_.Type -eq "MX" } |
                    Sort-Object Preference |
                    Select-Object -ExpandProperty NameExchange ) -join ", "

                $isM365MX = $mxHosts -match "mail\.protection\.outlook\.com"

                Write-Log "MX for $domain $mxHosts M365=$isM365MX"

                if ( -not $isM365MX )
                {
                    $Assessment.Findings += New-Finding `
                        -Type           "ThirdPartyMX" `
                        -Summary        "Domain $domain MX does not point to Exchange Online" `
                        -Category       "DNS" `
                        -Severity       "Medium" `
                        -Details        @( "MX: $mxHosts" ) `
                        -Impact         "Mail may be routed through a third-party filtering or relay service. This must be accounted for during migration." `
                        -Recommendation "Confirm mail routing strategy and update MX records as part of cutover planning."
                }
            }
            else
            {
                Write-Log "No MX record found for $domain" "WARN"

                $Assessment.Findings += New-Finding `
                    -Type           "NoMXRecord" `
                    -Summary        "No MX record found for domain $domain" `
                    -Category       "DNS" `
                    -Severity       "Medium" `
                    -Impact         "Domain may not be configured to receive mail." `
                    -Recommendation "Verify MX record configuration for this domain."
            }
        }
        catch
        {
            Write-Log "MX check failed for $domain $( $_.Exception.Message )" "WARN"
        }

        # -------------------------------------------------------------------
        # SPF Record
        # -------------------------------------------------------------------
        try
        {
            $spfRaw = Resolve-DNSRecord -Name $domain -Type TXT |
                Where-Object { $_.Strings -match "v=spf1" } |
                Select-Object -ExpandProperty Strings -ErrorAction SilentlyContinue

            if ( -not $spfRaw )
            {
                Write-Log "No SPF record found for $domain" "WARN"

                $Assessment.Findings += New-Finding `
                    -Type           "NoSPFRecord" `
                    -Summary        "No SPF record found for domain $domain" `
                    -Category       "DNS" `
                    -Severity       "High" `
                    -Impact         "Without an SPF record this domain is vulnerable to email spoofing." `
                    -Recommendation "Add an SPF record for this domain before migration."
            }
            else
            {
                Write-Log "SPF for $domain $spfRaw"

                if ( $spfRaw -match "~all" -or $spfRaw -match "\?all" -or $spfRaw -match "\+all" )
                {
                    $Assessment.Findings += New-Finding `
                        -Type           "WeakSPFRecord" `
                        -Summary        "SPF record for domain $domain is not sufficiently strict" `
                        -Category       "DNS" `
                        -Severity       "Medium" `
                        -Details        @( "SPF: $spfRaw" ) `
                        -Impact         "A permissive SPF policy does not fully prevent email spoofing." `
                        -Recommendation "Update SPF record to use -all for strict enforcement."
                }
            }
        }
        catch
        {
            Write-Log "SPF check failed for $domain $( $_.Exception.Message )" "WARN"
        }

        # -------------------------------------------------------------------
        # DKIM Record
        # Checks selector1 and selector2 (Microsoft default selectors)
        # -------------------------------------------------------------------
        try
        {
            $dkimFound = $false

            foreach ( $selector in @( "selector1", "selector2" ) )
            {
                $dkimCname = Resolve-DNSRecord -Name "$selector._domainkey.$domain" -Type CNAME

                if ( $dkimCname )
                {
                    $dkimTarget = $dkimCname | Where-Object { $_.Type -eq "CNAME" } | Select-Object -ExpandProperty NameHost -ErrorAction SilentlyContinue

                    if ( $dkimTarget )
                    {
                        $dkimTxt = Resolve-DNSRecord -Name $dkimTarget -Type TXT

                        if ( $dkimTxt )
                        {
                            $dkimRecord = $dkimTxt | Select-Object -ExpandProperty Strings -ErrorAction SilentlyContinue

                            if ( $dkimRecord -match "v=DKIM1" -or $dkimRecord -match "k=" )
                            {
                                Write-Log "DKIM found for $domain selector $selector"
                                $dkimFound = $true
                                break
                            }
                        }
                    }
                }
            }

            if ( -not $dkimFound )
            {
                Write-Log "No DKIM record found for $domain" "WARN"

                $Assessment.Findings += New-Finding `
                    -Type           "NoDKIMRecord" `
                    -Summary        "No DKIM record found for domain $domain" `
                    -Category       "DNS" `
                    -Severity       "High" `
                    -Impact         "Without DKIM this domain is vulnerable to email spoofing and messages may be flagged as spam." `
                    -Recommendation "Enable DKIM signing in Exchange Online for this domain before migration."
            }
        }
        catch
        {
            Write-Log "DKIM check failed for $domain $( $_.Exception.Message )" "WARN"
        }

        # -------------------------------------------------------------------
        # DMARC Record
        # -------------------------------------------------------------------
        try
        {
            $dmarcRaw = Resolve-DNSRecord -Name "_dmarc.$domain" -Type TXT |
                Select-Object -ExpandProperty Strings -ErrorAction SilentlyContinue

            if ( -not $dmarcRaw )
            {
                Write-Log "No DMARC record found for $domain" "WARN"

                $Assessment.Findings += New-Finding `
                    -Type           "NoDMARCRecord" `
                    -Summary        "No DMARC record found for domain $domain" `
                    -Category       "DNS" `
                    -Severity       "High" `
                    -Impact         "Without DMARC this domain is at risk of being abused by phishers and spammers." `
                    -Recommendation "Add a DMARC record for this domain. Start with p=none and progress to p=reject."
            }
            else
            {
                Write-Log "DMARC for $domain $dmarcRaw"

                if ( $dmarcRaw -match "p=none" )
                {
                    $Assessment.Findings += New-Finding `
                        -Type           "DMARCPolicyNone" `
                        -Summary        "DMARC policy for domain $domain is set to p=none" `
                        -Category       "DNS" `
                        -Severity       "Medium" `
                        -Details        @( "DMARC: $dmarcRaw" ) `
                        -Impact         "A DMARC policy of p=none does not prevent abuse of this domain." `
                        -Recommendation "Progress DMARC policy to p=quarantine then p=reject after validating SPF and DKIM alignment."
                }
                elseif ( $dmarcRaw -match "p=quarantine" )
                {
                    $Assessment.Findings += New-Finding `
                        -Type           "DMARCPolicyQuarantine" `
                        -Summary        "DMARC policy for domain $domain is set to p=quarantine" `
                        -Category       "DNS" `
                        -Severity       "Low" `
                        -Details        @( "DMARC: $dmarcRaw" ) `
                        -Impact         "DMARC is partially enforced. Unauthenticated messages are quarantined but not rejected." `
                        -Recommendation "Progress DMARC policy to p=reject for full enforcement after validating SPF and DKIM alignment."
                }
                else
                {
                    Write-Log "DMARC policy for $domain is p=reject -- fully enforced"
                }
            }
        }
        catch
        {
            Write-Log "DMARC check failed for $domain $( $_.Exception.Message )" "WARN"
        }
    }

    Write-Log "DNS data collection complete"
}