src/Office365DnsChecker.psm1

<#
Office365DnsChecker
Copyright (C) 2019-2023 Colin Cogle. All Rights Reserved.
 
This program is free software: you can redistribute it and/or modify it under
the terms of the GNU Affero General Public License as published by the Free
Software Foundation, either version 3 of the License, or (at your option) any
later version.
 
This program is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
 
You should have received a copy of the GNU Affero General Public License along
with this program. If not, see <https://www.gnu.org/licenses/>.
#>


#Requires -Version 5.1
#for automatic variables to detect the OS.

# Set strict mode.
Set-StrictMode -Version Latest

Function Test-Office365DNSRecords
{
    [CmdletBinding()]
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute(
        'PSUseSingularNouns', '',
        Justification='We are testing multiple DNS records.'
    )]
    Param(
        [Parameter(Mandatory, ValueFromPipeline)]
        [Alias('Name')]
        [ValidateNotNullOrEmpty()]
        [String[]] $DomainName,

        [Alias('China')]
        [Switch] $Use21Vianet
    )

    Process
    {
        $DomainName | ForEach-Object {
            Write-Output "Checking Office 365 DNS records for $_."
            Test-AzureADRecords -DomainName $_ -Use21Vianet:$Use21Vianet | Out-Null
            Test-ExchangeOnlineRecords -DomainName $_ | Out-Null
            Test-TeamsRecords -DomainName $_ | Out-Null
        }
    }
}

#region Helper cmdlets
Function Resolve-DNSNameCrossPlatform
{
    Param(
        [Parameter(Mandatory, Position=0, ValueFromPipeline)]
        [ValidateNotNullOrEmpty()]
        [String] $Name,

        [Parameter(Mandatory, Position=1)]
        [ValidateSet('CNAME', 'MX', 'SRV', 'TXT')]
        [String] $Type
    )

    Write-Verbose "Performing a DNS lookup for $Name ($Type)."

    # Check and see if the Resolve-DnsName cmdlet is available.
    # On Windows (Desktop and Core), it is available, and we can use it.
    If (Get-Command Resolve-DnsName -ErrorAction SilentlyContinue)
    {
        $dnsLookup = Resolve-DnsName -Name $Name -Type $Type -ErrorAction SilentlyContinue
        If (-Not $dnsLookup)
        {
            Write-Debug 'DNS record not found.'
            Return $null
        }

        Switch ($Type)
        {
            'CNAME' {
                # For whatever reason, CNAME lookups are returned as a [DnsRecord_PTR] type. Go figure.
                Return $dnsLookup | Where-Object {$_ -Is [Microsoft.DnsClient.Commands.DnsRecord_PTR]}
            }

            'MX' {
                Return $dnsLookup | Where-Object {$_ -Is [Microsoft.DnsClient.Commands.DnsRecord_MX]}
            }

            'SRV' {
                Return $dnsLookup | Where-Object {$_ -Is [Microsoft.DnsClient.Commands.DnsRecord_SRV]}
            }

            'TXT' {
                Return $dnsLookup | Where-Object {$_ -Is [Microsoft.DnsClient.Commands.DnsRecord_TXT]}
            }
        }
        Return 
    }
    # If Resolve-DnsName is not available, we need to use the system's copy of dig,
    # and try to emulate the style of output that Resolve-DnsName creates.
    Else
    {
        # Remove empty results.
        $dnsLookup = $(/usr/bin/dig -t $Type +short $Name) | Where-Object {$_ -ne ""}
        If (-Not $dnsLookup)
        {
            Write-Debug 'DNS record not found.'
        }

        # To mimic Resolve-DnsName, return results as custom objects with the same properties.
        # For brevity, I'm only implementing the types and members that this module will use.
        Switch ($Type)
        {
            'CNAME' {
                $CNAMEs = @()
                $dnsLookup | ForEach-Object {
                    Write-Debug "$Name is a CNAME for $_"
                    $CNAMEs += [PSCustomObject]@{
                        # dig always returns fully-qualified hostnames.
                        # Strip that trailing dot to return results like Resolve-DnsName does.
                        'NameHost' = $_ -Replace [RegEx]"\.$"
                    }
                }
                Return $CNAMEs
            }

            'MX' {
                $MXs = @()
                $dnsLookup | ForEach-Object {
                    $split = -Split $_
                    Write-Debug "$Name has the MX record $($split[1]) ($($split[0]))."
                    $MXs += [PSCustomObject]@{
                        # dig always returns fully-qualified hostnames.
                        # Strip that trailing dot to return results like Resolve-DnsName does.
                        'NameExchange' = $split[1] -Replace [RegEx]"\.$"
                        'Priority'     = $split[0] -As [Int]
                    }
                }
                Return $MXs | Sort-Object Priority
            }

            'SRV' {
                $SRVs = @()
                $dnsLookup | ForEach-Object {
                    $splits = -Split $_
                    
                    # dig always returns fully-qualified hostnames.
                    # Strip that trailing dot to return results like Resolve-DnsName does.
                    $NameTarget = $splits[3] -Replace [RegEx]"\.$" 
                    $Priority   = $splits[0] -As [Int]
                    $Weight     = $splits[1] -As [Int]
                    $Port       = $splits[2] -As [Int]
                    Write-Debug "$Name has a SRV record for ${NameTarget}:$Port (priority=$Priority, weight=$Weight)."

                    $SRVs += [PSCustomObject]@{
                        'Priority'   = $Priority
                        'Weight'     = $Weight
                        'Port'       = $Port
                        'NameTarget' = $NameTarget
                    }
                }
                Return $SRVs | Sort-Object Priority, Weight
            }

            'TXT' {
                $TXTs = @()
                $dnsLookup | ForEach-Object {
                    Write-Debug "$Name has the TXT record: $_"
                    $TXTs += [PSCustomObject]@{
                        # dig wraps TXT records in quotes.
                        # We need to remove those to emulate Resolve-DnsName.
                        'Strings' = ($_ -Replace "^`"" -Replace "`"$")
                    }
                }
                Return $TXTs
            }
        }
    }
}

Function Write-Success
{
    Param(
        [Parameter(Position=0)]
        [Alias('Object')]
        [String] $Message,

        [String] $Product
    )

    If ($Product)
    {
        Write-Host -ForegroundColor Green -Object "SUCCESS: ${Product}: $Message"
    }
    Else
    {
        Write-Host -ForegroundColor Green -Object "SUCCESS: $Message"
    }
    
}
#endregion Helper cmdlets

#region Azure AD cmdlets
Function Test-AzureADRecords
{
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute(
        'PSUseSingularNouns', '',
        Justification='We are testing multiple DNS records.'
    )]
    Param(
        [Parameter(Mandatory, ValueFromPipeline)]
        [Alias('Name')]
        [ValidateNotNullOrEmpty()]
        [String[]] $DomainName,

        [Alias('China')]
        [Switch] $Use21Vianet
    )

    Process
    {
        $DomainName | ForEach-Object {
            Test-AzureADClientConfigurationRecord -DomainName $_ -Use21Vianet:$Use21Vianet
            Test-AzureADEnterpriseEnrollmentRecord -DomainName $_
            Test-AzureADEnterpriseRegistrationRecord -DomainName $_
        }
    }
}

Function Test-AzureADClientConfigurationRecord
{
    [CmdletBinding()]
    Param(
        [Parameter(Mandatory, ValueFromPipeline)]
        [Alias('Name')]
        [ValidateNotNullOrEmpty()]
        [String[]] $DomainName,

        [Alias('China')]
        [Switch] $Use21Vianet
    )

    Begin
    {
        $shouldBe = 'clientconfig.microsoftonline-p.net'
        If ($Use21Vianet) {
            $shouldBe = 'clientconfig.partner.microsoftonline-p.net.cn'
        }
    }

    Process
    {
        $DomainName | ForEach-Object {
            Write-Output "Checking client configuration record for $_"

            $record = "msoid.$_"
            $dnsLookup = Resolve-DnsNameCrossPlatform -Type CNAME -Name $record

            # As of 2023, the msoid record is no longer required for Office 365
            # services outside of China. Thus, our code will no longer complain
            # if this DNS record is missing.
            If (-Not $dnsLookup -and -Not $Use21Vianet)
            {
                Write-Success -Product 'Azure AD' 'The client configuration CNAME record is not present.'
            }

            # However, if it exists and is not set to the default value of
            # clientconfig.microsoftonline-p.net, we will still complain.
            ElseIf ($dnsLookup.NameHost -ne $shouldBe)
            {
                $errorReport = @{
                    Message = 'The client configuration DNS record exists, but is not correct.'
                    Category = [System.Management.Automation.ErrorCategory]::InvalidData
                    CategoryReason = "The CNAME record $record was found, but points to the wrong target."
                    CategoryTargetName = $record
                    CategoryTargetType = 'CNAME'
                    ErrorID = 'MsoidCnameIncorrect'
                    RecommendedAction = "Change the CNAME record $record to point to $shouldBe."
                    TargetObject = $dnsLookup
                }
                Write-Error @errorReport
                Write-Information $errorReport.RecommendedAction
            }
            Else
            {
                If ($Use21Vianet) {
                    Write-Success -Product 'Azure AD' 'The client configuration CNAME record is present and correct for 21Vianet.'
                }
                Else {
                    Write-Success -Product 'Azure AD' 'The client configuration CNAME record is present, but correct.'
                }
            }
        }
    }
}

Function Test-AzureADEnterpriseEnrollmentRecord
{
    Param(
        [Parameter(Mandatory, ValueFromPipeline)]
        [Alias('Name')]
        [ValidateNotNullOrEmpty()]
        [String[]] $DomainName
    )

    Begin
    {
        $shouldBe = 'enterpriseenrollment.manage.microsoft.com'
    }

    Process {
        $DomainName | ForEach-Object {
            Write-Output "Checking AAD enterprise enrollment record for $_"
            $record = "enterpriseenrollment.$_"
            $dnsLookup = Resolve-DnsNameCrossPlatform -Type CNAME -Name $record

            If (-Not $dnsLookup)
            {
                $errorReport = @{
                    Message = 'The enterprise enrollment DNS record is missing.'
                    Category = [System.Management.Automation.ErrorCategory]::ObjectNotFound
                    CategoryReason = "The DNS record $record was not found."
                    CategoryTargetName = $record
                    CategoryTargetType = 'CNAME'
                    ErrorID = 'EnterpriseEnrollmentCnameMissing'
                    RecommendedAction = "Create a CNAME record for $record, pointing to $shouldBe."
                    TargetObject = $dnsLookup
                }
                Write-Error @errorReport
                Write-Information $errorReport.RecommendedAction
            }
            ElseIf ($dnsLookup.NameHost -ne $shouldBe)
            {
                $errorReport = @{
                    Message = "The enterprise enrollment DNS record exists, but is not correct."
                    Category = [System.Management.Automation.ErrorCategory]::InvalidData
                    CategoryReason = "The DNS record $record was found, but not correct."
                    CategoryTargetName = $record
                    CategoryTargetType = 'CNAME'
                    ErrorID = 'EnterpriseEnrollmentCnameIncorrect'
                    RecommendedAction = "Change the CNAME record $record to point to $shouldBe."
                    TargetObject = $dnsLookup
                }
                Write-Error @errorReport
                Write-Information $errorReport.RecommendedAction
            }
            Else
            {
                Write-Success -Product 'Azure AD' 'The enterprise enrollment DNS record is correct.'
            }
        }
    }
}

Function Test-AzureADEnterpriseRegistrationRecord
{
    Param(
        [Parameter(Mandatory, ValueFromPipeline)]
        [Alias('Name')]
        [ValidateNotNullOrEmpty()]
        [String[]] $DomainName
    )

    Begin
    {
        $shouldBe = 'enterpriseregistration.windows.net'
    }

    Process
    {
        $DomainName | ForEach-Object {
            Write-Output "Checking AAD enterprise registration record for $_"

            $record = "enterpriseregistration.$_"
            $dnsLookup = Resolve-DnsNameCrossPlatform -Type CNAME -Name $record

            If (-Not $dnsLookup)
            {
                $errorReport = @{
                    Message = 'The enterprise registration DNS record is missing.'
                    Category = [System.Management.Automation.ErrorCategory]::ObjectNotFound
                    CategoryReason = "The CNAME record $record does not exist."
                    CategoryTargetName = $record
                    CategoryTargetType = 'CNAME'
                    ErrorID = 'EnterpriseRegistrationCnameMissing'
                    RecommendedAction = "Create a CNAME record for $record, pointing to $shouldBe."
                    TargetObject = $dnsLookup
                }
                Write-Error @errorReport
                Write-Information $errorReport.RecommendedAction
            }
            ElseIf ($dnsLookup.NameHost -ne $shouldBe)
            {
                $errorReport = @{
                    Message = "The enterprise registration DNS record exists, but is not correct."
                    Category = [System.Management.Automation.ErrorCategory]::InvalidData
                    CategoryReason = "The CNAME record $record exists, but does not point to the correct target."
                    CategoryTargetName = $record
                    CategoryTargetType = 'CNAME'
                    ErrorID = 'EnterpriseRegistrationCnameIncorrect'
                    RecommendedAction = "Change the CNAME record $record to point to $shouldBe."
                    TargetObject = $dnsLookup
                }
                Write-Error @errorReport
                Write-Information $errorReport.RecommendedAction
            }
            Else
            {
                Write-Success -Product 'Azure AD' 'The enterprise registration DNS record is correct.'
            }
        }
    }
}
#endregion Azure AD cmdlets

#region Exchange Online cmdlets
Function Test-ExchangeOnlineRecords
{
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute(
        'PSUseSingularNouns', '',
        Justification='We are testing multiple DNS records.'
    )]
    Param(
        [Parameter(Mandatory, ValueFromPipeline)]
        [Alias('Name')]
        [ValidateNotNullOrEmpty()]
        [String[]] $DomainName,

        [Switch] $GroupByRecord
    )

    Process
    {
        $DomainName | ForEach-Object {
            Test-ExchangeOnlineMxRecord -DomainName $_
            Test-ExchangeOnlineAutodiscoverRecord -DomainName $_
            Test-ExchangeOnlineSpfRecord -DomainName $_
            Test-ExchangeOnlineSenderIdRecord -DomainName $_
            Test-ExchangeOnlineDkimRecords -DomainName $_
        }
    }
}

Function Test-ExchangeOnlineAutodiscoverRecord
{
    Param(
        [Parameter(Mandatory, ValueFromPipeline)]
        [Alias('Name')]
        [ValidateNotNullOrEmpty()]
        [String] $DomainName
    )

    Begin
    {
        $shouldBe  = 'autodiscover.outlook.com'
    }

    Process
    {
        $DomainName | ForEach-Object {
            Write-Output "Checking Exchange Autodiscover records for $_"

            $record    = "autodiscover.$_"
            $dnsLookup = Resolve-DNSNameCrossPlatform -Type CNAME -Name $record
            If (-Not $dnsLookup)
            {
                $errorReport = @{
                    Message = "The Autodiscover DNS CNAME record is missing."
                    Category = [System.Management.Automation.ErrorCategory]::ObjectNotFound
                    CategoryReason = "The CNAME record $record does not exist."
                    CategoryTargetName = $record
                    CategoryTargetType = 'CNAME'
                    ErrorID = 'AutodiscoverCnameMissing'
                    RecommendedAction = "Create a CNAME record for $record, pointing to $shouldBe."
                    TargetObject = $dnsLookup
                }
                Write-Error @errorReport
                Write-Information $errorReport.RecommendedAction
            }
            ElseIf ($dnsLookup.NameHost -ne $shouldBe)
            {
                $errorReport = @{
                    Message = "The Autodiscover DNS CNAME record exists, but is not correct."
                    Category = [System.Management.Automation.ErrorCategory]::InvalidData
                    CategoryReason = "The CNAME record $record was found, but points to the wrong target."
                    CategoryTargetName = $record
                    CategoryTargetType = 'CNAME'
                    ErrorID = 'AutodiscoverCnameMissing'
                    RecommendedAction = "Change the CNAME record $record to point to $shouldBe."
                    TargetObject = $dnsLookup
                }
                Write-Error @errorReport
                Write-Information $errorReport.RecommendedAction
            }
            Else
            {
                Write-Success -Product 'Exchange Online' 'The Autodiscover DNS CNAME record is correct.'
            }

            $record = "_autodiscover._tcp.$_"
            $dnsLookup = Resolve-DnsNameCrossPlatform -Type SRV -Name $record
            If ($dnsLookup)
            {
                $errorReport = @{
                    Message = "One or more Autodiscover DNS SRV records exist."
                    Category = [System.Management.Automation.ErrorCategory]::ResourceExists
                    CategoryReason = "The SRV record $record was found, but should not exist. They are not compatible with Exchange Online."
                    CategoryTargetName = $record
                    CategoryTargetType = 'SRV'
                    ErrorID = 'AutodiscoverSrvExists'
                    RecommendedAction = "Delete all SRV records for $record."
                    TargetObject = $dnsLookup
                }
                Write-Error @errorReport
                Write-Information $errorReport.RecommendedAction
            }
            Else
            {
                Write-Success -Product 'Exchange Online' -Message 'An Autodiscover DNS SRV record does not exist.'
            }
        }
    }
}

Function Test-ExchangeOnlineDkimRecords
{
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute(
        'PSUseSingularNouns', '',
        Justification='We are testing multiple DNS records.'
    )]
    Param(
        [Parameter(Mandatory, ValueFromPipeline)]
        [Alias('Name')]
        [ValidateNotNullOrEmpty()]
        [String[]] $DomainName,

        [ValidateNotNullOrEmpty()]
        [ValidateSet(1,2)]
        [Int[]] $Selectors = @(1,2)
    )

    Process
    {
        $DomainName | ForEach-Object {
            Write-Output "Checking Exchange Online DKIM records for $_"
            $domain = $_
            ForEach ($i in $Selectors)
            {
                $record         = "selector$i._domainkey.$domain"
                $dnsCnameLookup = Resolve-DnsNameCrossPlatform -Type CNAME -Name $record
                $shouldBeLike   = "selector$i-*._domainkey.*.onmicrosoft.com"
                If (-Not $dnsCnameLookup)
                {
                    $errorReport = @{
                        Message = "The DKIM CNAME record for selector$i is missing."
                        Category = [System.Management.Automation.ErrorCategory]::ObjectNotFound
                        CategoryReason = "The DNS record $record was not found."
                        CategoryTargetName = $record
                        CategoryTargetType = 'CNAME'
                        ErrorID = "DkimSelector${i}CnameMissing"
                        RecommendedAction = "Create a CNAME record for $record. Look in the Exchange Admin Center to find the target."
                        TargetObject = $dnsCnameLookup
                    }
                    Write-Error @errorReport
                    Write-Information $errorReport.RecommendedAction
                }
                ElseIf ($dnsCnameLookup.NameHost -NotLike $shouldBeLike)
                {
                    Write-Output $dnsCnameLookup
                    $errorReport = @{
                        Message = "The DKIM CNAME record for selector$i exists, but is incorrect."
                        Category = [System.Management.Automation.ErrorCategory]::InvalidData
                        CategoryReason = "The DNS record $record was found, but incorrect."
                        CategoryTargetName = $record
                        CategoryTargetType = 'CNAME'
                        ErrorID = "DkimSelector${i}CnameIncorrect"
                        RecommendedAction = "Change the CNAME record $record to point to the correct name, usually `"contoso-com._domainkey.contoso.onmicrosoft.com`". Look in the Exchange Admin Center to find the exact target."
                        TargetObject = $dnsCnameLookup
                    }
                    Write-Error @errorReport
                    Write-Information $errorReport.RecommendedAction
                }
                Else
                {
                    $dnsTxtLookup  = Resolve-DnsNameCrossPlatform -Type TXT -Name $record
                    $dnsTxtRecords = $null

                    # Wrapping this in a null check to avoid strict mode warnings.
                    If ($null -ne $dnsTxtLookup)
                    {
                        $dnsTxtRecords = $dnsTxtLookup | Where-Object {$_.Strings -NotMatch $shouldBeLike}
                    }

                    If (-Not $dnsTxtLookup)
                    {
                        $errorReport = @{
                            Message = "The DKIM CNAME record exists, but the TXT record for selector$i is missing."
                            Category = [System.Management.Automation.ErrorCategory]::ObjectNotFound
                            CategoryReason = "The DNS TXT record $record was not found."
                            CategoryTargetName = $record
                            CategoryTargetType = 'TXT'
                            ErrorID = "DkimSelector${i}TxtMissing"
                            RecommendedAction = 'Use the Exchange Admin Center or New-DkimSigningConfig to generate a new key pair.'
                            TargetObject = $dnsTxtLookup
                        }
                        Write-Error @errorReport
                        Write-Information $errorReport.RecommendedAction
                        Continue
                    }

                    If (($dnsTxtRecords | Measure-Object).Count -gt 1)
                    {
                        If ($dnsTxtRecords[0] -Match $DomainName)
                        {
                            $dnsTxtRecords[0].Strings = $dnsTxtRecords[1].Strings
                        }
                        Else
                        {
                            $errorReport = @{
                                Message = "Multiple DKIM TXT records for selector$i were found."
                                Category = [System.Management.Automation.ErrorCategory]::InvalidData
                                CategoryReason = "The DNS TXT record that $record points to was found, but returned multiple records."
                                CategoryTargetName = $record
                                CategoryTargetType = 'TXT'
                                ErrorID = "DkimSelector${i}TxtTooManyResults"
                                RecommendedAction = 'Try regenerating the DKIM key.'
                                TargetObject = $dnsTxtLookup
                            }
                            Write-Error @errorReport
                            Write-Information $errorReport.RecommendedAction
                            Continue
                        }
                    }

                    # Sometimes, the TXT record will wrap into two records, depending on the DNS server.
                    # Check them both, just to be sure.
                    If ($dnsTxtRecords[0].Strings -NotLike 'v=DKIM1;*' -and $dnsTxtRecords[0].Strings[0] -NotLike 'v=DKIM1;*')
                    {
                        $errorReport = @{
                            Message = "The DKIM TXT record for selector$i is not a valid key."
                            Category = [System.Management.Automation.ErrorCategory]::InvalidData
                            CategoryReason = "The DNS TXT record that $record points to was found, but is not a valid DKIM key."
                            CategoryTargetName = $record
                            CategoryTargetType = 'TXT'
                            ErrorID = "DkimSelector${i}TxtIncorrect"
                            RecommendedAction = 'Regenerate the DKIM key.'
                            TargetObject = $dnsTxtLookup
                        }
                        Write-Error @errorReport
                        Write-Information $errorReport.RecommendedAction
                        Continue
                    }

                    Write-Success -Product 'Exchange Online' "The DKIM key selector$i appears to be correct."
                }
            }
        }
    }
}

Function Test-ExchangeOnlineMxRecord
{
    Param(
        [Parameter(Mandatory, ValueFromPipeline)]
        [Alias('Name')]
        [ValidateNotNullOrEmpty()]
        [String[]] $DomainName
    )

    Process
    {
        $DomainName | ForEach-Object {
            Write-Output "Checking MX records for $_"
            $dnsLookup = $null   # this is to not upset StrictMode.
            $dnsLookup = Resolve-DnsNameCrossPlatform -Type 'MX' $_

            If (-Not $dnsLookup)
            {
                $errorReport = @{
                    Message = "No MX records were found for the domain $_."
                    Category = [System.Management.Automation.ErrorCategory]::ObjectNotFound
                    CategoryReason = "The MX record for $_ was not found."
                    CategoryTargetName = $_
                    CategoryTargetType = 'MX'
                    ErrorID = 'MxRecordMissing'
                    RecommendedAction = "Add an MX record for $_. The mail exchanger should be the value you see in the Microsoft 365 Admin Center (usually `"contoso-com.mail.protection.outlook.com`")."
                    TargetObject = $dnsLookup
                }
                Write-Error @errorReport
                Write-Information $errorReport.RecommendedAction
            }
            ElseIf (($dnsLookup | Measure-Object | Select-Object -ExpandProperty Count) -eq 1)
            {
                Write-Success -Product 'Exchange Online' "Exactly one MX record was found for the domain $_."
            }
            Else
            {
                Write-Warning "More than one MX record was found for the domain $_."
                $dnsLookup | ForEach-Object {Write-Debug $_}
            }

            If ($null -ne $dnsLookup)
            {
                If ($dnsLookup[0].NameExchange -Like '*.mail.protection.outlook.com')
                {
                Write-Success -Product 'Exchange Online' "The first MX record for the domain $_ appears correct."
                }
                Else
                {
                    Write-Warning "The first MX record for the doamin $_ does not appear to be correct. If you are using a third-party spam filter, this is normal."
                }
            }
        }
    }
}

Function Test-ExchangeOnlineSenderIdRecord
{
    Param(
        [Parameter(Mandatory, ValueFromPipeline)]
        [Alias('Name')]
        [ValidateNotNullOrEmpty()]
        [String[]] $DomainName
    )
    Test-ExchangeOnlineSpfRecord -DomainName $DomainName -SpfOrSenderID 'Sender ID'
}

Function Test-ExchangeOnlineSpfRecord
{
    [CmdletBinding()]
    Param(
        [Parameter(Mandatory, ValueFromPipeline)]
        [Alias('Name')]
        [ValidateNotNullOrEmpty()]
        [String[]] $DomainName,

        [ValidateSet('SPF', 'Sender ID')]
        [String] $SpfOrSenderID = 'SPF'
    )

    Begin
    {
        If ($SpfOrSenderID -eq 'SPF')
        {
            $ErrorCode = 'SPF'
        }
        Else
        {
            $ErrorCode = 'SenderId'
        }
    }

    Process
    {
        $DomainName | ForEach-Object {
            Write-Output "Performing $SpfOrSenderId checks for $_."

            $dnsLookup = Resolve-DnsNameCrossPlatform -Type TXT -Name $_ | Where-Object {
                # As per the RFC's, all SPF/Sender ID tokens are case-insensitive.
                ($_.Strings -Like 'v=spf1 *' -and $SpfOrSenderId -Eq 'SPF') -or `
                ($_.Strings -Like 'spf2.0/*' -and $SpfOrSenderId -Eq 'Sender ID')
            }

            If (-Not $dnsLookup) {
                # Sender ID records are pretty much non-existent.
                # If we don't find one, don't bother showing an error.
                If ($SpfOrSenderID -eq 'Sender ID')
                {
                    Write-Verbose 'No Sender ID record was found. This is fine.'
                }
                Else
                {
                    $errorReport = @{
                        Message = 'No SPF TXT record was found.'
                        Category = [System.Management.Automation.ErrorCategory]::ObjectNotFound
                        CategoryReason = "No SPF TXT record could be found for the domain $_."
                        CategoryTargetName = $_
                        CategoryTargetType = 'TXT'
                        ErrorID = 'SpfRecordMissing'
                        RecommendedAction = "Create an SPF TXT record for $_."
                        TargetObject = $dnsLookup
                    }
                    Write-Error @errorReport
                    Write-Information $errorReport.RecommendedAction
                }
            }
            ElseIf (($dnsLookup | Measure-Object | Select-Object -ExpandProperty Count) -gt 1)
            {
                Write-Warning "More than one $SpfOrSenderID record was found. Anti-spam filters' behavior may not be as expected."
                Write-Verbose "Only the first $SpfOrSenderID record will be evaluated."
            }
            Else
            {
                Write-Success -Product 'Exchange Online' "Exactly one $SpfOrSenderID record was found."

                $tokens = -Split ($dnsLookup[0].Strings)
                $correctToken  = 'include:spf.protection.outlook.com'

                If ($correctToken -In $tokens -or "+$correctToken" -In $tokens)
                {
                    Write-Success -Product 'Exchange Online' "The correct $SpfOrSenderID passing token was found"
                }
                ElseIf ("-$correctToken" -In $Tokens)
                {
                    $errorReport = @{
                        Message = "The $SpfOrSenderID token was found, but marked as a hard failure."
                        Category = [System.Management.Automation.ErrorCategory]::InvalidData
                        CategoryReason = "The token -$correctToken was found in the $SpfOrSenderID record, which fails all mail from Exchange Online."
                        CategoryTargetName = "-$correctToken"
                        CategoryTargetType = "${ErrorCode}Token"
                        ErrorID = "${ErrorCode}RecordExchangeOnlineSetToHardFail"
                        RecommendedAction = "In the $SpfOrSenderID record, remove the leading '-' before $correctToken."
                        TargetObject = $dnsLookup
                    }
                    Write-Error @errorReport
                    Write-Information $errorReport.RecommendedAction
                }
                ElseIf ("~$correctToken" -In $tokens)
                {
                    $errorReport = @{
                        Message = "The $SpfOrSenderID token was found, but marked as a soft failure."
                        Category = [System.Management.Automation.ErrorCategory]::InvalidData
                        CategoryReason = "The token ~$correctToken was found in the $SpfOrSenderID record, which may fail all mail from Exchange Online."
                        CategoryTargetName = "~$correctToken"
                        CategoryTargetType = "${ErrorCode}Token"
                        ErrorID = "${ErrorCode}RecordExchangeOnlineSetToSoftFail"
                        RecommendedAction = "In the $SpfOrSenderID record, remove the leading '~' before $correctToken."
                        TargetObject = $dnsLookup
                    }
                    Write-Error @errorReport
                    Write-Information $errorReport.RecommendedAction
                }
                ElseIf ("-$correctToken" -In $tokens)
                {
                    $errorReport = @{
                        Message = "The $SpfOrSenderID token was found, but marked as neutral."
                        Category = [System.Management.Automation.ErrorCategory]::InvalidData
                        CategoryReason = "The token ?$correctToken was found in the $SpfOrSenderID record, which makes no assertion about mail from Exchange Online."
                        CategoryTargetName = "?$correctToken"
                        CategoryTargetType = "${ErrorCode}Token"
                        ErrorID = "${ErrorCode}RecordExchangeOnlineSetToNeutral"
                        RecommendedAction = "In the $SpfOrSenderID record, remove the leading '?' before $correctToken."
                        TargetObject = $dnsLookup
                    }
                    Write-Error @errorReport
                    Write-Information $errorReport.RecommendedAction
                }
                Else
                {
                    $errorReport = @{
                        Message = "The $SpfOrSenderID record was found, but is missing the token for Exchange Online."
                        Category = [System.Management.Automation.ErrorCategory]::ObjectNotFound
                        CategoryReason = "The token $correctToken was not found in the SPF record."
                        CategoryTargetName = $_
                        CategoryTargetType = 'TXT'
                        ErrorID = "${ErrorCode}RecordExchangeOnlineMissing"
                        RecommendedAction = "Add the token $correctToken to the $SpfOrSenderID record."
                        TargetObject = $dnsLookup
                    }
                    Write-Error @errorReport
                    Write-Information $errorReport.RecommendedAction
                }
            }
        }
    }
}
#endregion Exchange Online cmdlets

#region Teams/Skype for Business Online cmdlets
Function Test-TeamsRecords
{
    [CmdletBinding()]
    [Alias(
        'Test-LyncRecords',
        'Test-SkypeForBusinessRecords',
        'Test-SkypeForBusinessOnlineRecords'
    )]
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute(
        'PSUseSingularNouns', '',
        Justification='We are testing multiple DNS records.'
    )]
    Param(
        [Parameter(Mandatory, ValueFromPipeline)]
        [Alias('Name')]
        [ValidateNotNullOrEmpty()]
        [String[]] $DomainName
    )

    Process {
        $DomainName | ForEach-Object {
            Test-TeamsAutodiscoverRecord -DomainName $_
            Test-TeamsSipCnameRecord -DomainName $_
            Test-TeamsSipSrvRecord -DomainName $_
            Test-TeamsSipFederationSrvRecord -DomainName $_
        }
    }
}

Function Test-TeamsAutodiscoverRecord
{
    [Alias(
        'Test-LyncDiscoverRecord',
        'Test-LyncAutodiscoverRecord',
        'Test-SkypeForBusinessAutodiscoverRecord',
        'Test-SkypeForBusinessOnlineAutodiscoverRecord'
    )]
    Param(
        [Parameter(Mandatory, ValueFromPipeline)]
        [Alias('Name')]
        [ValidateNotNullOrEmpty()]
        [String[]] $DomainName
    )
    
    Begin
    {
        $shouldBe = 'webdir.online.lync.com'
    }

    Process
    {
        $DomainName | ForEach-Object {
            Write-Output "Verifying the Teams/Skype autodiscover record for $_"

            $dnsLookup = Resolve-DnsNameCrossPlatform -Type CNAME -Name "lyncdiscover.$_" -ErrorAction SilentlyContinue
            If (-Not $dnsLookup)
            {
                $errorReport = @{
                    Message = 'The Skype/Teams Autodiscover DNS record is missing.'
                    Category = [System.Management.Automation.ErrorCategory]::ObjectNotFound
                    CategoryReason = "The CNAME record lyncdiscover.$_ does not exist."
                    CategoryTargetName = "lyncdiscover.$_"
                    CategoryTargetType = 'CNAME'
                    ErrorID = 'LyncDiscoverCnameMissing'
                    RecommendedAction = "Create a CNAME record for lyncdiscover.$_, pointing to $shouldBe."
                    TargetObject = $dnsLookup
                }
                Write-Error @errorReport
                Write-Information $errorReport.RecommendedAction
            }
            ElseIf ($dnsLookup.NameHost -ne $shouldBe)
            {
                Write-Warning -Message 'The Skype/Teams autodiscover DNS record exists, but is not correct. This may be intentional, if you have a Skype for Business Server deployment.'
            }
            Else
            {
                Write-Success -Product 'Teams/Skype' 'The autodiscover DNS record is correct.'
            }
        }
    }
}

Function Test-TeamsSipCnameRecord
{
    Param(
        [Parameter(Mandatory, ValueFromPipeline)]
        [Alias('Name')]
        [ValidateNotNullOrEmpty()]
        [String[]] $DomainName
    )

    Begin
    {
        $shouldBe = 'sipdir.online.lync.com'
    }

    Process
    {
        $DomainName | ForEach-Object {
            Write-Output "Checking the SIP CNAME record for $_"

            $record = "sip.$_"
            $dnsLookup = Resolve-DnsNameCrossPlatform -Type 'CNAME' -Name $record

            If (-Not $dnsLookup)
            {
                $errorReport = @{
                    Message = "The SIP DNS CNAME record is missing for $_."
                    Category = [System.Management.Automation.ErrorCategory]::ObjectNotFound
                    CategoryReason = "The CNAME record $record does not exist."
                    CategoryTargetName = $record
                    CategoryTargetType = 'CNAME'
                    ErrorID = 'SipCnameMissing'
                    RecommendedAction = "Create a CNAME record for $record, pointing to $shouldBe."
                    TargetObject = $dnsLookup
                }
                Write-Error @errorReport
                Write-Information $errorReport.RecommendedAction
            }
            ElseIf ($dnsLookup.NameHost -ne $shouldBe)
            {
                Write-Warning -Message "The SIP CNAME record exists for $_, but is not correct. This may be intentional, if you have a Skype for Business Server deployment."
            }
            Else
            {
                Write-Success -Product 'Teams/Skype' "The SIP CNAME record is correct for $_."
            }
        }
    }
}

Function Test-TeamsSipFederationSrvRecord
{
    [Alias(
        'Test-LyncSipFederationRecord',
        'Test-SkypeForBusinessSipFederationSrvRecord',
        'Test-SkypeForBusinessOnlineSipFederationRecord'
    )]
    Param(
        [Parameter(Mandatory, ValueFromPipeline)]
        [Alias('Name')]
        [ValidateNotNullOrEmpty()]
        [String[]] $DomainName
    )

    Begin
    {
        $targetShouldBe = 'sipfed.online.lync.com'
        $portShouldBe   = 5061
    }

    Process
    {
        $DomainName | ForEach-Object {
            $record = "_sipfederationtls._tcp.$_"
            $dnsLookup = Resolve-DnsNameCrossPlatform -Type 'SRV' -Name $record

            If (-Not $dnsLookup)
            {
                $errorReport = @{
                    Message = 'The SIP federation SRV record is missing.'
                    Category = [System.Management.Automation.ErrorCategory]::ObjectNotFound
                    CategoryReason = "A SRV record for $record was not found."
                    CategoryTargetName = $record
                    CategoryTargetType = 'SRV'
                    ErrorID = 'SipFederationSrvMissing'
                    RecommendedAction = "Create a SRV record for $record (port $portShouldBe) pointing to $targetShouldBe."
                    TargetObject = $dnsLookup
                }
                Write-Error @errorReport
                Write-Information $errorReport.RecommendedAction
            }
            ElseIf (($dnsLookup | Measure-Object | Select-Object -ExpandProperty Count) -gt 1)
            {
                $errorReport = @{
                    Message = 'Multiple SIP federation SRV records exist.'
                    Category = [System.Management.Automation.ErrorCategory]::InvalidData
                    CategoryReason = "The SRV record $record returned multiple records."
                    CategoryTargetName = $record
                    CategoryTargetType = 'SRV'
                    ErrorID = 'SipFederationSrvTooManyResults'
                    RecommendedAction = "Delete any extra SRV records for $record."
                    TargetObject = $dnsLookup
                }
                Write-Error @errorReport
                Write-Information $errorReport.RecommendedAction
            }
            ElseIf ($dnsLookup[0].NameTarget -ne $targetShouldBe -or $dnsLookup[0].Port -ne $portShouldBe)
            {
                If ($dnsLookup[0].NameTarget -ne $targetShouldBe)
                {
                    $errorReport = @{
                        Message = 'The SIP federation SRV record was found, but has the incorrect target.'
                        Category = [System.Management.Automation.ErrorCategory]::InvalidData
                        CategoryReason = "The DNS SRV record $record was found, but has the incorrect target."
                        CategoryTargetName = $record
                        CategoryTargetType = 'SRV'
                        ErrorID = 'SipFederationSrvIncorrectTarget'
                        RecommendedAction = "Change the SRV record for $record to point to $targetShouldBe."
                        TargetObject = $dnsLookup
                    }
                    Write-Error @errorReport
                    Write-Information $errorReport.RecommendedAction
                }

                If ($dnsLookup[0].Port -ne $portShouldBe)
                {
                    $errorReport = @{
                        Message = 'the SIP federation SRV record was found, but has the incorrect port.'
                        Category = [System.Management.Automation.ErrorCategory]::InvalidData
                        CategoryReason = "The DNS SRV record $record was found, but has the incorrect port."
                        CategoryTargetName = $record
                        CategoryTargetType = 'SRV'
                        ErrorID = 'sipFederationSrvIncorrectPort'
                        RecommendedAction = "Change the SRV record for $record to point to port $portShouldBe."
                        TargetObject = $dnsLookup
                    }
                    Write-Error @errorReport
                    Write-Information $errorReport.RecommendedAction
                }
            }
            Else
            {
                Write-Success -Product 'Teams/Skype' 'The SIP federation SRV record is correct.'
            }
        }
    }
}

Function Test-TeamsSipSrvRecord
{
    [Alias(
        'Test-LyncSipSrvRecord',
        'Test-SkypeForBusinessSipSrvRecord',
        'Test-SkypeForBusinessOnlineSipSrvRecord'
    )]
    Param(
        [Parameter(Mandatory, ValueFromPipeline)]
        [Alias('Name')]
        [ValidateNotNullOrEmpty()]
        [String[]] $DomainName
    )

    Begin
    {
        $targetShouldBe = 'sipdir.online.lync.com'
        $portShouldBe   = 443
    }

    Process
    {
        $DomainName | ForEach-Object {
            Write-Output "Checking the SIP/TLS service record for $_"

            $record = "_sip._tls.$_"
            $dnsLookup = Resolve-DnsNameCrossPlatform -Type 'SRV' -Name $record

            If (-Not $dnsLookup)
            {
                $errorReport = @{
                    Message = 'The SIP/TLS SRV record is missing.'
                    Category = [System.Management.Automation.ErrorCategory]::ObjectNotFound
                    CategoryReason = "A SRV record for $record was not found."
                    CategoryTargetName = $record
                    CategoryTargetType = 'SRV'
                    ErrorID = 'SipSrvMissing'
                    RecommendedAction = "Create a SRV record for $record (port $portShouldBe) pointing to $targetShouldBe."
                    TargetObject = $dnsLookup
                }
                Write-Error @errorReport
                Write-Information $errorReport.RecommendedAction
            }
            ElseIf (($dnsLookup | Measure-Object | Select-Object -ExpandProperty Count) -gt 1)
            {
                $errorReport = @{
                    Message = 'Multiple SIP/TLS SRV records exist.'
                    Category = [System.Management.Automation.ErrorCategory]::InvalidData
                    CategoryReason = "The SRV record $record returned multiple records."
                    CategoryTargetName = $record
                    CategoryTargetType = 'SRV'
                    ErrorID = 'SipSrvTooManyResults'
                    RecommendedAction = "Delete any extra SRV records for $record."
                    TargetObject = $dnsLookup
                }
                Write-Error @errorReport
                Write-Information $errorReport.RecommendedAction
            }
            ElseIf ($dnsLookup[0].NameTarget -ne $targetShouldBe -or $dnsLookup[0].Port -ne $portShouldBe)
            {
                If ($dnsLookup[0].NameTarget -ne $targetShouldBe)
                {
                    $errorReport = @{
                        Message = 'The SIP/TLS SRV record was found, but has the incorrect target.'
                        Category = [System.Management.Automation.ErrorCategory]::InvalidData
                        CategoryReason = "The DNS SRV record $record was found, but has the incorrect target."
                        CategoryTargetName = $record
                        CategoryTargetType = 'SRV'
                        ErrorID = 'SipSrvIncorrectTarget'
                        RecommendedAction = "Change the SRV record for $record to point to $targetShouldBe."
                        TargetObject = $dnsLookup
                    }
                    Write-Error @errorReport
                    Write-Information $errorReport.RecommendedAction
                }

                If ($dnsLookup[0].Port -ne $portShouldBe)
                {
                    $errorReport = @{
                        Message = 'The SIP/TLS SRV record was found, but has the incorrect port.'
                        Category = [System.Management.Automation.ErrorCategory]::InvalidData
                        CategoryReason = "The DNS SRV record $record was found, but has the incorrect port."
                        CategoryTargetName = $record
                        CategoryTargetType = 'SRV'
                        ErrorID = 'SipSrvIncorrectPort'
                        RecommendedAction = "Change the SRV record for $record to point to port $portShouldBe."
                        TargetObject = $dnsLookup
                    }
                    Write-Error @errorReport
                    Write-Information $errorReport.RecommendedAction
                }
            }
            Else
            {
                Write-Success -Product 'Teams/Skype' 'The SIP/TLS SRV record is correct.'
            }
        }
    }
}#endregion Teams/Skype for Business Online cmdlets

# SIG # Begin signature block
# MIIo5AYJKoZIhvcNAQcCoIIo1TCCKNECAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCCvdjxynzMoaDUM
# MxlIw/pT9NtCqbNeXlw6WidDGR7MQ6CCI6swggR+MIIC5qADAgECAhEApna5vdQ8
# txEq0UQhUxLsMzANBgkqhkiG9w0BAQwFADBBMQswCQYDVQQGEwJVUzEQMA4GA1UE
# ChMHQ2VydGVyYTEgMB4GA1UEAxMXQ2VydGVyYSBDb2RlIFNpZ25pbmcgQ0EwHhcN
# MjIxMTI1MDAwMDAwWhcNMjUxMTI0MjM1OTU5WjBPMQswCQYDVQQGEwJVUzEUMBIG
# A1UECAwLQ29ubmVjdGljdXQxFDASBgNVBAoMC0NvbGluIENvZ2xlMRQwEgYDVQQD
# DAtDb2xpbiBDb2dsZTB2MBAGByqGSM49AgEGBSuBBAAiA2IABIS0nGDy1zQpFKyt
# Jcg1PiDfvpNR79NCbfgewfNj/SLANVb3XbggjeibCl1fcefKLnXFv0DXHIKjYg0e
# hcFMbUQ1hqpwnnWQji1DcLeshAMdvWmTguYmtL6P4ik/BQDUuaOCAY8wggGLMB8G
# A1UdIwQYMBaAFP7HyA+eaTU9w8t0+WyaszQGqVwJMB0GA1UdDgQWBBSO8z1ie4Xj
# RAjUjX9ctrNH9aglYzAOBgNVHQ8BAf8EBAMCB4AwDAYDVR0TAQH/BAIwADATBgNV
# HSUEDDAKBggrBgEFBQcDAzBJBgNVHSAEQjBAMDQGCysGAQQBsjEBAgJlMCUwIwYI
# KwYBBQUHAgEWF2h0dHBzOi8vc2VjdGlnby5jb20vQ1BTMAgGBmeBDAEEATBIBgNV
# HR8EQTA/MD2gO6A5hjdodHRwOi8vQ2VydGVyYS5jcmwuc2VjdGlnby5jb20vQ2Vy
# dGVyYUNvZGVTaWduaW5nQ0EuY3JsMIGABggrBgEFBQcBAQR0MHIwQwYIKwYBBQUH
# MAKGN2h0dHA6Ly9DZXJ0ZXJhLmNydC5zZWN0aWdvLmNvbS9DZXJ0ZXJhQ29kZVNp
# Z25pbmdDQS5jcnQwKwYIKwYBBQUHMAGGH2h0dHA6Ly9DZXJ0ZXJhLm9jc3Auc2Vj
# dGlnby5jb20wDQYJKoZIhvcNAQEMBQADggGBAAslTgxzcZ0FYetE3IOghFsEtGV+
# yEM03ZrGFRGt7/DmHe4MK15XUsORJzN60eyNzxchQhV1S90jqQflkl6ImuvdaRve
# 586ZhYtW4tl2+2YbM26jwVqB9tT06W1SHb03+Vb29jjRbp5r+w3lEXxzGC660MFk
# 1L8kRQcqKjt0izVeVm6qKfNVQyak5xWpeX8n8NVaCqVWfijWlLDr8Ydeg9XeJy4H
# c9OweQ7+seRJzr/MgHQ0SFuXaRrbk0v5UmyoH83LZt/qo+XnrU+XeX870UVxucTl
# AitkDB6t/dvmetmXQGE5stJMyIK5jgtMqQ/q/GIrTFYMmcAsXxNQh8uv+jFa0HhF
# PZVhhdRbximJQUPyKb7IMuAzwdw1jrTcAF1FbkLlHXdu7dohbSfsN8ZA5Cr397wN
# n7UBs939mMBb4ZR+nBPFhibj5RISssbICi8z3LNb6CNuayOn3PtG/NRcf5T8iFyW
# /XbipYDJcxuQKwP8HWmlVIfQooRP6HR+Doee+DCCBY0wggR1oAMCAQICEA6bGI75
# 0C3n79tQ4ghAGFowDQYJKoZIhvcNAQEMBQAwZTELMAkGA1UEBhMCVVMxFTATBgNV
# BAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3LmRpZ2ljZXJ0LmNvbTEkMCIG
# A1UEAxMbRGlnaUNlcnQgQXNzdXJlZCBJRCBSb290IENBMB4XDTIyMDgwMTAwMDAw
# MFoXDTMxMTEwOTIzNTk1OVowYjELMAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lD
# ZXJ0IEluYzEZMBcGA1UECxMQd3d3LmRpZ2ljZXJ0LmNvbTEhMB8GA1UEAxMYRGln
# aUNlcnQgVHJ1c3RlZCBSb290IEc0MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIIC
# CgKCAgEAv+aQc2jeu+RdSjwwIjBpM+zCpyUuySE98orYWcLhKac9WKt2ms2uexuE
# DcQwH/MbpDgW61bGl20dq7J58soR0uRf1gU8Ug9SH8aeFaV+vp+pVxZZVXKvaJNw
# wrK6dZlqczKU0RBEEC7fgvMHhOZ0O21x4i0MG+4g1ckgHWMpLc7sXk7Ik/ghYZs0
# 6wXGXuxbGrzryc/NrDRAX7F6Zu53yEioZldXn1RYjgwrt0+nMNlW7sp7XeOtyU9e
# 5TXnMcvak17cjo+A2raRmECQecN4x7axxLVqGDgDEI3Y1DekLgV9iPWCPhCRcKtV
# gkEy19sEcypukQF8IUzUvK4bA3VdeGbZOjFEmjNAvwjXWkmkwuapoGfdpCe8oU85
# tRFYF/ckXEaPZPfBaYh2mHY9WV1CdoeJl2l6SPDgohIbZpp0yt5LHucOY67m1O+S
# kjqePdwA5EUlibaaRBkrfsCUtNJhbesz2cXfSwQAzH0clcOP9yGyshG3u3/y1Yxw
# LEFgqrFjGESVGnZifvaAsPvoZKYz0YkH4b235kOkGLimdwHhD5QMIR2yVCkliWzl
# DlJRR3S+Jqy2QXXeeqxfjT/JvNNBERJb5RBQ6zHFynIWIgnffEx1P2PsIV/EIFFr
# b7GrhotPwtZFX50g/KEexcCPorF+CiaZ9eRpL5gdLfXZqbId5RsCAwEAAaOCATow
# ggE2MA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFOzX44LScV1kTN8uZz/nupiu
# HA9PMB8GA1UdIwQYMBaAFEXroq/0ksuCMS1Ri6enIZ3zbcgPMA4GA1UdDwEB/wQE
# AwIBhjB5BggrBgEFBQcBAQRtMGswJAYIKwYBBQUHMAGGGGh0dHA6Ly9vY3NwLmRp
# Z2ljZXJ0LmNvbTBDBggrBgEFBQcwAoY3aHR0cDovL2NhY2VydHMuZGlnaWNlcnQu
# Y29tL0RpZ2lDZXJ0QXNzdXJlZElEUm9vdENBLmNydDBFBgNVHR8EPjA8MDqgOKA2
# hjRodHRwOi8vY3JsMy5kaWdpY2VydC5jb20vRGlnaUNlcnRBc3N1cmVkSURSb290
# Q0EuY3JsMBEGA1UdIAQKMAgwBgYEVR0gADANBgkqhkiG9w0BAQwFAAOCAQEAcKC/
# Q1xV5zhfoKN0Gz22Ftf3v1cHvZqsoYcs7IVeqRq7IviHGmlUIu2kiHdtvRoU9BNK
# ei8ttzjv9P+Aufih9/Jy3iS8UgPITtAq3votVs/59PesMHqai7Je1M/RQ0SbQyHr
# lnKhSLSZy51PpwYDE3cnRNTnf+hZqPC/Lwum6fI0POz3A8eHqNJMQBk1RmppVLC4
# oVaO7KTVPeix3P0c2PR3WlxUjG/voVA9/HYJaISfb8rbII01YBwCA8sgsKxYoA5A
# Y8WYIsGyWfVVa88nq2x2zm8jLfR+cWojayL/ErhULSd+2DrZ8LaHlv1b0VysGMNN
# n3O3AamfV6peKOK5lDCCBd4wggPGoAMCAQICEAH9bTD8o8pRqBu8ZA41Ay0wDQYJ
# KoZIhvcNAQEMBQAwgYgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpOZXcgSmVyc2V5
# MRQwEgYDVQQHEwtKZXJzZXkgQ2l0eTEeMBwGA1UEChMVVGhlIFVTRVJUUlVTVCBO
# ZXR3b3JrMS4wLAYDVQQDEyVVU0VSVHJ1c3QgUlNBIENlcnRpZmljYXRpb24gQXV0
# aG9yaXR5MB4XDTEwMDIwMTAwMDAwMFoXDTM4MDExODIzNTk1OVowgYgxCzAJBgNV
# BAYTAlVTMRMwEQYDVQQIEwpOZXcgSmVyc2V5MRQwEgYDVQQHEwtKZXJzZXkgQ2l0
# eTEeMBwGA1UEChMVVGhlIFVTRVJUUlVTVCBOZXR3b3JrMS4wLAYDVQQDEyVVU0VS
# VHJ1c3QgUlNBIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIICIjANBgkqhkiG9w0B
# AQEFAAOCAg8AMIICCgKCAgEAgBJlFzYOw9sIs9CsVw127c0n00ytUINh4qogTQkt
# ZAnczomfzD2p7PbPwdzx07HWezcoEStH2jnGvDoZtF+mvX2do2NCtnbyqTsrkfji
# b9DsFiCQCT7i6HTJGLSR1GJk23+jBvGIGGqQIjy8/hPwhxR79uQfjtTkUcYRZ0YI
# UcuGFFQ/vDP+fmyc/xadGL1RjjWmp2bIcmfbIWax1Jt4A8BQOujM8Ny8nkz+rwWW
# NR9XWrf/zvk9tyy29lTdyOcSOk2uTIq3XJq0tyA9yn8iNK5+O2hmAUTnAU5GU5sz
# YPeUvlM3kHND8zLDU+/bqv50TmnHa4xgk97Exwzf4TKuzJM7UXiVZ4vuPVb+DNBp
# DxsP8yUmazNt925H+nND5X4OpWaxKXwyhGNVicQNwZNUMBkTrNN9N6frXTpsNVzb
# QdcS2qlJC9/YgIoJk2KOtWbPJYjNhLixP6Q5D9kCnusSTJV882sFqV4Wg8y4Z+Lo
# E53MW4LTTLPtW//e5XOsIzstAL81VXQJSdhJWBp/kjbmUZIO8yZ9HE0XvMnsQybQ
# v0FfQKlERPSZ51eHnlAfV1SoPv10Yy+xUGUJ5lhCLkMaTLTwJUdZ+gQek9QmRkpQ
# gbLevni3/GcV4clXhB4PY9bpYrrWX1Uu6lzGKAgEJTm4Diup8kyXHAc/DVL17e8v
# gg8CAwEAAaNCMEAwHQYDVR0OBBYEFFN5v1qqK0rPVIDh2JvAnfKyA2bLMA4GA1Ud
# DwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBDAUAA4ICAQBc
# 1HwNz/cBfUGZZQxzxVKfy/jPmQZ/G9pDFZ+eAlVXlhTxUjwnh5Qo7R86ATeidvxT
# UMCEm8ZrTrqMIU+ijlVikfNpFdi8iOPEqgv976jpS1UqBiBtVXgpGe5fMFxLJBFV
# /ySabl4qK+4LTZ9/9wE4lBSVQwcJ+2Cp7hyrEoygml6nmGpZbYs/CPvI0UWvGBVk
# kBIPcyguxeIkTvxY7PD0Rf4is+svjtLZRWEFwZdvqHZyj4uMNq+/DQXOcY3mpm8f
# bKZxYsXY0INyDPFnEYkMnBNMcjTfvNVx36px3eG5bIw8El1l2r1XErZDa//l3k1m
# EVHPma7sF7bocZGM3kn+3TVxohUnlBzPYeMmu2+jZyUhXebdHQsuaBs7gq/sg2eF
# 1JhRdLG5mYCJ/394GVx5SmAukkCuTDcqLMnHYsgOXfc2W8rgJSUBtN0aB5x3AD/Q
# 3NXsPdT6uz/MhdZvf6kt37kC9/WXmrU12sNnsIdKqSieI47/XCdr4bBP8wfuAC7U
# WYfLUkGV6vRH1+5kQVV8jVkCld1incK57loodISlm7eQxwwH3/WJNnQy1ijBsLAL
# 4JxMwxzW/ONptUdGgS+igqvTY0RwxI3/LTO6rY97tXCIrj4Zz0Ao2PzIkLtdmSL1
# UuZYxR+IMUPuiB3Xxo48Q2odpxjefT0W8WL5ypCo/TCCBjwwggQkoAMCAQICECFm
# 8IpR6/yrzI9EMJGpSw4wDQYJKoZIhvcNAQEMBQAwgYgxCzAJBgNVBAYTAlVTMRMw
# EQYDVQQIEwpOZXcgSmVyc2V5MRQwEgYDVQQHEwtKZXJzZXkgQ2l0eTEeMBwGA1UE
# ChMVVGhlIFVTRVJUUlVTVCBOZXR3b3JrMS4wLAYDVQQDEyVVU0VSVHJ1c3QgUlNB
# IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTIyMDkwNzAwMDAwMFoXDTMyMDkw
# NjIzNTk1OVowQTELMAkGA1UEBhMCVVMxEDAOBgNVBAoTB0NlcnRlcmExIDAeBgNV
# BAMTF0NlcnRlcmEgQ29kZSBTaWduaW5nIENBMIIBojANBgkqhkiG9w0BAQEFAAOC
# AY8AMIIBigKCAYEAvp9xPhzayPelQMu7ycbIP8Kls73mzciRa7hO+f06rZl7Xw4F
# DKuA1Cu7nen1GFCPuqRvCqEizDiO4/WnM4nQcfVFkfpXfZf24qUztHzq5qsxlwpK
# W/Dkksj+I9A15W1dFbmToYswFElXzmKHSnZXoYMz+R4ZSwmnVB/XsvUPaAFi2dCr
# KN54pMcsBweUOKFunKWkji/MMnnPJGebOF1fLeDgyEHQvYuzlVfOWU3xjMiZYfqY
# gi8jo28qa0IYR17SdFZIgUWRlKhJnNKwyXfY8kElpfpeSbjM20jLch1+UhPXwTU/
# 5yHwXvUCSW4idXEihxbcleNXbeO8wfwfNHn2of4Y1w4mShxHFhDu/kPmzDIkpPct
# AmDyJfJfcL1E+aRFqGYhJwCOiMNQE9dfDkYL11Rtue3zmcpkqKbH6P6EI3UQSG1t
# H0OqY65xpSadXS/yGoXqOOEQpDf/U3trlyqroxhUhm0dN82CBqSXqMa23scYns1O
# 3u2kSPPHIEULOVq5AgMBAAGjggFmMIIBYjAfBgNVHSMEGDAWgBRTeb9aqitKz1SA
# 4dibwJ3ysgNmyzAdBgNVHQ4EFgQU/sfID55pNT3Dy3T5bJqzNAapXAkwDgYDVR0P
# AQH/BAQDAgGGMBIGA1UdEwEB/wQIMAYBAf8CAQAwEwYDVR0lBAwwCgYIKwYBBQUH
# AwMwIgYDVR0gBBswGTANBgsrBgEEAbIxAQICZTAIBgZngQwBBAEwUAYDVR0fBEkw
# RzBFoEOgQYY/aHR0cDovL2NybC51c2VydHJ1c3QuY29tL1VTRVJUcnVzdFJTQUNl
# cnRpZmljYXRpb25BdXRob3JpdHkuY3JsMHEGCCsGAQUFBwEBBGUwYzA6BggrBgEF
# BQcwAoYuaHR0cDovL2NydC51c2VydHJ1c3QuY29tL1VTRVJUcnVzdFJTQUFBQUNB
# LmNydDAlBggrBgEFBQcwAYYZaHR0cDovL29jc3AudXNlcnRydXN0LmNvbTANBgkq
# hkiG9w0BAQwFAAOCAgEAe11w9/hMUEgtubZdffaBE4vbRYL0hunnc2Yaup6rzig/
# GjVOaTA7gdoChGhuxDE0AoYMF1znfLBSuNrU6B8tO/ikxFprLayPz9IUmbhEd/Ry
# VbMimZiC7z74OfjIVx86Y279nJ0VmX6lgHvwc8QcAVMN00Qse97OD9EeWMuY+hB7
# 1mKUp6pTipoqKJD4+hs2fOxjXew9OBYu6wjlgK6kbuBo+R2T7EuYyyfWubg9Cpwg
# dzRSpWmRO5DMG+u0FojEtP8MITbtJ1bLOWZ0JVvGKDWqNLVBvxHE8DwaAx3IrlZ8
# 1lxLO3zEL/mpUnC6cdQlVkq3G7qdWfIdkaNhNAv3hu0tH3t8bLoXYDB6Kyp5hdGZ
# 1XAO7H4b7MVW1amciuBXys6/VvfWmR/9Wh1rjWuYtP+y94oLg1gEisa7+Qid2qy/
# WSKC7cjpzwmg+6BGb2oEAO56pZToRc5a8vE9XcMPMO6hxI+MGbpqioQ/Nwa+94Ep
# D2aGUkmqX3gP6kUBbvS4Pys0jLgKxlyZDfwJb+4CWQOoZaiZoLAr/Y9+9j2YkeQD
# rt1A2zEDgOHRLlXYQDPuVNSu014pt8yAMY1OnHQSrTKwBZ2Y5H8AOw1yyIsMQISq
# OcPiepvzMAwSMJtTedvFq51+kuBHgltH2AdDlPfT13i3CAqn3LcFhehUZU4VIPsw
# 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/bt1nz8MIIGwDCCBKigAwIBAgIQ
# DE1pckuU+jwqSj0pB4A9WjANBgkqhkiG9w0BAQsFADBjMQswCQYDVQQGEwJVUzEX
# MBUGA1UEChMORGlnaUNlcnQsIEluYy4xOzA5BgNVBAMTMkRpZ2lDZXJ0IFRydXN0
# ZWQgRzQgUlNBNDA5NiBTSEEyNTYgVGltZVN0YW1waW5nIENBMB4XDTIyMDkyMTAw
# MDAwMFoXDTMzMTEyMTIzNTk1OVowRjELMAkGA1UEBhMCVVMxETAPBgNVBAoTCERp
# Z2lDZXJ0MSQwIgYDVQQDExtEaWdpQ2VydCBUaW1lc3RhbXAgMjAyMiAtIDIwggIi
# MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDP7KUmOsap8mu7jcENmtuh6BSF
# dDMaJqzQHFUeHjZtvJJVDGH0nQl3PRWWCC9rZKT9BoMW15GSOBwxApb7crGXOlWv
# M+xhiummKNuQY1y9iVPgOi2Mh0KuJqTku3h4uXoW4VbGwLpkU7sqFudQSLuIaQyI
# xvG+4C99O7HKU41Agx7ny3JJKB5MgB6FVueF7fJhvKo6B332q27lZt3iXPUv7Y3U
# TZWEaOOAy2p50dIQkUYp6z4m8rSMzUy5Zsi7qlA4DeWMlF0ZWr/1e0BubxaompyV
# R4aFeT4MXmaMGgokvpyq0py2909ueMQoP6McD1AGN7oI2TWmtR7aeFgdOej4TJEQ
# ln5N4d3CraV++C0bH+wrRhijGfY59/XBT3EuiQMRoku7mL/6T+R7Nu8GRORV/zbq
# 5Xwx5/PCUsTmFntafqUlc9vAapkhLWPlWfVNL5AfJ7fSqxTlOGaHUQhr+1NDOdBk
# +lbP4PQK5hRtZHi7mP2Uw3Mh8y/CLiDXgazT8QfU4b3ZXUtuMZQpi+ZBpGWUwFjl
# 5S4pkKa3YWT62SBsGFFguqaBDwklU/G/O+mrBw5qBzliGcnWhX8T2Y15z2LF7OF7
# ucxnEweawXjtxojIsG4yeccLWYONxu71LHx7jstkifGxxLjnU15fVdJ9GSlZA076
# XepFcxyEftfO4tQ6dwIDAQABo4IBizCCAYcwDgYDVR0PAQH/BAQDAgeAMAwGA1Ud
# EwEB/wQCMAAwFgYDVR0lAQH/BAwwCgYIKwYBBQUHAwgwIAYDVR0gBBkwFzAIBgZn
# gQwBBAIwCwYJYIZIAYb9bAcBMB8GA1UdIwQYMBaAFLoW2W1NhS9zKXaaL3WMaiCP
# nshvMB0GA1UdDgQWBBRiit7QYfyPMRTtlwvNPSqUFN9SnDBaBgNVHR8EUzBRME+g
# TaBLhklodHRwOi8vY3JsMy5kaWdpY2VydC5jb20vRGlnaUNlcnRUcnVzdGVkRzRS
# U0E0MDk2U0hBMjU2VGltZVN0YW1waW5nQ0EuY3JsMIGQBggrBgEFBQcBAQSBgzCB
# gDAkBggrBgEFBQcwAYYYaHR0cDovL29jc3AuZGlnaWNlcnQuY29tMFgGCCsGAQUF
# BzAChkxodHRwOi8vY2FjZXJ0cy5kaWdpY2VydC5jb20vRGlnaUNlcnRUcnVzdGVk
# RzRSU0E0MDk2U0hBMjU2VGltZVN0YW1waW5nQ0EuY3J0MA0GCSqGSIb3DQEBCwUA
# A4ICAQBVqioa80bzeFc3MPx140/WhSPx/PmVOZsl5vdyipjDd9Rk/BX7NsJJUSx4
# iGNVCUY5APxp1MqbKfujP8DJAJsTHbCYidx48s18hc1Tna9i4mFmoxQqRYdKmEIr
# UPwbtZ4IMAn65C3XCYl5+QnmiM59G7hqopvBU2AJ6KO4ndetHxy47JhB8PYOgPvk
# /9+dEKfrALpfSo8aOlK06r8JSRU1NlmaD1TSsht/fl4JrXZUinRtytIFZyt26/+Y
# siaVOBmIRBTlClmia+ciPkQh0j8cwJvtfEiy2JIMkU88ZpSvXQJT657inuTTH4YB
# ZJwAwuladHUNPeF5iL8cAZfJGSOA1zZaX5YWsWMMxkZAO85dNdRZPkOaGK7DycvD
# +5sTX2q1x+DzBcNZ3ydiK95ByVO5/zQQZ/YmMph7/lxClIGUgp2sCovGSxVK05iQ
# RWAzgOAj3vgDpPZFR+XOuANCR+hBNnF3rf2i6Jd0Ti7aHh2MWsgemtXC8MYiqE+b
# vdgcmlHEL5r2X6cnl7qWLoVXwGDneFZ/au/ClZpLEQLIgpzJGgV8unG1TnqZbPTo
# ntRamMifv427GFxD9dAq6OJi7ngE273R+1sKqHB+8JeEeOMIA11HLGOoJTiXAdI/
# Otrl5fbmm9x+LMz/F0xNAKLY1gEOuIvu5uByVYksJxlh9ncBjDGCBI8wggSLAgEB
# MFYwQTELMAkGA1UEBhMCVVMxEDAOBgNVBAoTB0NlcnRlcmExIDAeBgNVBAMTF0Nl
# cnRlcmEgQ29kZSBTaWduaW5nIENBAhEApna5vdQ8txEq0UQhUxLsMzANBglghkgB
# ZQMEAgEFAKCBhDAYBgorBgEEAYI3AgEMMQowCKACgAChAoAAMBkGCSqGSIb3DQEJ
# AzEMBgorBgEEAYI3AgEEMBwGCisGAQQBgjcCAQsxDjAMBgorBgEEAYI3AgEVMC8G
# CSqGSIb3DQEJBDEiBCD6TFHNxwOILZPlDNAuM/8rnr5W2x5XMhX8HIbmnb1tbjAL
# BgcqhkjOPQIBBQAEZzBlAjBEsjqhD17zyRqVcTKbz0yzEXAJWE3WyKYasPtt9VR8
# Qk57zRm6l6CFfs/3OUBqhhECMQCZc28f0BYUCPtZaqzeHGGVXNiK86uSgtTUeQ1y
# AS7lKti+pB3iPYE0I9CJzzb3xcShggMgMIIDHAYJKoZIhvcNAQkGMYIDDTCCAwkC
# AQEwdzBjMQswCQYDVQQGEwJVUzEXMBUGA1UEChMORGlnaUNlcnQsIEluYy4xOzA5
# BgNVBAMTMkRpZ2lDZXJ0IFRydXN0ZWQgRzQgUlNBNDA5NiBTSEEyNTYgVGltZVN0
# YW1waW5nIENBAhAMTWlyS5T6PCpKPSkHgD1aMA0GCWCGSAFlAwQCAQUAoGkwGAYJ
# KoZIhvcNAQkDMQsGCSqGSIb3DQEHATAcBgkqhkiG9w0BCQUxDxcNMjMwNjAxMTUw
# MTMxWjAvBgkqhkiG9w0BCQQxIgQgff+YaY8m/kTikVM4XYjcEby8jr0FSIn9QCER
# OjNvgiQwDQYJKoZIhvcNAQEBBQAEggIAPDrPooP80eKjqF7Z7Qt+qbRM+9h8/S8q
# xa/DaCAsyKr/bvXqhFHDlPfS7HTczSDZgvMFhQGWVyln3ss/8hFQNF9INjgtbRLD
# gzbxgxUl4AVISlV9CvP6BbSOj6+KyBedOUafUoOTbnBWJsD9fu9GflGTb4//4lLc
# DixM1HvxJ5J653L1q/fFwJTogwubrSXd4FdHMRUG1eq4olUe0iAGeVZ8auUTr/eb
# DsD0NKpiRC/7GzpA9CJiDAz61SnYfyzjDj3tgBE2uMrbN9iWlIWyekUL9uNu1I7H
# TFdF9PbzXSe3Y8wJNi/fdwT/7mtnd8ja+YG8V750k7iQO6wuJI17dCKl+ZoVW0vS
# QPjTmx7r7kyg9ZzA0gVTFP5ciVq8eFETQighOFCKpmLOsTCEQEbRzFUijt51cLUp
# mbfuSM2Ya+VtsnZMmrh0XHc26oOqZqF/uabBLo05oAwTGmbQ2UtXW4nd4NzXkpaK
# E9X/jTj4Umeu6nUGNkynlblNOf1R5DlP/cBIE6ZQwM7KFlCWEu6SBCo+aR5dIblb
# Rx0tSzdDRjQNbLJ0injnMKQiLoypopoBTuWwcLunb2soBy5gia+tj4daWz3U+rGf
# Y3i+ziq8+jY2Gm6sSNvU2ANw30DcfQBTVokwg48uMLEQ7FLmLOi+93jitfsIbfgs
# UNTo/Dwtqec=
# SIG # End signature block