PSBlackListChecker.psm1

function Format-FirstXChars { 
    <#
    .SYNOPSIS
    This function returns the first X characters of a given text string.
 
    .DESCRIPTION
    The Format-FirstXChars function takes a text string and a number of characters as input and returns the first X characters of the text string.
 
    .PARAMETER Text
    The input text string from which the first X characters will be extracted.
 
    .PARAMETER NumberChars
    The number of characters to extract from the beginning of the input text string.
 
    .EXAMPLE
    Format-FirstXChars -Text "VERBOSE: Loading module from path 'C:\Users\pklys\.vscode\extensions\ms-vs" -NumberChars 15
    # Returns: VERBOSE: Loading
 
    .NOTES
    This function is useful for truncating long text strings to a specific length.
    #>

    param(
        [string] $Text,
        [int] $NumberChars
    )
    return ($Text.ToCharArray() | Select-Object -First $NumberChars) -join ''
}
function New-Runspace { 
    <#
    .SYNOPSIS
    Creates a new runspace pool with the specified minimum and maximum runspaces.
 
    .DESCRIPTION
    This function creates a new runspace pool with the specified minimum and maximum runspaces. It allows for concurrent execution of PowerShell scripts.
 
    .PARAMETER minRunspaces
    The minimum number of runspaces to be created in the runspace pool. Default is 1.
 
    .PARAMETER maxRunspaces
    The maximum number of runspaces to be created in the runspace pool. Default is the number of processors plus 1.
 
    .EXAMPLE
    $pool = New-Runspace -minRunspaces 2 -maxRunspaces 5
    Creates a runspace pool with a minimum of 2 and a maximum of 5 runspaces.
 
    .EXAMPLE
    $pool = New-Runspace
    Creates a runspace pool with default minimum and maximum runspaces.
 
    #>

    [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 { 
    <#
    .SYNOPSIS
    Sends an email with specified parameters.
 
    .DESCRIPTION
    This function sends an email using the provided parameters. It supports sending emails with attachments and inline attachments.
 
    .PARAMETER Email
    Specifies the email parameters including sender, recipients, server settings, and encoding.
 
    .PARAMETER Body
    Specifies the body of the email.
 
    .PARAMETER Attachment
    Specifies an array of file paths to be attached to the email.
 
    .PARAMETER InlineAttachments
    Specifies a dictionary of inline attachments to be included in the email.
 
    .PARAMETER Subject
    Specifies the subject of the email.
 
    .PARAMETER To
    Specifies an array of email addresses to send the email to.
 
    .PARAMETER Logger
    Specifies a custom object for logging purposes.
 
    .EXAMPLE
    Send-Email -Email $EmailParams -Body "Hello, this is a test email" -Attachment "C:\Files\attachment.txt" -Subject "Test Email" -To "recipient@example.com" -Logger $Logger
 
    .EXAMPLE
    $EmailParams = @{
        From = "sender@example.com"
        To = "recipient@example.com"
        Subject = "Test Email"
        Body = "Hello, this is a test email"
        Server = "smtp.example.com"
        Port = 587
        Password = "password"
        Encoding = "UTF8"
    }
    Send-Email -Email $EmailParams -Attachment "C:\Files\attachment.txt" -To "recipient@example.com" -Logger $Logger
    #>

    [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', [System.StringComparison]::CurrentCultureIgnoreCase)) {
                    $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
            }
        } else {
            return [PSCustomObject] @{
                Status = $False
                Error  = 'Email not sent (WhatIf)'
                SentTo = $MailSentTo
            }
        }
    } catch {
        $MailMessage.Dispose();
        return [PSCustomObject] @{
            Status = $False
            Error  = $($_.Exception.Message)
            SentTo = ""
        }
    }
}
function Start-Runspace { 
    <#
    .SYNOPSIS
    Starts a new runspace with the provided script block, parameters, and runspace pool.
 
    .DESCRIPTION
    This function creates a new runspace using the specified script block, parameters, and runspace pool. It then starts the runspace and returns an object containing the runspace and its status.
 
    .PARAMETER ScriptBlock
    The script block to be executed in the new runspace.
 
    .PARAMETER Parameters
    The parameters to be passed to the script block.
 
    .PARAMETER RunspacePool
    The runspace pool in which the new runspace will be created.
 
    .EXAMPLE
    $scriptBlock = {
        Get-Process
    }
    $parameters = @{
        Name = 'explorer.exe'
    }
    $runspacePool = [RunspaceFactory]::CreateRunspacePool(1, 5)
    $runspacePool.Open()
    $result = Start-Runspace -ScriptBlock $scriptBlock -Parameters $parameters -RunspacePool $runspacePool
    $result.Pipe | Receive-Job -Wait
 
    This example starts a new runspace that retrieves information about the 'explorer.exe' process.
 
    #>

    [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 { 
    <#
    .SYNOPSIS
    Stops and cleans up the specified runspaces.
 
    .DESCRIPTION
    This function stops and cleans up the specified runspaces by checking their status and handling any errors, warnings, and verbose messages. It also provides an option for extended output.
 
    .PARAMETER Runspaces
    Specifies the array of runspaces to stop.
 
    .PARAMETER FunctionName
    Specifies the name of the function associated with the runspaces.
 
    .PARAMETER RunspacePool
    Specifies the runspace pool to close and dispose of.
 
    .PARAMETER ExtendedOutput
    Indicates whether to include extended output in the result.
 
    .EXAMPLE
    Stop-Runspace -Runspaces $runspaceArray -FunctionName "MyFunction" -RunspacePool $pool -ExtendedOutput
    Stops the specified runspaces in the $runspaceArray associated with the function "MyFunction" using the runspace pool $pool and includes extended output.
 
    #>

    [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 { 
    <#
    .SYNOPSIS
    Get-MimeType function returns the MIME type of a file based on its extension.
 
    .DESCRIPTION
    This function takes a file name as input and returns the corresponding MIME type based on the file extension.
 
    .PARAMETER FileName
    Specifies the name of the file for which the MIME type needs to be determined.
 
    .EXAMPLE
    Get-MimeType -FileName "example.jpg"
    Returns "image/jpeg" as the MIME type for the file "example.jpg".
 
    .EXAMPLE
    Get-MimeType -FileName "example.png"
    Returns "image/png" as the MIME type for the file "example.png".
    #>

    [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 { 
    <#
    .SYNOPSIS
    Requests credentials for authentication purposes.
 
    .DESCRIPTION
    The Request-Credentials function is used to prompt the user for credentials. It provides options to input the username and password directly, read the password from a file, convert the password to a secure string, and handle various error scenarios.
 
    .PARAMETER UserName
    Specifies the username for authentication.
 
    .PARAMETER Password
    Specifies the password for authentication.
 
    .PARAMETER AsSecure
    Indicates whether the password should be converted to a secure string.
 
    .PARAMETER FromFile
    Specifies whether the password should be read from a file.
 
    .PARAMETER Output
    Indicates whether the function should return output in case of errors.
 
    .PARAMETER NetworkCredentials
    Specifies if network credentials are being requested.
 
    .PARAMETER Service
    Specifies the service for which credentials are being requested.
 
    .EXAMPLE
    Request-Credentials -UserName 'JohnDoe' -Password 'P@ssw0rd' -AsSecure
    Requests credentials for the user 'JohnDoe' with the password 'P@ssw0rd' in a secure format.
 
    .EXAMPLE
    Request-Credentials -FromFile -Password 'C:\Credentials.txt' -Output -Service 'FTP'
    Reads the password from the file 'C:\Credentials.txt' and returns an error message if the file is unreadable for the FTP service.
 
    #>

    [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'

    'drone.abuse.ch'
    'drone.abuse.ch'
    'duinv.aupads.org'
    'dul.dnsbl.sorbs.net'
    'dul.ru'
    'dyna.spamrats.com'

    'images.rbl.msrbl.net'
    'ips.backscatterer.org'
    'ix.dnsbl.manitu.net'
    'korea.services.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'

    'spam.abuse.ch'

    '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'

    'wormrbl.imp.ch'
    'xbl.spamhaus.org'

)
$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) {

            if (Get-Module -ListAvailable -Name PSSlack -ErrorAction SilentlyContinue) {
                Import-Module -Name PSSlack -Force -ErrorAction SilentlyContinue
            } else {
                Write-Warning "PSSlack module not found. Please install it using Install-Module -Name PSSlack"
                return
            }

            $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 @() -Cmdlet "*"
# SIG # Begin signature block
# MIItsQYJKoZIhvcNAQcCoIItojCCLZ4CAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCCJIDmQvkWwAS+4
# qd3kI+rje2hI5iakW6TWUnyCJ3h0c6CCJrQwggWNMIIEdaADAgECAhAOmxiO+dAt
# 5+/bUOIIQBhaMA0GCSqGSIb3DQEBDAUAMGUxCzAJBgNVBAYTAlVTMRUwEwYDVQQK
# EwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xJDAiBgNV
# BAMTG0RpZ2lDZXJ0IEFzc3VyZWQgSUQgUm9vdCBDQTAeFw0yMjA4MDEwMDAwMDBa
# Fw0zMTExMDkyMzU5NTlaMGIxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2Vy
# dCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xITAfBgNVBAMTGERpZ2lD
# ZXJ0IFRydXN0ZWQgUm9vdCBHNDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoC
# ggIBAL/mkHNo3rvkXUo8MCIwaTPswqclLskhPfKK2FnC4SmnPVirdprNrnsbhA3E
# MB/zG6Q4FutWxpdtHauyefLKEdLkX9YFPFIPUh/GnhWlfr6fqVcWWVVyr2iTcMKy
# unWZanMylNEQRBAu34LzB4TmdDttceItDBvuINXJIB1jKS3O7F5OyJP4IWGbNOsF
# xl7sWxq868nPzaw0QF+xembud8hIqGZXV59UWI4MK7dPpzDZVu7Ke13jrclPXuU1
# 5zHL2pNe3I6PgNq2kZhAkHnDeMe2scS1ahg4AxCN2NQ3pC4FfYj1gj4QkXCrVYJB
# MtfbBHMqbpEBfCFM1LyuGwN1XXhm2ToxRJozQL8I11pJpMLmqaBn3aQnvKFPObUR
# WBf3JFxGj2T3wWmIdph2PVldQnaHiZdpekjw4KISG2aadMreSx7nDmOu5tTvkpI6
# nj3cAORFJYm2mkQZK37AlLTSYW3rM9nF30sEAMx9HJXDj/chsrIRt7t/8tWMcCxB
# YKqxYxhElRp2Yn72gLD76GSmM9GJB+G9t+ZDpBi4pncB4Q+UDCEdslQpJYls5Q5S
# UUd0viastkF13nqsX40/ybzTQRESW+UQUOsxxcpyFiIJ33xMdT9j7CFfxCBRa2+x
# q4aLT8LWRV+dIPyhHsXAj6KxfgommfXkaS+YHS312amyHeUbAgMBAAGjggE6MIIB
# NjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTs1+OC0nFdZEzfLmc/57qYrhwP
# TzAfBgNVHSMEGDAWgBRF66Kv9JLLgjEtUYunpyGd823IDzAOBgNVHQ8BAf8EBAMC
# AYYweQYIKwYBBQUHAQEEbTBrMCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdp
# Y2VydC5jb20wQwYIKwYBBQUHMAKGN2h0dHA6Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNv
# bS9EaWdpQ2VydEFzc3VyZWRJRFJvb3RDQS5jcnQwRQYDVR0fBD4wPDA6oDigNoY0
# aHR0cDovL2NybDMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0QXNzdXJlZElEUm9vdENB
# LmNybDARBgNVHSAECjAIMAYGBFUdIAAwDQYJKoZIhvcNAQEMBQADggEBAHCgv0Nc
# Vec4X6CjdBs9thbX979XB72arKGHLOyFXqkauyL4hxppVCLtpIh3bb0aFPQTSnov
# Lbc47/T/gLn4offyct4kvFIDyE7QKt76LVbP+fT3rDB6mouyXtTP0UNEm0Mh65Zy
# oUi0mcudT6cGAxN3J0TU53/oWajwvy8LpunyNDzs9wPHh6jSTEAZNUZqaVSwuKFW
# juyk1T3osdz9HNj0d1pcVIxv76FQPfx2CWiEn2/K2yCNNWAcAgPLILCsWKAOQGPF
# mCLBsln1VWvPJ6tsds5vIy30fnFqI2si/xK4VC0nftg62fC2h5b9W9FcrBjDTZ9z
# twGpn1eqXijiuZQwggWQMIIDeKADAgECAhAFmxtXno4hMuI5B72nd3VcMA0GCSqG
# SIb3DQEBDAUAMGIxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMx
# GTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xITAfBgNVBAMTGERpZ2lDZXJ0IFRy
# dXN0ZWQgUm9vdCBHNDAeFw0xMzA4MDExMjAwMDBaFw0zODAxMTUxMjAwMDBaMGIx
# CzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3
# dy5kaWdpY2VydC5jb20xITAfBgNVBAMTGERpZ2lDZXJ0IFRydXN0ZWQgUm9vdCBH
# NDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAL/mkHNo3rvkXUo8MCIw
# aTPswqclLskhPfKK2FnC4SmnPVirdprNrnsbhA3EMB/zG6Q4FutWxpdtHauyefLK
# EdLkX9YFPFIPUh/GnhWlfr6fqVcWWVVyr2iTcMKyunWZanMylNEQRBAu34LzB4Tm
# dDttceItDBvuINXJIB1jKS3O7F5OyJP4IWGbNOsFxl7sWxq868nPzaw0QF+xembu
# d8hIqGZXV59UWI4MK7dPpzDZVu7Ke13jrclPXuU15zHL2pNe3I6PgNq2kZhAkHnD
# eMe2scS1ahg4AxCN2NQ3pC4FfYj1gj4QkXCrVYJBMtfbBHMqbpEBfCFM1LyuGwN1
# XXhm2ToxRJozQL8I11pJpMLmqaBn3aQnvKFPObURWBf3JFxGj2T3wWmIdph2PVld
# QnaHiZdpekjw4KISG2aadMreSx7nDmOu5tTvkpI6nj3cAORFJYm2mkQZK37AlLTS
# YW3rM9nF30sEAMx9HJXDj/chsrIRt7t/8tWMcCxBYKqxYxhElRp2Yn72gLD76GSm
# M9GJB+G9t+ZDpBi4pncB4Q+UDCEdslQpJYls5Q5SUUd0viastkF13nqsX40/ybzT
# QRESW+UQUOsxxcpyFiIJ33xMdT9j7CFfxCBRa2+xq4aLT8LWRV+dIPyhHsXAj6Kx
# fgommfXkaS+YHS312amyHeUbAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYD
# VR0PAQH/BAQDAgGGMB0GA1UdDgQWBBTs1+OC0nFdZEzfLmc/57qYrhwPTzANBgkq
# hkiG9w0BAQwFAAOCAgEAu2HZfalsvhfEkRvDoaIAjeNkaA9Wz3eucPn9mkqZucl4
# XAwMX+TmFClWCzZJXURj4K2clhhmGyMNPXnpbWvWVPjSPMFDQK4dUPVS/JA7u5iZ
# aWvHwaeoaKQn3J35J64whbn2Z006Po9ZOSJTROvIXQPK7VB6fWIhCoDIc2bRoAVg
# X+iltKevqPdtNZx8WorWojiZ83iL9E3SIAveBO6Mm0eBcg3AFDLvMFkuruBx8lbk
# apdvklBtlo1oepqyNhR6BvIkuQkRUNcIsbiJeoQjYUIp5aPNoiBB19GcZNnqJqGL
# FNdMGbJQQXE9P01wI4YMStyB0swylIQNCAmXHE/A7msgdDDS4Dk0EIUhFQEI6FUy
# 3nFJ2SgXUE3mvk3RdazQyvtBuEOlqtPDBURPLDab4vriRbgjU2wGb2dVf0a1TD9u
# KFp5JtKkqGKX0h7i7UqLvBv9R0oN32dmfrJbQdA75PQ79ARj6e/CVABRoIoqyc54
# zNXqhwQYs86vSYiv85KZtrPmYQ/ShQDnUBrkG5WdGaG5nLGbsQAe79APT0JsyQq8
# 7kP6OnGlyE0mpTX9iV28hWIdMtKgK1TtmlfB2/oQzxm3i0objwG2J5VT6LaJbVu8
# aNQj6ItRolb58KaAoNYes7wPD1N1KarqE3fk3oyBIa0HEEcRrYc9B9F1vM/zZn4w
# ggauMIIElqADAgECAhAHNje3JFR82Ees/ShmKl5bMA0GCSqGSIb3DQEBCwUAMGIx
# CzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3
# dy5kaWdpY2VydC5jb20xITAfBgNVBAMTGERpZ2lDZXJ0IFRydXN0ZWQgUm9vdCBH
# NDAeFw0yMjAzMjMwMDAwMDBaFw0zNzAzMjIyMzU5NTlaMGMxCzAJBgNVBAYTAlVT
# MRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjE7MDkGA1UEAxMyRGlnaUNlcnQgVHJ1
# c3RlZCBHNCBSU0E0MDk2IFNIQTI1NiBUaW1lU3RhbXBpbmcgQ0EwggIiMA0GCSqG
# SIb3DQEBAQUAA4ICDwAwggIKAoICAQDGhjUGSbPBPXJJUVXHJQPE8pE3qZdRodbS
# g9GeTKJtoLDMg/la9hGhRBVCX6SI82j6ffOciQt/nR+eDzMfUBMLJnOWbfhXqAJ9
# /UO0hNoR8XOxs+4rgISKIhjf69o9xBd/qxkrPkLcZ47qUT3w1lbU5ygt69OxtXXn
# HwZljZQp09nsad/ZkIdGAHvbREGJ3HxqV3rwN3mfXazL6IRktFLydkf3YYMZ3V+0
# VAshaG43IbtArF+y3kp9zvU5EmfvDqVjbOSmxR3NNg1c1eYbqMFkdECnwHLFuk4f
# sbVYTXn+149zk6wsOeKlSNbwsDETqVcplicu9Yemj052FVUmcJgmf6AaRyBD40Nj
# gHt1biclkJg6OBGz9vae5jtb7IHeIhTZgirHkr+g3uM+onP65x9abJTyUpURK1h0
# QCirc0PO30qhHGs4xSnzyqqWc0Jon7ZGs506o9UD4L/wojzKQtwYSH8UNM/STKvv
# mz3+DrhkKvp1KCRB7UK/BZxmSVJQ9FHzNklNiyDSLFc1eSuo80VgvCONWPfcYd6T
# /jnA+bIwpUzX6ZhKWD7TA4j+s4/TXkt2ElGTyYwMO1uKIqjBJgj5FBASA31fI7tk
# 42PgpuE+9sJ0sj8eCXbsq11GdeJgo1gJASgADoRU7s7pXcheMBK9Rp6103a50g5r
# mQzSM7TNsQIDAQABo4IBXTCCAVkwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4E
# FgQUuhbZbU2FL3MpdpovdYxqII+eyG8wHwYDVR0jBBgwFoAU7NfjgtJxXWRM3y5n
# P+e6mK4cD08wDgYDVR0PAQH/BAQDAgGGMBMGA1UdJQQMMAoGCCsGAQUFBwMIMHcG
# CCsGAQUFBwEBBGswaTAkBggrBgEFBQcwAYYYaHR0cDovL29jc3AuZGlnaWNlcnQu
# Y29tMEEGCCsGAQUFBzAChjVodHRwOi8vY2FjZXJ0cy5kaWdpY2VydC5jb20vRGln
# aUNlcnRUcnVzdGVkUm9vdEc0LmNydDBDBgNVHR8EPDA6MDigNqA0hjJodHRwOi8v
# Y3JsMy5kaWdpY2VydC5jb20vRGlnaUNlcnRUcnVzdGVkUm9vdEc0LmNybDAgBgNV
# HSAEGTAXMAgGBmeBDAEEAjALBglghkgBhv1sBwEwDQYJKoZIhvcNAQELBQADggIB
# AH1ZjsCTtm+YqUQiAX5m1tghQuGwGC4QTRPPMFPOvxj7x1Bd4ksp+3CKDaopafxp
# wc8dB+k+YMjYC+VcW9dth/qEICU0MWfNthKWb8RQTGIdDAiCqBa9qVbPFXONASIl
# zpVpP0d3+3J0FNf/q0+KLHqrhc1DX+1gtqpPkWaeLJ7giqzl/Yy8ZCaHbJK9nXzQ
# cAp876i8dU+6WvepELJd6f8oVInw1YpxdmXazPByoyP6wCeCRK6ZJxurJB4mwbfe
# Kuv2nrF5mYGjVoarCkXJ38SNoOeY+/umnXKvxMfBwWpx2cYTgAnEtp/Nh4cku0+j
# Sbl3ZpHxcpzpSwJSpzd+k1OsOx0ISQ+UzTl63f8lY5knLD0/a6fxZsNBzU+2QJsh
# IUDQtxMkzdwdeDrknq3lNHGS1yZr5Dhzq6YBT70/O3itTK37xJV77QpfMzmHQXh6
# OOmc4d0j/R0o08f56PGYX/sr2H7yRp11LB4nLCbbbxV7HhmLNriT1ObyF5lZynDw
# N7+YAN8gFk8n+2BnFqFmut1VwDophrCYoCvtlUG3OtUVmDG0YgkPCr2B2RP+v6TR
# 81fZvAT6gt4y3wSJ8ADNXcL50CN/AAvkdgIm2fBldkKmKYcJRyvmfxqkhQ/8mJb2
# VVQrH4D6wPIOK+XW+6kvRBVK5xMOHds3OBqhK/bt1nz8MIIGsDCCBJigAwIBAgIQ
# CK1AsmDSnEyfXs2pvZOu2TANBgkqhkiG9w0BAQwFADBiMQswCQYDVQQGEwJVUzEV
# MBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29t
# MSEwHwYDVQQDExhEaWdpQ2VydCBUcnVzdGVkIFJvb3QgRzQwHhcNMjEwNDI5MDAw
# MDAwWhcNMzYwNDI4MjM1OTU5WjBpMQswCQYDVQQGEwJVUzEXMBUGA1UEChMORGln
# aUNlcnQsIEluYy4xQTA/BgNVBAMTOERpZ2lDZXJ0IFRydXN0ZWQgRzQgQ29kZSBT
# aWduaW5nIFJTQTQwOTYgU0hBMzg0IDIwMjEgQ0ExMIICIjANBgkqhkiG9w0BAQEF
# AAOCAg8AMIICCgKCAgEA1bQvQtAorXi3XdU5WRuxiEL1M4zrPYGXcMW7xIUmMJ+k
# jmjYXPXrNCQH4UtP03hD9BfXHtr50tVnGlJPDqFX/IiZwZHMgQM+TXAkZLON4gh9
# NH1MgFcSa0OamfLFOx/y78tHWhOmTLMBICXzENOLsvsI8IrgnQnAZaf6mIBJNYc9
# URnokCF4RS6hnyzhGMIazMXuk0lwQjKP+8bqHPNlaJGiTUyCEUhSaN4QvRRXXegY
# E2XFf7JPhSxIpFaENdb5LpyqABXRN/4aBpTCfMjqGzLmysL0p6MDDnSlrzm2q2AS
# 4+jWufcx4dyt5Big2MEjR0ezoQ9uo6ttmAaDG7dqZy3SvUQakhCBj7A7CdfHmzJa
# wv9qYFSLScGT7eG0XOBv6yb5jNWy+TgQ5urOkfW+0/tvk2E0XLyTRSiDNipmKF+w
# c86LJiUGsoPUXPYVGUztYuBeM/Lo6OwKp7ADK5GyNnm+960IHnWmZcy740hQ83eR
# Gv7bUKJGyGFYmPV8AhY8gyitOYbs1LcNU9D4R+Z1MI3sMJN2FKZbS110YU0/EpF2
# 3r9Yy3IQKUHw1cVtJnZoEUETWJrcJisB9IlNWdt4z4FKPkBHX8mBUHOFECMhWWCK
# ZFTBzCEa6DgZfGYczXg4RTCZT/9jT0y7qg0IU0F8WD1Hs/q27IwyCQLMbDwMVhEC
# AwEAAaOCAVkwggFVMBIGA1UdEwEB/wQIMAYBAf8CAQAwHQYDVR0OBBYEFGg34Ou2
# O/hfEYb7/mF7CIhl9E5CMB8GA1UdIwQYMBaAFOzX44LScV1kTN8uZz/nupiuHA9P
# MA4GA1UdDwEB/wQEAwIBhjATBgNVHSUEDDAKBggrBgEFBQcDAzB3BggrBgEFBQcB
# AQRrMGkwJAYIKwYBBQUHMAGGGGh0dHA6Ly9vY3NwLmRpZ2ljZXJ0LmNvbTBBBggr
# BgEFBQcwAoY1aHR0cDovL2NhY2VydHMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0VHJ1
# c3RlZFJvb3RHNC5jcnQwQwYDVR0fBDwwOjA4oDagNIYyaHR0cDovL2NybDMuZGln
# aWNlcnQuY29tL0RpZ2lDZXJ0VHJ1c3RlZFJvb3RHNC5jcmwwHAYDVR0gBBUwEzAH
# BgVngQwBAzAIBgZngQwBBAEwDQYJKoZIhvcNAQEMBQADggIBADojRD2NCHbuj7w6
# mdNW4AIapfhINPMstuZ0ZveUcrEAyq9sMCcTEp6QRJ9L/Z6jfCbVN7w6XUhtldU/
# SfQnuxaBRVD9nL22heB2fjdxyyL3WqqQz/WTauPrINHVUHmImoqKwba9oUgYftzY
# gBoRGRjNYZmBVvbJ43bnxOQbX0P4PpT/djk9ntSZz0rdKOtfJqGVWEjVGv7XJz/9
# kNF2ht0csGBc8w2o7uCJob054ThO2m67Np375SFTWsPK6Wrxoj7bQ7gzyE84FJKZ
# 9d3OVG3ZXQIUH0AzfAPilbLCIXVzUstG2MQ0HKKlS43Nb3Y3LIU/Gs4m6Ri+kAew
# Q3+ViCCCcPDMyu/9KTVcH4k4Vfc3iosJocsL6TEa/y4ZXDlx4b6cpwoG1iZnt5Lm
# Tl/eeqxJzy6kdJKt2zyknIYf48FWGysj/4+16oh7cGvmoLr9Oj9FpsToFpFSi0HA
# SIRLlk2rREDjjfAVKM7t8RhWByovEMQMCGQ8M4+uKIw8y4+ICw2/O/TOHnuO77Xr
# y7fwdxPm5yg/rBKupS8ibEH5glwVZsxsDsrFhsP2JjMMB0ug0wcCampAMEhLNKhR
# ILutG4UI4lkNbcoFUCvqShyepf2gpx8GdOfy1lKQ/a+FSCH5Vzu0nAPthkX0tGFu
# v2jiJmCG6sivqf6UHedjGzqGVnhOMIIGwjCCBKqgAwIBAgIQBUSv85SdCDmmv9s/
# X+VhFjANBgkqhkiG9w0BAQsFADBjMQswCQYDVQQGEwJVUzEXMBUGA1UEChMORGln
# aUNlcnQsIEluYy4xOzA5BgNVBAMTMkRpZ2lDZXJ0IFRydXN0ZWQgRzQgUlNBNDA5
# NiBTSEEyNTYgVGltZVN0YW1waW5nIENBMB4XDTIzMDcxNDAwMDAwMFoXDTM0MTAx
# MzIzNTk1OVowSDELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDkRpZ2lDZXJ0LCBJbmMu
# MSAwHgYDVQQDExdEaWdpQ2VydCBUaW1lc3RhbXAgMjAyMzCCAiIwDQYJKoZIhvcN
# AQEBBQADggIPADCCAgoCggIBAKNTRYcdg45brD5UsyPgz5/X5dLnXaEOCdwvSKOX
# ejsqnGfcYhVYwamTEafNqrJq3RApih5iY2nTWJw1cb86l+uUUI8cIOrHmjsvlmbj
# aedp/lvD1isgHMGXlLSlUIHyz8sHpjBoyoNC2vx/CSSUpIIa2mq62DvKXd4ZGIX7
# ReoNYWyd/nFexAaaPPDFLnkPG2ZS48jWPl/aQ9OE9dDH9kgtXkV1lnX+3RChG4PB
# uOZSlbVH13gpOWvgeFmX40QrStWVzu8IF+qCZE3/I+PKhu60pCFkcOvV5aDaY7Mu
# 6QXuqvYk9R28mxyyt1/f8O52fTGZZUdVnUokL6wrl76f5P17cz4y7lI0+9S769Sg
# LDSb495uZBkHNwGRDxy1Uc2qTGaDiGhiu7xBG3gZbeTZD+BYQfvYsSzhUa+0rRUG
# FOpiCBPTaR58ZE2dD9/O0V6MqqtQFcmzyrzXxDtoRKOlO0L9c33u3Qr/eTQQfqZc
# ClhMAD6FaXXHg2TWdc2PEnZWpST618RrIbroHzSYLzrqawGw9/sqhux7UjipmAmh
# cbJsca8+uG+W1eEQE/5hRwqM/vC2x9XH3mwk8L9CgsqgcT2ckpMEtGlwJw1Pt7U2
# 0clfCKRwo+wK8REuZODLIivK8SgTIUlRfgZm0zu++uuRONhRB8qUt+JQofM604qD
# y0B7AgMBAAGjggGLMIIBhzAOBgNVHQ8BAf8EBAMCB4AwDAYDVR0TAQH/BAIwADAW
# BgNVHSUBAf8EDDAKBggrBgEFBQcDCDAgBgNVHSAEGTAXMAgGBmeBDAEEAjALBglg
# hkgBhv1sBwEwHwYDVR0jBBgwFoAUuhbZbU2FL3MpdpovdYxqII+eyG8wHQYDVR0O
# BBYEFKW27xPn783QZKHVVqllMaPe1eNJMFoGA1UdHwRTMFEwT6BNoEuGSWh0dHA6
# Ly9jcmwzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydFRydXN0ZWRHNFJTQTQwOTZTSEEy
# NTZUaW1lU3RhbXBpbmdDQS5jcmwwgZAGCCsGAQUFBwEBBIGDMIGAMCQGCCsGAQUF
# BzABhhhodHRwOi8vb2NzcC5kaWdpY2VydC5jb20wWAYIKwYBBQUHMAKGTGh0dHA6
# Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydFRydXN0ZWRHNFJTQTQwOTZT
# SEEyNTZUaW1lU3RhbXBpbmdDQS5jcnQwDQYJKoZIhvcNAQELBQADggIBAIEa1t6g
# qbWYF7xwjU+KPGic2CX/yyzkzepdIpLsjCICqbjPgKjZ5+PF7SaCinEvGN1Ott5s
# 1+FgnCvt7T1IjrhrunxdvcJhN2hJd6PrkKoS1yeF844ektrCQDifXcigLiV4JZ0q
# BXqEKZi2V3mP2yZWK7Dzp703DNiYdk9WuVLCtp04qYHnbUFcjGnRuSvExnvPnPp4
# 4pMadqJpddNQ5EQSviANnqlE0PjlSXcIWiHFtM+YlRpUurm8wWkZus8W8oM3NG6w
# QSbd3lqXTzON1I13fXVFoaVYJmoDRd7ZULVQjK9WvUzF4UbFKNOt50MAcN7MmJ4Z
# iQPq1JE3701S88lgIcRWR+3aEUuMMsOI5ljitts++V+wQtaP4xeR0arAVeOGv6wn
# LEHQmjNKqDbUuXKWfpd5OEhfysLcPTLfddY2Z1qJ+Panx+VPNTwAvb6cKmx5Adza
# ROY63jg7B145WPR8czFVoIARyxQMfq68/qTreWWqaNYiyjvrmoI1VygWy2nyMpqy
# 0tg6uLFGhmu6F/3Ed2wVbK6rr3M66ElGt9V/zLY4wNjsHPW2obhDLN9OTH0eaHDA
# dwrUAuBcYLso/zjlUlrWrBciI0707NMX+1Br/wd3H3GXREHJuEbTbDJ8WC9nR2Xl
# G3O2mflrLAZG70Ee8PBf4NvZrZCARK+AEEGKMIIHXzCCBUegAwIBAgIQB8JSdCgU
# otar/iTqF+XdLjANBgkqhkiG9w0BAQsFADBpMQswCQYDVQQGEwJVUzEXMBUGA1UE
# ChMORGlnaUNlcnQsIEluYy4xQTA/BgNVBAMTOERpZ2lDZXJ0IFRydXN0ZWQgRzQg
# Q29kZSBTaWduaW5nIFJTQTQwOTYgU0hBMzg0IDIwMjEgQ0ExMB4XDTIzMDQxNjAw
# MDAwMFoXDTI2MDcwNjIzNTk1OVowZzELMAkGA1UEBhMCUEwxEjAQBgNVBAcMCU1p
# a2/FgsOzdzEhMB8GA1UECgwYUHJ6ZW15c8WCYXcgS8WCeXMgRVZPVEVDMSEwHwYD
# VQQDDBhQcnplbXlzxYJhdyBLxYJ5cyBFVk9URUMwggIiMA0GCSqGSIb3DQEBAQUA
# A4ICDwAwggIKAoICAQCUmgeXMQtIaKaSkKvbAt8GFZJ1ywOH8SwxlTus4McyrWmV
# OrRBVRQA8ApF9FaeobwmkZxvkxQTFLHKm+8knwomEUslca8CqSOI0YwELv5EwTVE
# h0C/Daehvxo6tkmNPF9/SP1KC3c0l1vO+M7vdNVGKQIQrhxq7EG0iezBZOAiukNd
# GVXRYOLn47V3qL5PwG/ou2alJ/vifIDad81qFb+QkUh02Jo24SMjWdKDytdrMXi0
# 235CN4RrW+8gjfRJ+fKKjgMImbuceCsi9Iv1a66bUc9anAemObT4mF5U/yQBgAuA
# o3+jVB8wiUd87kUQO0zJCF8vq2YrVOz8OJmMX8ggIsEEUZ3CZKD0hVc3dm7cWSAw
# 8/FNzGNPlAaIxzXX9qeD0EgaCLRkItA3t3eQW+IAXyS/9ZnnpFUoDvQGbK+Q4/bP
# 0ib98XLfQpxVGRu0cCV0Ng77DIkRF+IyR1PcwVAq+OzVU3vKeo25v/rntiXCmCxi
# W4oHYO28eSQ/eIAcnii+3uKDNZrI15P7VxDrkUIc6FtiSvOhwc3AzY+vEfivUkFK
# RqwvSSr4fCrrkk7z2Qe72Zwlw2EDRVHyy0fUVGO9QMuh6E3RwnJL96ip0alcmhKA
# BGoIqSW05nXdCUbkXmhPCTT5naQDuZ1UkAXbZPShKjbPwzdXP2b8I9nQ89VSgQID
# AQABo4ICAzCCAf8wHwYDVR0jBBgwFoAUaDfg67Y7+F8Rhvv+YXsIiGX0TkIwHQYD
# VR0OBBYEFHrxaiVZuDJxxEk15bLoMuFI5233MA4GA1UdDwEB/wQEAwIHgDATBgNV
# HSUEDDAKBggrBgEFBQcDAzCBtQYDVR0fBIGtMIGqMFOgUaBPhk1odHRwOi8vY3Js
# My5kaWdpY2VydC5jb20vRGlnaUNlcnRUcnVzdGVkRzRDb2RlU2lnbmluZ1JTQTQw
# OTZTSEEzODQyMDIxQ0ExLmNybDBToFGgT4ZNaHR0cDovL2NybDQuZGlnaWNlcnQu
# Y29tL0RpZ2lDZXJ0VHJ1c3RlZEc0Q29kZVNpZ25pbmdSU0E0MDk2U0hBMzg0MjAy
# MUNBMS5jcmwwPgYDVR0gBDcwNTAzBgZngQwBBAEwKTAnBggrBgEFBQcCARYbaHR0
# cDovL3d3dy5kaWdpY2VydC5jb20vQ1BTMIGUBggrBgEFBQcBAQSBhzCBhDAkBggr
# BgEFBQcwAYYYaHR0cDovL29jc3AuZGlnaWNlcnQuY29tMFwGCCsGAQUFBzAChlBo
# dHRwOi8vY2FjZXJ0cy5kaWdpY2VydC5jb20vRGlnaUNlcnRUcnVzdGVkRzRDb2Rl
# U2lnbmluZ1JTQTQwOTZTSEEzODQyMDIxQ0ExLmNydDAJBgNVHRMEAjAAMA0GCSqG
# SIb3DQEBCwUAA4ICAQC3EeHXUPhpe31K2DL43Hfh6qkvBHyR1RlD9lVIklcRCR50
# ZHzoWs6EBlTFyohvkpclVCuRdQW33tS6vtKPOucpDDv4wsA+6zkJYI8fHouW6Tqa
# 1W47YSrc5AOShIcJ9+NpNbKNGih3doSlcio2mUKCX5I/ZrzJBkQpJ0kYha/pUST2
# CbE3JroJf2vQWGUiI+J3LdiPNHmhO1l+zaQkSxv0cVDETMfQGZKKRVESZ6Fg61b0
# djvQSx510MdbxtKMjvS3ZtAytqnQHk1ipP+Rg+M5lFHrSkUlnpGa+f3nuQhxDb7N
# 9E8hUVevxALTrFifg8zhslVRH5/Df/CxlMKXC7op30/AyQsOQxHW1uNx3tG1DMgi
# zpwBasrxh6wa7iaA+Lp07q1I92eLhrYbtw3xC2vNIGdMdN7nd76yMIjdYnAn7r38
# wwtaJ3KYD0QTl77EB8u/5cCs3ShZdDdyg4K7NoJl8iEHrbqtooAHOMLiJpiL2i9Y
# n8kQMB6/Q6RMO3IUPLuycB9o6DNiwQHf6Jt5oW7P09k5NxxBEmksxwNbmZvNQ65Z
# n3exUAKqG+x31Egz5IZ4U/jPzRalElEIpS0rgrVg8R8pEOhd95mEzp5WERKFyXhe
# 6nB6bSYHv8clLAV0iMku308rpfjMiQkqS3LLzfUJ5OHqtKKQNMLxz9z185UCszGC
# BlMwggZPAgEBMH0waTELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDkRpZ2lDZXJ0LCBJ
# bmMuMUEwPwYDVQQDEzhEaWdpQ2VydCBUcnVzdGVkIEc0IENvZGUgU2lnbmluZyBS
# U0E0MDk2IFNIQTM4NCAyMDIxIENBMQIQB8JSdCgUotar/iTqF+XdLjANBglghkgB
# ZQMEAgEFAKCBhDAYBgorBgEEAYI3AgEMMQowCKACgAChAoAAMBkGCSqGSIb3DQEJ
# AzEMBgorBgEEAYI3AgEEMBwGCisGAQQBgjcCAQsxDjAMBgorBgEEAYI3AgEVMC8G
# CSqGSIb3DQEJBDEiBCDGsVz0qbnxr6eBzgncuDK3G6E3ugUUIoedXCQmNFTs2DAN
# BgkqhkiG9w0BAQEFAASCAgALBGKWIHinjw7rp4miU+CHIZ16L/Uv0eCC3lWG8etU
# CySmuFzAiOEXpkix4tLzfC1sQD3HaIKdwCEc+Ns4RXVQvu3e+fH2RjbIH0/KNNS4
# OEsuUG9s1JVanwZoSADS+tcNuNzLgXuQhaJP3wfz3kT7HxKbmy6n2Ijxk6BUiZQV
# wSoF2CQKV0Rqh2D7cGFnL/K2XH/Ws3dYFA/FaBzS8ogFLk70uRcgak/2ln/QjBTb
# VDRKJTaLfbYuTb3JdN41Jjw9CnF0ybzVrQ1yIVGjtN4Tis90BhDPutRiOWrNCPaT
# G7uFNX+FoI0ODjS/f/rzHHNyM9nvvci4iEhsRkLYMh6aqhrv+DeV7jsqHaXeD9uz
# CpyDvEhhl2eJQQXz4/ICv1QyE8SE/2VYItEib3uM64qcUaZ0WiV90NapzWyEBTuK
# gqvKdGM0m6U1MUN9h3xFgmb5afKJtuA2B7Mi9zxoU+5j7HAHoWV9uvjQ8ax6qgJ9
# TTUYIT54Vx/71qho2ACjyJeEpnAJ9OlGZ7RZemJXoFX1u4S2WMReTT05OUTS506Q
# KMWFeUUyBBBXDHP7ZrgsO/HzS9wgTObAeyvn1B2kCn9LQEJsag6oF1n6SItgvSHK
# 2+b1b0TZjB/d8rbytsIAy1G1p54tgMCvf9ECTe4OriqQfFlN0sBzH8ueb9SWVSPy
# sqGCAyAwggMcBgkqhkiG9w0BCQYxggMNMIIDCQIBATB3MGMxCzAJBgNVBAYTAlVT
# MRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjE7MDkGA1UEAxMyRGlnaUNlcnQgVHJ1
# c3RlZCBHNCBSU0E0MDk2IFNIQTI1NiBUaW1lU3RhbXBpbmcgQ0ECEAVEr/OUnQg5
# pr/bP1/lYRYwDQYJYIZIAWUDBAIBBQCgaTAYBgkqhkiG9w0BCQMxCwYJKoZIhvcN
# AQcBMBwGCSqGSIb3DQEJBTEPFw0yNDA3MjExOTU4MTZaMC8GCSqGSIb3DQEJBDEi
# BCCNdEfa8gVR5c51sBY+0fwb2BM6byBBch6j2yyXLxY1cjANBgkqhkiG9w0BAQEF
# AASCAgCQ7V/tRe4+NX9EZCiL6DSv+6TVv3fpZbdkdrQOCFzC+Xw2INODKSQH06Cl
# 52uFZXr4MN/LYDNScdyKBdW9CvzN8Kup1Zu/br+Tb6gNykdMf0MrJDNK3vBFdy8Q
# AIwRzo7o4o05NAGbVTGOd9iACMdI712wKCFro5nbkUV9qzSk0W5a0b0B2kYhY1s8
# D6kKsskqUAST2ONxFncd3LGL6xuhiHV+X6X8vDqnRtcvgdnv86CDOGJcq2tV05sz
# HJywFSM/CUypauS3YgWhQjOFz9qXevYRoz+ACVi/HoQL2ahUUKUm6qB0gnMPz860
# asvb8RWJdojZG8aBDYMbaCcy5KjccMG0cWdmnhAayE+5cF/WsI78sKmZq5dmrZdS
# 7XPWt+l3N2MiEOIQIddarWnTPf0RpTvmnltLR81dRzPo2/fT7E7NrDwAaBTJ8Mtz
# FbZLJq9IH/OcvkpOC+uD9zowesVsDikXrxekXRTUdaHglK87LCTr4ECSJ9O5q9Q7
# yISUnzT3p8RpbwWyTm+HCAnPrpoyCesS/jEF1UeU5/s7y9eCKkQApRn2zNX3eEuS
# G3+J2woHVgvCUhHRdISnOyvA7QRcOkpN4hdY+A2Xm8TeeHMlyyZs933Xo/t30ecC
# aN1RC1Ybe3LsYED6tCD3ed4xiMt0HBoUXK+cCqp4kpTNUIxJEg==
# SIG # End signature block