AzureValidation/Microsoft.AzureStack.AzureValidation.Internal.psm1

<#############################################################
 # #
 # Copyright (C) Microsoft Corporation. All rights reserved. #
 # #
 #############################################################>


Import-LocalizedData LocalizedData -BaseDirectory $PSScriptRoot -Filename Microsoft.AzureStack.AzureValidation.Strings.psd1

# Test accounts can log on, catch: temporary passwords and account belongs to target Azure Cloud.
Function Test-AzureRMAccount
{
    param ([pscredential]$credential,[string]$AzureEnvironment)
    $thisFunction = $MyInvocation.MyCommand.Name
    try 
    {
        #Read Azure environment info
        Write-Log -message ("Checking account(s) {0} can log on to {1}" -f $credential.username, $AzureEnvironment) -function $thisFunction
        $login = Login-AzureRmAccount -EnvironmentName $AzureEnvironment -Credential $credential -ErrorAction Stop
        Write-Log -message ("Login Successful for {0} on {1}" -f $credential.username, $AzureEnvironment) -function $thisFunction
        $result = 'OK'
    }
    catch 
    {
        $result = 'Fail'
        if ($_.Exception.Message -eq 'Sequence contains no elements')
        {
            $errorDetails += ($LocalizedData.MicrosoftAccountNotSupported -f $credential.UserName)
            Write-Log -message ($LocalizedData.MicrosoftAccountNotSupported -f $credential.UserName) -function $thisFunction -type Error
        }
        elseif ($_.Exception.InnerException.ErrorCode -eq 'user_password_expired')
        {
            $errorDetails += ($LocalizedData.ForceResetPassword -f $credential.UserName)
            Write-Log -message ($LocalizedData.ForceResetPassword -f $credential.UserName) -function $thisFunction -type Error
        }
        elseif ($_.Exception.InnerException.ErrorCode -eq 'unknown_user_type')
        {
            $errorDetails += ($LocalizedData.UnknownUserType -f $credential.UserName, $AzureEnvironment)
            Write-Log -message ($LocalizedData.UnknownUserType -f $credential.UserName, $AzureEnvironment) -function $thisFunction -type Error
        }
        else
        {
            $errorDetails += $_.Exception.Message
            Write-Log -message ("Failed with exception: {0}" -f $_) -function $thisFunction -type Error
        }
    }
    @{'Test' = 'AccountLogon';'result' = $result; 'errorDetails' = $errorDetails; 'Assets' = @{'credential' = $credential.UserName; 'AzureEnvironment' = $AzureEnvironment; 'AADDirectoryTenantName' = $login.Context.tenant.TenantId}}
}

# Call install code to check Service Administrator
Function Test-AzsServiceAdministrator
{
    [OutputType([Hashtable])]
    [CmdletBinding()]
    Param(
            [Parameter(Mandatory=$true)]
            [pscredential] 
            $AADServiceAdministrator,

            [Parameter(Mandatory=$true)]
            [string]
            $AzureEnvironment,
            
            [Parameter(Mandatory=$true)]
            [string]
            $AADDirectoryTenantName

         )
    $thisFunction = $MyInvocation.MyCommand.Name
    $null = Import-Module $PSScriptRoot\AzureADConfiguration.psm1 -force
    $ErrorDetails = @()

    try
    {
        Write-Log -message "Launching get tenant details with: Get-AzureADTenantDetails -AzureEnvironment $AzureEnvironment -AADAdminCredential $AADServiceAdministrator -AADDirectoryTenantName $AADDirectoryTenantName -ErrorAction SilentlyContinue" -function $thisFunction
        $tenantDetails = Get-AzureADTenantDetails -AzureEnvironment $AzureEnvironment -AADAdminCredential $AADServiceAdministrator -AADDirectoryTenantName $AADDirectoryTenantName -ErrorAction SilentlyContinue
    }
    catch
    {
        if ($_.ErrorRecord.Exception.Message -match 'is not an administrator of the Azure Active Directory tenant')
        {
            $ErrorDetails += ($LocalizedData.NotAdminOfTenant -f $AADServiceAdministrator.UserName,$AADDirectoryTenantName )
            Write-Log -message ("Get-AzureADTenantDetails failed with: {0}" -f $LocalizedData.NotAdminOfTenant) -function $thisFunction -type Error
            $result = 'Fail'
        }
    }
    if ($tenantDetails)
    {
        $result = 'OK'
        Write-Log -message "Get-AzureADTenantDetails completed" -function $thisFunction
    }
    else
    {
        $result = 'Fail'
        $ErrorDetails += ($LocalizedData.NotAdminOfTenant -f $AADServiceAdministrator.UserName,$AADDirectoryTenantName )
        Write-Log -message ($LocalizedData.NotAdminOfTenant -f $AADServiceAdministrator.UserName,$AADDirectoryTenantName) -function $thisFunction -type Error
    }
    @{'Test' = 'ServiceAdministrator';'result' = $result; 'errorDetails' = $errorDetails; 'Assets' = @{'AADServiceAdmin' = $AADServiceAdministrator.UserName; 'AzureEnvironment' = $AzureEnvironment; 'AADDirectoryTenantName' = $AADDirectoryTenantName}}
}
Function Test-AzsRegistrationAccount
{
    [OutputType([Hashtable])]
    [CmdletBinding()]
    Param(
            [Parameter(Mandatory=$true)]
            [psobject] 
            $subscription,

            [Parameter(Mandatory=$true)]
            [string]
            $subscriptionId

         )
    $thisFunction = $MyInvocation.MyCommand.Name
    $errorDetail = @()
    $supportedSubscriptionTypes = 'EnterpriseAgreement_','CSP_'
    Write-Log -message ("Testing if subscription {0} is one of type {1}" -f $subscription.subscriptionid,($supportedSubscriptionTypes -join ',')) -function $thisFunction
    foreach ($supportedSubscriptionType in $supportedSubscriptionTypes)
    {
        if ($subscription.subscriptionPolicies.quotaId -match $supportedSubscriptionType)
        {
            $supported = $true
            Write-Log -message ("Success subscription {0} is of type {1}" -f $subscription.subscriptionid,$subscription.subscriptionPolicies.quotaId) -function $thisFunction
            break
        }
        else
        {
            $supported = $false
            Write-Log -message ("Subscription {0} is of type {1}" -f $subscription.subscriptionid,$subscription.subscriptionPolicies.quotaId) -function $thisFunction -type Error
        }
    }
    # If both subscription types don't match, give one failure reason.
    if ($supported -ne $true)
    {
        $errorDetail += ($LocalizedData.SubscriptionNotSupported -f $subscription.subscriptionid,$subscription.subscriptionPolicies.quotaId)
        Write-Log -message ($LocalizedData.SubscriptionNotSupported -f $subscription.subscriptionid,$subscription.subscriptionPolicies.quotaId) -function $thisFunction -type Error
    }
    
    # Check subscription is enabled
    if ($subscription.state -eq 'Enabled')
    {
        $enabled = $true
        Write-Log -message ("Subscription {0} is enabled" -f $subscription.subscriptionid) -function $thisFunction
    }
    else
    {
        $enabled = $false
        $errorDetail += ($LocalizedData.SubscriptionNotEnabled -f $subscription.subscriptionid)
        Write-Log -message ($LocalizedData.SubscriptionNotEnabled -f $subscription.subscriptionid) -function $thisFunction
    }

    # Check subscriptions match
    Write-Log -message ("Checking subscription {0} matches given subscription {1}" -f $subscription.subscriptionid, $subscriptionid) -function $thisFunction
    if ($subscription.subscriptionid -eq $subscriptionid)
    {
        $subscriptionMatch = $true
        Write-Log -message ("Subscription {0} matches given subscription {1}" -f $subscription.subscriptionid, $subscriptionid) -function $thisFunction
    }
    Else
    {
        $subscriptionMatch = $false
        $errorDetail += ($LocalizedData.SubscriptionNotMatch -f $subscription.subscriptionid, $subscriptionid)
        Write-Log -message ($LocalizedData.SubscriptionNotMatch -f $subscription.subscriptionid, $subscriptionid) -function $thisFunction -type Error
    }
    if ($supported -AND $enabled -AND $subscriptionMatch)
    {
        $result = 'OK'
        Write-Log -message ("Overall check for subscription {0} is success" -f $subscription.subscriptionid) -function $thisFunction
    }
    else
    {
        $result = 'Fail'
        Write-Log -message ("Overall check for subscription {0} is error with detail {1}: " -f $subscription.subscriptionid, ($errorDetail -join ',')) -function $thisFunction -Type Error
    }
    @{'Test' = 'RegistrationAccount';'result' = $result; 'errorDetails' = $errorDetail;'Assets' = @{'SubscriptionId' = $subscription.subscriptionid; 'SubscriptionType' = $subscription.subscriptionPolicies.quotaId; 'Enabled' = $enabled}}
}

# Get subscription detail via REST so we can see the subscription type (CSP, EA, PAYG etc.)
function Get-AzureSubscriptionDetail
{
    [OutputType([Hashtable])]
    [CmdletBinding()]
    param ([string]$tenantid,
           [string]$subscriptionid,
           [pscredential]$credential,
           [string]$AzureEnvironment
          )
    $thisFunction = $MyInvocation.MyCommand.Name
    try
    {
        Write-Log -message ("TenantID: {0}" -f $tenantid) -function $thisFunction
        Write-Log -message ("SubscriptionId: {0}" -f $subscriptionid) -function $thisFunction
        $errorDetails = @()
        # Set well-known client ID for AzurePowerShell
        $clientId = "1950a258-227b-4e31-a9cf-717495945fc2"

        # Set redirect URI for Azure PowerShell
        $redirectUri = "urn:ietf:wg:oauth:2.0:oob"
 
        # Set Resource App URI as ARM
        Write-Log -message ("Retrieving ARMURI for {0}" -f $AzureEnvironment) -function $thisFunction
        $resourceAppIdURI = (Get-AzureURIs -AzureEnvironment $AzureEnvironment).ARMUri
        Write-Log -message ("Retrieved ARMURI {0}" -f $resourceAppIdURI) -function $thisFunction
        
        # Set Authority to Azure AD Tenant
        $authority = "{0}{1}" -f (Get-AzureURIs -AzureEnvironment $AzureEnvironment).LoginUri, $tenantid
        Write-Log -message ("Authority {0}" -f $authority) -function $thisFunction

        # Create Authentication Context tied to Azure AD Tenant
        $authContext = New-Object "Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationContext" -ArgumentList $authority

        # Acquire token
        $userCreds = new-object Microsoft.IdentityModel.Clients.ActiveDirectory.UserCredential($credential.userName, $credential.Password)
        $authResult = $authContext.AcquireToken($resourceAppIdURI, $clientId, $userCreds)

        # Output bearer token
        $authHeader = $authResult.CreateAuthorizationHeader()
        $header = @{
        'Content-Type'='application/json'
        'Authorization'=$authHeader
        }

        # Make call
        $ApiVersion = '2017-08-01'
        $URI = $resourceAppIdURI + "subscriptions/${subscriptionId}?api-version=$ApiVersion"
        Write-Log -message ("Making call to uri {0} with header {1}" -f $MyInvocation.MyCommand.Name, $uri,$header) -function $thisFunction
        $subscription = Invoke-RestMethod -Uri $URI -Method GET -Headers $header
    }
    catch
    {
        if ($_.exception.Message -match 'Forbidden|Unauthorized')
        {
            $errorDetails += ($LocalizedData.UserNotAuthorizedForSubscription -f $credential.username, $tenantid, $subscriptionid)
            Write-Log -message ($LocalizedData.UserNotAuthorizedForSubscription -f $credential.username, $tenantid, $subscriptionid) -function $thisFunction -type Error
        }
        else
        {
            $errorDetails += ("{0} threw an error: {1}" -f $MyInvocation.MyCommand.Name, $_.exception)
            Write-Log -message ("error: {0}" -f $_.exception) -function $thisFunction -type Error
        }
    }
    @{'Test' = 'GetSubscription'; 'subscription' = $subscription; 'errorDetails' = $errorDetails; 'AADDirectoryTenantName' = $tenantid; 'subscriptionid' = $subscriptionid; 'credential' = $credential.UserName; 'AzureEnvironment' = $AzureEnvironment}
}

function Test-AzureRMAccountWrapper
{
    param ([pscredential]$credential,$AzureEnvironment)
    $thisFunction = $MyInvocation.MyCommand.Name
    # Make sure any accounts passed can be logged onto successfully
    Write-Log -message ("Checking Account(s) can logon: {0} " -f ($credential.UserName -join ',')) -function $thisFunction
    Write-Host "Checking Account(s) can logon: " -nonewline
    $loginResult = Test-AzureRMAccount -credential $credential -AzureEnvironment $AzureEnvironment
    if ($loginResult.result -contains 'Fail')
    {
        Write-Host "Fail " -foregroundColor Red
        Write-Host "Error Details for logon test: "
        $loginResult.errorDetails | ForEach-Object {Write-Host "$_" -foregroundcolor Yellow}
    }
    else 
    {
        Write-Host "OK" -foregroundColor Green
    }
    $loginResult
}

function Write-Log
{
    param([string]$Message,
          [string]$Type = 'Info',
          [string]$Function
         )
        $outfile = "$PSScriptRoot\AADChecker.log"
        $entry = "[{0}] [{1}] [{2}] {3}" -f ([datetime]::now).tostring(), $type, $function, $message 
        $entry | Out-File -FilePath $outfile -Append -Force
}
# SIG # Begin signature block
# MIIdqAYJKoZIhvcNAQcCoIIdmTCCHZUCAQExCzAJBgUrDgMCGgUAMGkGCisGAQQB
# gjcCAQSgWzBZMDQGCisGAQQBgjcCAR4wJgIDAQAABBAfzDtgWUsITrck0sYpfvNR
# AgEAAgEAAgEAAgEAAgEAMCEwCQYFKw4DAhoFAAQUcmqi4CQT2gW3nF9UlW6tw4KZ
# tKagghhUMIIEwjCCA6qgAwIBAgITMwAAAL+RbPt8GiTgIgAAAAAAvzANBgkqhkiG
# 9w0BAQUFADB3MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4G
# A1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSEw
# HwYDVQQDExhNaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EwHhcNMTYwOTA3MTc1ODQ5
# WhcNMTgwOTA3MTc1ODQ5WjCBsjELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hp
# bmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jw
# b3JhdGlvbjEMMAoGA1UECxMDQU9DMScwJQYDVQQLEx5uQ2lwaGVyIERTRSBFU046
# NTdDOC0yRDE1LTFDOEIxJTAjBgNVBAMTHE1pY3Jvc29mdCBUaW1lLVN0YW1wIFNl
# cnZpY2UwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCt7X+GwPaidVcV
# TRT2yohV/L1dpTMCvf4DHlCY0GUmhEzD4Yn22q/qnqZTHDd8IlI/OHvKhWC9ksKE
# F+BgBHtUQPSg7s6+ZXy69qX64r6m7X/NYizeK31DsScLsDHnqsbnwJaNZ2C2u5hh
# cKsHvc8BaSsv/nKlr6+eg2iX2y9ai1uB1ySNeunEtdfchAr1U6Qb7AJHrXMTdKl8
# ptLov67aFU0rRRMwQJOWHR+o/gQa9v4z/f43RY2PnMRoF7Dztn6ditoQ9CgTiMdS
# MtsqFWMAQNMt5bZ8oY1hmgkSDN6FwTjVyUEE6t3KJtgX2hMHjOVqtHXQlud0GR3Z
# LtAOMbS7AgMBAAGjggEJMIIBBTAdBgNVHQ4EFgQU5GwaORrHk1i0RjZlB8QAt3kX
# nBEwHwYDVR0jBBgwFoAUIzT42VJGcArtQPt2+7MrsMM1sw8wVAYDVR0fBE0wSzBJ
# oEegRYZDaHR0cDovL2NybC5taWNyb3NvZnQuY29tL3BraS9jcmwvcHJvZHVjdHMv
# TWljcm9zb2Z0VGltZVN0YW1wUENBLmNybDBYBggrBgEFBQcBAQRMMEowSAYIKwYB
# BQUHMAKGPGh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2kvY2VydHMvTWljcm9z
# b2Z0VGltZVN0YW1wUENBLmNydDATBgNVHSUEDDAKBggrBgEFBQcDCDANBgkqhkiG
# 9w0BAQUFAAOCAQEAjt62jcZ+2YBqm7RKit827DRU9OKioi6HEERT0X0bL+JjUTu3
# 7k4piPcK3J/0cfktWuPjrYSuySa/NbkmlvAhQV4VpoWxipx3cZplF9HK9IH4t8AD
# YDxUI5u1xb2r24aExGIzWY+1uH92bzTKbAjuwNzTMQ1z10Kca4XXPI4HFZalXxgL
# fbjCkV3IKNspU1TILV0Dzk0tdKAwx/MoeZN1HFcB9WjzbpFnCVH+Oy/NyeJOyiNE
# 4uT/6iyHz1+XCqf2nIrV/DXXsJYKwifVlOvSJ4ZrV40MYucq3lWQuKERfXivLFXl
# dKyXQrS4eeToRPSevRisc0GBYuZczpkdeN5faDCCBgEwggPpoAMCAQICEzMAAADE
# 6Yn4eoFQ6f8AAAAAAMQwDQYJKoZIhvcNAQELBQAwfjELMAkGA1UEBhMCVVMxEzAR
# BgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1p
# Y3Jvc29mdCBDb3Jwb3JhdGlvbjEoMCYGA1UEAxMfTWljcm9zb2Z0IENvZGUgU2ln
# bmluZyBQQ0EgMjAxMTAeFw0xNzA4MTEyMDIwMjRaFw0xODA4MTEyMDIwMjRaMHQx
# CzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRt
# b25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xHjAcBgNVBAMTFU1p
# Y3Jvc29mdCBDb3Jwb3JhdGlvbjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC
# ggEBAIiKuCTDB4+agHkV/CZg/HKILPr0o5eIlka3o8tfiS86My4ekXj6fKkfggG1
# essavAPKRuvFmff7BB3yhQr/Im6h8mc9xScY5Sgf9QSUQWPs47oVjO0TmjXeOHBU
# bzvsrUUJMEnBvo8wmQzLdsn3c5UWd9GLu5THCIUg7R6oNfFxwuB0AEuK0tyR69Z4
# /o36rWCIPb25H65il7/FhLGQrtavK9NU+zXazXGS5h7/7HFry38IdnTgEFFI1PEA
# yEhMowc15VkN/XycyOZa44X11poPH46m5IQXwdbKnx0Bx/1IpxOSM5chSDL4wiSi
# ALK+U8qDbilbge84boDzu+wTC+sCAwEAAaOCAYAwggF8MB8GA1UdJQQYMBYGCisG
# AQQBgjdMCAEGCCsGAQUFBwMDMB0GA1UdDgQWBBTL1mKEz2A56v9nwlzSyLurt8MT
# mDBSBgNVHREESzBJpEcwRTENMAsGA1UECxMETU9QUjE0MDIGA1UEBRMrMjMwMDEy
# K2M4MDRiNWVhLTQ5YjQtNDIzOC04MzYyLWQ4NTFmYTIyNTRmYzAfBgNVHSMEGDAW
# gBRIbmTlUAXTgqoXNzcitW2oynUClTBUBgNVHR8ETTBLMEmgR6BFhkNodHRwOi8v
# d3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NybC9NaWNDb2RTaWdQQ0EyMDExXzIw
# MTEtMDctMDguY3JsMGEGCCsGAQUFBwEBBFUwUzBRBggrBgEFBQcwAoZFaHR0cDov
# L3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9jZXJ0cy9NaWNDb2RTaWdQQ0EyMDEx
# XzIwMTEtMDctMDguY3J0MAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADggIB
# AAYWH9tXwlDII0+iUXjX7fj9zb3VwPH5G1btU8hpRwXVxMvs4vyZW5VfETgowAVF
# E+CaeYi8Zqvbu+sCVSO3PSN4QW2u+PEAWpSZihzMCZXQmhxEMKmlFse6R1v1KzSL
# n49YN8NOHK8iyhDN2IIQqTXwriLIjySmgYvfJxzkZh2JPi7/VwNNwW6DoDLrtLMv
# UFZdBrEVjMgdY7dzDOPWeiYPKpZFpzKDPpY+V0l3I4n+sRDHiuUIFVHFK1oxWzlq
# lqikiGuWKG/xxK7qvUUXzGJOgbVUGkeOmKVtwG4nxvgnH8jtIKkLsfHOC5qU4mqd
# aYOhNtdtIP6F1f/DuJc2Cf49FMGYFKnAhszvgsGrVSRDGLVIhXiG0PnSnT8Z2RSJ
# 542faCSIaDupx4BOJucIIUxj/ZyTFU0ztVZgT9dKuTiO/y7dsV+kQ2vJeM+xu2uP
# g2yHcqrqpfuf3RrWOfxkyW0+COV8g7GtvKO6e8+WVqR6WMsSR2LSIe/8PMQxC/cv
# PmSlN29gUD+3RJBPoAuLvn5Y9sdnh2HbnpjEyIzLb0fhwC6U7bH2sDBt7GpJqOmW
# dsi9CMT+O/WuczcGslbPGdS79ZTKhxzygGoBT7YbgXOz01siPzpYGN+I7mfESacv
# 3CWLPV7Q7DREkR28kQx2gj7vxNgtoQQCjkj5790CzwOiMIIGBzCCA++gAwIBAgIK
# YRZoNAAAAAAAHDANBgkqhkiG9w0BAQUFADBfMRMwEQYKCZImiZPyLGQBGRYDY29t
# MRkwFwYKCZImiZPyLGQBGRYJbWljcm9zb2Z0MS0wKwYDVQQDEyRNaWNyb3NvZnQg
# Um9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkwHhcNMDcwNDAzMTI1MzA5WhcNMjEw
# NDAzMTMwMzA5WjB3MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQ
# MA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9u
# MSEwHwYDVQQDExhNaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EwggEiMA0GCSqGSIb3
# DQEBAQUAA4IBDwAwggEKAoIBAQCfoWyx39tIkip8ay4Z4b3i48WZUSNQrc7dGE4k
# D+7Rp9FMrXQwIBHrB9VUlRVJlBtCkq6YXDAm2gBr6Hu97IkHD/cOBJjwicwfyzMk
# h53y9GccLPx754gd6udOo6HBI1PKjfpFzwnQXq/QsEIEovmmbJNn1yjcRlOwhtDl
# KEYuJ6yGT1VSDOQDLPtqkJAwbofzWTCd+n7Wl7PoIZd++NIT8wi3U21StEWQn0gA
# SkdmEScpZqiX5NMGgUqi+YSnEUcUCYKfhO1VeP4Bmh1QCIUAEDBG7bfeI0a7xC1U
# n68eeEExd8yb3zuDk6FhArUdDbH895uyAc4iS1T/+QXDwiALAgMBAAGjggGrMIIB
# pzAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBQjNPjZUkZwCu1A+3b7syuwwzWz
# DzALBgNVHQ8EBAMCAYYwEAYJKwYBBAGCNxUBBAMCAQAwgZgGA1UdIwSBkDCBjYAU
# DqyCYEBWJ5flJRP8KuEKU5VZ5KShY6RhMF8xEzARBgoJkiaJk/IsZAEZFgNjb20x
# GTAXBgoJkiaJk/IsZAEZFgltaWNyb3NvZnQxLTArBgNVBAMTJE1pY3Jvc29mdCBS
# b290IENlcnRpZmljYXRlIEF1dGhvcml0eYIQea0WoUqgpa1Mc1j0BxMuZTBQBgNV
# HR8ESTBHMEWgQ6BBhj9odHRwOi8vY3JsLm1pY3Jvc29mdC5jb20vcGtpL2NybC9w
# cm9kdWN0cy9taWNyb3NvZnRyb290Y2VydC5jcmwwVAYIKwYBBQUHAQEESDBGMEQG
# CCsGAQUFBzAChjhodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpL2NlcnRzL01p
# Y3Jvc29mdFJvb3RDZXJ0LmNydDATBgNVHSUEDDAKBggrBgEFBQcDCDANBgkqhkiG
# 9w0BAQUFAAOCAgEAEJeKw1wDRDbd6bStd9vOeVFNAbEudHFbbQwTq86+e4+4LtQS
# ooxtYrhXAstOIBNQmd16QOJXu69YmhzhHQGGrLt48ovQ7DsB7uK+jwoFyI1I4vBT
# Fd1Pq5Lk541q1YDB5pTyBi+FA+mRKiQicPv2/OR4mS4N9wficLwYTp2Oawpylbih
# OZxnLcVRDupiXD8WmIsgP+IHGjL5zDFKdjE9K3ILyOpwPf+FChPfwgphjvDXuBfr
# Tot/xTUrXqO/67x9C0J71FNyIe4wyrt4ZVxbARcKFA7S2hSY9Ty5ZlizLS/n+YWG
# zFFW6J1wlGysOUzU9nm/qhh6YinvopspNAZ3GmLJPR5tH4LwC8csu89Ds+X57H21
# 46SodDW4TsVxIxImdgs8UoxxWkZDFLyzs7BNZ8ifQv+AeSGAnhUwZuhCEl4ayJ4i
# IdBD6Svpu/RIzCzU2DKATCYqSCRfWupW76bemZ3KOm+9gSd0BhHudiG/m4LBJ1S2
# sWo9iaF2YbRuoROmv6pH8BJv/YoybLL+31HIjCPJZr2dHYcSZAI9La9Zj7jkIeW1
# sMpjtHhUBdRBLlCslLCleKuzoJZ1GtmShxN1Ii8yqAhuoFuMJb+g74TKIdbrHk/J
# mu5J4PcBZW+JC33Iacjmbuqnl84xKf8OxVtc2E0bodj6L54/LlUWa8kTo/0wggd6
# MIIFYqADAgECAgphDpDSAAAAAAADMA0GCSqGSIb3DQEBCwUAMIGIMQswCQYDVQQG
# EwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwG
# A1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTIwMAYDVQQDEylNaWNyb3NvZnQg
# Um9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgMjAxMTAeFw0xMTA3MDgyMDU5MDla
# Fw0yNjA3MDgyMTA5MDlaMH4xCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5n
# dG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9y
# YXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25pbmcgUENBIDIwMTEw
# ggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCr8PpyEBwurdhuqoIQTTS6
# 8rZYIZ9CGypr6VpQqrgGOBoESbp/wwwe3TdrxhLYC/A4wpkGsMg51QEUMULTiQ15
# ZId+lGAkbK+eSZzpaF7S35tTsgosw6/ZqSuuegmv15ZZymAaBelmdugyUiYSL+er
# CFDPs0S3XdjELgN1q2jzy23zOlyhFvRGuuA4ZKxuZDV4pqBjDy3TQJP4494HDdVc
# eaVJKecNvqATd76UPe/74ytaEB9NViiienLgEjq3SV7Y7e1DkYPZe7J7hhvZPrGM
# XeiJT4Qa8qEvWeSQOy2uM1jFtz7+MtOzAz2xsq+SOH7SnYAs9U5WkSE1JcM5bmR/
# U7qcD60ZI4TL9LoDho33X/DQUr+MlIe8wCF0JV8YKLbMJyg4JZg5SjbPfLGSrhwj
# p6lm7GEfauEoSZ1fiOIlXdMhSz5SxLVXPyQD8NF6Wy/VI+NwXQ9RRnez+ADhvKwC
# gl/bwBWzvRvUVUvnOaEP6SNJvBi4RHxF5MHDcnrgcuck379GmcXvwhxX24ON7E1J
# MKerjt/sW5+v/N2wZuLBl4F77dbtS+dJKacTKKanfWeA5opieF+yL4TXV5xcv3co
# KPHtbcMojyyPQDdPweGFRInECUzF1KVDL3SV9274eCBYLBNdYJWaPk8zhNqwiBfe
# nk70lrC8RqBsmNLg1oiMCwIDAQABo4IB7TCCAekwEAYJKwYBBAGCNxUBBAMCAQAw
# HQYDVR0OBBYEFEhuZOVQBdOCqhc3NyK1bajKdQKVMBkGCSsGAQQBgjcUAgQMHgoA
# UwB1AGIAQwBBMAsGA1UdDwQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQY
# MBaAFHItOgIxkEO5FAVO4eqnxzHRI4k0MFoGA1UdHwRTMFEwT6BNoEuGSWh0dHA6
# Ly9jcmwubWljcm9zb2Z0LmNvbS9wa2kvY3JsL3Byb2R1Y3RzL01pY1Jvb0NlckF1
# dDIwMTFfMjAxMV8wM18yMi5jcmwwXgYIKwYBBQUHAQEEUjBQME4GCCsGAQUFBzAC
# hkJodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpL2NlcnRzL01pY1Jvb0NlckF1
# dDIwMTFfMjAxMV8wM18yMi5jcnQwgZ8GA1UdIASBlzCBlDCBkQYJKwYBBAGCNy4D
# MIGDMD8GCCsGAQUFBwIBFjNodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3Bz
# L2RvY3MvcHJpbWFyeWNwcy5odG0wQAYIKwYBBQUHAgIwNB4yIB0ATABlAGcAYQBs
# AF8AcABvAGwAaQBjAHkAXwBzAHQAYQB0AGUAbQBlAG4AdAAuIB0wDQYJKoZIhvcN
# AQELBQADggIBAGfyhqWY4FR5Gi7T2HRnIpsLlhHhY5KZQpZ90nkMkMFlXy4sPvjD
# ctFtg/6+P+gKyju/R6mj82nbY78iNaWXXWWEkH2LRlBV2AySfNIaSxzzPEKLUtCw
# /WvjPgcuKZvmPRul1LUdd5Q54ulkyUQ9eHoj8xN9ppB0g430yyYCRirCihC7pKkF
# DJvtaPpoLpWgKj8qa1hJYx8JaW5amJbkg/TAj/NGK978O9C9Ne9uJa7lryft0N3z
# Dq+ZKJeYTQ49C/IIidYfwzIY4vDFLc5bnrRJOQrGCsLGra7lstnbFYhRRVg4MnEn
# Gn+x9Cf43iw6IGmYslmJaG5vp7d0w0AFBqYBKig+gj8TTWYLwLNN9eGPfxxvFX1F
# p3blQCplo8NdUmKGwx1jNpeG39rz+PIWoZon4c2ll9DuXWNB41sHnIc+BncG0Qax
# dR8UvmFhtfDcxhsEvt9Bxw4o7t5lL+yX9qFcltgA1qFGvVnzl6UJS0gQmYAf0AAp
# xbGbpT9Fdx41xtKiop96eiL6SJUfq/tHI4D1nvi/a7dLl+LrdXga7Oo3mXkYS//W
# syNodeav+vyL6wuA6mk7r/ww7QRMjt/fdW1jkT3RnVZOT7+AVyKheBEyIXrvQQqx
# P/uozKRdwaGIm1dxVk5IRcBCyZt2WwqASGv9eZ/BvW1taslScxMNelDNMYIEvjCC
# BLoCAQEwgZUwfjELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAO
# BgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEo
# MCYGA1UEAxMfTWljcm9zb2Z0IENvZGUgU2lnbmluZyBQQ0EgMjAxMQITMwAAAMTp
# ifh6gVDp/wAAAAAAxDAJBgUrDgMCGgUAoIHSMBkGCSqGSIb3DQEJAzEMBgorBgEE
# AYI3AgEEMBwGCisGAQQBgjcCAQsxDjAMBgorBgEEAYI3AgEVMCMGCSqGSIb3DQEJ
# BDEWBBRIhlDu/FMxlW4vki+PFei/CV8nqzByBgorBgEEAYI3AgEMMWQwYqBIgEYA
# TQBpAGMAcgBvAHMAbwBmAHQAIABBAHoAdQByAGUAUwB0AGEAYwBrACAAUABhAHIA
# dABuAGUAcgBUAG8AbwBsAGsAaQB0oRaAFGh0dHA6Ly9Db2RlU2lnbkluZm8gMA0G
# CSqGSIb3DQEBAQUABIIBABlErW521CwtZDbhv9wrFcKF2iMIobU/bWM//N2i4Q5o
# KP09SjcypwwQpdCVk7Zss18J203AKDxSSaZYyhXOKBnYCqgmvS+6CojCrvLCMlqA
# hOmy0R4q1b4rNy+JSeGACV4WVAupfk0qG+qtVDMdPbS1Zzvr7yKX7HexJwxU/vwn
# TuXk71IVtTpaiAk5QNQEXRMxmUUMxmZP9pQCxSgBSAXeH7/CoO7S0JkPLaNmJ9pQ
# 2l7lxP7TjllWfcj/7mSQMGXMVLVIgtYpcbdjnGvqYWyup2Eq7ZI/WOQc1BK+bK7+
# BBup3DS1hFFyY/dOT58TzcExm8cKdvuSngxpz2M2QFqhggIoMIICJAYJKoZIhvcN
# AQkGMYICFTCCAhECAQEwgY4wdzELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hp
# bmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jw
# b3JhdGlvbjEhMB8GA1UEAxMYTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBAhMzAAAA
# v5Fs+3waJOAiAAAAAAC/MAkGBSsOAwIaBQCgXTAYBgkqhkiG9w0BCQMxCwYJKoZI
# hvcNAQcBMBwGCSqGSIb3DQEJBTEPFw0xODA0MjcwOTI5MzBaMCMGCSqGSIb3DQEJ
# BDEWBBRo24HrjwKzua6Ci0A7+q2JGANPYjANBgkqhkiG9w0BAQUFAASCAQBaKS3B
# A6CfuGMbpknlREU1dR0i9Ol3dGp86PuFdYJRuJukoQ+pQuTINNopUw0aV44kswA9
# TlMaCBAxuYjfbn5qumra1yNHmPgDHzrkS7hmwGThgBLjf+QneBASDg1V6lDfUXCN
# omQGTlZEBig73EMmLTy5py4lltgnuDDr9piRGu1L+w0sY+HdXobfJCWhrwG+NOhF
# J6PRIzwKe/y+OOyeMECbqnS9nCXFJeBo3UQy7O3GScMFybEEAPQ7hVz3ll0dWINR
# UfCTAmk+N1Sto4svhczsQu+Pt61q5Yh3gWWxRwstLiQtIx+PJK2zI7DW3zR1ie2v
# y3jvUC1DfimK25Xq
# SIG # End signature block