From b213ac3642c996697908d0223cd1712a476d6c75 Mon Sep 17 00:00:00 2001 From: Fabian Bader Date: Sun, 14 Jul 2024 19:20:16 +0200 Subject: [PATCH] fix: More robust detection of Windows in Windows PowerShell --- ...nvertFrom-MailAuthenticationRecordDkim.ps1 | 42 +++---- ...vertFrom-MailAuthenticationRecordDmarc.ps1 | 110 +++++++++--------- ...ConvertFrom-MailAuthenticationRecordMx.ps1 | 26 ++--- ...onvertFrom-MailAuthenticationRecordSpf.ps1 | 66 +++++------ .../exchange/Get-MailAuthenticationRecord.ps1 | 62 +++++----- .../cisa/exchange/Resolve-SPFRecord.ps1 | 39 +++---- 6 files changed, 171 insertions(+), 174 deletions(-) diff --git a/powershell/public/cisa/exchange/ConvertFrom-MailAuthenticationRecordDkim.ps1 b/powershell/public/cisa/exchange/ConvertFrom-MailAuthenticationRecordDkim.ps1 index 588e4b6a..80ab1b0e 100644 --- a/powershell/public/cisa/exchange/ConvertFrom-MailAuthenticationRecordDkim.ps1 +++ b/powershell/public/cisa/exchange/ConvertFrom-MailAuthenticationRecordDkim.ps1 @@ -29,7 +29,7 @@ warnings : Function ConvertFrom-MailAuthenticationRecordDkim { [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingWriteHost', '', Justification = 'Colors are beautiful')] - [OutputType([DKIMRecord],[System.String])] + [OutputType([DKIMRecord], [System.String])] [cmdletbinding()] param( [Parameter(Mandatory)] @@ -50,7 +50,7 @@ Function ConvertFrom-MailAuthenticationRecordDkim { class DKIMRecord { [string]$record [string]$keyType = "rsa" #k - [string[]]$hash = @("sha1","sha256") #h + [string[]]$hash = @("sha1", "sha256") #h [string]$notes #n [string]$publicKey #p [bool]$validBase64 @@ -63,16 +63,16 @@ Function ConvertFrom-MailAuthenticationRecordDkim { hidden $matchKeyType = "k\s*=\s*(?'k'[^;]+)\s*;\s*" hidden $matchPublicKey = "p\s*=\s*(?'p'[^;]+)\s*;\s*" - DKIMRecord([string]$record){ + DKIMRecord([string]$record) { $this.record = $record $match = $record -match $this.matchRecord - if(-not $match){ + if (-not $match) { $this.warnings = "v: Record does not match version format" break } - $p = [regex]::Match($record,$this.matchPublicKey,$this.option) - $this.publicKey = ($p.Groups|Where-Object{$_.Name -eq "p"}).Value - $bytes = [System.Convert]::FromBase64String(($p.Groups|Where-Object{$_.Name -eq "p"}).Value) + $p = [regex]::Match($record, $this.matchPublicKey, $this.option) + $this.publicKey = ($p.Groups | Where-Object { $_.Name -eq "p" }).Value + $bytes = [System.Convert]::FromBase64String(($p.Groups | Where-Object { $_.Name -eq "p" }).Value) $this.validBase64 = $null -ne $bytes } } @@ -90,14 +90,14 @@ Function ConvertFrom-MailAuthenticationRecordDkim { QuickTimeout = $QuickTimeout ErrorAction = "Stop" } - try{ - if($isWindows){ + try { + if ( $isWindows -or $PSVersionTable.PSEdition -eq "Desktop" ) { $dkimRecord = [DKIMRecord]::new((Resolve-DnsName @dkimSplat | ` - Where-Object {$_.Type -eq "TXT"} | ` - Where-Object {$_.Strings -match $matchRecord}).Strings) - }else{ - $cmdletCheck = Get-Command "Resolve-Dns" - if($cmdletCheck){ + Where-Object { $_.Type -eq "TXT" } | ` + Where-Object { $_.Strings -match $matchRecord }).Strings) + } else { + $cmdletCheck = Get-Command "Resolve-Dns" -ErrorAction SilentlyContinue + if ($cmdletCheck) { $dkimSplatAlt = @{ Query = $dkimSplat.Name QueryType = $dkimSplat.Type @@ -105,23 +105,23 @@ Function ConvertFrom-MailAuthenticationRecordDkim { ErrorAction = $dkimSplat.ErrorAction } $record = ((Resolve-Dns @dkimSplatAlt).Answers | ` - Where-Object {$_.RecordType -eq "TXT"} | ` - Where-Object {$_.Text -imatch $matchRecord}).Text - if($record){ + Where-Object { $_.RecordType -eq "TXT" } | ` + Where-Object { $_.Text -imatch $matchRecord }).Text + if ($record) { $dkimRecord = [DKIMRecord]::new($record) - }else{ + } else { return "Failure to obtain record" } - }else{ + } else { Write-Error "`nFor non-Windows platforms, please install DnsClient-PS module." Write-Host "`n Install-Module DnsClient-PS -Scope CurrentUser`n" -ForegroundColor Yellow return "Missing dependency, Resolve-Dns not available" } } - }catch [System.Management.Automation.CommandNotFoundException]{ + } catch [System.Management.Automation.CommandNotFoundException] { Write-Error $_ return "Unsupported platform, Resolve-DnsName not available" - }catch{ + } catch { Write-Error $_ return "Failure to obtain record" } diff --git a/powershell/public/cisa/exchange/ConvertFrom-MailAuthenticationRecordDmarc.ps1 b/powershell/public/cisa/exchange/ConvertFrom-MailAuthenticationRecordDmarc.ps1 index c7c3bd34..60881c3a 100644 --- a/powershell/public/cisa/exchange/ConvertFrom-MailAuthenticationRecordDmarc.ps1 +++ b/powershell/public/cisa/exchange/ConvertFrom-MailAuthenticationRecordDmarc.ps1 @@ -34,7 +34,7 @@ warnings : {sp: No subdomain policy set, adkim: No DKIM alignment se Function ConvertFrom-MailAuthenticationRecordDmarc { [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingWriteHost', '', Justification = 'Colors are beautiful')] - [OutputType([DMARCRecord],[System.String])] + [OutputType([DMARCRecord], [System.String])] [cmdletbinding()] param( [Parameter(Mandatory)] @@ -57,11 +57,11 @@ Function ConvertFrom-MailAuthenticationRecordDmarc { hidden $option = [Text.RegularExpressions.RegexOptions]::IgnoreCase hidden $matchUri = "(?'uri'mailto:(?'address'[^,!]*)(?:!(?'size'\d+(?:k|m|g|t)))?)(?:,|$)" - DMARCRecordUri([string]$uri){ + DMARCRecordUri([string]$uri) { $this.uri = $uri - $match = [regex]::Match($uri,$this.matchUri,$this.option) - $this.mailAddress = ($match.Groups|Where-Object{$_.Name -eq "address"}).Value - $this.reportSizeLimit = ($match.Groups|Where-Object{$_.Name -eq "size"}).Value + $match = [regex]::Match($uri, $this.matchUri, $this.option) + $this.mailAddress = ($match.Groups | Where-Object { $_.Name -eq "address" }).Value + $this.reportSizeLimit = ($match.Groups | Where-Object { $_.Name -eq "size" }).Value } } @@ -69,20 +69,20 @@ Function ConvertFrom-MailAuthenticationRecordDmarc { class DMARCRecord { [string]$record [bool]$valid - [ValidateSet("none","quarantine","reject")] + [ValidateSet("none", "quarantine", "reject")] [string]$policy #p [string]$policySubdomain #sp - [ValidateRange(0,100)] + [ValidateRange(0, 100)] [int]$percentage = 100 #pct [DMARCRecordUri[]]$reportAggregate #rua [DMARCRecordUri[]]$reportForensic #ruf - [ValidateSet("0","1","d","s")] + [ValidateSet("0", "1", "d", "s")] [string[]]$reportFailure #fo [string[]]$reportFailureFormats = "afrf" #rf [int]$reportFrequency = 86400 #ri - [ValidateSet("r","s")] + [ValidateSet("r", "s")] [string]$alignmentDkim = "r" #adkim - [ValidateSet("r","s")] + [ValidateSet("r", "s")] [string]$alignmentSpf = "r" #aspf [string]$version = "DMARC1" [string[]]$warnings @@ -102,11 +102,11 @@ Function ConvertFrom-MailAuthenticationRecordDmarc { hidden $matchFormat = "(?'format'[^:\s]*)(?:\s*:|\s*$)" hidden $matchPct = "pct\s*=\s*(?'pct'\d{1,3})(?:$|\s*;\s*)" - DMARCRecord([string]$record){ + DMARCRecord([string]$record) { $this.record = $record $init = $record -match $this.matchInit $this.valid = $init - if(-not $init){ + if (-not $init) { $this.warnings += "v/p: Record version (v) and policy (p) configuration is not proper" exit } @@ -114,78 +114,78 @@ Function ConvertFrom-MailAuthenticationRecordDmarc { $this.policy = $Matches["p"] $sp = $record -match $this.matchSp - if(-not $sp){ + if (-not $sp) { $this.warnings += "sp: No subdomain policy set" - }else{ + } else { $this.policySubdomain = $Matches["sp"] } $rua = $record -match $this.matchRua - if(-not $rua){ + if (-not $rua) { $this.warnings += "rua: No aggregate report URI set" - }else{ - $uris = [regex]::Matches($Matches["rua"],$this.matchUri,$this.option) - foreach($uri in ($uris.Groups|Where-Object{$_.Name -eq "uri"})){ + } else { + $uris = [regex]::Matches($Matches["rua"], $this.matchUri, $this.option) + foreach ($uri in ($uris.Groups | Where-Object { $_.Name -eq "uri" })) { $this.reportAggregate += [DMARCRecordUri]::new("$uri") } - if(($uris.Groups|Where-Object{$_.Name -eq "uri"}|Measure-Object).Count -gt 2){ + if (($uris.Groups | Where-Object { $_.Name -eq "uri" } | Measure-Object).Count -gt 2) { $this.warnings += "ruf: More than 2 URIs set and may be ignored" } } $ruf = $record -match $this.matchRuf - if(-not $ruf){ + if (-not $ruf) { $this.warnings += "ruf: No forensic report URI set" - }else{ - $uris = [regex]::Matches($Matches["ruf"],$this.matchUri,$this.option) - foreach($uri in ($uris.Groups|Where-Object{$_.Name -eq "uri"})){ + } else { + $uris = [regex]::Matches($Matches["ruf"], $this.matchUri, $this.option) + foreach ($uri in ($uris.Groups | Where-Object { $_.Name -eq "uri" })) { $this.reportForensic += [DMARCRecordUri]::new("$uri") } - if(($uris.Groups|Where-Object{$_.Name -eq "uri"}|Measure-Object).Count -gt 2){ + if (($uris.Groups | Where-Object { $_.Name -eq "uri" } | Measure-Object).Count -gt 2) { $this.warnings += "ruf: More than 2 URIs set and may be ignored" } } $adkim = $record -match $this.matchAdkim - if(-not $adkim){ + if (-not $adkim) { $this.warnings += "adkim: No DKIM alignment set, defaults to relaxed" - }else{ + } else { $this.alignmentDkim = $Matches["adkim"] } $aspf = $record -match $this.matchAspf - if(-not $aspf){ + if (-not $aspf) { $this.warnings += "aspf: No SPF alignment set, defaults to relaxed" - }else{ + } else { $this.alignmentSpf = $Matches["aspf"] } $ri = $record -match $this.matchRi - if(-not $ri){ + if (-not $ri) { $this.warnings += "ri: No report interval set, defaults to 86400 seconds" - }else{ + } else { $this.ri = $Matches["ri"] } $fo = $record -match $this.matchFo - if(-not $fo){ + if (-not $fo) { $this.reportFailure = "0" $this.warnings += "fo: No failure reporting option specified, default (0) report when all mechanisms fail to pass" - }elseif($fo -and -not $ruf){ + } elseif ($fo -and -not $ruf) { $this.warnings += "fo: Failure reporting option specified, but no ruf URI set" - }else{ - $options = [regex]::Matches($Matches["fo"],$this.matchOptions,$this.option) - foreach($option in ($options.Groups|Where-Object{$_.Name -eq "opt"})){ + } else { + $options = [regex]::Matches($Matches["fo"], $this.matchOptions, $this.option) + foreach ($option in ($options.Groups | Where-Object { $_.Name -eq "opt" })) { $this.reportFailure += $option } } $rf = $record -match $this.matchRf - if(-not $rf){ + if (-not $rf) { $this.warnings += "rf: No failure report format specified, defaults to afrf" - }else{ - $formats = [regex]::Matches($Matches["rf"],$this.matchFormat,$this.option) - foreach($format in $formats.Groups|Where-Object{$_.Name -eq "format"}){ + } else { + $formats = [regex]::Matches($Matches["rf"], $this.matchFormat, $this.option) + foreach ($format in $formats.Groups | Where-Object { $_.Name -eq "format" }) { switch ($format.Value) { "afrf" { $this.reportFailureFormats += $format.Value @@ -200,9 +200,9 @@ Function ConvertFrom-MailAuthenticationRecordDmarc { } $pct = $record -match $this.matchPct - if(-not $pct){ + if (-not $pct) { $this.warnings += "pct: No percentage of messages specified to apply policy to, defaults to 100" - }else{ + } else { $this.percentage = $Matches["pct"] } } @@ -221,14 +221,14 @@ Function ConvertFrom-MailAuthenticationRecordDmarc { QuickTimeout = $QuickTimeout ErrorAction = "Stop" } - try{ - if($IsWindows){ + try { + if ( $isWindows -or $PSVersionTable.PSEdition -eq "Desktop") { $dmarcRecord = [DMARCRecord]::new((Resolve-DnsName @dmarcSplat | ` - Where-Object {$_.Type -eq "TXT"} | ` - Where-Object {$_.Strings -match $matchRecord}).Strings) - }else{ - $cmdletCheck = Get-Command "Resolve-Dns" - if($cmdletCheck){ + Where-Object { $_.Type -eq "TXT" } | ` + Where-Object { $_.Strings -match $matchRecord }).Strings) + } else { + $cmdletCheck = Get-Command "Resolve-Dns" -ErrorAction SilentlyContinue + if ($cmdletCheck) { $dmarcSplatAlt = @{ Query = $dmarcSplat.Name QueryType = $dmarcSplat.Type @@ -236,23 +236,23 @@ Function ConvertFrom-MailAuthenticationRecordDmarc { ErrorAction = $dmarcSplat.ErrorAction } $record = ((Resolve-Dns @dmarcSplatAlt).Answers | ` - Where-Object {$_.RecordType -eq "TXT"} | ` - Where-Object {$_.Text -imatch $matchRecord}).Text - if($record){ + Where-Object { $_.RecordType -eq "TXT" } | ` + Where-Object { $_.Text -imatch $matchRecord }).Text + if ($record) { $dmarcRecord = [DMARCRecord]::new($record) - }else{ + } else { return "Failure to obtain record" } - }else{ + } else { Write-Error "`nFor non-Windows platforms, please install DnsClient-PS module." Write-Host "`n Install-Module DnsClient-PS -Scope CurrentUser`n" -ForegroundColor Yellow return "Missing dependency, Resolve-Dns not available" } } - }catch [System.Management.Automation.CommandNotFoundException]{ + } catch [System.Management.Automation.CommandNotFoundException] { Write-Error $_ return "Unsupported platform, Resolve-DnsName not available" - }catch{ + } catch { Write-Error $_ return "Failure to obtain record" } diff --git a/powershell/public/cisa/exchange/ConvertFrom-MailAuthenticationRecordMx.ps1 b/powershell/public/cisa/exchange/ConvertFrom-MailAuthenticationRecordMx.ps1 index c3f5fc20..811a38c6 100644 --- a/powershell/public/cisa/exchange/ConvertFrom-MailAuthenticationRecordMx.ps1 +++ b/powershell/public/cisa/exchange/ConvertFrom-MailAuthenticationRecordMx.ps1 @@ -16,7 +16,7 @@ microsoft.com MX 1731 Answer microsoft-com.m Function ConvertFrom-MailAuthenticationRecordMx { [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingWriteHost', '', Justification = 'Colors are beautiful')] - [OutputType([PSCustomObject],[System.String])] + [OutputType([PSCustomObject], [System.String])] [cmdletbinding()] param( [Parameter(Mandatory)] @@ -38,14 +38,14 @@ Function ConvertFrom-MailAuthenticationRecordMx { QuickTimeout = $QuickTimeout ErrorAction = "Stop" } - try{ - if($isWindows){ - $mxRecords = Resolve-DnsName @mxSplat | Where-Object {$_.Type -eq "MX"} - $mxRecords = $mxRecords|ConvertTo-Json|ConvertFrom-Json - }else{ + try { + if ( $isWindows -or $PSVersionTable.PSEdition -eq "Desktop" ) { + $mxRecords = Resolve-DnsName @mxSplat | Where-Object { $_.Type -eq "MX" } + $mxRecords = $mxRecords | ConvertTo-Json | ConvertFrom-Json + } else { Write-Verbose "Is not Windows, checking for Resolve-Dns" - $cmdletCheck = Get-Command "Resolve-Dns" - if($cmdletCheck){ + $cmdletCheck = Get-Command "Resolve-Dns" -ErrorAction SilentlyContinue + if ($cmdletCheck) { Write-Verbose "Resolve-Dns exists, querying records" $mxSplatAlt = @{ Query = $mxSplat.Name @@ -53,7 +53,7 @@ Function ConvertFrom-MailAuthenticationRecordMx { NameServer = $mxSplat.Server ErrorAction = $mxSplat.ErrorAction } - $answers = (Resolve-Dns @mxSplatAlt).Answers | Where-Object {$_.RecordType -eq "MX"} + $answers = (Resolve-Dns @mxSplatAlt).Answers | Where-Object { $_.RecordType -eq "MX" } $mxRecords = $answers | ForEach-Object { [PSCustomObject]@{ Name = $_.DomainName @@ -63,20 +63,20 @@ Function ConvertFrom-MailAuthenticationRecordMx { Preference = $_.Preference } } - }else{ + } else { Write-Error "`nFor non-Windows platforms, please install DnsClient-PS module." Write-Host "`n Install-Module DnsClient-PS -Scope CurrentUser`n" -ForegroundColor Yellow return "Missing dependency, Resolve-Dns not available" } } - }catch [System.Management.Automation.CommandNotFoundException]{ + } catch [System.Management.Automation.CommandNotFoundException] { Write-Error $_ return "Unsupported platform, Resolve-DnsName not available" - }catch{ + } catch { Write-Error $_ return "Failure to obtain record" } return $mxRecords - } + } } \ No newline at end of file diff --git a/powershell/public/cisa/exchange/ConvertFrom-MailAuthenticationRecordSpf.ps1 b/powershell/public/cisa/exchange/ConvertFrom-MailAuthenticationRecordSpf.ps1 index f57021dc..66e85428 100644 --- a/powershell/public/cisa/exchange/ConvertFrom-MailAuthenticationRecordSpf.ps1 +++ b/powershell/public/cisa/exchange/ConvertFrom-MailAuthenticationRecordSpf.ps1 @@ -23,7 +23,7 @@ warnings : Function ConvertFrom-MailAuthenticationRecordSpf { [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingWriteHost', '', Justification = 'Colors are beautiful')] - [OutputType([SPFRecord],[System.String])] + [OutputType([SPFRecord], [System.String])] [cmdletbinding()] param( [Parameter(Mandatory)] @@ -49,35 +49,35 @@ Function ConvertFrom-MailAuthenticationRecordSpf { class SPFRecordTerm { [string]$term #term [string]$directive #directive - [ValidateSet("+","-","~","?","")] + [ValidateSet("+", "-", "~", "?", "")] [string]$qualifier #qual - [ValidateSet("all","include","a","mx","ptr","ip4","ip6","exists","")] + [ValidateSet("all", "include", "a", "mx", "ptr", "ip4", "ip6", "exists", "")] [string]$mechanism #mech [string]$mechanismTarget #mechTarget [string]$mechanismTargetCidr #cidr - [ValidateSet("redirect","exp","")] + [ValidateSet("redirect", "exp", "")] [string]$modifier #mod [string]$modifierTarget #modTarget hidden $option = [Text.RegularExpressions.RegexOptions]::IgnoreCase hidden $matchTerms = "\s*(?'term'(?'directive'(?'qual'\+|-|~|\?)?(?'mech'all|include|a|mx|ptr|ip4|ip6|exists)(?::?(?'mechTarget'[^\s]+?(?'cidr'\/[^\s]+)?))?)(?:\s|$)|(?'mod'redirect|exp)(?:=(?'modTarget'[^\s]+))(?:\s|$))" - SPFRecordTerm([string]$term){ + SPFRecordTerm([string]$term) { $this.term = $term - $match = [regex]::Match($term,$this.matchTerms,$this.option) - $this.directive = ($match.Groups|Where-Object{$_.Name -eq "directive"}).Value - $qVal = ($match.Groups|Where-Object{$_.Name -eq "qual"}).Value - if($qVal -eq ""){ + $match = [regex]::Match($term, $this.matchTerms, $this.option) + $this.directive = ($match.Groups | Where-Object { $_.Name -eq "directive" }).Value + $qVal = ($match.Groups | Where-Object { $_.Name -eq "qual" }).Value + if ($qVal -eq "") { $q = "?" - }else{ + } else { $q = $qVal } $this.qualifier = $q - $this.mechanism = ($match.Groups|Where-Object{$_.Name -eq "mech"}).Value - $this.mechanismTarget = ($match.Groups|Where-Object{$_.Name -eq "mechTarget"}).Value - $this.mechanismTargetCidr = ($match.Groups|Where-Object{$_.Name -eq "cidr"}).Value - $this.modifier = ($match.Groups|Where-Object{$_.Name -eq "mod"}).Value - $this.modifierTarget = ($match.Groups|Where-Object{$_.Name -eq "modTarget"}).Value + $this.mechanism = ($match.Groups | Where-Object { $_.Name -eq "mech" }).Value + $this.mechanismTarget = ($match.Groups | Where-Object { $_.Name -eq "mechTarget" }).Value + $this.mechanismTargetCidr = ($match.Groups | Where-Object { $_.Name -eq "cidr" }).Value + $this.modifier = ($match.Groups | Where-Object { $_.Name -eq "mod" }).Value + $this.modifierTarget = ($match.Groups | Where-Object { $_.Name -eq "modTarget" }).Value } } @@ -93,19 +93,19 @@ Function ConvertFrom-MailAuthenticationRecordSpf { #https://datatracker.ietf.org/doc/html/rfc7208#section-12 hidden $matchTerms = "\s*(?'term'(?'directive'(?'qual'\+|-|~|\?)?(?'mech'all|include|a|mx|ptr|ip4|ip6|exists)(?::?(?'mechTarget'[^\s]+?(?'cidr'\/[^\s]+)?))?)(?:\s|$)|(?'mod'redirect|exp)(?:=(?'modTarget'[^\s]+))(?:\s|$))" - SPFRecord([string]$inputRecord){ + SPFRecord([string]$inputRecord) { $this.record = $inputRecord - $match = [regex]::Matches($inputRecord,$this.matchRecord,$this.option) - if(-not $match){ + $match = [regex]::Matches($inputRecord, $this.matchRecord, $this.option) + if (-not $match) { $this.warnings += "v: Record does not match spf1 version format" break } - if(($match|Measure-Object).Count -gt 1){ + if (($match | Measure-Object).Count -gt 1) { $this.warnings += "v: Multiple records match spf1 version format" break } - $recordTerms = [regex]::Matches($inputRecord,$this.matchTerms,$this.option) - foreach($term in ($recordTerms.Groups|Where-Object{$_.Name -eq "term"})){ + $recordTerms = [regex]::Matches($inputRecord, $this.matchTerms, $this.option) + foreach ($term in ($recordTerms.Groups | Where-Object { $_.Name -eq "term" })) { $this.terms += [SPFRecordTerm]::new($term.Value) } } @@ -123,14 +123,14 @@ Function ConvertFrom-MailAuthenticationRecordSpf { QuickTimeout = $QuickTimeout ErrorAction = "Stop" } - try{ - if($IsWindows){ + try { + if ( $isWindows -or $PSVersionTable.PSEdition -eq "Desktop" ) { $spfRecord = [SPFRecord]::new((Resolve-DnsName @spfSplat | ` - Where-Object {$_.Type -eq "TXT"} | ` - Where-Object {$_.Strings -imatch $matchRecord}).Strings) - }else{ - $cmdletCheck = Get-Command "Resolve-Dns" - if($cmdletCheck){ + Where-Object { $_.Type -eq "TXT" } | ` + Where-Object { $_.Strings -imatch $matchRecord }).Strings) + } else { + $cmdletCheck = Get-Command "Resolve-Dns" -ErrorAction SilentlyContinue + if ($cmdletCheck) { $spfSplatAlt = @{ Query = $spfSplat.Name QueryType = $spfSplat.Type @@ -138,18 +138,18 @@ Function ConvertFrom-MailAuthenticationRecordSpf { ErrorAction = $spfSplat.ErrorAction } $spfRecord = [SPFRecord]::new(((Resolve-Dns @spfSplatAlt).Answers | ` - Where-Object {$_.RecordType -eq "TXT"} | ` - Where-Object {$_.Text -imatch $matchRecord}).Text) - }else{ + Where-Object { $_.RecordType -eq "TXT" } | ` + Where-Object { $_.Text -imatch $matchRecord }).Text) + } else { Write-Error "`nFor non-Windows platforms, please install DnsClient-PS module." Write-Host "`n Install-Module DnsClient-PS -Scope CurrentUser`n" -ForegroundColor Yellow return "Missing dependency, Resolve-Dns not available" } } - }catch [System.Management.Automation.CommandNotFoundException]{ + } catch [System.Management.Automation.CommandNotFoundException] { Write-Error $_ return "Unsupported platform, Resolve-DnsName not available" - }catch{ + } catch { Write-Error $_ return "Failure to obtain record" } diff --git a/powershell/public/cisa/exchange/Get-MailAuthenticationRecord.ps1 b/powershell/public/cisa/exchange/Get-MailAuthenticationRecord.ps1 index 63e18227..2959a283 100644 --- a/powershell/public/cisa/exchange/Get-MailAuthenticationRecord.ps1 +++ b/powershell/public/cisa/exchange/Get-MailAuthenticationRecord.ps1 @@ -29,7 +29,7 @@ Function Get-MailAuthenticationRecord { [string]$DkimSelector = "selector1", - [ValidateSet("All","DKIM","DMARC","MX","SPF")] + [ValidateSet("All", "DKIM", "DMARC", "MX", "SPF")] [string[]]$Records = "All", [switch]$QuickTimeout, @@ -38,10 +38,10 @@ Function Get-MailAuthenticationRecord { ) begin { - if($Records -contains "All"){ + if ($Records -contains "All") { $all = $dkim = $dmarc = $mx = $spf = $true - }else{ - foreach($record in $Records){ + } else { + foreach ($record in $Records) { Set-Variable -Name $record -Value $true } } @@ -57,11 +57,11 @@ Function Get-MailAuthenticationRecord { dkimRecord = $null } - if(($__MtSession.DnsCache|Where-Object{$_.domain -eq $DomainName}|Measure-Object).Count -eq 0){ + if (($__MtSession.DnsCache | Where-Object { $_.domain -eq $DomainName } | Measure-Object).Count -eq 0) { $__MtSession.DnsCache += $recordSet $mtDnsCache = $recordSet - }else{ - $mtDnsCache = $__MtSession.DnsCache|Where-Object{$_.domain -eq $DomainName} + } else { + $mtDnsCache = $__MtSession.DnsCache | Where-Object { $_.domain -eq $DomainName } } $splat = @{ @@ -71,64 +71,64 @@ Function Get-MailAuthenticationRecord { NoHostsFile = $NoHostsFile } - if($mx -or $all){ - if($null -ne $mtDnsCache.mxRecords){ + if ($mx -or $all) { + if ($null -ne $mtDnsCache.mxRecords) { Write-Verbose "MX records exist in cache - Use Clear-MtDnsCache to reset" $recordSet.mxRecords = $mtDnsCache.mxRecords - }else{ + } else { $recordSet.mxRecords = ConvertFrom-MailAuthenticationRecordMx @splat - ($__MtSession.DnsCache|Where-Object{$_.domain -eq $DomainName}).mxRecords = $recordSet.mxRecords + ($__MtSession.DnsCache | Where-Object { $_.domain -eq $DomainName }).mxRecords = $recordSet.mxRecords } } - if($spf -or $all){ - if($null -ne $mtDnsCache.spfRecord){ + if ($spf -or $all) { + if ($null -ne $mtDnsCache.spfRecord) { Write-Verbose "SPF record exist in cache - Use Clear-MtDnsCache to reset" $recordSet.spfRecord = $mtDnsCache.spfRecord Write-Verbose "SPF record exist in cache - Skipping SPF Lookups" $recordSet.spfLookups = $mtDnsCache.spfLookups - }else{ + } else { $recordSet.spfRecord = ConvertFrom-MailAuthenticationRecordSpf @splat - if($recordSet.spfRecord.GetType() -ne "SPFRecord"){ - if($recordSet.spfRecord.terms.modifier -contains "redirect"){ + if ($recordSet.spfRecord.GetType() -ne "SPFRecord") { + if ($recordSet.spfRecord.terms.modifier -contains "redirect") { Write-Verbose "SPF redirect modifier found, recursing" - $redirect = ($recordSet.spfRecord.terms|Where-Object {` - $_.modifier -eq "redirect" - }).modifierTarget + $redirect = ($recordSet.spfRecord.terms | Where-Object {` + $_.modifier -eq "redirect" + }).modifierTarget Get-MailAuthenticationRecord -DomainName $redirect } Write-Verbose "SPF record resolved, checking lookups" - if($null -ne $mtDnsCache.spfLookups){ + if ($null -ne $mtDnsCache.spfLookups) { Write-Verbose "SPF Lookups records exist in cache - Use Clear-MtDnsCache to reset" $recordSet.spfLookups = $mtDnsCache.spfLookups - }else{ + } else { Write-Verbose "SPF Lookups records not in cache, querying" $recordSet.spfLookups = Resolve-SPFRecord -Name $DomainName -Server $DnsServerIpAddress - ($__MtSession.DnsCache|Where-Object{$_.domain -eq $DomainName}).spfLookups = $recordSet.spfLookups + ($__MtSession.DnsCache | Where-Object { $_.domain -eq $DomainName }).spfLookups = $recordSet.spfLookups } } - ($__MtSession.DnsCache|Where-Object{$_.domain -eq $DomainName}).spfRecord = $recordSet.spfRecord + ($__MtSession.DnsCache | Where-Object { $_.domain -eq $DomainName }).spfRecord = $recordSet.spfRecord } } - if($dkim -or $all){ - if($null -ne $mtDnsCache.dkimRecord){ + if ($dkim -or $all) { + if ($null -ne $mtDnsCache.dkimRecord) { Write-Verbose "DKIM record exist in cache - Use Clear-MtDnsCache to reset" $recordSet.dkimRecord = $mtDnsCache.dkimRecord - }else{ + } else { $recordSet.dkimRecord = ConvertFrom-MailAuthenticationRecordDkim @splat -DkimSelector $DkimSelector - ($__MtSession.DnsCache|Where-Object{$_.domain -eq $DomainName}).dkimRecord = $recordSet.dkimRecord + ($__MtSession.DnsCache | Where-Object { $_.domain -eq $DomainName }).dkimRecord = $recordSet.dkimRecord } } - if($dmarc -or $all){ - if($null -ne $mtDnsCache.dmarcRecord){ + if ($dmarc -or $all) { + if ($null -ne $mtDnsCache.dmarcRecord) { Write-Verbose "DMARC record exist in cache - Use Clear-MtDnsCache to reset" $recordSet.dmarcRecord = $mtDnsCache.dmarcRecord - }else{ + } else { $recordSet.dmarcRecord = ConvertFrom-MailAuthenticationRecordDmarc @splat - ($__MtSession.DnsCache|Where-Object{$_.domain -eq $DomainName}).dmarcRecord = $recordSet.dmarcRecord + ($__MtSession.DnsCache | Where-Object { $_.domain -eq $DomainName }).dmarcRecord = $recordSet.dmarcRecord } } diff --git a/powershell/public/cisa/exchange/Resolve-SPFRecord.ps1 b/powershell/public/cisa/exchange/Resolve-SPFRecord.ps1 index 4397ae22..7501c76a 100644 --- a/powershell/public/cisa/exchange/Resolve-SPFRecord.ps1 +++ b/powershell/public/cisa/exchange/Resolve-SPFRecord.ps1 @@ -13,7 +13,7 @@ function Resolve-SPFRecord { [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingWriteHost', '', Justification = 'Colors are beautiful')] - [OutputType([spfrecord[]],[System.String])] + [OutputType([spfrecord[]], [System.String])] [CmdletBinding()] param ( # Domain Name @@ -67,12 +67,12 @@ function Resolve-SPFRecord { # DNS Lookup Limit = 10 # https://tools.ietf.org/html/rfc7208#section-4.6.4 # Query DNS Record - try{ - if($isWindows){ + try { + if ($isWindows -or $PSVersionTable.PSEdition -eq "Desktop") { $DNSRecords = Resolve-DnsName -Server $Server -Name $Name -Type TXT - }else{ - $cmdletCheck = Get-Command "Resolve-Dns" - if($cmdletCheck){ + } else { + $cmdletCheck = Get-Command "Resolve-Dns" -ErrorAction SilentlyContinue + if ($cmdletCheck) { $answers = (Resolve-Dns -NameServer $Server -Query $Name -QueryType TXT).Answers $DNSRecords = $answers | ForEach-Object { [PSCustomObject]@{ @@ -82,16 +82,16 @@ function Resolve-SPFRecord { Strings = $_.Text } } - }else{ + } else { Write-Error "`nFor non-Windows platforms, please install DnsClient-PS module." Write-Host "`n Install-Module DnsClient-PS -Scope CurrentUser`n" -ForegroundColor Yellow return "Missing dependency, Resolve-Dns not available" } } - }catch [System.Management.Automation.CommandNotFoundException]{ + } catch [System.Management.Automation.CommandNotFoundException] { Write-Error $_ return "Unsupported platform, Resolve-DnsName not available" - }catch{ + } catch { Write-Error $_ return "Failure to obtain record" } @@ -103,13 +103,11 @@ function Resolve-SPFRecord { if ( $SPFCount -eq 0) { # If there is no error show an error Write-Error "No SPF record found for `"$Name`"" - } - elseif ( $SPFCount -ge 2 ) { + } elseif ( $SPFCount -ge 2 ) { # Multiple DNS Records are not allowed # https://tools.ietf.org/html/rfc7208#section-3.2 Write-Error "There is more than one SPF for domain `"$Name`"" - } - else { + } else { # Multiple Strings in a Single DNS Record # https://tools.ietf.org/html/rfc7208#section-3.3 $SPFString = $SPFRecord.Strings -join '' @@ -122,8 +120,7 @@ function Resolve-SPFRecord { Write-Verbose "[REDIRECT]`t$RedirectRecord" # Follow the include and resolve the include Resolve-SPFRecord -Name "$RedirectRecord" -Server $Server -Referrer $Name - } - else { + } else { # Extract the qualifier $Qualifier = switch ( $SPFDirectives -match "^[+-?~]all$" -replace "all" ) { @@ -158,9 +155,9 @@ function Resolve-SPFRecord { } '^a:.*$' { Write-Verbose "[A]`tSPF entry: $SPFDirective" - if($IsWindows){ + if ( $isWindows -or $PSVersionTable.PSEdition -eq "Desktop" ) { $DNSRecords = Resolve-DnsName -Server $Server -Name $Name -Type A - }else{ + } else { $answers = (Resolve-Dns -NameServer $Server -Query $Name -QueryType A).Answers $DNSRecords = $answers | ForEach-Object { [PSCustomObject]@{ @@ -185,9 +182,9 @@ function Resolve-SPFRecord { } '^mx:.*$' { Write-Verbose "[MX]`tSPF entry: $SPFDirective" - if($IsWindows){ + if ( $isWindows -or $PSVersionTable.PSEdition -eq "Desktop" ) { $DNSRecords = Resolve-DnsName -Server $Server -Name $Name -Type MX - }else{ + } else { $answers = (Resolve-Dns -NameServer $Server -Query $Name -QueryType MX).Answers $DNSRecords = $answers | ForEach-Object { [PSCustomObject]@{ @@ -201,9 +198,9 @@ function Resolve-SPFRecord { } foreach ($MXRecords in ($DNSRecords.NameExchange) ) { # Check SPF record - if($isWindows){ + if ( $isWindows -or $PSVersionTable.PSEdition -eq "Desktop" ) { $DNSRecords = Resolve-DnsName -Server $Server -Name $MXRecords -Type A - }else{ + } else { $answers = (Resolve-Dns -NameServer $Server -Query $Name -QueryType A).Answers $DNSRecords = $answers | ForEach-Object { [PSCustomObject]@{