src/Office365DnsChecker.psm1
<#
Office365DnsChecker Copyright (C) 2019-2024 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 { [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSReviewUnusedParameter', 'DANERequired', Justification='This parameter is used in the Process block.')] [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSReviewUnusedParameter', 'Use21Vianet', Justification='This parameter is used in the Process block.')] [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', 'result', Justification='$result is assigned and used across the Begin/Process/End blocks.')] [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseSingularNouns', '', Justification='We are testing multiple DNS records.')] [CmdletBinding()] [OutputType([Bool])] Param( [Parameter(Mandatory, ValueFromPipeline)] [Alias('Name')] [ValidateNotNullOrEmpty()] [String[]] $DomainName, [Alias('China')] [Switch] $Use21Vianet, [Switch] $DANERequired ) Begin { $result = $true } Process { $DomainName | ForEach-Object { Write-Output "Checking Office 365 DNS records for $_." $result = Test-EntraIDRecords -DomainName $_ -Use21Vianet:$Use21Vianet | Out-Null $result = Test-ExchangeOnlineRecords -DomainName $_ -DANERequired:$DANERequired | Out-Null $result = Test-TeamsRecords -DomainName $_ | Out-Null } } End { Return $result } } #region Helper cmdlets Function Resolve-DNSNameCrossPlatform { Param( [Parameter(Mandatory, Position=0)] [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 'DnsClient\Resolve-DnsName' -ErrorAction SilentlyContinue) { $dnsLookup = DnsClient\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 { [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingWriteHost', '', Justification='I need a PS 5.1-compatible way to show colored output.')] [OutputType([Void])] 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 Entra cmdlets Function Test-EntraIDRecords { [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSReviewUnusedParameter', 'Use21Vianet', Justification='This parameter is used in the Process block.')] [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseSingularNouns', '', Justification='We are testing multiple DNS records.')] [Alias('Test-AzureADRecords')] [OutputType([Bool])] Param( [Parameter(Mandatory, ValueFromPipeline)] [Alias('Name')] [ValidateNotNullOrEmpty()] [String[]] $DomainName, [Alias('China')] [Switch] $Use21Vianet ) Begin { $result = $true } Process { $DomainName | ForEach-Object { $result = $result -and (Test-EntraIDClientConfigurationRecord -DomainName $_ -Use21Vianet:$Use21Vianet) $result = $result -and (Test-EntraIDEnterpriseEnrollmentRecord -DomainName $_) $result = $result -and (Test-EntraIDEnterpriseRegistrationRecord -DomainName $_) } } End { Return $result } } Function Test-EntraIDClientConfigurationRecord { [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', 'result', Justification='$result is assigned and used across the Begin/Process/End blocks.')] [Alias('Test-AzureADClientConfigurationRecord')] [CmdletBinding()] [OutputType([Bool])] Param( [Parameter(Mandatory, ValueFromPipeline)] [Alias('Name')] [ValidateNotNullOrEmpty()] [String[]] $DomainName, [Alias('China')] [Switch] $Use21Vianet ) Begin { $result = $true $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 'Entra ID' '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 $result = $false } Else { If ($Use21Vianet) { Write-Success -Product 'Entra ID' 'The client configuration CNAME record is present and correct for 21Vianet.' } Else { Write-Success -Product 'Entra ID' 'The client configuration CNAME record is present, but correct.' } } } } End { Return $result } } Function Test-EntraIDEnterpriseEnrollmentRecord { [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', 'result', Justification='$result is assigned and used across the Begin/Process/End blocks.')] [Alias('Test-AzureADEnterpriseEnrollmentRecord')] [OutputType([Bool])] Param( [Parameter(Mandatory, ValueFromPipeline)] [Alias('Name')] [ValidateNotNullOrEmpty()] [String[]] $DomainName ) Begin { $result = $true $shouldBe = 'enterpriseenrollment.manage.microsoft.com' } Process { $DomainName | ForEach-Object { Write-Output "Checking Entra ID 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 $result = $false } 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 $result = $false } Else { Write-Success -Product 'Entra ID' 'The enterprise enrollment DNS record is correct.' } } } End { Return $result } } Function Test-EntraIDEnterpriseRegistrationRecord { [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', 'result', Justification='$result is assigned and used across the Begin/Process/End blocks.')] [Alias('Test-AzureADEnterpriseRegistrationRecord')] [OutputType([Bool])] Param( [Parameter(Mandatory, ValueFromPipeline)] [Alias('Name')] [ValidateNotNullOrEmpty()] [String[]] $DomainName ) Begin { $result = $true $shouldBe = 'enterpriseregistration.windows.net' } Process { $DomainName | ForEach-Object { Write-Output "Checking Entra ID 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 $result = $false } 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 $result = $false } Else { Write-Success -Product 'Entra ID' 'The enterprise registration DNS record is correct.' } } } End { Return $result } } #endregion Entra ID cmdlets #region Exchange Online cmdlets Function Test-ExchangeOnlineRecords { [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSReviewUnusedParameter', 'DANERequired', Justification='This parameter is used in the Process block.')] [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', 'result', Justification='$result is assigned and used across the Begin/Process/End blocks.')] [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseSingularNouns', '', Justification='We are testing multiple DNS records.')] [OutputType([Bool])] Param( [Parameter(Mandatory, ValueFromPipeline)] [Alias('Name')] [ValidateNotNullOrEmpty()] [String[]] $DomainName, [Switch] $DANERequired ) Begin { $result = $true } Process { $DomainName | ForEach-Object { $result = $result -and (Test-ExchangeOnlineMxRecord -DomainName $_ -DANERequired:$DANERequired) $result = $result -and (Test-ExchangeOnlineAutodiscoverRecord -DomainName $_) $result = $result -and (Test-ExchangeOnlineSpfRecord -DomainName $_) $result = $result -and (Test-ExchangeOnlineSenderIdRecord -DomainName $_) $result = $result -and (Test-ExchangeOnlineDkimRecords -DomainName $_) } } End { Return $result } } Function Test-ExchangeOnlineAutodiscoverRecord { [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', 'result', Justification='$result is assigned and used across the Begin/Process/End blocks.')] [OutputType([Bool])] Param( [Parameter(Mandatory, ValueFromPipeline)] [Alias('Name')] [ValidateNotNullOrEmpty()] [String] $DomainName ) Begin { $result = $true $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 $result = $false } 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 $result = $false } 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 $result = $false } Else { Write-Success -Product 'Exchange Online' -Message 'An Autodiscover DNS SRV record does not exist.' } } } End { Return $result } } Function Test-ExchangeOnlineDkimRecords { [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSReviewUnusedParameter', 'Selectors', Justification='This parameter is used in the Process block.')] [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', 'result', Justification='$result is assigned and used across the Begin/Process/End blocks.')] [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseSingularNouns', '', Justification='We are testing multiple DNS records.')] [OutputType([Bool])] Param( [Parameter(Mandatory, ValueFromPipeline)] [Alias('Name')] [ValidateNotNullOrEmpty()] [String[]] $DomainName, [ValidateNotNullOrEmpty()] [ValidateSet(1,2)] [Int[]] $Selectors = @(1,2) ) Begin { $result = $true } 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 $result = $false } 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 $result = $false } 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 $result = $false 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 $result = $false 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 $result = $false Continue } Write-Success -Product 'Exchange Online' "The DKIM key selector$i appears to be correct." } } } } End { Return $result } } Function Test-ExchangeOnlineMxRecord { [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSReviewUnusedParameter', 'DANERequired', Justification='This parameter is used in the Process block.')] [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', 'result', Justification='$result is assigned and used across the Begin/Process/End blocks.')] [OutputType([Bool])] Param( [Parameter(Mandatory, ValueFromPipeline)] [Alias('Name')] [ValidateNotNullOrEmpty()] [String[]] $DomainName, [Switch] $DANERequired ) Begin { # Note that there are valid reasons why we might not be using # Microsoft's MX record. Warnings will not set $result to $false. $result = $true } 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 $result = $false } 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') { $msg = "The first MX record for the domain $_ appears correct, but DANE/DNSSEC are NOT supported." If ($DANERequired) { Write-Warning -Product 'Exchange Online' $msg } Else { Write-Success -Product 'Exchange Online' $msg } } ElseIf ($dnsLookup[0].NameExchange -Like '*.mx.microsoft') { Write-Success -Product 'Exchange Online' "The first MX record for the domain $_ appears correct, and DANE/DNSSEC are supported." } 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." } } } } End { Return $result } } Function Test-ExchangeOnlineSenderIdRecord { [OutputType([Bool])] Param( [Parameter(Mandatory, ValueFromPipeline)] [Alias('Name')] [ValidateNotNullOrEmpty()] [String[]] $DomainName ) Begin { $result = $true } Process { $result = $result -and (Test-ExchangeOnlineSpfRecord -DomainName $DomainName -SpfOrSenderID 'Sender ID') } End { Return $result } } Function Test-ExchangeOnlineSpfRecord { [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', 'result', Justification='$result is assigned and used across the Begin/Process/End blocks.')] [CmdletBinding()] [OutputType([Bool])] Param( [Parameter(Mandatory, ValueFromPipeline)] [Alias('Name')] [ValidateNotNullOrEmpty()] [String[]] $DomainName, [ValidateSet('SPF', 'Sender ID')] [String] $SpfOrSenderID = 'SPF' ) Begin { $result = $true 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 $result = $false } } 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 $result = $false } 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 $result = $false } 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 $result = $false } 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 $result = $false } } } } End { Return $result } } #endregion Exchange Online cmdlets #region Teams/Skype for Business Online cmdlets Function Test-TeamsRecords { [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', 'result', Justification='$result is assigned and used across the Begin/Process/End blocks.')] [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseSingularNouns', '', Justification='We are testing multiple DNS records.')] [CmdletBinding()] [OutputType([Bool])] [Alias( 'Test-LyncRecords', 'Test-SkypeForBusinessRecords', 'Test-SkypeForBusinessOnlineRecords' )] Param( [Parameter(Mandatory, ValueFromPipeline)] [Alias('Name')] [ValidateNotNullOrEmpty()] [String[]] $DomainName ) Begin { $result = $true } Process { $DomainName | ForEach-Object { $result = $result -and (Test-TeamsAutodiscoverRecord -DomainName $_) $result = $result -and (Test-TeamsSipCnameRecord -DomainName $_) $result = $result -and (Test-TeamsSipSrvRecord -DomainName $_) $result = $result -and (Test-TeamsSipFederationSrvRecord -DomainName $_) } } End { Return $result } } Function Test-TeamsAutodiscoverRecord { [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', 'result', Justification='$result is assigned and used across the Begin/Process/End blocks.')] [OutputType([Bool])] [Alias( 'Test-LyncDiscoverRecord', 'Test-LyncAutodiscoverRecord', 'Test-SkypeForBusinessAutodiscoverRecord', 'Test-SkypeForBusinessOnlineAutodiscoverRecord' )] Param( [Parameter(Mandatory, ValueFromPipeline)] [Alias('Name')] [ValidateNotNullOrEmpty()] [String[]] $DomainName ) Begin { $result = $true $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 $result = $false } 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.' } } } End { Return $result } } Function Test-TeamsSipCnameRecord { [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', 'result', Justification='$result is assigned and used across the Begin/Process/End blocks.')] [OutputType([Bool])] Param( [Parameter(Mandatory, ValueFromPipeline)] [Alias('Name')] [ValidateNotNullOrEmpty()] [String[]] $DomainName ) Begin { $result = $true $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 $result = $false } 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 $_." } } } End { Return $result } } Function Test-TeamsSipFederationSrvRecord { [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', 'result', Justification='$result is assigned and used across the Begin/Process/End blocks.')] [OutputType([Bool])] [Alias( 'Test-LyncSipFederationRecord', 'Test-SkypeForBusinessSipFederationSrvRecord', 'Test-SkypeForBusinessOnlineSipFederationRecord' )] Param( [Parameter(Mandatory, ValueFromPipeline)] [Alias('Name')] [ValidateNotNullOrEmpty()] [String[]] $DomainName ) Begin { $result = $true $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 $result = $false } 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 $result = $false } 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 $result = $false } 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 $result = $false } } Else { Write-Success -Product 'Teams/Skype' 'The SIP federation SRV record is correct.' } } } End { Return $result } } Function Test-TeamsSipSrvRecord { [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', 'result', Justification='$result is assigned and used across the Begin/Process/End blocks.')] [OutputType([Bool])] [Alias( 'Test-LyncSipSrvRecord', 'Test-SkypeForBusinessSipSrvRecord', 'Test-SkypeForBusinessOnlineSipSrvRecord' )] Param( [Parameter(Mandatory, ValueFromPipeline)] [Alias('Name')] [ValidateNotNullOrEmpty()] [String[]] $DomainName ) Begin { $result = $true $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 $result = $false } 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 $result = $false } 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 $result = $false } 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 $result = $false } } Else { Write-Success -Product 'Teams/Skype' 'The SIP/TLS SRV record is correct.' } } } End { Return $result } } #endregion Teams/Skype for Business Online cmdlets # SIG # Begin signature block # MIIo5gYJKoZIhvcNAQcCoIIo1zCCKNMCAQExDzANBglghkgBZQMEAgEFADB5Bgor # BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG # KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCDJag+f1DAejGAC # 7Ranp9iQ94GIVq+hxjfkHnG8H3QgQaCCI60wggR+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/bt1nz8MIIGwjCCBKqgAwIBAgIQ # BUSv85SdCDmmv9s/X+VhFjANBgkqhkiG9w0BAQsFADBjMQswCQYDVQQGEwJVUzEX # MBUGA1UEChMORGlnaUNlcnQsIEluYy4xOzA5BgNVBAMTMkRpZ2lDZXJ0IFRydXN0 # ZWQgRzQgUlNBNDA5NiBTSEEyNTYgVGltZVN0YW1waW5nIENBMB4XDTIzMDcxNDAw # MDAwMFoXDTM0MTAxMzIzNTk1OVowSDELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDkRp # Z2lDZXJ0LCBJbmMuMSAwHgYDVQQDExdEaWdpQ2VydCBUaW1lc3RhbXAgMjAyMzCC # AiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAKNTRYcdg45brD5UsyPgz5/X # 5dLnXaEOCdwvSKOXejsqnGfcYhVYwamTEafNqrJq3RApih5iY2nTWJw1cb86l+uU # UI8cIOrHmjsvlmbjaedp/lvD1isgHMGXlLSlUIHyz8sHpjBoyoNC2vx/CSSUpIIa # 2mq62DvKXd4ZGIX7ReoNYWyd/nFexAaaPPDFLnkPG2ZS48jWPl/aQ9OE9dDH9kgt # XkV1lnX+3RChG4PBuOZSlbVH13gpOWvgeFmX40QrStWVzu8IF+qCZE3/I+PKhu60 # pCFkcOvV5aDaY7Mu6QXuqvYk9R28mxyyt1/f8O52fTGZZUdVnUokL6wrl76f5P17 # cz4y7lI0+9S769SgLDSb495uZBkHNwGRDxy1Uc2qTGaDiGhiu7xBG3gZbeTZD+BY # QfvYsSzhUa+0rRUGFOpiCBPTaR58ZE2dD9/O0V6MqqtQFcmzyrzXxDtoRKOlO0L9 # c33u3Qr/eTQQfqZcClhMAD6FaXXHg2TWdc2PEnZWpST618RrIbroHzSYLzrqawGw # 9/sqhux7UjipmAmhcbJsca8+uG+W1eEQE/5hRwqM/vC2x9XH3mwk8L9CgsqgcT2c # kpMEtGlwJw1Pt7U20clfCKRwo+wK8REuZODLIivK8SgTIUlRfgZm0zu++uuRONhR # B8qUt+JQofM604qDy0B7AgMBAAGjggGLMIIBhzAOBgNVHQ8BAf8EBAMCB4AwDAYD # VR0TAQH/BAIwADAWBgNVHSUBAf8EDDAKBggrBgEFBQcDCDAgBgNVHSAEGTAXMAgG # BmeBDAEEAjALBglghkgBhv1sBwEwHwYDVR0jBBgwFoAUuhbZbU2FL3MpdpovdYxq # II+eyG8wHQYDVR0OBBYEFKW27xPn783QZKHVVqllMaPe1eNJMFoGA1UdHwRTMFEw # T6BNoEuGSWh0dHA6Ly9jcmwzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydFRydXN0ZWRH # NFJTQTQwOTZTSEEyNTZUaW1lU3RhbXBpbmdDQS5jcmwwgZAGCCsGAQUFBwEBBIGD # MIGAMCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdpY2VydC5jb20wWAYIKwYB # BQUHMAKGTGh0dHA6Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydFRydXN0 # ZWRHNFJTQTQwOTZTSEEyNTZUaW1lU3RhbXBpbmdDQS5jcnQwDQYJKoZIhvcNAQEL # BQADggIBAIEa1t6gqbWYF7xwjU+KPGic2CX/yyzkzepdIpLsjCICqbjPgKjZ5+PF # 7SaCinEvGN1Ott5s1+FgnCvt7T1IjrhrunxdvcJhN2hJd6PrkKoS1yeF844ektrC # QDifXcigLiV4JZ0qBXqEKZi2V3mP2yZWK7Dzp703DNiYdk9WuVLCtp04qYHnbUFc # jGnRuSvExnvPnPp44pMadqJpddNQ5EQSviANnqlE0PjlSXcIWiHFtM+YlRpUurm8 # wWkZus8W8oM3NG6wQSbd3lqXTzON1I13fXVFoaVYJmoDRd7ZULVQjK9WvUzF4UbF # KNOt50MAcN7MmJ4ZiQPq1JE3701S88lgIcRWR+3aEUuMMsOI5ljitts++V+wQtaP # 4xeR0arAVeOGv6wnLEHQmjNKqDbUuXKWfpd5OEhfysLcPTLfddY2Z1qJ+Panx+VP # NTwAvb6cKmx5AdzaROY63jg7B145WPR8czFVoIARyxQMfq68/qTreWWqaNYiyjvr # moI1VygWy2nyMpqy0tg6uLFGhmu6F/3Ed2wVbK6rr3M66ElGt9V/zLY4wNjsHPW2 # obhDLN9OTH0eaHDAdwrUAuBcYLso/zjlUlrWrBciI0707NMX+1Br/wd3H3GXREHJ # uEbTbDJ8WC9nR2XlG3O2mflrLAZG70Ee8PBf4NvZrZCARK+AEEGKMYIEjzCCBIsC # AQEwVjBBMQswCQYDVQQGEwJVUzEQMA4GA1UEChMHQ2VydGVyYTEgMB4GA1UEAxMX # Q2VydGVyYSBDb2RlIFNpZ25pbmcgQ0ECEQCmdrm91Dy3ESrRRCFTEuwzMA0GCWCG # SAFlAwQCAQUAoIGEMBgGCisGAQQBgjcCAQwxCjAIoAKAAKECgAAwGQYJKoZIhvcN # AQkDMQwGCisGAQQBgjcCAQQwHAYKKwYBBAGCNwIBCzEOMAwGCisGAQQBgjcCARUw # LwYJKoZIhvcNAQkEMSIEIIz1RZ/DNdBE3iJAlxhRzYBfWXnwNuRld2lMgoh9EHle # MAsGByqGSM49AgEFAARnMGUCMHiJmgNHtHEslxgxVeGzQ45T8JhTjaC3sHcbi0WY # 32p8+8F3whPJEjYQjMKqJOBDmwIxANTW4D2zEVaT+ca2xnbdhrE+XxK8U4rRZ5md # 3uwqyqPtKGkatuJeH5I6+F4pcG1unKGCAyAwggMcBgkqhkiG9w0BCQYxggMNMIID # CQIBATB3MGMxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjE7 # MDkGA1UEAxMyRGlnaUNlcnQgVHJ1c3RlZCBHNCBSU0E0MDk2IFNIQTI1NiBUaW1l # U3RhbXBpbmcgQ0ECEAVEr/OUnQg5pr/bP1/lYRYwDQYJYIZIAWUDBAIBBQCgaTAY # BgkqhkiG9w0BCQMxCwYJKoZIhvcNAQcBMBwGCSqGSIb3DQEJBTEPFw0yNDAyMTMy # MjAzMzRaMC8GCSqGSIb3DQEJBDEiBCDAjAzys3tXEhVxNOLQhRyDnRWNMjGO9s+o # Zudv140lLjANBgkqhkiG9w0BAQEFAASCAgB+CwR+kBtHOSXJH+ohhC81P2mKTYaO # KMgIAPlymlvbQFUOFJLqtl/9l9zhM6wQfDCiiSAW1eVboRk6JLVmvkM9f8ffkzeq # g7N0JuE0biPpAgVFASNuAUk+lLxLCl2kI5RdXpIV+joZehcKGjDtR2dqYcQ9inRk # FyVn4kfLRBs53wcWjzjaq/lrURb8GzB0Ct2cONpbhCo9Ksd7Dt5LmRDjGolmvTkM # v0ymR6LTfOcLGfNH7VKoL6AqMHSmMRRFrtf+DWLY3vuQUqIyXmpgUPQ1nLrU0Y8P # 6eRdxRBa7Kp1F9f4wNEEQvQ06Eb4upXMOXwzRXkd4iqoAxdUP9wk4RvA2P+8T530 # rRl4gBEr0eJT5tqSs3FEfx8TYDrlkQTR4Sw/5dNbv1G7XUw6wUhcjmwGzd8xEHQB # SE7Iil7m++mdzZuk1jNRSNQKbAQKB26Gu3OKJE/tFbVjca2x0nuDiOfPGLfwSuJu # yADYhL20oY15WRPG9NL/Z08vXq8f8LOjuFRFN22SZnd4Z9RUry1IY3oGp1iFM5jD # lg1eIope/OqekATZtaL62CpVrUTJv9J9Y3Vx3TBx1BwUdvSOstPaAxaUpvgB0mIw # 1YfVQfKXNZLhBY/HX3dD5fl7L81DgDWZzv10HaOwbjuTqXJj2pOoE+vZtuhJzb61 # LtgLX0OebR4c8g== # SIG # End signature block |