使用PowerShell解析Windows DNS区域文件并转换格式
优化Windows DNS区域文件解析与格式转换的PowerShell方案
需求说明
需要解析Windows DNS区域文件,提取所有DNS记录并转换为可导入其他DNS软件的格式,要求每条记录用单个空格分隔,同时正确处理TXT(移除括号)、SRV(提取优先级、权重、端口、目标)等特殊类型记录。现有正则表达式和PowerShell代码存在解析失败的情况,需要优化实现。
DNS区域文件示例
; ; Zone NS records ; @ NS servweb.test.com. servweb.test.com. A 31.233.196.98 @ NS servweb3.test.com. servweb3.test.com. A 91.133.157.25 ; ; Zone records ; @ A 85.233.152.58 @ MX 10 mail.factory.com. @ MX 20 mx2.factory.com. @ MX 50 servmail3.test.fr. @ TXT ( "v=spf1 include:spf.sendinblue.com mx ~all" ) @ TXT ( "MS=ms94956023" ) _dmarc TXT ( "v=DMARC1; p=none; sp=none; rua=mailto:dmarc@mailinblue.com!10m; ruf=mailto:dmarc@mailinblue.com!10m; rf=afrf; pct=100; ri=86400" ) mail._domainkey TXT ( "k=rsa;p=MIGfMA0GCSqNADCBiQKBgQDeMVIzrCa3T14JsNY0viLQBwXsa7shBD6TrBkswsFUToPyMRWC9tbR/5ey0nRBH0ZVxp+lsmTxid2Y2z+FApQ6raISgXE" ) _autodiscover._tcp SRV 0 0 443 owa.factory.com. _sipfederationtls._tcp SRV 100 1 5061 sipfed.online.lync.com. _sip._tls SRV 100 1 443 sipdir.online.lync.com. appli A 193.233.140.115 cpanel A 85.33.152.58 cpcalendars A 85.33.152.58 cpcontacts A 85.33.152.58 ftp CNAME factory.com. lyncdiscover CNAME webdir.online.lync.com. mail A 90.115.33.93
现有实现代码
$domains = Import-Csv -Path C:\temp\export_domaines_termines.csv -Delimiter ";" foreach ($domain in $domains){ $ndd = $domain.Name $nomfichier = "$ndd.dns" $records = get-content -Path "C:\temp\dns\dns\$nomfichier" | Where-Object { $_ -notmatch '^\;' -and $_.trim() -ne "" } | Select-Object -Skip 10 $fichiergandi = New-Item -Path "C:\temp\dns\$nomfichier.txt" -ItemType File -Force -Confirm:$false foreach ($record in $records){ $type = $prio = $value = $donnees = $name = $null $match = Select-String '(\S+)\s+([A-Za-z\s]+)[ \t](\S+)' -InputObject $record $type = $match.matches.groups[2].value if ($type -like "MX") { $match = Select-String '(\S+)\s+([A-Za-z\s]+)[ \t](\S+)[ \t](\S+)' -InputObject $record $name = $match.matches.groups[1].value $prio = $match.matches.groups[3].value $MX = $match.matches.groups[4].value "$name 10800 IN $type $prio $MX" | Out-File -FilePath $fichiergandi -Append } elseif ($type -like "TXT") { $match = Select-String '(\S+)\s+([A-Za-z\s]+)[ \t](\S+)[ \t]\(([^\)]+)\)' -InputObject $record $name = $match.matches.groups[1].value $donnees = ($match.matches.groups[4].value).Trim() "$name 10800 IN $type $donnees" | Out-File -FilePath $fichiergandi -Append } elseif ($type -like "SRV") { $match = Select-String '(\S+)\s+([A-Za-z\s]+)[ \t](\S+)\s(\S+)\s(\S+)\s(\S+)' -InputObject $record $name = $match.matches.groups[1].value $prio = $match.matches.groups[3].value $poids = $match.matches.groups[4].value $port = $match.matches.groups[5].value $cible = $match.matches.groups[6].value "$name 10800 IN $type $prio $poids $port $cible" | Out-File -FilePath $fichiergandi -Append } else { $name = $match.matches.groups[1].value $donnees = $match.matches.groups[3].value "$name 10800 IN $type $donnees" | Out-File -FilePath $fichiergandi -Append } } }
目标输出格式
@ 10800 IN A 85.236.152.133 @ 10800 IN MX 10 mail.example.com. @ 10800 IN MX 20 mx2.example.com. @ 10800 IN MX 50 servmail3.example.fr. @ 10800 IN TXT "Sendinblue-code:022f2cda7e5e0c6d945db" @ 10800 IN TXT "v=spf1 include:spf.sendinblue.com mx ~all" @ 10800 IN TXT "MS=ms94956023" _autodiscover._tcp 10800 IN SRV 0 0 443 owa.example.com. _sipfederationtls._tcp 10800 IN SRV 100 1 5061 sipfed.online.lync.com. _sip._tls 10800 IN SRV 100 1 443 sipdir.online.lync.com.
优化后的实现方案
核心优化点
- 替换固定
Select-Object -Skip 10为动态过滤注释/空行,适配不同结构的DNS文件 - 使用更精准的正则表达式,一次匹配核心字段,避免多次调用
Select-String - 统一记录处理逻辑,简化分支判断
- 更可靠地处理TXT记录的括号和空格,SRV记录的多字段拆分
优化代码
$domains = Import-Csv -Path C:\temp\export_domaines_termines.csv -Delimiter ";" $ttl = "10800" # 统一TTL值 foreach ($domain in $domains) { $ndd = $domain.Name $nomfichier = "$ndd.dns" $dnsFilePath = Join-Path -Path "C:\temp\dns\dns" -ChildPath $nomfichier $outputFilePath = Join-Path -Path "C:\temp\dns" -ChildPath "$nomfichier.txt" # 读取并过滤有效记录:跳过注释行、空行 $records = Get-Content -Path $dnsFilePath | Where-Object { $trimmed = $_.Trim() $trimmed -ne "" -and $trimmed -notmatch '^;' } # 初始化输出内容数组 $outputContent = @() foreach ($record in $records) { $record = $record.Trim() # 匹配通用记录结构:名称 类型 后续字段 if ($record -match '^(\S+)\s+([A-Z]+)\s+(.*)$') { $name = $matches[1] $type = $matches[2] $content = $matches[3] switch ($type) { "MX" { # 拆分MX的优先级和目标地址 if ($content -match '^(\d+)\s+(\S+)$') { $outputContent += "$name $ttl IN $type $($matches[1]) $($matches[2])" } } "TXT" { # 移除TXT记录的包裹括号,保留内部文本和引号 $cleanContent = $content -replace '^\s*\(\s*(.*?)\s*\)\s*$', '$1' $outputContent += "$name $ttl IN $type $cleanContent" } "SRV" { # 拆分SRV的优先级、权重、端口、目标地址 if ($content -match '^(\d+)\s+(\d+)\s+(\d+)\s+(\S+)$') { $outputContent += "$name $ttl IN $type $($matches[1]) $($matches[2]) $($matches[3]) $($matches[4])" } } default { # 处理A、CNAME、NS等简单类型记录 $outputContent += "$name $ttl IN $type $content" } } } } # 一次性写入文件,提升效率 $outputContent | Out-File -FilePath $outputFilePath -Encoding utf8 -Force }
代码说明
- 动态过滤:通过
Where-Object跳过所有注释行(以;开头)和空行,无需依赖固定行数的Skip,兼容性更强 - 正则匹配:用一个基础正则匹配所有记录的名称、类型和内容部分,再针对不同类型做细分处理
- TXT处理:用正则移除包裹内容的括号,保留内部的引号和文本
- 性能优化:先收集所有输出内容到数组,再一次性写入文件,比循环
Append效率更高 - 可读性:用
switch替代多分支if-else,逻辑更清晰
内容的提问来源于stack exchange,提问作者Marek
相关产品推荐
相关产品推荐

