public/GrantAppConsentAndManagementPermissionToAiAdmin.ps1

<#
.SYNOPSIS
Grants app consent and management permissions to users assigned the AI Admin role.
 
.DESCRIPTION
The GrantAppConsentAndManagementPermissionToAiAdmin function is used to assign Microsoft Graph application permissions and create a custom role for users holding the AI Admin role. It ensures necessary modules are installed, collects AI Admin users, creates or reuses a custom permission grant policy, assigns specific permissions, and finally assigns a custom role for managing app and connector permissions.
 
This function is particularly useful for administrators configuring Microsoft Graph Connectors or enabling custom connector management capabilities for Copilot scenarios.
 
.FUNCTION GrantAppConsentAndManagementPermissionToAiAdmin
Automates the assignment of Graph API permissions and custom roles to AI Admins to enable them to manage external connectors and consent to applications.
 
.PARAMETER AiAdminRoleDefinitionId
Optional. Role Definition ID for the AI Admin role. Default is "d2562ede-74db-457e-a7b6-544e236ebb61".
 
.PARAMETER PolicyName
Optional. Name of the permission grant policy to create or reuse. Default is "Grant-Consent-Permissions-To-AIAdmins".
 
.PARAMETER UnifiedRoleName
Optional. Name of the custom role to assign to AI Admins. Default is "Custom Connector Management for Copilot".
 
.NOTES
- Requires Microsoft Graph PowerShell SDK modules.
- Prompts for user confirmation before proceeding with changes.
- Requires the following Microsoft Graph permissions: RoleManagement.ReadWrite.Directory, Policy.ReadWrite.PermissionGrant, User.Read.All.
- Creates or updates permission grant policies and assigns required permissions.
- Automatically assigns the created role to users with the AI Admin role.
 
#>


function GrantAppConsentAndManagementPermissionToAiAdmin {
    param(
        [string]$AiAdminRoleDefinitionId = "d2562ede-74db-457e-a7b6-544e236ebb61",
        [string]$PolicyName = "Grant-Consent-Permissions-To-AIAdmins",
        [string]$UnifiedRoleName = "Custom Connector Management for Copilot"
    )

    # === Required Modules ===
    $requiredModules = @(
        "Microsoft.Graph.Authentication",
        "Microsoft.Graph.Identity.DirectoryManagement", 
        "Microsoft.Graph.Users",
        "Microsoft.Graph.Identity.Governance",
        "Microsoft.Graph.Identity.SignIns"
    )

    Write-Host "=======================================================================" -ForegroundColor Cyan
    Write-Host "Grant App registration and consent permissions for Copilot connectors" -ForegroundColor Cyan
    Write-Host "=======================================================================" -ForegroundColor Cyan
    Write-Host ""
    Write-Host "This script performs the following actions:" -ForegroundColor Yellow
    Write-Host "- Grants application registration permissions to users who are currently assigned with an AI Administrator role."
    Write-Host "- Grants permissions to consent to Graph Connector-related API permissions by doing the following:"
    Write-Host " - Creates a custom consent policy named 'Grant Consent Permissions To AIAdmins' to allow consent to ExternalItem.* and ExternalConnection.*."
    Write-Host " - Creates a custom role named 'Custom Connector Management for Copilot' and assigns the consent policy to this role."
    Write-Host " - Assigns the custom role to existing AI Administrators."
    Write-Host ""

    Write-Host "=======================================================================" -ForegroundColor Cyan
    Write-Host "Installing Required Modules (This may take a few minutes)" -ForegroundColor Cyan
    Write-Host "=======================================================================" -ForegroundColor Cyan

    foreach ($module in $requiredModules) {
        Write-Host "Checking for $module module..."
        if (-not (Get-Module -ListAvailable -Name $module)) {
            Write-Host "$module module not found. Installing..."
            try {
                Install-Module -Name $module -Scope CurrentUser -Force -AllowClobber
                Write-Host "$module module installed successfully." -ForegroundColor Green
            } catch {
                Write-Error "Failed to install $module module. Error: $_"
                return
            }
        } else {
            Write-Host "$module module is already installed." -ForegroundColor Green
        }

        Write-Host "Importing $module module..."
        Import-Module $module -Force
    }

    Write-Host "`nAll required modules have been installed and imported successfully!" -ForegroundColor Green

    Write-Host "=======================================================================" -ForegroundColor Cyan
    Write-Host "Login With Admin Account" -ForegroundColor Cyan
    Write-Host "=======================================================================" -ForegroundColor Cyan
    Write-Host ""
    Write-Host "- Please log in with an administrator account and grant the necessary permissions for the cmdlet to execute:" -ForegroundColor Yellow
    Write-Host "- After logging in, return to this window." -ForegroundColor Yellow
    Write-Host "- You will be able to review the users who will be granted permissions before the cmdlet proceeds further." -ForegroundColor Yellow
    Write-Host ""

    $initialConsent = Read-Host "Do you want to proceed to login with your admin account? (yes/no)"
    if ($initialConsent -ne "yes" -and $initialConsent -ne "y") {
        Write-Warning "Operation cancelled by user."
        return
    }

    Connect-MgGraph -Scopes RoleManagement.ReadWrite.Directory, Policy.ReadWrite.PermissionGrant, User.Read.All -NoWelcome

    $assignments = Get-MgRoleManagementDirectoryRoleAssignment -Filter "roleDefinitionId eq '$AiAdminRoleDefinitionId'"
    $userPrincipalInfos = @()

    foreach ($assignment in $assignments) {
        try {
            $user = Get-MgUser -UserId $assignment.PrincipalId -ErrorAction SilentlyContinue
            if ($user) {
                $userPrincipalInfos += @{
                    Id = $user.Id
                    Email = $user.UserPrincipalName
                }
            }
        } catch {}
    }

    if (-not $userPrincipalInfos) {
        Write-Warning "No users with AI Admin role found. Exiting."
        return
    }

    Write-Host "=======================================================================" -ForegroundColor Cyan
    Write-Host "Review Users And Permissions" -ForegroundColor Cyan
    Write-Host "=======================================================================" -ForegroundColor Cyan
    Write-Host "Users who will be granted the permissions:" -ForegroundColor Green
    for ($i = 0; $i -lt $userPrincipalInfos.Count; $i++) {
        Write-Host "$($i + 1). $($userPrincipalInfos[$i].Email)" -ForegroundColor Green
    }
    Write-Host ""
    Write-Host "Permissions that will be granted:" -ForegroundColor Yellow
    Write-Host "- Ability to consent to ExternalConnection.* and ExternalItem.*(ReadWrite.OwnedBy, ReadWrite.All, Read.All)" -ForegroundColor Yellow
    Write-Host "- Register application and manage permissions for apps owned" -ForegroundColor Yellow
    Write-Host ""

    $continueWithGraphConnector = Read-Host "Do you want to continue with assigning these permissions to these users? (yes/no)"
    if ($continueWithGraphConnector -ne "yes" -and $continueWithGraphConnector -ne "y") {
        Write-Warning "Operation cancelled by user."
        return
    }

    Write-Host "`nCreating custom consent policy -'$PolicyName'..." -ForegroundColor Green

    $permissions = @{
        "f431331c-49a6-499f-be1c-62af19c34a9d" = "ExternalConnection.ReadWrite.OwnedBy"
        "1914711b-a1cb-4793-b019-c2ce0ed21b8c" = "ExternalConnection.Read.All"
        "8116ae0f-55c2-452d-9944-d18420f5b2c8" = "ExternalItem.ReadWrite.OwnedBy"
        "34c37bc0-2b40-4d5e-85e1-2365cd256d79" = "ExternalConnection.ReadWrite.All"
        "7a7cffad-37d2-4f48-afa4-c6ab129adcc2" = "ExternalItem.Read.All"
        "38c3d6ee-69ee-422f-b954-e17819665354" = "ExternalItem.ReadWrite.All"
    }

    $permissionIds = $permissions.Keys
    $appManagementPermissions = @(
        "microsoft.directory/applications/create",
        "microsoft.directory/applications/permissions/update"
    )

    $policy = Get-MgPolicyPermissionGrantPolicy -Filter "id eq '$PolicyName'" -ErrorAction SilentlyContinue
    if (-not $policy) {
        $policy = New-MgPolicyPermissionGrantPolicy -Id $PolicyName `
            -DisplayName $PolicyName `
            -Description "App registration and consent permissions for Copilot connectors"
        Write-Host "Policy created."
    }else {
        Write-Host "Policy already exists."
    }
    # Microsoft Graph app resource id
    $resourceAppId = "00000003-0000-0000-c000-000000000000"
    $existingIncludes = Get-MgPolicyPermissionGrantPolicyInclude -PermissionGrantPolicyId $policy.Id -ErrorAction SilentlyContinue

    foreach ($permId in $permissionIds) {
        $exists = $existingIncludes | Where-Object {
            $_.PermissionType -eq "Application" -and
            $_.ResourceApplication -eq $resourceAppId -and
            $_.Permissions -contains $permId
        }

        if (-not $exists) {
            New-MgPolicyPermissionGrantPolicyInclude -PermissionGrantPolicyId $policy.Id `
                -PermissionType "Application" `
                -ResourceApplication $resourceAppId `
                -Permissions $permId > $null
        }
    }

    Write-Host "`nCreating custom role - '$UnifiedRoleName'..." -ForegroundColor Green

    $existingRole = Get-MgRoleManagementDirectoryRoleDefinition | Where-Object { $_.DisplayName -eq $UnifiedRoleName }

    if (-not $existingRole) {
        $unifiedRoleDefinition = @{
            displayName = $UnifiedRoleName
            description = "App registration and consent permissions for Copilot connectors"
            isEnabled = $true
            assignableScopes = @("/")
            rolePermissions = @(
                @{
                    allowedResourceActions = @(
                        "microsoft.directory/servicePrincipals/managePermissionGrantsForAll.$($policy.Id)"
                    ) + $appManagementPermissions
                }
            )
        }

        $unifiedRole = New-MgRoleManagementDirectoryRoleDefinition -BodyParameter $unifiedRoleDefinition
        Write-Host "Role created."
    } else {
        $unifiedRole = $existingRole
        Write-Host "Role already exists."
    }

Write-Host "`nAssigning custom role to the users..." -ForegroundColor Green
[int]$failureCount = 0
$totalUsers = $userPrincipalInfos.Count

foreach ($info in $userPrincipalInfos) {
    $userId = $info.Id
    $email = $info.Email

    try {
        $existingAssignment = Get-MgRoleManagementDirectoryRoleAssignment -Filter "principalId eq '$userId' and roleDefinitionId eq '$($unifiedRole.Id)'" -ErrorAction Stop

        if (-not $existingAssignment) {
            Write-Host "- $email"
            New-MgRoleManagementDirectoryRoleAssignment -PrincipalId $userId `
                -RoleDefinitionId $unifiedRole.Id `
                -DirectoryScopeId "/" > $null
        } else {
            Write-Host "User $email already has role assigned. Skipping."
        }
    } catch {
        Write-Warning "Failed to assign role to user $email. Error: $_"
        $failureCount++ 
    }
}

# === Final Summary Output ===

Write-Host "`nSummary:" -ForegroundColor Cyan

if ($failureCount -eq $totalUsers) {
    Write-Host "Failed to grant permission." -ForegroundColor Red
    Write-Host "Please retry." -ForegroundColor Red
} elseif ($failureCount -gt 0) {
    Write-Host "Failed to grant permission for $failureCount out of $totalUsers users." -ForegroundColor Red
    Write-Host "Please retry." -ForegroundColor Red
} else {
    Write-Host "Successfully granted permissions to all $totalUsers users." -ForegroundColor Green
}

}

# SIG # Begin signature block
# MIIoewYJKoZIhvcNAQcCoIIobDCCKGgCAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCAX+riIfwQlQfWA
# d0E6p64dSO32ZTJXUPLdRBRJ+jlJWKCCDXYwggX0MIID3KADAgECAhMzAAAEBGx0
# Bv9XKydyAAAAAAQEMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD
# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p
# bmcgUENBIDIwMTEwHhcNMjQwOTEyMjAxMTE0WhcNMjUwOTExMjAxMTE0WjB0MQsw
# CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u
# ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
# AQC0KDfaY50MDqsEGdlIzDHBd6CqIMRQWW9Af1LHDDTuFjfDsvna0nEuDSYJmNyz
# NB10jpbg0lhvkT1AzfX2TLITSXwS8D+mBzGCWMM/wTpciWBV/pbjSazbzoKvRrNo
# DV/u9omOM2Eawyo5JJJdNkM2d8qzkQ0bRuRd4HarmGunSouyb9NY7egWN5E5lUc3
# a2AROzAdHdYpObpCOdeAY2P5XqtJkk79aROpzw16wCjdSn8qMzCBzR7rvH2WVkvF
# HLIxZQET1yhPb6lRmpgBQNnzidHV2Ocxjc8wNiIDzgbDkmlx54QPfw7RwQi8p1fy
# 4byhBrTjv568x8NGv3gwb0RbAgMBAAGjggFzMIIBbzAfBgNVHSUEGDAWBgorBgEE
# AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQU8huhNbETDU+ZWllL4DNMPCijEU4w
# RQYDVR0RBD4wPKQ6MDgxHjAcBgNVBAsTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEW
# MBQGA1UEBRMNMjMwMDEyKzUwMjkyMzAfBgNVHSMEGDAWgBRIbmTlUAXTgqoXNzci
# tW2oynUClTBUBgNVHR8ETTBLMEmgR6BFhkNodHRwOi8vd3d3Lm1pY3Jvc29mdC5j
# b20vcGtpb3BzL2NybC9NaWNDb2RTaWdQQ0EyMDExXzIwMTEtMDctMDguY3JsMGEG
# CCsGAQUFBwEBBFUwUzBRBggrBgEFBQcwAoZFaHR0cDovL3d3dy5taWNyb3NvZnQu
# Y29tL3BraW9wcy9jZXJ0cy9NaWNDb2RTaWdQQ0EyMDExXzIwMTEtMDctMDguY3J0
# MAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADggIBAIjmD9IpQVvfB1QehvpC
# Ge7QeTQkKQ7j3bmDMjwSqFL4ri6ae9IFTdpywn5smmtSIyKYDn3/nHtaEn0X1NBj
# L5oP0BjAy1sqxD+uy35B+V8wv5GrxhMDJP8l2QjLtH/UglSTIhLqyt8bUAqVfyfp
# h4COMRvwwjTvChtCnUXXACuCXYHWalOoc0OU2oGN+mPJIJJxaNQc1sjBsMbGIWv3
# cmgSHkCEmrMv7yaidpePt6V+yPMik+eXw3IfZ5eNOiNgL1rZzgSJfTnvUqiaEQ0X
# dG1HbkDv9fv6CTq6m4Ty3IzLiwGSXYxRIXTxT4TYs5VxHy2uFjFXWVSL0J2ARTYL
# E4Oyl1wXDF1PX4bxg1yDMfKPHcE1Ijic5lx1KdK1SkaEJdto4hd++05J9Bf9TAmi
# u6EK6C9Oe5vRadroJCK26uCUI4zIjL/qG7mswW+qT0CW0gnR9JHkXCWNbo8ccMk1
# sJatmRoSAifbgzaYbUz8+lv+IXy5GFuAmLnNbGjacB3IMGpa+lbFgih57/fIhamq
# 5VhxgaEmn/UjWyr+cPiAFWuTVIpfsOjbEAww75wURNM1Imp9NJKye1O24EspEHmb
# DmqCUcq7NqkOKIG4PVm3hDDED/WQpzJDkvu4FrIbvyTGVU01vKsg4UfcdiZ0fQ+/
# V0hf8yrtq9CkB8iIuk5bBxuPMIIHejCCBWKgAwIBAgIKYQ6Q0gAAAAAAAzANBgkq
# hkiG9w0BAQsFADCBiDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24x
# EDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlv
# bjEyMDAGA1UEAxMpTWljcm9zb2Z0IFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5
# IDIwMTEwHhcNMTEwNzA4MjA1OTA5WhcNMjYwNzA4MjEwOTA5WjB+MQswCQYDVQQG
# EwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwG
# A1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSgwJgYDVQQDEx9NaWNyb3NvZnQg
# Q29kZSBTaWduaW5nIFBDQSAyMDExMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIIC
# CgKCAgEAq/D6chAcLq3YbqqCEE00uvK2WCGfQhsqa+laUKq4BjgaBEm6f8MMHt03
# a8YS2AvwOMKZBrDIOdUBFDFC04kNeWSHfpRgJGyvnkmc6Whe0t+bU7IKLMOv2akr
# rnoJr9eWWcpgGgXpZnboMlImEi/nqwhQz7NEt13YxC4Ddato88tt8zpcoRb0Rrrg
# OGSsbmQ1eKagYw8t00CT+OPeBw3VXHmlSSnnDb6gE3e+lD3v++MrWhAfTVYoonpy
# 4BI6t0le2O3tQ5GD2Xuye4Yb2T6xjF3oiU+EGvKhL1nkkDstrjNYxbc+/jLTswM9
# sbKvkjh+0p2ALPVOVpEhNSXDOW5kf1O6nA+tGSOEy/S6A4aN91/w0FK/jJSHvMAh
# dCVfGCi2zCcoOCWYOUo2z3yxkq4cI6epZuxhH2rhKEmdX4jiJV3TIUs+UsS1Vz8k
# A/DRelsv1SPjcF0PUUZ3s/gA4bysAoJf28AVs70b1FVL5zmhD+kjSbwYuER8ReTB
# w3J64HLnJN+/RpnF78IcV9uDjexNSTCnq47f7Fufr/zdsGbiwZeBe+3W7UvnSSmn
# Eyimp31ngOaKYnhfsi+E11ecXL93KCjx7W3DKI8sj0A3T8HhhUSJxAlMxdSlQy90
# lfdu+HggWCwTXWCVmj5PM4TasIgX3p5O9JawvEagbJjS4NaIjAsCAwEAAaOCAe0w
# ggHpMBAGCSsGAQQBgjcVAQQDAgEAMB0GA1UdDgQWBBRIbmTlUAXTgqoXNzcitW2o
# ynUClTAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTALBgNVHQ8EBAMCAYYwDwYD
# VR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBRyLToCMZBDuRQFTuHqp8cx0SOJNDBa
# BgNVHR8EUzBRME+gTaBLhklodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20vcGtpL2Ny
# bC9wcm9kdWN0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFfMDNfMjIuY3JsMF4GCCsG
# AQUFBwEBBFIwUDBOBggrBgEFBQcwAoZCaHR0cDovL3d3dy5taWNyb3NvZnQuY29t
# L3BraS9jZXJ0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFfMDNfMjIuY3J0MIGfBgNV
# HSAEgZcwgZQwgZEGCSsGAQQBgjcuAzCBgzA/BggrBgEFBQcCARYzaHR0cDovL3d3
# dy5taWNyb3NvZnQuY29tL3BraW9wcy9kb2NzL3ByaW1hcnljcHMuaHRtMEAGCCsG
# AQUFBwICMDQeMiAdAEwAZQBnAGEAbABfAHAAbwBsAGkAYwB5AF8AcwB0AGEAdABl
# AG0AZQBuAHQALiAdMA0GCSqGSIb3DQEBCwUAA4ICAQBn8oalmOBUeRou09h0ZyKb
# C5YR4WOSmUKWfdJ5DJDBZV8uLD74w3LRbYP+vj/oCso7v0epo/Np22O/IjWll11l
# hJB9i0ZQVdgMknzSGksc8zxCi1LQsP1r4z4HLimb5j0bpdS1HXeUOeLpZMlEPXh6
# I/MTfaaQdION9MsmAkYqwooQu6SpBQyb7Wj6aC6VoCo/KmtYSWMfCWluWpiW5IP0
# wI/zRive/DvQvTXvbiWu5a8n7dDd8w6vmSiXmE0OPQvyCInWH8MyGOLwxS3OW560
# STkKxgrCxq2u5bLZ2xWIUUVYODJxJxp/sfQn+N4sOiBpmLJZiWhub6e3dMNABQam
# ASooPoI/E01mC8CzTfXhj38cbxV9Rad25UAqZaPDXVJihsMdYzaXht/a8/jyFqGa
# J+HNpZfQ7l1jQeNbB5yHPgZ3BtEGsXUfFL5hYbXw3MYbBL7fQccOKO7eZS/sl/ah
# XJbYANahRr1Z85elCUtIEJmAH9AAKcWxm6U/RXceNcbSoqKfenoi+kiVH6v7RyOA
# 9Z74v2u3S5fi63V4GuzqN5l5GEv/1rMjaHXmr/r8i+sLgOppO6/8MO0ETI7f33Vt
# Y5E90Z1WTk+/gFcioXgRMiF670EKsT/7qMykXcGhiJtXcVZOSEXAQsmbdlsKgEhr
# /Xmfwb1tbWrJUnMTDXpQzTGCGlswghpXAgEBMIGVMH4xCzAJBgNVBAYTAlVTMRMw
# EQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVN
# aWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNp
# Z25pbmcgUENBIDIwMTECEzMAAAQEbHQG/1crJ3IAAAAABAQwDQYJYIZIAWUDBAIB
# BQCggeYwGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQwHAYKKwYBBAGCNwIBCzEO
# MAwGCisGAQQBgjcCARUwLwYJKoZIhvcNAQkEMSIEIJwwXxSfbbMC1Qw0odu35epR
# tDcwRTStfVjGxwJar5BMMHoGCisGAQQBgjcCAQwxbDBqoEyASgBNAGkAYwByAG8A
# cwBvAGYAdAAuAEcAcgBhAHAAaAAuAEMAbwBuAG4AZQBjAHQAbwByAHMALgBDAG0A
# ZABsAGUAdAAuAGQAbABsoRqAGGh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbTANBgkq
# hkiG9w0BAQEFAASCAQBg6o99BTKt1MfHHi81hGptRBDPd6oHGMC315XC1I1oZ85C
# 8PpfZqf675E4B1amp+GpHNN1RDp4K4040qxdu4yjvRXjYKA1b0M/EMtrG2haLrRf
# pVbv17iOMqvzHwih6oeHMjz8RbAksC+rifRHa7WekqLkh1WVMDmhRvSd5a//wUPY
# eCMyFiOcFkWYwN3k8/gfPrgPGo70rJFzhvvRSMy/+TF8I6dCo2wctZ1QsgwBPZpj
# /vKFg26TlMPwZxKykkqUo4E2F7fvFh/8xNhMvnhDW/GTOMBmQRVyz5rOj56duPxJ
# 2nkuNbVZemS4n4cGGsB8eMKqMLWCkmuy08GmCCXmoYIXrTCCF6kGCisGAQQBgjcD
# AwExgheZMIIXlQYJKoZIhvcNAQcCoIIXhjCCF4ICAQMxDzANBglghkgBZQMEAgEF
# ADCCAVoGCyqGSIb3DQEJEAEEoIIBSQSCAUUwggFBAgEBBgorBgEEAYRZCgMBMDEw
# DQYJYIZIAWUDBAIBBQAEILyCfRpClnVcvvIi+p+KCRrzgpFLv82+OMZ6dahNkA1D
# AgZoUxm/stMYEzIwMjUwNzA3MTIzMDQ1LjM2NlowBIACAfSggdmkgdYwgdMxCzAJ
# BgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25k
# MR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xLTArBgNVBAsTJE1pY3Jv
# c29mdCBJcmVsYW5kIE9wZXJhdGlvbnMgTGltaXRlZDEnMCUGA1UECxMeblNoaWVs
# ZCBUU1MgRVNOOjU1MUEtMDVFMC1EOTQ3MSUwIwYDVQQDExxNaWNyb3NvZnQgVGlt
# ZS1TdGFtcCBTZXJ2aWNloIIR+zCCBygwggUQoAMCAQICEzMAAAIB0UVZmBDMQk8A
# AQAAAgEwDQYJKoZIhvcNAQELBQAwfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldh
# c2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBD
# b3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIw
# MTAwHhcNMjQwNzI1MTgzMTIyWhcNMjUxMDIyMTgzMTIyWjCB0zELMAkGA1UEBhMC
# VVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNV
# BAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEtMCsGA1UECxMkTWljcm9zb2Z0IEly
# ZWxhbmQgT3BlcmF0aW9ucyBMaW1pdGVkMScwJQYDVQQLEx5uU2hpZWxkIFRTUyBF
# U046NTUxQS0wNUUwLUQ5NDcxJTAjBgNVBAMTHE1pY3Jvc29mdCBUaW1lLVN0YW1w
# IFNlcnZpY2UwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC1at/4fMO7
# uyTnTLlgeF4IgkbS7FFIUVwc16T5N31mbImPbQQSNpTwkMm7mlZzik8CwEjfw0QA
# nVv4oGeVK2pTy69cXiqcRKEN2Lyc+xllCxNkMpYCBQzaJlM6JYi5lwWzlkLz/4aW
# tUszmHVj2d8yHkHgOdRA5cyt6YBP0yS9SGDe5piCaouWZjI4OZtriVdkG7XThIsA
# Wxc5+X9MuGlOhPjrLuUj2xsj26rf8B6uHdo+LaSce8QRrOKVd6ihc0sLB274izqj
# yRAui5SfcrBRCbRvtpS2y/Vf86A+aw4mLrI3cthbIchK+s24isniJg2Ad0EG6ZBg
# rwuNmZBpMoVpzGGZcnSraDNoh/EXbIjAz5X2xCqQeSD9D6JIM2kyvqav87CSc4Qi
# MjSDpkw7KaK+kKHMM2g/P2GQreBUdkpbs1Xz5QFc3gbRoFfr18pRvEEEvKTZwL4+
# E6hSOSXpQLz9zSG6qPnFfyb5hUiTzV7u3oj5X8TjJdF55mCvQWFio2m9OMZxo7Za
# uQ/leaxhLsi8w8h/gMLIglRlqqgExOgAkkcZF74M+oIeDpuYY+b3sys5a/Xr8Kjp
# L1xAORen28xJJFBZfLgq0mFl+a4PPa+vWPDg16LHC4gMbDWa1X9N1Ij6+ksl9SIu
# X9v3D+0kH3YEAtBPx7Vgfw2mF06jXELCRwIDAQABo4IBSTCCAUUwHQYDVR0OBBYE
# FLByr1uWoug8+JWvKb2YWYVZvLJSMB8GA1UdIwQYMBaAFJ+nFV0AXmJdg/Tl0mWn
# G1M1GelyMF8GA1UdHwRYMFYwVKBSoFCGTmh0dHA6Ly93d3cubWljcm9zb2Z0LmNv
# bS9wa2lvcHMvY3JsL01pY3Jvc29mdCUyMFRpbWUtU3RhbXAlMjBQQ0ElMjAyMDEw
# KDEpLmNybDBsBggrBgEFBQcBAQRgMF4wXAYIKwYBBQUHMAKGUGh0dHA6Ly93d3cu
# bWljcm9zb2Z0LmNvbS9wa2lvcHMvY2VydHMvTWljcm9zb2Z0JTIwVGltZS1TdGFt
# cCUyMFBDQSUyMDIwMTAoMSkuY3J0MAwGA1UdEwEB/wQCMAAwFgYDVR0lAQH/BAww
# CgYIKwYBBQUHAwgwDgYDVR0PAQH/BAQDAgeAMA0GCSqGSIb3DQEBCwUAA4ICAQA6
# NADLPRxXO1MUapdfkktHEUr87+gx7nm4OoQLxV3WBtwzbwoFQ+C9Qg9eb+90M3Yh
# UGRYebAKngAhzLh1m2S5SZ3R+e7ppP0y+jWd2wunZglwygUsS3dO2uIto76Lgau/
# RlQu1ZdQ8Bb8yflJyOCfTFl24Y8EP9ezcnv6B6337xm8GKmyD83umiKZg5WwfEtx
# 6btXld0w2zK1Ob+4KiaEz/CBHkqUNhNU0BcHFxIox4lqIPdXX4eE2RWWIyXlU4/6
# fDnFYXnm72Hp4XYbt4E+pP6pIVD6tAJB0is3TIbVA308muiC4r4UlAl1DN18PdFZ
# WxyIHKBthpmVPVwYkjUjJDvgNDRQF1Ol94azKsRD08jxDKpUupvarsu0joMkw2mF
# i76Ov//SymvVRW/IM+25GdsZBE2LUI7AlyP05iaWQWAo14J9sNPtTe4Q69aiZ6Rf
# rRj+bm61FxQ9V4A92GQH4PENp6/cnXLAM13K73XWcBU+BGTIqAwrdRIsbfsR2Vq0
# OTwXK4KvHi2IfKoc7fATrE/DfZDx7++a5A+gFm5fepR6gUizJkR6cerZJwy6eFyp
# bfZJRUCLmhnhned/t0CA1q7bU0Q/CBb7bCSs2oODsenzIfKg4puAQG7pERBu9J9n
# kqHg9X5LaDF/a6roahgOeWoAE4xjDPfT0hKLRs8yHzCCB3EwggVZoAMCAQICEzMA
# AAAVxedrngKbSZkAAAAAABUwDQYJKoZIhvcNAQELBQAwgYgxCzAJBgNVBAYTAlVT
# MRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQK
# ExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xMjAwBgNVBAMTKU1pY3Jvc29mdCBSb290
# IENlcnRpZmljYXRlIEF1dGhvcml0eSAyMDEwMB4XDTIxMDkzMDE4MjIyNVoXDTMw
# MDkzMDE4MzIyNVowfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24x
# EDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlv
# bjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIwMTAwggIiMA0G
# CSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDk4aZM57RyIQt5osvXJHm9DtWC0/3u
# nAcH0qlsTnXIyjVX9gF/bErg4r25PhdgM/9cT8dm95VTcVrifkpa/rg2Z4VGIwy1
# jRPPdzLAEBjoYH1qUoNEt6aORmsHFPPFdvWGUNzBRMhxXFExN6AKOG6N7dcP2CZT
# fDlhAnrEqv1yaa8dq6z2Nr41JmTamDu6GnszrYBbfowQHJ1S/rboYiXcag/PXfT+
# jlPP1uyFVk3v3byNpOORj7I5LFGc6XBpDco2LXCOMcg1KL3jtIckw+DJj361VI/c
# +gVVmG1oO5pGve2krnopN6zL64NF50ZuyjLVwIYwXE8s4mKyzbnijYjklqwBSru+
# cakXW2dg3viSkR4dPf0gz3N9QZpGdc3EXzTdEonW/aUgfX782Z5F37ZyL9t9X4C6
# 26p+Nuw2TPYrbqgSUei/BQOj0XOmTTd0lBw0gg/wEPK3Rxjtp+iZfD9M269ewvPV
# 2HM9Q07BMzlMjgK8QmguEOqEUUbi0b1qGFphAXPKZ6Je1yh2AuIzGHLXpyDwwvoS
# CtdjbwzJNmSLW6CmgyFdXzB0kZSU2LlQ+QuJYfM2BjUYhEfb3BvR/bLUHMVr9lxS
# UV0S2yW6r1AFemzFER1y7435UsSFF5PAPBXbGjfHCBUYP3irRbb1Hode2o+eFnJp
# xq57t7c+auIurQIDAQABo4IB3TCCAdkwEgYJKwYBBAGCNxUBBAUCAwEAATAjBgkr
# BgEEAYI3FQIEFgQUKqdS/mTEmr6CkTxGNSnPEP8vBO4wHQYDVR0OBBYEFJ+nFV0A
# XmJdg/Tl0mWnG1M1GelyMFwGA1UdIARVMFMwUQYMKwYBBAGCN0yDfQEBMEEwPwYI
# KwYBBQUHAgEWM2h0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMvRG9jcy9S
# ZXBvc2l0b3J5Lmh0bTATBgNVHSUEDDAKBggrBgEFBQcDCDAZBgkrBgEEAYI3FAIE
# DB4KAFMAdQBiAEMAQTALBgNVHQ8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAfBgNV
# HSMEGDAWgBTV9lbLj+iiXGJo0T2UkFvXzpoYxDBWBgNVHR8ETzBNMEugSaBHhkVo
# dHRwOi8vY3JsLm1pY3Jvc29mdC5jb20vcGtpL2NybC9wcm9kdWN0cy9NaWNSb29D
# ZXJBdXRfMjAxMC0wNi0yMy5jcmwwWgYIKwYBBQUHAQEETjBMMEoGCCsGAQUFBzAC
# hj5odHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpL2NlcnRzL01pY1Jvb0NlckF1
# dF8yMDEwLTA2LTIzLmNydDANBgkqhkiG9w0BAQsFAAOCAgEAnVV9/Cqt4SwfZwEx
# JFvhnnJL/Klv6lwUtj5OR2R4sQaTlz0xM7U518JxNj/aZGx80HU5bbsPMeTCj/ts
# 0aGUGCLu6WZnOlNN3Zi6th542DYunKmCVgADsAW+iehp4LoJ7nvfam++Kctu2D9I
# dQHZGN5tggz1bSNU5HhTdSRXud2f8449xvNo32X2pFaq95W2KFUn0CS9QKC/GbYS
# EhFdPSfgQJY4rPf5KYnDvBewVIVCs/wMnosZiefwC2qBwoEZQhlSdYo2wh3DYXMu
# LGt7bj8sCXgU6ZGyqVvfSaN0DLzskYDSPeZKPmY7T7uG+jIa2Zb0j/aRAfbOxnT9
# 9kxybxCrdTDFNLB62FD+CljdQDzHVG2dY3RILLFORy3BFARxv2T5JL5zbcqOCb2z
# AVdJVGTZc9d/HltEAY5aGZFrDZ+kKNxnGSgkujhLmm77IVRrakURR6nxt67I6Ile
# T53S0Ex2tVdUCbFpAUR+fKFhbHP+CrvsQWY9af3LwUFJfn6Tvsv4O+S3Fb+0zj6l
# MVGEvL8CwYKiexcdFYmNcP7ntdAoGokLjzbaukz5m/8K6TT4JDVnK+ANuOaMmdbh
# IurwJ0I9JZTmdHRbatGePu1+oDEzfbzL6Xu/OHBE0ZDxyKs6ijoIYn/ZcGNTTY3u
# gm2lBRDBcQZqELQdVTNYs6FwZvKhggNWMIICPgIBATCCAQGhgdmkgdYwgdMxCzAJ
# BgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25k
# MR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xLTArBgNVBAsTJE1pY3Jv
# c29mdCBJcmVsYW5kIE9wZXJhdGlvbnMgTGltaXRlZDEnMCUGA1UECxMeblNoaWVs
# ZCBUU1MgRVNOOjU1MUEtMDVFMC1EOTQ3MSUwIwYDVQQDExxNaWNyb3NvZnQgVGlt
# ZS1TdGFtcCBTZXJ2aWNloiMKAQEwBwYFKw4DAhoDFQDX7bpxH/IfXTQOI0UZaG4C
# /atgGqCBgzCBgKR+MHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9u
# MRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRp
# b24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwMA0GCSqG
# SIb3DQEBCwUAAgUA7BX6wDAiGA8yMDI1MDcwNzA3NTAyNFoYDzIwMjUwNzA4MDc1
# MDI0WjB0MDoGCisGAQQBhFkKBAExLDAqMAoCBQDsFfrAAgEAMAcCAQACAghUMAcC
# AQACAhOrMAoCBQDsF0xAAgEAMDYGCisGAQQBhFkKBAIxKDAmMAwGCisGAQQBhFkK
# AwKgCjAIAgEAAgMHoSChCjAIAgEAAgMBhqAwDQYJKoZIhvcNAQELBQADggEBAANF
# Mqd8Bk+/9T6fOL95v0FCre1YZmcOUVfV/pD5AEHoDad9zUUzHtP7PPjw/bFeiBNF
# oHB8By4GdZnbGPdlxGnukXN+EmSm0w4pkTjWrphL01w8XH5vQmPS+Q16jKBEM867
# z9RHKdtTY3rFig8wgCLRVKgYICL1rJzMQLtE1WK4ceEy3RFpfn0wwTaCmNqzH7e6
# gdywLqwKP/UbhQRzjgM3zu2BetQZn3VdYerq6yHZosY4MRnMUNVw6mMTobCOzWQU
# AXthb/PC1+wz+H66hvw5SGp7A5gfWvMlKZB0Yjs+KJkWxj79QPPEolT0cnREtnAi
# +r/BWMgMm/faxiPLqDUxggQNMIIECQIBATCBkzB8MQswCQYDVQQGEwJVUzETMBEG
# A1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWlj
# cm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFt
# cCBQQ0EgMjAxMAITMwAAAgHRRVmYEMxCTwABAAACATANBglghkgBZQMEAgEFAKCC
# AUowGgYJKoZIhvcNAQkDMQ0GCyqGSIb3DQEJEAEEMC8GCSqGSIb3DQEJBDEiBCC3
# pp6nH2SoKDpHP0GIcsfNgAexe40rrwl08FIUIiL2+zCB+gYLKoZIhvcNAQkQAi8x
# geowgecwgeQwgb0EIFhrsjpMlBFybHQdpJNZl0mCjB2uX35muvSkh2oe1zgjMIGY
# MIGApH4wfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNV
# BAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQG
# A1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIwMTACEzMAAAIB0UVZmBDM
# Qk8AAQAAAgEwIgQgjao2isP13XB0uZOnYtp//Z8YXc0MiNAFra3+Uj+C54IwDQYJ
# KoZIhvcNAQELBQAEggIAVApDirTQd+suFvKa2lFvcqBpoKjfPjtmAUovBoNntRYK
# pMNnVtEs8sZZsxSEAgaKz7w0R9Kcd2DY/lflNCswoKPvXzZ8DV5w9N3HPScmwCJY
# cptFYJQt83FUVFJP8AEU94l0vnxy6bGWMSLu99YYU3ZvkQ/8iGzfGdghJ5Bfh7Zr
# w6hdbGcT9BXdXhNPoeLLozq547RD5RW96+qz0BdmjEutRZFJJKNWqOKMXDcema2i
# QOF7fD8uRxQwqXBoSCTdS7wLb+x7PJBAQv73JJvwoDHaIwpMJplNm5MXHlywoowJ
# AisQyIUc8E2WO23078qWMF6k8mvNgoDsOrfzGjVzF6wXNDkG/68s+MgiP/p5jqEl
# hwUkAHELNnRhk76e2bQWy5hhzC/vjjiXVx3I51Xg3HsGXk7mh8+0Pzj4QD2d8rsZ
# DT0PodR145MVhXhBs1x9Ob7ySRGCqEy2NdVvFwX7tpUHDryAmsN+hs3RNEJKDDsV
# Xo9ZzSnx9/gSL7O8+igqOj1RRzRbaey4Bke76QVSOLIwpG3z9wR4Bv7b6fDHWS49
# Et1T4ghDTSkKX2LYd+uEw98xZ0SyKJIaDVmdfJMArAo2ri8ZrwZ7nBU75sTN8ro/
# E1C23QloTIdDxiXorwylEV1HgcrJYYLjdt5QPPr6lHcoYsb9M1eEjSyot2BnvVs=
# SIG # End signature block