Common/Resolve-DnsRecord.ps1
|
<#
.SYNOPSIS Cross-platform DNS record resolver for M365-Assess. .DESCRIPTION Wraps Resolve-DnsName (Windows) and dig (macOS/Linux) behind a unified interface so DNS lookups work on any platform. Returns PSCustomObjects with the same property shapes the rest of the codebase expects: - TXT records → .Strings ([string[]]) - CNAME records → .NameHost ([string]) .PARAMETER Name The DNS name to query (e.g. 'contoso.com', '_dmarc.contoso.com'). .PARAMETER Type Record type — TXT or CNAME. .PARAMETER Server Optional DNS server IP to query (e.g. '8.8.8.8'). .PARAMETER DnsOnly Accepted for call-site compatibility with Resolve-DnsName but ignored on the dig path (dig always uses DNS-only resolution). .EXAMPLE Resolve-DnsRecord -Name contoso.com -Type TXT Resolve-DnsRecord -Name '_dmarc.contoso.com' -Type TXT -Server 8.8.8.8 #> function Resolve-DnsRecord { [CmdletBinding()] param( [Parameter(Mandatory)] [string]$Name, [Parameter(Mandatory)] [ValidateSet('TXT', 'CNAME')] [string]$Type, [string]$Server, [switch]$DnsOnly ) # ── One-time backend detection (cached for session) ────────────── if ($null -eq $script:DnsBackend) { if (Get-Command -Name Resolve-DnsName -ErrorAction SilentlyContinue) { $script:DnsBackend = 'ResolveDnsName' } elseif (Get-Command -Name dig -ErrorAction SilentlyContinue) { $script:DnsBackend = 'Dig' } else { $script:DnsBackend = 'None' Write-Warning 'Resolve-DnsRecord: Neither Resolve-DnsName (Windows) nor dig (macOS/Linux) is available. DNS lookups will be skipped. Install dig via: brew install bind (macOS) or apt install dnsutils (Linux).' } } # ── Windows: delegate to Resolve-DnsName ───────────────────────── if ($script:DnsBackend -eq 'ResolveDnsName') { $params = @{ Name = $Name Type = $Type DnsOnly = $true ErrorAction = $ErrorActionPreference } if ($Server) { $params['Server'] = $Server } return @(Resolve-DnsName @params) } # ── macOS / Linux: parse dig output ────────────────────────────── if ($script:DnsBackend -eq 'Dig') { $digArgs = @('+short', $Type, $Name) if ($Server) { $digArgs = @("@$Server") + $digArgs } try { $raw = & dig @digArgs 2>&1 if ($LASTEXITCODE -ne 0) { if ($ErrorActionPreference -eq 'Stop') { throw "dig query failed for $Name ($Type): $raw" } return $null } $lines = @($raw | Where-Object { $_ -and $_ -notmatch '^\s*$' -and $_ -notmatch '^;;' }) if ($lines.Count -eq 0) { return $null } switch ($Type) { 'TXT' { foreach ($line in $lines) { # dig +short returns TXT data in quotes, possibly # split across multiple quoted segments on one line. # Reassemble them into a single string array entry # to match Resolve-DnsName .Strings behaviour. $segments = @([regex]::Matches($line, '"([^"]*)"') | ForEach-Object { $_.Groups[1].Value }) if ($segments.Count -eq 0) { # Unquoted fallback (shouldn't happen with dig +short TXT) $segments = @($line.Trim()) } [PSCustomObject]@{ Name = $Name Type = 'TXT' Strings = [string[]]$segments } } } 'CNAME' { # dig +short CNAME returns a single line like: # selector1-contoso._domainkey.contoso.onmicrosoft.com. $target = $lines[0].TrimEnd('.') [PSCustomObject]@{ Name = $Name Type = 'CNAME' NameHost = $target } } } } catch { if ($ErrorActionPreference -eq 'Stop') { throw } return $null } return } # ── No backend available ───────────────────────────────────────── if ($ErrorActionPreference -eq 'Stop') { throw "No DNS resolution backend available. Cannot resolve $Name ($Type)." } return $null } |