Skip to content

Commit

Permalink
Merge pull request #348 from maester365/FixWindowsDetection
Browse files Browse the repository at this point in the history
Robust detection of Windows in Windows PowerShell
  • Loading branch information
f-bader committed Jul 14, 2024
2 parents 0c0d118 + b213ac3 commit 5277110
Show file tree
Hide file tree
Showing 6 changed files with 171 additions and 174 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand All @@ -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
Expand All @@ -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
}
}
Expand All @@ -90,38 +90,38 @@ 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
NameServer = $dkimSplat.Server
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"
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand All @@ -57,32 +57,32 @@ 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
}
}

#[DMARCRecord]::new("v=DMARC1; p=reject; pct=100; rua=mailto:[email protected]; ruf=mailto:[email protected]; fo=1")
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
Expand All @@ -102,90 +102,90 @@ 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
}
$this.version = $Matches["v"]
$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
Expand All @@ -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"]
}
}
Expand All @@ -221,38 +221,38 @@ 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
NameServer = $dmarcSplat.Server
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"
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand All @@ -38,22 +38,22 @@ 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
QueryType = $mxSplat.Type
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
Expand All @@ -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
}
}
}
Loading

0 comments on commit 5277110

Please sign in to comment.