Checks/EXO/MET-EXO001-DMARC.ps1

# Use pre-fetched accepted domains from context when available; fall back to a live query.
$domains = $null
if ($METContext -and $METContext.AcceptedDomains.Count -gt 0) {
    $domains = @($METContext.AcceptedDomains | Where-Object { $_.Default -or $_.DomainType -eq 'Authoritative' })
}

if (-not $domains) {
    try {
        $domains = @(Get-AcceptedDomain -ErrorAction Stop | Where-Object { $_.Default -or $_.DomainType -eq 'Authoritative' })
    }
    catch {
        New-METCheckResult -CheckId 'MET-EXO001' -Category EXO -Name 'DMARC' `
            -Result Fail -Severity High -AffectedObject 'Accepted Domains' `
            -Finding 'Unable to retrieve accepted domains' `
            -Recommendation 'Ensure the account has Exchange View-Only Recipients permission.' `
            -ReferenceUrl 'https://aka.ms/dmarc' -ErrorMessage $_.ToString()
        return
    }
}

function Get-METDmarcRecommendation {
    param(
        [Parameter(Mandatory)] [string] $DomainName,
        [Parameter(Mandatory)] [bool] $IsOnMicrosoftDomain
    )

    if ($IsOnMicrosoftDomain) {
        return "Add a DMARC TXT record for $DomainName in Microsoft 365 admin center (Settings > Domains > $DomainName > DNS records). Recommended value: 'v=DMARC1; p=reject; rua=mailto:dmarc-reports@$DomainName'."
    }

    return "Publish a DMARC TXT record at _dmarc.$DomainName with at minimum 'v=DMARC1; p=quarantine; rua=mailto:dmarc-reports@$DomainName'."
}

foreach ($domain in $domains) {
    $domainName = [string]$domain.DomainName
    $isMailOnMicrosoft = $domainName -match '(?i)\.mail\.onmicrosoft\.com$'
    $isOnMicrosoftDomain = $domainName -match '(?i)\.onmicrosoft\.com$'

    if ($isMailOnMicrosoft) {
        New-METCheckResult -CheckId 'MET-EXO001' -Category EXO -Name 'DMARC' `
            -Result NotApplicable -Severity Informational -AffectedObject $domainName `
            -Finding 'mail.onmicrosoft.com service domain is Microsoft-managed and not intended for customer DMARC DNS management.' `
            -Recommendation 'No action needed unless Microsoft guidance for this service domain changes.' `
            -ReferenceUrl 'https://aka.ms/dmarc'
        continue
    }

    $dmarcRecord = $null
    try {
        $dns = Resolve-METDnsName -Name "_dmarc.$domainName" -Type TXT
        $dmarcRecord = $dns | Where-Object { $_.Strings -match '^v=DMARC1' } | Select-Object -First 1
    }
    catch {
        New-METCheckResult -CheckId 'MET-EXO001' -Category EXO -Name 'DMARC' `
            -Result Warning -Severity High -AffectedObject $domainName `
            -Finding 'Unable to determine DMARC status because the DNS lookup failed' `
            -Recommendation 'Restore DNS connectivity or install dig/nslookup, then rerun the assessment.' `
            -ReferenceUrl 'https://aka.ms/dmarc' -ErrorMessage $_.ToString()
        continue
    }

    if (-not $dmarcRecord) {
        New-METCheckResult -CheckId 'MET-EXO001' -Category EXO -Name 'DMARC' `
            -Result Fail -Severity High -AffectedObject $domainName `
            -Finding 'No DMARC TXT record found' `
            -Recommendation (Get-METDmarcRecommendation -DomainName $domainName -IsOnMicrosoftDomain $isOnMicrosoftDomain) `
            -ReferenceUrl 'https://aka.ms/dmarc'
        continue
    }

    $record = ($dmarcRecord.Strings -join '') -join ''
    $issues = [System.Collections.Generic.List[string]]::new()

    if ($record -match 'p=none') {
        $issues.Add("DMARC policy is 'none' - no enforcement; emails failing DMARC are not quarantined or rejected")
    }
    elseif ($record -notmatch 'p=(quarantine|reject)') {
        $issues.Add('DMARC policy is not set to quarantine or reject')
    }

    if ($record -notmatch 'rua=') {
        $issues.Add('No aggregate reporting address (rua=) configured - DMARC reports will not be received')
    }

    if ($issues.Count -gt 0) {
        New-METCheckResult -CheckId 'MET-EXO001' -Category EXO -Name 'DMARC' `
            -Result Fail -Severity High -AffectedObject $domainName `
            -Finding "$($issues -join '; ') | Record: $record" `
            -Recommendation "Update DMARC policy to 'quarantine' or 'reject' and add an rua= reporting address." `
            -ReferenceUrl 'https://aka.ms/dmarc'
    }
    else {
        New-METCheckResult -CheckId 'MET-EXO001' -Category EXO -Name 'DMARC' `
            -Result Pass -Severity High -AffectedObject $domainName `
            -Finding "DMARC record present with enforcement policy and reporting configured | Record: $record" `
            -ReferenceUrl 'https://aka.ms/dmarc'
    }
}