PSBlackListChecker.psm1

function Format-FirstXChars {
    <#
    .SYNOPSIS
    Short description
 
    .DESCRIPTION
    Long description
 
    .PARAMETER Text
    Parameter description
 
    .PARAMETER NumberChars
    Parameter description
 
    .EXAMPLE
    Format-FirstChars -Text "VERBOSE: Loading module from path 'C:\Users\pklys\.vscode\extensions\ms-vs" -NumberChars 15
 
    .NOTES
    General notes
    #>

    param([string] $Text,
        [int] $NumberChars)
    return ($Text.ToCharArray() | Select-Object -First $NumberChars) -join ''
}
function New-Runspace {
    [cmdletbinding()]
    param ([int] $minRunspaces = 1,
        [int] $maxRunspaces = [int]$env:NUMBER_OF_PROCESSORS + 1)
    $RunspacePool = [RunspaceFactory]::CreateRunspacePool($minRunspaces, $maxRunspaces)
    $RunspacePool.Open()
    return $RunspacePool
}
function Send-Email {
    [CmdletBinding(SupportsShouldProcess = $true)]
    param ([alias('EmailParameters')][System.Collections.IDictionary] $Email,
        [string] $Body,
        [string[]] $Attachment,
        [System.Collections.IDictionary] $InlineAttachments,
        [string] $Subject,
        [string[]] $To,
        [PSCustomObject] $Logger)
    try {
        if ($Email.EmailTo) {
            $EmailParameters = $Email.Clone()
            $EmailParameters.EmailEncoding = $EmailParameters.EmailEncoding -replace "-", ''
            $EmailParameters.EmailEncodingSubject = $EmailParameters.EmailEncodingSubject -replace "-", ''
            $EmailParameters.EmailEncodingBody = $EmailParameters.EmailEncodingSubject -replace "-", ''
            $EmailParameters.EmailEncodingAlternateView = $EmailParameters.EmailEncodingAlternateView -replace "-", ''
        } else {
            $EmailParameters = @{EmailFrom  = $Email.From
                EmailTo                     = $Email.To
                EmailCC                     = $Email.CC
                EmailBCC                    = $Email.BCC
                EmailReplyTo                = $Email.ReplyTo
                EmailServer                 = $Email.Server
                EmailServerPassword         = $Email.Password
                EmailServerPasswordAsSecure = $Email.PasswordAsSecure
                EmailServerPasswordFromFile = $Email.PasswordFromFile
                EmailServerPort             = $Email.Port
                EmailServerLogin            = $Email.Login
                EmailServerEnableSSL        = $Email.EnableSsl
                EmailEncoding               = $Email.Encoding -replace "-", ''
                EmailEncodingSubject        = $Email.EncodingSubject -replace "-", ''
                EmailEncodingBody           = $Email.EncodingBody -replace "-", ''
                EmailEncodingAlternateView  = $Email.EncodingAlternateView -replace "-", ''
                EmailSubject                = $Email.Subject
                EmailPriority               = $Email.Priority
                EmailDeliveryNotifications  = $Email.DeliveryNotifications
                EmailUseDefaultCredentials  = $Email.UseDefaultCredentials
            }
        }
    } catch {
        return @{Status = $False
            Error       = $($_.Exception.Message)
            SentTo      = ''
        }
    }
    $SmtpClient = [System.Net.Mail.SmtpClient]::new()
    if ($EmailParameters.EmailServer) { $SmtpClient.Host = $EmailParameters.EmailServer } else {
        return @{Status = $False
            Error       = "Email Server Host is not set."
            SentTo      = ''
        }
    }
    if ($EmailParameters.EmailServerPort) { $SmtpClient.Port = $EmailParameters.EmailServerPort } else {
        return @{Status = $False
            Error       = "Email Server Port is not set."
            SentTo      = ''
        }
    }
    if ($EmailParameters.EmailServerLogin) {
        $Credentials = Request-Credentials -UserName $EmailParameters.EmailServerLogin -Password $EmailParameters.EmailServerPassword -AsSecure:$EmailParameters.EmailServerPasswordAsSecure -FromFile:$EmailParameters.EmailServerPasswordFromFile -NetworkCredentials
        $SmtpClient.Credentials = $Credentials
    }
    if ($EmailParameters.EmailServerEnableSSL) { $SmtpClient.EnableSsl = $EmailParameters.EmailServerEnableSSL }
    $MailMessage = [System.Net.Mail.MailMessage]::new()
    $MailMessage.From = $EmailParameters.EmailFrom
    if ($To) { foreach ($T in $To) { $MailMessage.To.add($($T)) } } else { if ($EmailParameters.Emailto) { foreach ($To in $EmailParameters.Emailto) { $MailMessage.To.add($($To)) } } }
    if ($EmailParameters.EmailCC) { foreach ($CC in $EmailParameters.EmailCC) { $MailMessage.CC.add($($CC)) } }
    if ($EmailParameters.EmailBCC) { foreach ($BCC in $EmailParameters.EmailBCC) { $MailMessage.BCC.add($($BCC)) } }
    if ($EmailParameters.EmailReplyTo) { $MailMessage.ReplyTo = $EmailParameters.EmailReplyTo }
    $MailMessage.IsBodyHtml = $true
    if ($Subject -eq '') { $MailMessage.Subject = $EmailParameters.EmailSubject } else { $MailMessage.Subject = $Subject }
    $MailMessage.Priority = [System.Net.Mail.MailPriority]::$($EmailParameters.EmailPriority)
    if ($EmailParameters.EmailEncodingSubject) { $MailMessage.SubjectEncoding = [System.Text.Encoding]::$($EmailParameters.EmailEncodingSubject) } elseif ($EmailParameters.EmailEncoding) { $MailMessage.SubjectEncoding = [System.Text.Encoding]::$($EmailParameters.EmailEncoding) }
    if ($EmailParameters.EmailEncodingBody) { $MailMessage.BodyEncoding = [System.Text.Encoding]::$($EmailParameters.EmailEncodingBody) } elseif ($EmailParameters.EmailEncoding) { $MailMessage.BodyEncoding = [System.Text.Encoding]::$($EmailParameters.EmailEncoding) }
    if ($EmailParameters.EmailUseDefaultCredentials) { $SmtpClient.UseDefaultCredentials = $EmailParameters.EmailUseDefaultCredentials }
    if ($EmailParameters.EmailDeliveryNotifications) { $MailMessage.DeliveryNotificationOptions = $EmailParameters.EmailDeliveryNotifications }
    if ($PSBoundParameters.ContainsKey('InlineAttachments')) {
        if ($EmailParameters.EmailEncodingAlternateView) { $BodyPart = [Net.Mail.AlternateView]::CreateAlternateViewFromString($Body, [System.Text.Encoding]::$($EmailParameters.EmailEncodingAlternateView) , 'text/html') } else { $BodyPart = [Net.Mail.AlternateView]::CreateAlternateViewFromString($Body, [System.Text.Encoding]::UTF8, 'text/html') }
        $MailMessage.AlternateViews.Add($BodyPart)
        foreach ($Entry in $InlineAttachments.GetEnumerator()) {
            try {
                $FilePath = $Entry.Value
                Write-Verbose $FilePath
                if ($Entry.Value.StartsWith('http')) {
                    $FileName = $Entry.Value.Substring($Entry.Value.LastIndexOf("/") + 1)
                    $FilePath = Join-Path $env:temp $FileName
                    Invoke-WebRequest -Uri $Entry.Value -OutFile $FilePath
                }
                $ContentType = Get-MimeType -FileName $FilePath
                $InAttachment = [Net.Mail.LinkedResource]::new($FilePath, $ContentType)
                $InAttachment.ContentId = $Entry.Key
                $BodyPart.LinkedResources.Add($InAttachment)
            } catch {
                $ErrorMessage = $_.Exception.Message -replace "`n", " " -replace "`r", " "
                Write-Error "Error inlining attachments: $ErrorMessage"
            }
        }
    } else { $MailMessage.Body = $Body }
    if ($PSBoundParameters.ContainsKey('Attachment')) {
        foreach ($Attach in $Attachment) {
            if (Test-Path -LiteralPath $Attach) {
                try {
                    $File = [Net.Mail.Attachment]::new($Attach)
                    $MailMessage.Attachments.Add($File)
                } catch {
                    $ErrorMessage = $_.Exception.Message -replace "`n", " " -replace "`r", " "
                    if ($Logger) { $Logger.AddErrorRecord("Error attaching file $Attach`: $ErrorMessage") } else { Write-Error "Error attaching file $Attach`: $ErrorMessage" }
                }
            }
        }
    }
    try {
        $MailSentTo = "$($MailMessage.To) $($MailMessage.CC) $($MailMessage.BCC)".Trim()
        if ($pscmdlet.ShouldProcess("$MailSentTo", "Send-Email")) {
            $SmtpClient.Send($MailMessage)
            $MailMessage.Dispose()
            return [PSCustomObject] @{Status = $True
                Error                        = ""
                SentTo                       = $MailSentTo
            }
        }
    } catch {
        $MailMessage.Dispose()
        return [PSCustomObject] @{Status = $False
            Error                        = $($_.Exception.Message)
            SentTo                       = ""
        }
    }
}
function Start-Runspace {
    [cmdletbinding()]
    param ([ScriptBlock] $ScriptBlock,
        [System.Collections.IDictionary] $Parameters,
        [System.Management.Automation.Runspaces.RunspacePool] $RunspacePool)
    if ($ScriptBlock -ne '') {
        $runspace = [PowerShell]::Create()
        $null = $runspace.AddScript($ScriptBlock)
        if ($null -ne $Parameters) { $null = $runspace.AddParameters($Parameters) }
        $runspace.RunspacePool = $RunspacePool
        [PSCustomObject]@{Pipe = $runspace
            Status             = $runspace.BeginInvoke()
        }
    }
}
function Stop-Runspace {
    [cmdletbinding()]
    param([Array] $Runspaces,
        [string] $FunctionName,
        [System.Management.Automation.Runspaces.RunspacePool] $RunspacePool,
        [switch] $ExtendedOutput)
    [Array] $List = While (@($Runspaces | Where-Object -FilterScript { $null -ne $_.Status }).count -gt 0) {
        foreach ($Runspace in $Runspaces | Where-Object { $_.Status.IsCompleted -eq $true }) {
            $Errors = foreach ($e in $($Runspace.Pipe.Streams.Error)) {
                Write-Error -ErrorRecord $e
                $e
            }
            foreach ($w in $($Runspace.Pipe.Streams.Warning)) { Write-Warning -Message $w }
            foreach ($v in $($Runspace.Pipe.Streams.Verbose)) { Write-Verbose -Message $v }
            if ($ExtendedOutput) {
                @{Output   = $Runspace.Pipe.EndInvoke($Runspace.Status)
                    Errors = $Errors
                }
            } else { $Runspace.Pipe.EndInvoke($Runspace.Status) }
            $Runspace.Status = $null
        }
    }
    $RunspacePool.Close()
    $RunspacePool.Dispose()
    if ($List.Count -eq 1) { return , $List } else { return $List }
}
function Get-MimeType {
    [CmdletBinding()]
    param ([Parameter(Mandatory = $true)]
        [string] $FileName)
    $MimeMappings = @{'.jpeg' = 'image/jpeg'
        '.jpg'                = 'image/jpeg'
        '.png'                = 'image/png'
    }
    $Extension = [System.IO.Path]::GetExtension($FileName)
    $ContentType = $MimeMappings[ $Extension ]
    if ([string]::IsNullOrEmpty($ContentType)) { return New-Object System.Net.Mime.ContentType } else { return New-Object System.Net.Mime.ContentType($ContentType) }
}
function Request-Credentials {
    [CmdletBinding()]
    param([string] $UserName,
        [string] $Password,
        [switch] $AsSecure,
        [switch] $FromFile,
        [switch] $Output,
        [switch] $NetworkCredentials,
        [string] $Service)
    if ($FromFile) {
        if (($Password -ne '') -and (Test-Path $Password)) {
            Write-Verbose "Request-Credentials - Reading password from file $Password"
            $Password = Get-Content -Path $Password
        } else {
            if ($Output) { return @{Status = $false; Output = $Service; Extended = 'File with password unreadable.' } } else {
                Write-Warning "Request-Credentials - Secure password from file couldn't be read. File not readable. Terminating."
                return
            }
        }
    }
    if ($AsSecure) {
        try { $NewPassword = $Password | ConvertTo-SecureString -ErrorAction Stop } catch {
            $ErrorMessage = $_.Exception.Message -replace "`n", " " -replace "`r", " "
            if ($ErrorMessage -like '*Key not valid for use in specified state*') {
                if ($Output) { return @{Status = $false; Output = $Service; Extended = "Couldn't use credentials provided. Most likely using credentials from other user/session/computer." } } else {
                    Write-Warning -Message "Request-Credentials - Couldn't use credentials provided. Most likely using credentials from other user/session/computer."
                    return
                }
            } else {
                if ($Output) { return @{Status = $false; Output = $Service; Extended = $ErrorMessage } } else {
                    Write-Warning -Message "Request-Credentials - $ErrorMessage"
                    return
                }
            }
        }
    } else { $NewPassword = $Password }
    if ($UserName -and $NewPassword) {
        if ($AsSecure) { $Credentials = New-Object System.Management.Automation.PSCredential($Username, $NewPassword) } else {
            Try { $SecurePassword = $Password | ConvertTo-SecureString -AsPlainText -Force -ErrorAction Stop } catch {
                $ErrorMessage = $_.Exception.Message -replace "`n", " " -replace "`r", " "
                if ($ErrorMessage -like '*Key not valid for use in specified state*') {
                    if ($Output) { return @{Status = $false; Output = $Service; Extended = "Couldn't use credentials provided. Most likely using credentials from other user/session/computer." } } else {
                        Write-Warning -Message "Request-Credentials - Couldn't use credentials provided. Most likely using credentials from other user/session/computer."
                        return
                    }
                } else {
                    if ($Output) { return @{Status = $false; Output = $Service; Extended = $ErrorMessage } } else {
                        Write-Warning -Message "Request-Credentials - $ErrorMessage"
                        return
                    }
                }
            }
            $Credentials = New-Object System.Management.Automation.PSCredential($Username, $SecurePassword)
        }
    } else {
        if ($Output) { return @{Status = $false; Output = $Service; Extended = 'Username or/and Password is empty' } } else {
            Write-Warning -Message 'Request-Credentials - UserName or Password are empty.'
            return
        }
    }
    if ($NetworkCredentials) { return $Credentials.GetNetworkCredential() } else { return $Credentials }
}
function Set-EmailBody($TableData, $TableWelcomeMessage) {
    $body = @("<p><i>$TableWelcomeMessage</i>"
        if ($($TableData | Measure-Object).Count -gt 0) {
            $TableData | ConvertTo-Html -Fragment | Out-String
            $body = $body -replace ' Added', "<font color=`"green`"><b> Added</b></font>"
            $body = $body -replace ' Removed', "<font color=`"red`"><b> Removed</b></font>"
            $body = $body -replace ' Deleted', "<font color=`"red`"><b> Deleted</b></font>"
            $body = $body -replace ' Changed', "<font color=`"blue`"><b> Changed</b></font>"
            $body = $body -replace ' Change', "<font color=`"blue`"><b> Change</b></font>"
            $body = $body -replace ' Disabled', "<font color=`"red`"><b> Disabled</b></font>"
            $body = $body -replace ' Enabled', "<font color=`"green`"><b> Enabled</b></font>"
            $body = $body -replace ' Locked out', "<font color=`"red`"><b> Locked out</b></font>"
            $body = $body -replace ' Lockouts', "<font color=`"red`"><b> Lockouts</b></font>"
            $body = $body -replace ' Unlocked', "<font color=`"green`"><b> Unlocked</b></font>"
            $body = $body -replace ' Reset', "<font color=`"blue`"><b> Reset</b></font>"
            '</p>'
        } else { '<br><i>No changes happend during that period.</i></p>' })
    return $body
}
function Set-EmailHead {
    [cmdletBinding()]
    param([System.Collections.IDictionary] $FormattingOptions)
    $head = @"
<!DOCTYPE html>
<head>
    <meta charset="utf-8" />
    <meta content="width=device-width, initial-scale=1" name="viewport" />
    <style>
    BODY {
        background-color: white;
        font-family: $($FormattingOptions.FontFamily);
        font-size: $($FormattingOptions.FontSize);
    }
 
    TABLE {
        border-width: 1px;
        border-style: solid;
        border-color: black;
        border-collapse: collapse;
        font-family: $($FormattingOptions.FontTableDataFamily);
        font-size: $($FormattingOptions.FontTableDataSize);
    }
 
    TH {
        border-width: 1px;
        padding: 3px;
        border-style: solid;
        border-color: black;
        background-color: #00297A;
        color: white;
        font-family: $($FormattingOptions.FontTableHeadingFamily);
        font-size: $($FormattingOptions.FontTableHeadingSize);
    }
    TR {
        font-family: $($FormattingOptions.FontTableDataFamily);
        font-size: $($FormattingOptions.FontTableDataSize);
    }
 
    UL {
        font-family: $($FormattingOptions.FontFamily);
        font-size: $($FormattingOptions.FontSize);
    }
 
    LI {
        font-family: $($FormattingOptions.FontFamily);
        font-size: $($FormattingOptions.FontSize);
    }
 
    TD {
        border-width: 1px;
        padding-right: 2px;
        padding-left: 2px;
        padding-top: 0px;
        padding-bottom: 0px;
        border-style: solid;
        border-color: black;
        background-color: white;
        font-family: $($FormattingOptions.FontTableDataFamily);
        font-size: $($FormattingOptions.FontTableDataSize);
    }
 
    H2 {
        font-family: $($FormattingOptions.FontHeadingFamily);
        font-size: $($FormattingOptions.FontHeadingSize);
    }
 
    P {
        font-family: $($FormattingOptions.FontFamily);
        font-size: $($FormattingOptions.FontSize);
    }
</style>
</head>
"@

    return $Head
}
function Set-EmailReportBranding {
    param([alias('FormattingOptions')] $FormattingParameters)
    if ($FormattingParameters.CompanyBranding.Link) { $Report = "<a style=`"text-decoration:none`" href=`"$($FormattingParameters.CompanyBranding.Link)`" class=`"clink logo-container`">" } else { $Report = '' }
    if ($FormattingParameters.CompanyBranding.Inline) { $Report += "<img width=<fix> height=<fix> src=`"cid:logo`" border=`"0`" class=`"company-logo`" alt=`"company-logo`"></a>" } else { $Report += "<img width=<fix> height=<fix> src=`"$($FormattingParameters.CompanyBranding.Logo)`" border=`"0`" class=`"company-logo`" alt=`"company-logo`"></a>" }
    if ($FormattingParameters.CompanyBranding.Width -ne "") { $Report = $Report -replace "width=<fix>", "width=$($FormattingParameters.CompanyBranding.Width)" } else { $Report = $Report -replace "width=<fix>", "" }
    if ($FormattingParameters.CompanyBranding.Height -ne "") { $Report = $Report -replace "height=<fix>", "height=$($FormattingParameters.CompanyBranding.Height)" } else { $Report = $Report -replace "height=<fix>", "" }
    return $Report
}
function Set-EmailReportDetails {
    param($FormattingOptions,
        $ReportOptions,
        $TimeToGenerate)
    $DateReport = Get-Date
    $Report = @("<p style=`"background-color:white;font-family:$($FormattingOptions.FontFamily);font-size:$($FormattingOptions.FontSize)`">"
        "<strong>Report Time:</strong> $DateReport <br>"
        "<strong>Time to generate:</strong> $($TimeToGenerate.Hours) hours, $($TimeToGenerate.Minutes) minutes, $($TimeToGenerate.Seconds) seconds, $($TimeToGenerate.Milliseconds) milliseconds <br>"
        if ($PSVersionTable.Platform -ne 'Unix') { "<strong>Account Executing Report :</strong> $env:userdomain\$($env:username.toupper()) on $($env:ComputerName.toUpper()) <br>" } else {}
        '<strong>Checking for monitored IPs :</strong>'
        '<ul>'
        foreach ($ip in $ReportOptions.MonitoredIps.Values) { "<li>ip:</strong> $ip</li>" }
        '</ul>'
        '</p>')
    return $Report
}
[string[]] $Script:BlackLists = @('b.barracudacentral.org'
    'spam.rbl.msrbl.net'
    'zen.spamhaus.org'
    'bl.deadbeef.com'
    'bl.spamcop.net'
    'blackholes.five-ten-sg.com'
    'blacklist.woody.ch'
    'bogons.cymru.com'
    'cbl.abuseat.org'
    'combined.abuse.ch'
    'combined.rbl.msrbl.net'
    'db.wpbl.info'
    'dnsbl-1.uceprotect.net'
    'dnsbl-2.uceprotect.net'
    'dnsbl-3.uceprotect.net'
    'dnsbl.cyberlogic.net'
    'dnsbl.inps.de'
    'dnsbl.sorbs.net'
    'drone.abuse.ch'
    'drone.abuse.ch'
    'duinv.aupads.org'
    'dul.dnsbl.sorbs.net'
    'dul.ru'
    'dyna.spamrats.com'
    'http.dnsbl.sorbs.net'
    'images.rbl.msrbl.net'
    'ips.backscatterer.org'
    'ix.dnsbl.manitu.net'
    'korea.services.net'
    'misc.dnsbl.sorbs.net'
    'noptr.spamrats.com'
    'ohps.dnsbl.net.au'
    'omrs.dnsbl.net.au'
    'orvedb.aupads.org'
    'osps.dnsbl.net.au'
    'osrs.dnsbl.net.au'
    'owfs.dnsbl.net.au'
    'owps.dnsbl.net.au'
    'pbl.spamhaus.org'
    'phishing.rbl.msrbl.net'
    'probes.dnsbl.net.au'
    'proxy.bl.gweep.ca'
    'proxy.block.transip.nl'
    'psbl.surriel.com'
    'rbl.interserver.net'
    'rdts.dnsbl.net.au'
    'relays.bl.gweep.ca'
    'relays.bl.kundenserver.de'
    'relays.nether.net'
    'residential.block.transip.nl'
    'ricn.dnsbl.net.au'
    'rmst.dnsbl.net.au'
    'sbl.spamhaus.org'
    'short.rbl.jp'
    'smtp.dnsbl.sorbs.net'
    'socks.dnsbl.sorbs.net'
    'spam.abuse.ch'
    'spam.dnsbl.sorbs.net'
    'spam.spamrats.com'
    'spamlist.or.kr'
    'spamrbl.imp.ch'
    't3direct.dnsbl.net.au'
    'ubl.lashback.com'
    'ubl.unsubscore.com'
    'virbl.bit.nl'
    'virus.rbl.jp'
    'virus.rbl.msrbl.net'
    'web.dnsbl.sorbs.net'
    'wormrbl.imp.ch'
    'xbl.spamhaus.org'
    'zombie.dnsbl.sorbs.net')
$Script:ScriptBlockNetDNS = { param ([string] $Server,
        [string] $IP,
        [bool] $QuickTimeout,
        [bool] $Verbose)
    if ($Verbose) { $verbosepreference = 'continue' }
    $ReversedIP = ($IP -split '\.')[3..0] -join '.'
    $FQDN = "$ReversedIP.$Server"
    try { $DnsCheck = [Net.DNS]::GetHostAddresses($fqdn) } catch { $DnsCheck = $null }
    if ($null -ne $DnsCheck) {
        $ServerData = [PSCustomObject] @{IP = $IP
            FQDN                            = $FQDN
            BlackList                       = $Server
            IsListed                        = if ($null -eq $DNSCheck.IPAddressToString) { $false } else { $true }
            Answer                          = $DnsCheck.IPAddressToString -join ', '
            TTL                             = ''
        }
    } else {
        $ServerData = [PSCustomObject] @{IP = $IP
            FQDN                            = $FQDN
            BlackList                       = $Server
            IsListed                        = $false
            Answer                          = ""
            TTL                             = ''
        }
    }
    return $ServerData }
$Script:ScriptBlockResolveDNS = { param ([string] $Server,
        [string] $IP,
        [bool] $QuickTimeout,
        [bool] $Verbose,
        [string[]] $DNSServer = '')
    if ($Verbose) { $verbosepreference = 'continue' }
    [string] $ReversedIP = ($IP -split '\.')[3..0] -join '.'
    [string] $FQDN = "$ReversedIP.$Server"
    [int] $Count = 0
    [bool] $Loaded = $false
    Do {
        try {
            Import-Module -Name 'DnsClient' -Verbose:$false
            $Loaded = $true
        } catch { Write-Warning "DNSClient Import Error ($Server / $FQDN / $IP): $_. Retrying." }
        $Count++
        if ($Loaded -eq $false -and $Count -eq 5) { Write-Warning "DNSClient Import failed. Skipping check on $Server / $FQDN / $IP" }
    } until ($Loaded -eq $false -or $Count -eq 5)
    if ($DNSServer -ne '') { $DnsCheck = Resolve-DnsName -Name $fqdn -ErrorAction SilentlyContinue -NoHostsFile -QuickTimeout:$QuickTimeout -Server $DNSServer -DnsOnly } else { $DnsCheck = Resolve-DnsName -Name $fqdn -ErrorAction SilentlyContinue -NoHostsFile -QuickTimeout:$QuickTimeout -DnsOnly }
    if ($null -ne $DnsCheck) {
        $ServerData = [PSCustomObject] @{IP = $IP
            FQDN                            = $FQDN
            BlackList                       = $Server
            IsListed                        = if ($null -eq $DNSCheck.IpAddress) { $false } else { $true }
            Answer                          = $DnsCheck.IPAddress -join ', '
            TTL                             = $DnsCheck.TTL -join ', '
        }
    } else {
        $ServerData = [PSCustomObject] @{IP = $IP
            FQDN                            = $FQDN
            BlackList                       = $Server
            IsListed                        = $false
            Answer                          = ''
            TTL                             = ''
        }
    }
    return $ServerData }
$Script:ScriptBlockNetDNSSlow = { param ([string[]] $Servers,
        [string[]] $IPs,
        [bool] $QuickTimeout,
        [bool] $Verbose)
    if ($Verbose) { $verbosepreference = 'continue' }
    $Blacklisted = foreach ($Server in $Servers) {
        foreach ($IP in $IPS) {
            [string] $ReversedIP = ($IP -split '\.')[3..0] -join '.'
            [string] $FQDN = "$ReversedIP.$Server"
            try { $DnsCheck = [Net.DNS]::GetHostAddresses($FQDN) } catch { $DnsCheck = $null }
            if ($null -ne $DnsCheck) {
                [PSCustomObject] @{IP = $ip
                    FQDN              = $fqdn
                    BlackList         = $server
                    IsListed          = if ($null -eq $DNSCheck.IPAddressToString) { $false } else { $true }
                    Answer            = $DnsCheck.IPAddressToString -join ', '
                    TTL               = ''
                }
            } else {
                [PSCustomObject] @{IP = $IP
                    FQDN              = $FQDN
                    BlackList         = $Server
                    IsListed          = $false
                    Answer            = ''
                    TTL               = ''
                }
            }
        }
    }
    return $Blacklisted }
$Script:ScriptBlockResolveDNSSlow = { param ([string[]] $Servers,
        [string[]] $IPs,
        [bool] $QuickTimeout,
        [bool] $Verbose,
        [string[]] $DNSServer = '')
    if ($Verbose) { $verbosepreference = 'continue' }
    $Blacklisted = foreach ($Server in $Servers) {
        foreach ($IP in $IPS) {
            $ReversedIP = ($IP -split '\.')[3..0] -join '.'
            $FQDN = "$ReversedIP.$Server"
            if ($DNSServer -ne '') { $DnsCheck = Resolve-DnsName -Name $fqdn -ErrorAction SilentlyContinue -NoHostsFile -QuickTimeout:$QuickTimeout -Server $DNSServer -DnsOnly } else { $DnsCheck = Resolve-DnsName -Name $fqdn -ErrorAction SilentlyContinue -NoHostsFile -QuickTimeout:$QuickTimeout -DnsOnly }
            if ($null -ne $DnsCheck) {
                [PSCustomObject] @{IP = $IP
                    FQDN              = $FQDN
                    BlackList         = $Server
                    IsListed          = if ($null -eq $DNSCheck.IpAddress) { $false } else { $true }
                    Answer            = $DnsCheck.IPAddress -join ', '
                    TTL               = $DnsCheck.TTL -join ', '
                }
            } else {
                [PSCustomObject] @{IP = $IP
                    FQDN              = $FQDN
                    BlackList         = $Server
                    IsListed          = $false
                    Answer            = ''
                    TTL               = ''
                }
            }
        }
    }
    return $Blacklisted }
function Search-BlackList {
    <#
    .SYNOPSIS
    Search-Blacklist searches if particular IP is blacklisted on DNSBL Blacklists.
 
    .DESCRIPTION
    Long description
 
    .PARAMETER IPs
    Parameter description
 
    .PARAMETER BlacklistServers
    Parameter description
 
    .PARAMETER ReturnAll
    Parameter description
 
    .PARAMETER RunType
    Parameter description
 
    .PARAMETER SortBy
    Parameter description
 
    .PARAMETER SortDescending
    Parameter description
 
    .PARAMETER QuickTimeout
    Parameter description
 
    .PARAMETER MaxRunspaces
    Parameter description
 
    .PARAMETER ExtendedOutput
    Parameter description
 
    .EXAMPLE
    Search-BlackList -IP '89.25.253.1' | Format-Table
 
    .EXAMPLE
    Search-BlackList -IP '89.25.253.1' -SortBy Blacklist | Format-Table
 
    .EXAMPLE
    Search-BlackList -IP '89.25.253.1','195.55.55.55' -SortBy Ip -ReturnAll | Format-Table
 
    .NOTES
    General notes
    #>

    [cmdletbinding()]
    param
    ([alias('IP')][string[]] $IPs,
        [string[]] $BlacklistServers = $Script:BlackLists,
        [switch] $ReturnAll,
        [ValidateSet('NoWorkflowAndRunSpaceNetDNS', 'NoWorkflowAndRunSpaceResolveDNS', 'RunSpaceWithResolveDNS', 'RunSpaceWithNetDNS', 'WorkflowResolveDNS', 'WorkflowWithNetDNS')]
        [string]$RunType,
        [ValidateSet('IP', 'BlackList', 'IsListed', 'Answer', 'FQDN')][string] $SortBy = 'IsListed',
        [switch] $SortDescending,
        [switch] $QuickTimeout,
        [int] $MaxRunspaces = 10,
        [string[]] $DNSServer = '',
        [switch] $ExtendedOutput)
    if ($PSCmdlet.MyInvocation.BoundParameters["Verbose"].IsPresent) { $Verbose = $true } else { $Verbose = $false }
    if ($RunType -eq 'WorkflowResolveDNS') {
        Write-Warning 'Worflows are not supported anymore due to PowerShell 6 complaining. Please use other modes.'
        Exit
    } elseif ($RunType -eq 'WorkflowWithNetDNS') {
        Write-Warning 'Worflows are not supported anymore due to PowerShell 6 complaining. Please use other modes.'
        Exit
    }
    if ($RunType -eq '') { if ($PSVersionTable.Platform -eq 'Unix') { $RunType = 'RunSpaceWithNetDNS' } else { $RunType = 'RunSpaceWithResolveDNS' } }
    if ($PSVersionTable.Platform -eq 'Unix') {
        if ($RunType -eq 'RunSpaceWithResolveDNS') {
            $RunType = 'RunSpaceWithNetDNS'
            Write-Warning 'Search-BlackList - changing RunType to RunSpaceWithNetDNS since Resolve-DNSName is not available on Linux/MacOS'
        } elseif ($RunType -eq 'NoWorkflowAndRunSpaceResolveDNS') {
            $RunType = 'NoWorkflowAndRunSpaceNetDNS'
            Write-Warning 'Search-BlackList - changing RunType to RunSpaceWithNetDNS since Resolve-DNSName is not available on Linux/MacOS'
        }
    }
    if ($DNSServer -ne '' -and $RunType -like 'NetDNS') {
        Write-Warning 'Search-BlackList - Setting DNSServer is not supported for Net.DNS. Resetting to default values.'
        $DNSServer = ''
    }
    Write-Verbose "Search-Blacklist - Runtype: $RunType ReturnAll: $ReturnAll, SortBy: $SortBy MaxRunspaces: $MaxRunspaces SortDescending: $SortDescending"
    If ($RunType -eq 'NoWorkflowAndRunSpaceNetDNS') { $Table = Invoke-Command -ScriptBlock $Script:ScriptBlockNetDNSSlow -ArgumentList $BlacklistServers, $IPs, $QuickTimeout, $Verbose } elseif ($RunType -eq 'NoWorkflowAndRunSpaceResolveDNS') { $Table = Invoke-Command -ScriptBlock $Script:ScriptBlockResolveDNSSlow -ArgumentList $BlacklistServers, $IPs, $QuickTimeout, $Verbose, $DNSServer } elseif ($RunType -eq 'RunSpaceWithResolveDNS') {
        $pool = New-Runspace -maxRunspaces $maxRunspaces -Verbose:$Verbose
        $runspaces = foreach ($Server in $BlacklistServers) {
            foreach ($IP in $IPs) {
                $Parameters = @{Server = $Server
                    IP                 = $IP
                    QuickTimeout       = $QuickTimeout
                    Verbose            = $Verbose
                    DNSServer          = $DNSServer
                }
                Start-Runspace -ScriptBlock $Script:ScriptBlockResolveDNS -Parameters $Parameters -RunspacePool $pool -Verbose:$Verbose
            }
        }
        $Output = Stop-Runspace -Runspaces $runspaces -FunctionName 'Search-BlackList' -RunspacePool $pool -Verbose:$Verbose -ErrorAction Continue -ErrorVariable MyErrors -ExtendedOutput:$ExtendedOutput
        if ($ExtendedOutput) {
            $Output
            Exit
        } else { $Table = $Output }
    } elseif ($RunType -eq 'RunSpaceWithNetDNS') {
        $pool = New-Runspace -maxRunspaces $maxRunspaces -Verbose:$Verbose
        $runspaces = foreach ($server in $BlacklistServers) {
            foreach ($ip in $IPs) {
                $Parameters = @{Server = $Server
                    IP                 = $IP
                    QuickTimeout       = $QuickTimeout
                    Verbose            = $Verbose
                }
                Start-Runspace -ScriptBlock $Script:ScriptBlockNetDNS -Parameters $Parameters -RunspacePool $pool -Verbose:$Verbose
            }
        }
        $Output = Stop-Runspace -Runspaces $runspaces -FunctionName 'Search-BlackList' -RunspacePool $pool -Verbose:$Verbose -ExtendedOutput:$ExtendedOutput
        if ($ExtendedOutput) {
            $Output
            Exit
        } else { $Table = $Output }
    }
    if ($SortDescending -eq $true) { $Table = $Table | Sort-Object $SortBy -Descending } else { $Table = $Table | Sort-Object $SortBy }
    if ($ReturnAll -eq $true) { return $Table | Select-Object IP, FQDN, BlackList, IsListed, Answer, TTL } else { return $Table | Where-Object { $_.IsListed -eq $true } | Select-Object IP, FQDN, BlackList, IsListed, Answer, TTL }
}
function Start-ReportBlackLists {
    [cmdletbinding()]
    param([System.Collections.IDictionary] $EmailParameters,
        [System.Collections.IDictionary] $FormattingParameters,
        [System.Collections.IDictionary] $ReportOptions,
        [switch] $OutputErrors)
    $Errors = @{Teams = $false
        Slack         = $false
        Discord       = $false
    }
    $TeamID = Format-FirstXChars -Text $ReportOptions.NotificationsTeams.TeamsID -NumberChars 25
    $SlackID = Format-FirstXChars -Text $ReportOptions.NotificationsSlack.Uri -NumberChars 25
    $DiscordID = Format-FirstXChars -Text $ReportOptions.NotificationsDiscord.Uri -NumberChars 25
    Write-Verbose "Start-ReportBlackLists - TeamsID: $TeamID"
    Write-Verbose "Start-ReportBlackLists - SlackID: $SlackID"
    Write-Verbose "Start-ReportBlackLists - DiscordID: $DiscordID"
    $Ips = foreach ($ip in $ReportOptions.MonitoredIps.Values) { $ip }
    if ($null -eq $ReportOptions.NotificationsEmail) {
        $ReportOptions.NotificationsEmail = @{Use = $true
            EmailPriorityWhenBlacklisted          = $ReportOptions.EmailPriorityWhenBlacklisted
            EmailPriorityStandard                 = $ReportOptions.EmailPriorityStandard
            EmailAllResults                       = $ReportOptions.EmailAllResults
            EmailAlways                           = $ReportOptions.EmailAlways
        }
    }
    $Time = Measure-Command -Expression { if ($null -eq $ReportOptions.SortBy) { $ReportOptions.SortBy = 'IsListed' }
        if ($null -eq $ReportOptions.SortDescending) { $ReportOptions.SortDescending = $true }
        if ($ReportOptions.NotificationsEmail.EmailAllResults) { $BlackListCheck = Search-BlackList -IP $Ips -SortBy $ReportOptions.SortBy -SortDescending:$ReportOptions.SortDescending -ReturnAll -Verbose } else { $BlackListCheck = Search-BlackList -IP $Ips -SortBy $ReportOptions.SortBy -SortDescending:$ReportOptions.SortDescending -Verbose } }
    $EmailBody = @(Set-EmailHead -FormattingOptions $FormattingParameters
        Set-EmailReportBranding -FormattingOptions $FormattingParameters
        Set-EmailReportDetails -FormattingOptions $FormattingParameters -ReportOptions $ReportOptions -TimeToGenerate $Time
        Set-EmailBody -TableData $BlackListCheck -TableWelcomeMessage 'Following blacklisted servers')
    if ($BlackListCheck.IsListed -contains $true) { $EmailParameters.EmailPriority = $ReportOptions.NotificationsEmail.EmailPriorityWhenBlacklisted } else { $EmailParameters.EmailPriority = $ReportOptions.NotificationsEmail.EmailPriorityStandard }
    [string] $Email = $EmailBody | Out-String
    if ($ReportOptions.NotificationsEmail.Use) { if ($ReportOptions.NotificationsEmail.EmailAlways -eq $true -or $BlackListCheck.IsListed -contains $true) { if ($FormattingParameters.CompanyBranding.Inline) { $SendMail = Send-Email -EmailParameters $EmailParameters -Body $Email -InlineAttachments @{logo = $FormattingParameters.CompanyBranding.Logo } -Verbose } else { $SendMail = Send-Email -EmailParameters $EmailParameters -Body $Email } } }
    if ($BlackListCheck.IsListed -contains $true) {
        $BlackListLimited = $BlackListCheck | Where-Object { $_.IsListed -eq $true }
        if ($ReportOptions.NotificationsTeams.Use) {
            [string] $MessageTitle = $ReportOptions.NotificationsTeams.MessageTitle
            [string] $ActivityImageLink = $ReportOptions.NotificationsTeams.MessageImageLink
            [RGBColors] $Color = [RGBColors]::Red
            $Sections = @(foreach ($Server in $BlackListLimited) {
                    [string] $ActivityTitle = "Blacklisted IP **$($Server.IP)**"
                    if ($ReportOptions.NotificationsTeams.MessageButtons) {
                        $Button1 = New-TeamsButton -Name "Check BlackList" -Link "https://mxtoolbox.com/SuperTool.aspx?action=blacklist%3a$($Server.Ip)&run=toolpage"
                        $Button2 = New-TeamsButton -Name "Check SMTP" -Link "https://mxtoolbox.com/SuperTool.aspx?action=smtp%3a$($Server.Ip)&run=toolpage"
                        New-TeamsSection -ActivityTitle $ActivityTitle -ActivitySubtitle "Found on blacklist **$($Server.Blacklist)**" -ActivityImageLink $ActivityImageLink -ActivityText "Everybody panic!" -Buttons $Button1, $Button2
                    } else { New-TeamsSection -ActivityTitle $ActivityTitle -ActivitySubtitle "Found on blacklist **$($Server.Blacklist)**" -ActivityImageLink $ActivityImageLink -ActivityText "Responses: $($Server.Answer)" }
                })
            try { $TeamsOutput = Send-TeamsMessage -Uri $ReportOptions.NotificationsTeams.TeamsID -MessageTitle $MessageTitle -Color $Color -Sections $Sections -Supress $false } catch {
                $ErrorMessage = $_.Exception.Message -replace "`n", " " -replace "`r", " "
                Write-Warning "Couldn't send to Teams - Error occured: $ErrorMessage"
                $Errors.Teams = $true
            }
        }
        if ($ReportOptions.NotificationsSlack.Use) {
            $MessageTitle = $ReportOptions.NotificationsSlack.MessageTitle
            [string] $ActivityImageLink = $ReportOptions.NotificationsSlack.MessageImageLink
            $Attachments = @(foreach ($Server in $BlackListLimited) { New-SlackMessageAttachment -Color $_PSSlackColorMap.red -Title "IP $($Server.IP) is Blacklisted" -TitleLink "https://mxtoolbox.com/SuperTool.aspx?action=blacklist%3a$($Server.Ip)&run=toolpage" -Text $ReportOptions.NotificationsSlack.MessageText -Pretext "Found on blacklist $($Server.Blacklist)" -Fallback 'Your client is bad' })
            try { $SlackOutput = New-SlackMessage -Attachments $Attachments -Channel $ReportOptions.NotificationsSlack.Channel -IconEmoji $ReportOptions.NotificationsSlack.MessageEmoji -AsUser -Username $ReportOptions.NotificationsSlack.MessageAsUser | Send-SlackMessage -Uri $ReportOptions.NotificationsSlack.URI } catch {
                $ErrorMessage = $_.Exception.Message -replace "`n", " " -replace "`r", " "
                Write-Warning "Couldn't send to Slack - Error occured: $ErrorMessage"
                $Errors.Slack = $true
            }
        }
        if ($ReportOptions.NotificationsDiscord.Use) {
            if ($null -eq $ReportOptions.NotificationsDiscord.MessageInline) { $ReportOptions.NotificationsDiscord.MessageInline = $false }
            try {
                $Facts = foreach ($Server in $BlackListLimited) {
                    [string] $ActivityTitle = "Blacklisted IP $($Server.IP)"
                    [string] $ActivityValue = "Found on blacklist $($Server.Blacklist)"
                    New-DiscordFact -Name $ActivityTitle -Value $ActivityValue -Inline $ReportOptions.NotificationsDiscord.MessageInline
                }
                $Thumbnail = New-DiscordThumbnail -Url $ReportOptions.NotificationsDiscord.MessageImageLink
                $Author = New-DiscordAuthor -Name 'PSBlacklistChecker' -IconUrl $ReportOptions.NotificationsDiscord.MessageImageLink
                $Section = New-DiscordSection -Title $ReportOptions.NotificationsDiscord.MessageText -Description '' -Facts $Facts -Color $ReportOptions.NotificationsDiscord.MessageColor -Author $Author -Thumbnail $Thumbnail
                Send-DiscordMessage -WebHookUrl $ReportOptions.NotificationsDiscord.Uri -Sections $Section -AvatarName $ReportOptions.NotificationsDiscord.MessageAsUser -AvatarUrl $ReportOptions.NotificationsDiscord.MessageAsUserImage -Verbose
            } catch {
                $ErrorMessage = $_.Exception.Message -replace "`n", " " -replace "`r", " "
                Write-Warning "Couldn't send to Discord - Error occured: $ErrorMessage"
                $Errors.Discord = $true
            }
        }
        if ($OutputErrors) { return $Errors }
    }
}
Export-ModuleMember -Function @('Search-BlackList', 'Start-ReportBlackLists') -Alias @()
# SIG # Begin signature block
# MIIgQAYJKoZIhvcNAQcCoIIgMTCCIC0CAQExCzAJBgUrDgMCGgUAMGkGCisGAQQB
# gjcCAQSgWzBZMDQGCisGAQQBgjcCAR4wJgIDAQAABBAfzDtgWUsITrck0sYpfvNR
# AgEAAgEAAgEAAgEAAgEAMCEwCQYFKw4DAhoFAAQUZ9iXBqkqU+8XcU4raLnPiaIs
# DOCgghtvMIIDtzCCAp+gAwIBAgIQDOfg5RfYRv6P5WD8G/AwOTANBgkqhkiG9w0B
# AQUFADBlMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYD
# VQQLExB3d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVk
# IElEIFJvb3QgQ0EwHhcNMDYxMTEwMDAwMDAwWhcNMzExMTEwMDAwMDAwWjBlMQsw
# CQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cu
# ZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3Qg
# Q0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCtDhXO5EOAXLGH87dg
# +XESpa7cJpSIqvTO9SA5KFhgDPiA2qkVlTJhPLWxKISKityfCgyDF3qPkKyK53lT
# XDGEKvYPmDI2dsze3Tyoou9q+yHyUmHfnyDXH+Kx2f4YZNISW1/5WBg1vEfNoTb5
# a3/UsDg+wRvDjDPZ2C8Y/igPs6eD1sNuRMBhNZYW/lmci3Zt1/GiSw0r/wty2p5g
# 0I6QNcZ4VYcgoc/lbQrISXwxmDNsIumH0DJaoroTghHtORedmTpyoeb6pNnVFzF1
# roV9Iq4/AUaG9ih5yLHa5FcXxH4cDrC0kqZWs72yl+2qp/C3xag/lRbQ/6GW6whf
# GHdPAgMBAAGjYzBhMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0G
# A1UdDgQWBBRF66Kv9JLLgjEtUYunpyGd823IDzAfBgNVHSMEGDAWgBRF66Kv9JLL
# gjEtUYunpyGd823IDzANBgkqhkiG9w0BAQUFAAOCAQEAog683+Lt8ONyc3pklL/3
# cmbYMuRCdWKuh+vy1dneVrOfzM4UKLkNl2BcEkxY5NM9g0lFWJc1aRqoR+pWxnmr
# EthngYTffwk8lOa4JiwgvT2zKIn3X/8i4peEH+ll74fg38FnSbNd67IJKusm7Xi+
# fT8r87cmNW1fiQG2SVufAQWbqz0lwcy2f8Lxb4bG+mRo64EtlOtCt/qMHt1i8b5Q
# Z7dsvfPxH2sMNgcWfzd8qVttevESRmCD1ycEvkvOl77DZypoEd+A5wwzZr8TDRRu
# 838fYxAe+o0bJW1sj6W3YQGx0qMmoRBxna3iw/nDmVG3KwcIzi7mULKn+gpFL6Lw
# 8jCCBTAwggQYoAMCAQICEAQJGBtf1btmdVNDtW+VUAgwDQYJKoZIhvcNAQELBQAw
# ZTELMAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQ
# d3d3LmRpZ2ljZXJ0LmNvbTEkMCIGA1UEAxMbRGlnaUNlcnQgQXNzdXJlZCBJRCBS
# b290IENBMB4XDTEzMTAyMjEyMDAwMFoXDTI4MTAyMjEyMDAwMFowcjELMAkGA1UE
# BhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3LmRpZ2lj
# ZXJ0LmNvbTExMC8GA1UEAxMoRGlnaUNlcnQgU0hBMiBBc3N1cmVkIElEIENvZGUg
# U2lnbmluZyBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAPjTsxx/
# DhGvZ3cH0wsxSRnP0PtFmbE620T1f+Wondsy13Hqdp0FLreP+pJDwKX5idQ3Gde2
# qvCchqXYJawOeSg6funRZ9PG+yknx9N7I5TkkSOWkHeC+aGEI2YSVDNQdLEoJrsk
# acLCUvIUZ4qJRdQtoaPpiCwgla4cSocI3wz14k1gGL6qxLKucDFmM3E+rHCiq85/
# 6XzLkqHlOzEcz+ryCuRXu0q16XTmK/5sy350OTYNkO/ktU6kqepqCquE86xnTrXE
# 94zRICUj6whkPlKWwfIPEvTFjg/BougsUfdzvL2FsWKDc0GCB+Q4i2pzINAPZHM8
# np+mM6n9Gd8lk9ECAwEAAaOCAc0wggHJMBIGA1UdEwEB/wQIMAYBAf8CAQAwDgYD
# VR0PAQH/BAQDAgGGMBMGA1UdJQQMMAoGCCsGAQUFBwMDMHkGCCsGAQUFBwEBBG0w
# azAkBggrBgEFBQcwAYYYaHR0cDovL29jc3AuZGlnaWNlcnQuY29tMEMGCCsGAQUF
# BzAChjdodHRwOi8vY2FjZXJ0cy5kaWdpY2VydC5jb20vRGlnaUNlcnRBc3N1cmVk
# SURSb290Q0EuY3J0MIGBBgNVHR8EejB4MDqgOKA2hjRodHRwOi8vY3JsNC5kaWdp
# Y2VydC5jb20vRGlnaUNlcnRBc3N1cmVkSURSb290Q0EuY3JsMDqgOKA2hjRodHRw
# Oi8vY3JsMy5kaWdpY2VydC5jb20vRGlnaUNlcnRBc3N1cmVkSURSb290Q0EuY3Js
# ME8GA1UdIARIMEYwOAYKYIZIAYb9bAACBDAqMCgGCCsGAQUFBwIBFhxodHRwczov
# L3d3dy5kaWdpY2VydC5jb20vQ1BTMAoGCGCGSAGG/WwDMB0GA1UdDgQWBBRaxLl7
# KgqjpepxA8Bg+S32ZXUOWDAfBgNVHSMEGDAWgBRF66Kv9JLLgjEtUYunpyGd823I
# DzANBgkqhkiG9w0BAQsFAAOCAQEAPuwNWiSz8yLRFcgsfCUpdqgdXRwtOhrE7zBh
# 134LYP3DPQ/Er4v97yrfIFU3sOH20ZJ1D1G0bqWOWuJeJIFOEKTuP3GOYw4TS63X
# X0R58zYUBor3nEZOXP+QsRsHDpEV+7qvtVHCjSSuJMbHJyqhKSgaOnEoAjwukaPA
# JRHinBRHoXpoaK+bp1wgXNlxsQyPu6j4xRJon89Ay0BEpRPw5mQMJQhCMrI2iiQC
# /i9yfhzXSUWW6Fkd6fp0ZGuy62ZD2rOwjNXpDd32ASDOmTFjPQgaGLOBm0/GkxAG
# /AeB+ova+YJJ92JuoVP6EpQYhS6SkepobEQysmah5xikmmRR7zCCBT0wggQloAMC
# AQICEATV3B9I6snYUgC6zZqbKqcwDQYJKoZIhvcNAQELBQAwcjELMAkGA1UEBhMC
# VVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3LmRpZ2ljZXJ0
# LmNvbTExMC8GA1UEAxMoRGlnaUNlcnQgU0hBMiBBc3N1cmVkIElEIENvZGUgU2ln
# bmluZyBDQTAeFw0yMDA2MjYwMDAwMDBaFw0yMzA3MDcxMjAwMDBaMHoxCzAJBgNV
# BAYTAlBMMRIwEAYDVQQIDAnFmmzEhXNraWUxETAPBgNVBAcTCEthdG93aWNlMSEw
# HwYDVQQKDBhQcnplbXlzxYJhdyBLxYJ5cyBFVk9URUMxITAfBgNVBAMMGFByemVt
# eXPFgmF3IEvFgnlzIEVWT1RFQzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC
# ggEBAL+ygd4sga4ZC1G2xXvasYSijwWKgwapZ69wLaWaZZIlY6YvXTGQnIUnk+Tg
# 7EoT7mQiMSaeSPOrn/Im6N74tkvRfQJXxY1cnt3U8//U5grhh/CULdd6M3/Z4h3n
# MCq7LQ1YVaa4MYub9F8WOdXO84DANoNVG/t7YotL4vzqZil3S9pHjaidp3kOXGJc
# vxrCPAkRFBKvUmYo23QPFa0Rd0qA3bFhn97WWczup1p90y2CkOf28OVOOObv1fNE
# EqMpLMx0Yr04/h+LPAAYn6K4YtIu+m3gOhGuNc3B+MybgKePAeFIY4EQzbqvCMy1
# iuHZb6q6ggRyqrJ6xegZga7/gV0CAwEAAaOCAcUwggHBMB8GA1UdIwQYMBaAFFrE
# uXsqCqOl6nEDwGD5LfZldQ5YMB0GA1UdDgQWBBQYsTUn6BxQICZOCZA0CxS0TZSU
# ZjAOBgNVHQ8BAf8EBAMCB4AwEwYDVR0lBAwwCgYIKwYBBQUHAwMwdwYDVR0fBHAw
# bjA1oDOgMYYvaHR0cDovL2NybDMuZGlnaWNlcnQuY29tL3NoYTItYXNzdXJlZC1j
# cy1nMS5jcmwwNaAzoDGGL2h0dHA6Ly9jcmw0LmRpZ2ljZXJ0LmNvbS9zaGEyLWFz
# c3VyZWQtY3MtZzEuY3JsMEwGA1UdIARFMEMwNwYJYIZIAYb9bAMBMCowKAYIKwYB
# BQUHAgEWHGh0dHBzOi8vd3d3LmRpZ2ljZXJ0LmNvbS9DUFMwCAYGZ4EMAQQBMIGE
# BggrBgEFBQcBAQR4MHYwJAYIKwYBBQUHMAGGGGh0dHA6Ly9vY3NwLmRpZ2ljZXJ0
# LmNvbTBOBggrBgEFBQcwAoZCaHR0cDovL2NhY2VydHMuZGlnaWNlcnQuY29tL0Rp
# Z2lDZXJ0U0hBMkFzc3VyZWRJRENvZGVTaWduaW5nQ0EuY3J0MAwGA1UdEwEB/wQC
# MAAwDQYJKoZIhvcNAQELBQADggEBAJq9bM+JbCwEYuMBtXoNAfH1SRaMLXnLe0py
# VK6el0Z1BtPxiNcF4iyHqMNVD4iOrgzLEVzx1Bf/sYycPEnyG8Gr2tnl7u1KGSjY
# enX4LIXCZqNEDQCeTyMstNv931421ERByDa0wrz1Wz5lepMeCqXeyiawqOxA9fB/
# 106liR12vL2tzGC62yXrV6WhD6W+s5PpfEY/chuIwVUYXp1AVFI9wi2lg0gaTgP/
# rMfP1wfVvaKWH2Bm/tU5mwpIVIO0wd4A+qOhEia3vn3J2Zz1QDxEprLcLE9e3Gmd
# G5+8xEypTR23NavhJvZMgY2kEXBEKEEDaXs0LoPbn6hMcepR2A4wggZqMIIFUqAD
# AgECAhADAZoCOv9YsWvW1ermF/BmMA0GCSqGSIb3DQEBBQUAMGIxCzAJBgNVBAYT
# AlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2Vy
# dC5jb20xITAfBgNVBAMTGERpZ2lDZXJ0IEFzc3VyZWQgSUQgQ0EtMTAeFw0xNDEw
# MjIwMDAwMDBaFw0yNDEwMjIwMDAwMDBaMEcxCzAJBgNVBAYTAlVTMREwDwYDVQQK
# EwhEaWdpQ2VydDElMCMGA1UEAxMcRGlnaUNlcnQgVGltZXN0YW1wIFJlc3BvbmRl
# cjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKNkXfx8s+CCNeDg9sYq
# 5kl1O8xu4FOpnx9kWeZ8a39rjJ1V+JLjntVaY1sCSVDZg85vZu7dy4XpX6X51Id0
# iEQ7Gcnl9ZGfxhQ5rCTqqEsskYnMXij0ZLZQt/USs3OWCmejvmGfrvP9Enh1DqZb
# FP1FI46GRFV9GIYFjFWHeUhG98oOjafeTl/iqLYtWQJhiGFyGGi5uHzu5uc0LzF3
# gTAfuzYBje8n4/ea8EwxZI3j6/oZh6h+z+yMDDZbesF6uHjHyQYuRhDIjegEYNu8
# c3T6Ttj+qkDxss5wRoPp2kChWTrZFQlXmVYwk/PJYczQCMxr7GJCkawCwO+k8IkR
# j3cCAwEAAaOCAzUwggMxMA4GA1UdDwEB/wQEAwIHgDAMBgNVHRMBAf8EAjAAMBYG
# A1UdJQEB/wQMMAoGCCsGAQUFBwMIMIIBvwYDVR0gBIIBtjCCAbIwggGhBglghkgB
# hv1sBwEwggGSMCgGCCsGAQUFBwIBFhxodHRwczovL3d3dy5kaWdpY2VydC5jb20v
# Q1BTMIIBZAYIKwYBBQUHAgIwggFWHoIBUgBBAG4AeQAgAHUAcwBlACAAbwBmACAA
# dABoAGkAcwAgAEMAZQByAHQAaQBmAGkAYwBhAHQAZQAgAGMAbwBuAHMAdABpAHQA
# dQB0AGUAcwAgAGEAYwBjAGUAcAB0AGEAbgBjAGUAIABvAGYAIAB0AGgAZQAgAEQA
# aQBnAGkAQwBlAHIAdAAgAEMAUAAvAEMAUABTACAAYQBuAGQAIAB0AGgAZQAgAFIA
# ZQBsAHkAaQBuAGcAIABQAGEAcgB0AHkAIABBAGcAcgBlAGUAbQBlAG4AdAAgAHcA
# aABpAGMAaAAgAGwAaQBtAGkAdAAgAGwAaQBhAGIAaQBsAGkAdAB5ACAAYQBuAGQA
# IABhAHIAZQAgAGkAbgBjAG8AcgBwAG8AcgBhAHQAZQBkACAAaABlAHIAZQBpAG4A
# IABiAHkAIAByAGUAZgBlAHIAZQBuAGMAZQAuMAsGCWCGSAGG/WwDFTAfBgNVHSME
# GDAWgBQVABIrE5iymQftHt+ivlcNK2cCzTAdBgNVHQ4EFgQUYVpNJLZJMp1KKnka
# g0v0HonByn0wfQYDVR0fBHYwdDA4oDagNIYyaHR0cDovL2NybDMuZGlnaWNlcnQu
# Y29tL0RpZ2lDZXJ0QXNzdXJlZElEQ0EtMS5jcmwwOKA2oDSGMmh0dHA6Ly9jcmw0
# LmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEFzc3VyZWRJRENBLTEuY3JsMHcGCCsGAQUF
# BwEBBGswaTAkBggrBgEFBQcwAYYYaHR0cDovL29jc3AuZGlnaWNlcnQuY29tMEEG
# CCsGAQUFBzAChjVodHRwOi8vY2FjZXJ0cy5kaWdpY2VydC5jb20vRGlnaUNlcnRB
# c3N1cmVkSURDQS0xLmNydDANBgkqhkiG9w0BAQUFAAOCAQEAnSV+GzNNsiaBXJuG
# ziMgD4CH5Yj//7HUaiwx7ToXGXEXzakbvFoWOQCd42yE5FpA+94GAYw3+puxnSR+
# /iCkV61bt5qwYCbqaVchXTQvH3Gwg5QZBWs1kBCge5fH9j/n4hFBpr1i2fAnPTgd
# KG86Ugnw7HBi02JLsOBzppLA044x2C/jbRcTBu7kA7YUq/OPQ6dxnSHdFMoVXZJB
# 2vkPgdGZdA0mxA5/G7X1oPHGdwYoFenYk+VVFvC7Cqsc21xIJ2bIo4sKHOWV2q7E
# LlmgYd3a822iYemKC23sEhi991VUQAOSK2vCUcIKSK+w1G7g9BQKOhvjjz3Kr2qN
# e9zYRDCCBs0wggW1oAMCAQICEAb9+QOWA63qAArrPye7uhswDQYJKoZIhvcNAQEF
# BQAwZTELMAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UE
# CxMQd3d3LmRpZ2ljZXJ0LmNvbTEkMCIGA1UEAxMbRGlnaUNlcnQgQXNzdXJlZCBJ
# RCBSb290IENBMB4XDTA2MTExMDAwMDAwMFoXDTIxMTExMDAwMDAwMFowYjELMAkG
# A1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3LmRp
# Z2ljZXJ0LmNvbTEhMB8GA1UEAxMYRGlnaUNlcnQgQXNzdXJlZCBJRCBDQS0xMIIB
# IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA6IItmfnKwkKVpYBzQHDSnlZU
# XKnE0kEGj8kz/E1FkVyBn+0snPgWWd+etSQVwpi5tHdJ3InECtqvy15r7a2wcTHr
# zzpADEZNk+yLejYIA6sMNP4YSYL+x8cxSIB8HqIPkg5QycaH6zY/2DDD/6b3+6LN
# b3Mj/qxWBZDwMiEWicZwiPkFl32jx0PdAug7Pe2xQaPtP77blUjE7h6z8rwMK5nQ
# xl0SQoHhg26Ccz8mSxSQrllmCsSNvtLOBq6thG9IhJtPQLnxTPKvmPv2zkBdXPao
# 8S+v7Iki8msYZbHBc63X8djPHgp0XEK4aH631XcKJ1Z8D2KkPzIUYJX9BwSiCQID
# AQABo4IDejCCA3YwDgYDVR0PAQH/BAQDAgGGMDsGA1UdJQQ0MDIGCCsGAQUFBwMB
# BggrBgEFBQcDAgYIKwYBBQUHAwMGCCsGAQUFBwMEBggrBgEFBQcDCDCCAdIGA1Ud
# IASCAckwggHFMIIBtAYKYIZIAYb9bAABBDCCAaQwOgYIKwYBBQUHAgEWLmh0dHA6
# Ly93d3cuZGlnaWNlcnQuY29tL3NzbC1jcHMtcmVwb3NpdG9yeS5odG0wggFkBggr
# BgEFBQcCAjCCAVYeggFSAEEAbgB5ACAAdQBzAGUAIABvAGYAIAB0AGgAaQBzACAA
# QwBlAHIAdABpAGYAaQBjAGEAdABlACAAYwBvAG4AcwB0AGkAdAB1AHQAZQBzACAA
# YQBjAGMAZQBwAHQAYQBuAGMAZQAgAG8AZgAgAHQAaABlACAARABpAGcAaQBDAGUA
# cgB0ACAAQwBQAC8AQwBQAFMAIABhAG4AZAAgAHQAaABlACAAUgBlAGwAeQBpAG4A
# ZwAgAFAAYQByAHQAeQAgAEEAZwByAGUAZQBtAGUAbgB0ACAAdwBoAGkAYwBoACAA
# bABpAG0AaQB0ACAAbABpAGEAYgBpAGwAaQB0AHkAIABhAG4AZAAgAGEAcgBlACAA
# aQBuAGMAbwByAHAAbwByAGEAdABlAGQAIABoAGUAcgBlAGkAbgAgAGIAeQAgAHIA
# ZQBmAGUAcgBlAG4AYwBlAC4wCwYJYIZIAYb9bAMVMBIGA1UdEwEB/wQIMAYBAf8C
# AQAweQYIKwYBBQUHAQEEbTBrMCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdp
# Y2VydC5jb20wQwYIKwYBBQUHMAKGN2h0dHA6Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNv
# bS9EaWdpQ2VydEFzc3VyZWRJRFJvb3RDQS5jcnQwgYEGA1UdHwR6MHgwOqA4oDaG
# NGh0dHA6Ly9jcmwzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEFzc3VyZWRJRFJvb3RD
# QS5jcmwwOqA4oDaGNGh0dHA6Ly9jcmw0LmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEFz
# c3VyZWRJRFJvb3RDQS5jcmwwHQYDVR0OBBYEFBUAEisTmLKZB+0e36K+Vw0rZwLN
# MB8GA1UdIwQYMBaAFEXroq/0ksuCMS1Ri6enIZ3zbcgPMA0GCSqGSIb3DQEBBQUA
# A4IBAQBGUD7Jtygkpzgdtlspr1LPUukxR6tWXHvVDQtBs+/sdR90OPKyXGGinJXD
# UOSCuSPRujqGcq04eKx1XRcXNHJHhZRW0eu7NoR3zCSl8wQZVann4+erYs37iy2Q
# wsDStZS9Xk+xBdIOPRqpFFumhjFiqKgz5Js5p8T1zh14dpQlc+Qqq8+cdkvtX8JL
# FuRLcEwAiR78xXm8TBJX/l/hHrwCXaj++wc4Tw3GXZG5D2dFzdaD7eeSDY2xaYxP
# +1ngIw/Sqq4AfO6cQg7PkdcntxbuD8O9fAqg7iwIVYUiuOsYGk38KiGtSTGDR5V3
# cdyxG0tLHBCcdxTBnU8vWpUIKRAmMYIEOzCCBDcCAQEwgYYwcjELMAkGA1UEBhMC
# VVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3LmRpZ2ljZXJ0
# LmNvbTExMC8GA1UEAxMoRGlnaUNlcnQgU0hBMiBBc3N1cmVkIElEIENvZGUgU2ln
# bmluZyBDQQIQBNXcH0jqydhSALrNmpsqpzAJBgUrDgMCGgUAoHgwGAYKKwYBBAGC
# NwIBDDEKMAigAoAAoQKAADAZBgkqhkiG9w0BCQMxDAYKKwYBBAGCNwIBBDAcBgor
# BgEEAYI3AgELMQ4wDAYKKwYBBAGCNwIBFTAjBgkqhkiG9w0BCQQxFgQUIQfwBdWG
# R6Z1tXOi/G/glim68aQwDQYJKoZIhvcNAQEBBQAEggEANJSXauixD72h8+vkZYtD
# k2v9NRzDfkwonHmUxiptyvNELUyCsUDZtn95n0NZKSc8bZTkbnRFezODO51UEM7Z
# GG7bLELwwTA5kvEn1cum+rEosPngoO8dT9kyKjEJnkrKRbtpc3oR3iiuwX/W/r2c
# 9zoKXLpEnJI3Ph6IjnPWvObZvQGyhFV215dvZCOWPUUbDRoe6dsjzHprTLBJoPvl
# SsXdjgPHD6wcJdDl3nWQfvlgtG+/xl5KOCugTdJbyCktLrcngKtbRufDtvFQW7Ns
# Hl/VxIM1IaLF/wlEFUTB4NOSncVgoa4UCZb/TMWmMKwRQS1lbadmM6PGU7EvtJ2W
# PaGCAg8wggILBgkqhkiG9w0BCQYxggH8MIIB+AIBATB2MGIxCzAJBgNVBAYTAlVT
# MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j
# b20xITAfBgNVBAMTGERpZ2lDZXJ0IEFzc3VyZWQgSUQgQ0EtMQIQAwGaAjr/WLFr
# 1tXq5hfwZjAJBgUrDgMCGgUAoF0wGAYJKoZIhvcNAQkDMQsGCSqGSIb3DQEHATAc
# BgkqhkiG9w0BCQUxDxcNMjAxMDAzMTYyMzM5WjAjBgkqhkiG9w0BCQQxFgQUzQxx
# BjCC/FcPtSmlOyIDQgSdziMwDQYJKoZIhvcNAQEBBQAEggEAPCVB2D7c5WYWo5UG
# U8Ad2hDzKI53eYtDx4XaxHl+OjskKVnLVS8RGLMs/wrNUiUx5eHPRUATAo23myD0
# 1hmsUV8CS0AmdEhXvzx5z/QXF/TtNEaLTXDYy3Hr24Su92wp7D84Wq+vFHc8LtOv
# Ko6XuT/BbXDQ8d7uBFDW5aw/bS5enW2TKloUkFyMJq2N1dGt0VMMp79eaRVG3Xez
# FUZ+Aft9tsPdTEk7HJncbIrLgAkEG+HLgrnoxS5/txglN4R7zrCbKeANi0OuvME8
# sUU2C2TRPiT1+4IbMDR2kq2gJ2POKhUzQg6TgMW5whYiaRtNAoDf7wZX72tQiys5
# BlfXCw==
# SIG # End signature block