Public/ServiceEM/New-EntraOpsServiceEntraGroup.ps1

<#
.SYNOPSIS
    Creates Entra security and Microsoft 365 groups for each service role.

.DESCRIPTION
    Creates one Entra group per entry in the ServiceRoles object. Security groups
    are created for roles with an empty groupType or groupType = "Security". Microsoft 365
    (Unified) groups are created for roles with groupType = "Unified".

    ControlPlane, ManagementPlane and CatalogPlane security groups are created as role-assignable
    (isAssignableToRole = $true) to protect their members and owners from less privileged administrators.
    WorkloadPlane-Admins are role-assignable only with -RoleAssignableWorkloadPlaneAdmins; all other security
    groups (e.g. WorkloadPlane-Users) and Unified groups are not role-assignable. Creating role-assignable groups
    requires the caller to have Privileged Role Administrator or Global Administrator.
    The setting only applies when a group is created; Entra ID can't change it afterwards.

    Group names follow the convention:
    <GroupPrefix><Delimiter><ServiceName><Delimiter><AccessLevel><Delimiter><RoleName>
    A GroupPrefix that ends with '-', '_' or '.' (e.g. "SG_") is followed directly by the ServiceName.

    Idempotent: an existing group with exactly the expected MailNickname is reused, but only when it can't
    have been created by a less privileged user: ControlPlane, ManagementPlane and CatalogPlane groups must
    be role-assignable security groups; all other groups must be role-assignable or already be a resource of
    the catalog Catalog-<ServiceName>. Otherwise, or when the MailNickname is ambiguous, the function stops
    before creating any group. Other groups with the <ServiceName>. MailNickname prefix aren't returned.
    Only called directly for custom implementations; New-EntraOpsServiceBootstrap
    is the standard entry point.

.PARAMETER ServiceName
    Name of the service. Forms the central segment of the group MailNickname
    and DisplayName.

.PARAMETER WorkloadPlaneAdmin
    Optional Graph API owner URL for the group owner, in the form:
    "https://graph.microsoft.com/v1.0/users/<ObjectId>"
    
    This can also be provided as just the ObjectId (GUID), and the function
    will automatically construct the proper OData bind URL. Only set as owner of
    WorkloadPlane groups; omit to create groups without owners.

.PARAMETER GroupPrefix
    Prefix prepended to all group DisplayNames and MailNicknames. Defaults to "SG".

.PARAMETER GroupNamingDelimiter
    Delimiter between name segments. Only "-" is supported: the other ServiceEM cmdlets find the groups
    by their "-" delimited display name suffix (e.g. *-ControlPlane-Admins).

.PARAMETER RoleAssignableWorkloadPlaneAdmins
    Creates WorkloadPlane-Admins as role-assignable group.

.PARAMETER ServiceRoles
    EntraOps service roles object. Each row produces one group. The accessLevel,
    name, and groupType columns control the group variant.

.PARAMETER logPrefix
    Text prepended to verbose messages. Defaults to the function name.

.PARAMETER CreatedGroupIds
    Used by New-EntraOpsServiceBootstrap to record the IDs returned by group creation, including when a
    later step fails. Existing groups are never recorded. This collection is only used for failure cleanup.

.EXAMPLE
    New-EntraOpsServiceEntraGroup `
        -ServiceName "MyService" `
        -WorkloadPlaneAdmin "https://graph.microsoft.com/v1.0/users/00000000-0000-0000-0000-000000000001" `
        -ServiceRoles $roles

    Creates all security and Microsoft 365 groups for "MyService". Returns all group objects.

#>

function New-EntraOpsServiceEntraGroup {
    [OutputType([psobject[]])]
    [cmdletbinding()]
    param(
        [Parameter(Mandatory)]
        [string]$ServiceName,

        [string]$WorkloadPlaneAdmin,

        [string]$GroupPrefix = "SG",
        [ValidateSet('-')]
        [string]$GroupNamingDelimiter = "-",

        [switch]$RoleAssignableWorkloadPlaneAdmins,

        [Parameter(Mandatory)]
        [psobject[]]$ServiceRoles,

        [Parameter(DontShow)]
        [AllowEmptyCollection()]
        [System.Collections.Generic.List[string]]$CreatedGroupIds,

        [string]$logPrefix = "[$($MyInvocation.MyCommand)]"
    )

    begin {
        # ServiceName is part of mailNickname and of the $search query below
        if ($ServiceName -notmatch '^[A-Za-z0-9_.-]+$') {
            throw "ServiceName '$ServiceName' is invalid. Use letters, digits, '_', '.' or '-'."
        }
        $groupNamePrefix = if ($GroupPrefix -match '[-_.]$') { $GroupPrefix } else { "$GroupPrefix$GroupNamingDelimiter" }
        # Normalize WorkloadPlaneAdmin to the OData bind format
        if (-not [string]::IsNullOrWhiteSpace($WorkloadPlaneAdmin)) {
            # Check if WorkloadPlaneAdmin is already in OData URL format (users or servicePrincipals)
            if ($WorkloadPlaneAdmin -match '^https://graph\.microsoft\.com/v1\.0/(users|servicePrincipals)/') {
                $ownerUri = $WorkloadPlaneAdmin
                Write-Verbose "$logPrefix WorkloadPlaneAdmin provided as OData URL: $ownerUri"
            } else {
                # Assume it's just an ObjectId and construct the OData URL
                # Validate it looks like a GUID
                if ($WorkloadPlaneAdmin -match '^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$') {
                    $ownerUri = "https://graph.microsoft.com/v1.0/users/$WorkloadPlaneAdmin"
                    Write-Verbose "$logPrefix WorkloadPlaneAdmin converted to OData URL: $ownerUri"
                } else {
                    throw "WorkloadPlaneAdmin must be either a valid GUID (ObjectId) or a full OData URL (https://graph.microsoft.com/v1.0/users/<ObjectId> or https://graph.microsoft.com/v1.0/servicePrincipals/<ObjectId>). Received: $WorkloadPlaneAdmin"
                }
            }
        } else {
            Write-Verbose "$logPrefix No group owner requested; creating groups without owners"
        }

        try {
            #Groups
            $groups = @()
            Write-Verbose "$logPrefix Looking up Groups"
            $groups += Invoke-EntraOpsMsGraphQuery -Method GET -Uri "/v1.0/groups?`$search=`"mailNickname:$ServiceName.`"" -ConsistencyLevel "eventual" -OutputType PSObject -DisableCache -ThrowOnFailure
            # $search also matches tokens inside the nickname (e.g. PIM.<ServiceName>.*)
            $groups = @($groups | Where-Object { $_.MailNickname -like "$ServiceName.*" })
        } catch {
            # Without the lookup result every group would be created again (mailNickname isn't unique for security groups)
            throw "Failed to look up the existing groups of '$ServiceName'; no group was created: $($_.Exception.Message)"
        }

        $catalogDisplayName = "Catalog-$ServiceName"
        $provenance = @{ CatalogResourceIds = $null }
        $getCatalogResourceIds = {
            if ($null -eq $provenance.CatalogResourceIds) {
                $encodedCatalogDisplayName = ConvertTo-EntraOpsODataStringLiteral -Value $catalogDisplayName
                $catalogs = @(Invoke-EntraOpsMsGraphQuery -Method GET -Uri "/v1.0/identityGovernance/entitlementManagement/catalogs?`$filter=displayName eq '$encodedCatalogDisplayName'&`$expand=resources" -OutputType PSObject -DisableCache -ThrowOnFailure |
                    Where-Object { $_.DisplayName -eq $catalogDisplayName })
                $provenance.CatalogResourceIds = if ($catalogs.Count -eq 1) { @($catalogs[0].Resources.OriginId | Where-Object { $_ }) } else { @() }
            }
            $provenance.CatalogResourceIds
        }
        $assertReusableGroup = {
            param($existingGroup, [bool]$RequireRoleAssignable)
            $details = $existingGroup
            if ($null -eq $existingGroup.IsAssignableToRole -or $null -eq $existingGroup.SecurityEnabled) {
                $details = Invoke-EntraOpsMsGraphQuery -Method GET -Uri "/v1.0/groups/$($existingGroup.Id)?`$select=id,isAssignableToRole,securityEnabled" -OutputType PSObject -DisableCache -ThrowOnFailure
            }
            if ($details.IsAssignableToRole -eq $true -and $details.SecurityEnabled -eq $true) { return }
            $groupLabel = "'$($existingGroup.DisplayName)' ($($existingGroup.Id), mailNickname $($existingGroup.MailNickname))"
            if ($RequireRoleAssignable) {
                throw "Existing group $groupLabel isn't a role-assignable security group and isn't reused, because any user who can create groups could have created it. Verify and delete the group, or rename its mailNickname, then run the landing zone again."
            }
            if ($existingGroup.Id -notin @(& $getCatalogResourceIds)) {
                throw "Existing group $groupLabel isn't a resource of the catalog '$catalogDisplayName' and isn't reused, because any user who can create groups could have created it. Verify the group and delete it, or add it as resource to '$catalogDisplayName', then run the landing zone again."
            }
        }
        $expectedNicknames = [System.Collections.Generic.List[string]]::new()
        # Base parameters shared by all group types
        $groupParams = @{
            description     = ""
            securityEnabled = $true
        }

        # Unified (Microsoft 365) groups cannot be role-assignable
        $unifiedParams = $groupParams + @{
            displayName        = ""
            mailNickname       = ""
            groupTypes         = @("Unified")
            mailEnabled        = $true
            isAssignableToRole = $false
            #"members@odata.bind" = $members
        }

        $secParams = $groupParams + @{
            displayName        = ""
            mailNickname       = ""
            mailEnabled        = $false
            isAssignableToRole = $true
        }
    }

    process {
        Write-Verbose "$logPrefix Beginning EntraGroup"

        Write-Verbose "$logPrefix Processing $(($ServiceRoles|Measure-Object).Count) Groups"
        foreach ($ServiceRole in $ServiceRoles) {
            $validationErrors = @()
            
            # Validate ServiceRole properties
            if ([string]::IsNullOrWhiteSpace($ServiceRole.Name)) {
                $validationErrors += "ServiceRole.Name is required"
            }
            
            # Construct names
            $unifiedParams.Description = "Team $(($ServiceRole.accessLevel +" "+ $ServiceRole.name).trim()) supporting $ServiceName"
            $unifiedParams.DisplayName = "$ServiceName $($ServiceRole.Name)"
            $unifiedParams.MailNickname = "$ServiceName.$($ServiceRole.Name)"
            $secParams.Description = "Team $(($ServiceRole.accessLevel +" "+ $ServiceRole.name).trim()) supporting $ServiceName"
            if ([string]::IsNullOrEmpty($ServiceRole.accessLevel)) {
                $secParams.DisplayName = "$groupNamePrefix$ServiceName$($GroupNamingDelimiter)$($ServiceRole.Name)"
                $secParams.MailNickname = "$ServiceName.$($ServiceRole.Name)"
            } else {
                $secParams.DisplayName = "$groupNamePrefix$ServiceName$($GroupNamingDelimiter)$($ServiceRole.accessLevel)$($GroupNamingDelimiter)$($ServiceRole.Name)"
                $secParams.MailNickname = "$ServiceName.$($ServiceRole.accessLevel).$($ServiceRole.Name)"
            }
            $secParams.isAssignableToRole = $ServiceRole.accessLevel -in @('ControlPlane', 'ManagementPlane', 'CatalogPlane') -or
            ($RoleAssignableWorkloadPlaneAdmins -and $ServiceRole.accessLevel -eq 'WorkloadPlane' -and $ServiceRole.Name -eq 'Admins')
            
            # Validate displayName length (max 256 chars)
            if ($secParams.DisplayName.Length -gt 256) {
                $validationErrors += "DisplayName '$($secParams.DisplayName)' exceeds maximum length of 256 characters (current: $($secParams.DisplayName.Length))"
            }
            if ($unifiedParams.DisplayName.Length -gt 256) {
                $validationErrors += "DisplayName '$($unifiedParams.DisplayName)' exceeds maximum length of 256 characters (current: $($unifiedParams.DisplayName.Length))"
            }
            
            # Validate mailNickname length (max 64 chars) and format
            $mailNicknamePattern = '^[a-zA-Z0-9_.-]+$'
            if ($secParams.MailNickname.Length -gt 64) {
                $validationErrors += "MailNickname '$($secParams.MailNickname)' exceeds maximum length of 64 characters (current: $($secParams.MailNickname.Length))"
            }
            if ($secParams.MailNickname -notmatch $mailNicknamePattern) {
                $validationErrors += "MailNickname '$($secParams.MailNickname)' contains invalid characters. Only alphanumeric, underscore, dot, and hyphen allowed."
            }
            if ($unifiedParams.MailNickname.Length -gt 64) {
                $validationErrors += "MailNickname '$($unifiedParams.MailNickname)' exceeds maximum length of 64 characters (current: $($unifiedParams.MailNickname.Length))"
            }
            if ($unifiedParams.MailNickname -notmatch $mailNicknamePattern) {
                $validationErrors += "MailNickname '$($unifiedParams.MailNickname)' contains invalid characters. Only alphanumeric, underscore, dot, and hyphen allowed."
            }
            
            # Throw if validation errors found
            if ($validationErrors.Count -gt 0) {
                throw "VALIDATION FAILED for ServiceRole '$($ServiceRole.Name)':`n - $($validationErrors -join "`n - ")"
            }
            
            Write-Verbose "$logPrefix Validated group parameters for '$($ServiceRole.Name)'"
            # Owners can manage membership, so only WorkloadPlane groups get one
            $secParams.Remove("owners@odata.bind")
            if ($ServiceRole.accessLevel -eq "WorkloadPlane" -and -not [string]::IsNullOrWhiteSpace($ownerUri)) {
                $secParams["owners@odata.bind"] = @($ownerUri)
            }
            $isSecurityGroup = [string]::IsNullOrEmpty($ServiceRole.groupType) -or $ServiceRole.groupType -eq "Security"
            $expectedNickname = if ($ServiceRole.groupType -eq "Unified") { $unifiedParams.MailNickname } elseif ($isSecurityGroup) { $secParams.MailNickname } else { $null }
            if ($expectedNickname) {
                $expectedNicknames.Add($expectedNickname)
                $existingGroups = @($groups | Where-Object { $_.MailNickname -eq $expectedNickname })
                if ($existingGroups.Count -gt 1) {
                    throw "Found $($existingGroups.Count) groups with mailNickname '$expectedNickname' ($($existingGroups.Id -join ', ')). Delete the groups that weren't created by ServiceEM, then run the landing zone again."
                }
                if ($existingGroups.Count -eq 1) {
                    $requireRoleAssignable = $ServiceRole.groupType -ne "Unified" -and $ServiceRole.accessLevel -in @('ControlPlane', 'ManagementPlane', 'CatalogPlane')
                    & $assertReusableGroup $existingGroups[0] $requireRoleAssignable
                    Write-Verbose "$logPrefix Reusing existing group $($existingGroups[0].DisplayName) ($($existingGroups[0].Id))"
                }
            }
            try {
                if ($ServiceRole.groupType -eq "Unified" -and $groups.MailNickname -notcontains $unifiedParams.MailNickname) {
                    Write-Verbose "$logPrefix $($unifiedParams|ConvertTo-Json -Compress)"
                    $newGroup = Invoke-EntraOpsMsGraphQuery -Method POST -Uri "/v1.0/groups" -Body ($unifiedParams | ConvertTo-Json -Depth 10) -OutputType PSObject -ThrowOnFailure
                    if ($null -ne $CreatedGroupIds -and $newGroup.Id) { $CreatedGroupIds.Add($newGroup.Id) }
                    $groups += $newGroup
                } elseif ($isSecurityGroup -and $groups.MailNickname -notcontains $secParams.MailNickname) {
                    Write-Verbose "$logPrefix $($secParams|ConvertTo-Json -Compress)"
                    $newGroup = Invoke-EntraOpsMsGraphQuery -Method POST -Uri "/v1.0/groups" -Body ($secParams | ConvertTo-Json -Depth 10) -OutputType PSObject -ThrowOnFailure
                    if ($null -ne $CreatedGroupIds -and $newGroup.Id) { $CreatedGroupIds.Add($newGroup.Id) }
                    $groups += $newGroup
                }
            } catch {
                throw "Failed to create group for service role '$($ServiceRole.accessLevel) $($ServiceRole.Name)': $($_.Exception.Message)"
            }
        }
    }

    end {
        Write-Verbose "$logPrefix Verifying Groups are available"
        $refIds = @($groups | Where-Object { $_.MailNickname -in $expectedNicknames } | ForEach-Object { $_.id } | Where-Object { $_ })
        $check = @{}
        $confirmed = Wait-EntraOpsServiceEMCondition -Activity "Groups" -logPrefix $logPrefix -Condition {
            $check.Groups = @()
            $check.Groups += Invoke-EntraOpsMsGraphQuery -Method GET -Uri "/v1.0/groups?`$search=`"mailNickname:$ServiceName.`"" -ConsistencyLevel "eventual" -OutputType PSObject -DisableCache
            $check.Groups = @($check.Groups | Where-Object { $_.MailNickname -in $expectedNicknames })
            $chkIds = @($check.Groups.id | Where-Object { $_ })
            $refIds.Count -gt 0 -and $chkIds.Count -ge $refIds.Count -and (Compare-Object $refIds $chkIds | Measure-Object).Count -eq 0
        }
        if (-not $confirmed) {
            throw "Group object consistency with Entra not achieved"
        }
        return [psobject[]]$check.Groups
    }
}

# SIG # Begin signature block
# MIIniAYJKoZIhvcNAQcCoIIneTCCJ3UCAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCCQ75SE0OlUP37P
# zDI9kZs+DAsA+C43BJB+pQon4d8VsKCCIDwwggXJMIIEsaADAgECAhAbtY8lKt8j
# AEkoya49fu0nMA0GCSqGSIb3DQEBDAUAMH4xCzAJBgNVBAYTAlBMMSIwIAYDVQQK
# ExlVbml6ZXRvIFRlY2hub2xvZ2llcyBTLkEuMScwJQYDVQQLEx5DZXJ0dW0gQ2Vy
# dGlmaWNhdGlvbiBBdXRob3JpdHkxIjAgBgNVBAMTGUNlcnR1bSBUcnVzdGVkIE5l
# dHdvcmsgQ0EwHhcNMjEwNTMxMDY0MzA2WhcNMjkwOTE3MDY0MzA2WjCBgDELMAkG
# A1UEBhMCUEwxIjAgBgNVBAoTGVVuaXpldG8gVGVjaG5vbG9naWVzIFMuQS4xJzAl
# BgNVBAsTHkNlcnR1bSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTEkMCIGA1UEAxMb
# Q2VydHVtIFRydXN0ZWQgTmV0d29yayBDQSAyMIICIjANBgkqhkiG9w0BAQEFAAOC
# Ag8AMIICCgKCAgEAvfl4+ObVgAxknYYblmRnPyI6HnUBfe/7XGeMycxca6mR5rlC
# 5SBLm9qbe7mZXdmbgEvXhEArJ9PoujC7Pgkap0mV7ytAJMKXx6fumyXvqAoAl4Va
# qp3cKcniNQfrcE1K1sGzVrihQTib0fsxf4/gX+GxPw+OFklg1waNGPmqJhCrKtPQ
# 0WeNG0a+RzDVLnLRxWPa52N5RH5LYySJhi40PylMUosqp8DikSiJucBb+R3Z5yet
# /5oCl8HGUJKbAiy9qbk0WQq/hEr/3/6zn+vZnuCYI+yma3cWKtvMrTscpIfcRnNe
# GWJoRVfkkIJCu0LW8GHgwaM9ZqNd9BjuiMmNF0UpmTJ1AjHuKSbIawLmtWJFfzcV
# WiNoidQ+3k4nsPBADLxNF8tNorMe0AZa3faTz1d1mfX6hhpneLO/lv403L3nUlbl
# s+V1e9dBkQXcXWnjlQ1DufyDljmVe2yAWk8TcsbXfSl6RLpSpCrVQUYJIP4ioLZb
# MI28iQzV13D4h1L92u+sUS4Hs07+0AnacO+Y+lbmbdu1V0vc5SwlFcieLnhO+Nqc
# noYsylfzGuXIkosagpZ6w7xQEmnYDlpGizrrJvojybawgb5CAKT41v4wLsfSRvbl
# jnX98sy50IdbzAYQYLuDNbdeZ95H7JlI8aShFf6tjGKOOVVPORa5sWOd/7cCAwEA
# AaOCAT4wggE6MA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFLahVDkCw6A/joq8
# +tT4HKbROg79MB8GA1UdIwQYMBaAFAh2zcsH/yT2xc3tu5C84oQ3RnX3MA4GA1Ud
# DwEB/wQEAwIBBjAvBgNVHR8EKDAmMCSgIqAghh5odHRwOi8vY3JsLmNlcnR1bS5w
# bC9jdG5jYS5jcmwwawYIKwYBBQUHAQEEXzBdMCgGCCsGAQUFBzABhhxodHRwOi8v
# c3ViY2Eub2NzcC1jZXJ0dW0uY29tMDEGCCsGAQUFBzAChiVodHRwOi8vcmVwb3Np
# dG9yeS5jZXJ0dW0ucGwvY3RuY2EuY2VyMDkGA1UdIAQyMDAwLgYEVR0gADAmMCQG
# CCsGAQUFBwIBFhhodHRwOi8vd3d3LmNlcnR1bS5wbC9DUFMwDQYJKoZIhvcNAQEM
# BQADggEBAFHCoVgWIhCL/IYx1MIy01z4S6Ivaj5N+KsIHu3V6PrnCA3st8YeDrJ1
# BXqxC/rXdGoABh+kzqrya33YEcARCNQOTWHFOqj6seHjmOriY/1B9ZN9DbxdkjuR
# mmW60F9MvkyNaAMQFtXx0ASKhTP5N+dbLiZpQjy6zbzUeulNndrnQ/tjUoCFBMQl
# lVXwfqefAcVbKPjgzoZwpic7Ofs4LphTZSJ1Ldf23SIikZbr3WjtP6MZl9M7JYjs
# NhI9qX7OAo0FmpKnJ25FspxihjcNpDOO16hO0EoXQ0zF8ads0h5YbBRRfopUofbv
# n3l6XYGaFpAP4bvxSgD5+d2+7arszgowggZrMIIEU6ADAgECAhBX5gfP8LabvnxC
# XoItEBa/MA0GCSqGSIb3DQEBCwUAMFYxCzAJBgNVBAYTAlBMMSEwHwYDVQQKExhB
# c3NlY28gRGF0YSBTeXN0ZW1zIFMuQS4xJDAiBgNVBAMTG0NlcnR1bSBDb2RlIFNp
# Z25pbmcgMjAyMSBDQTAeFw0yNjEwMDYwODI4MjNaFw0yNzEwMDYwODI4MjJaMIGQ
# MQswCQYDVQQGEwJERTEdMBsGA1UECAwUUmhpbmVsYW5kLVBhbGF0aW5hdGUxEjAQ
# BgNVBAcMCUxhaG5zdGVpbjEeMBwGA1UECgwVT3BlbiBTb3VyY2UgRGV2ZWxvcGVy
# MS4wLAYDVQQDDCVPcGVuIFNvdXJjZSBEZXZlbG9wZXIgVGhvbWFzIE5hdW5oZWlt
# MIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEAphp2ljSmVsRB6gvkEq7A
# DUMENpKZiDFoAfF1ktD1nCwaVyI+bJ0ROjHwAn4iJgS+vlBDPOUIw2GLHkiqUhOg
# HSwzg6gj45GNmt6zxsKP0zPlhMB/fb0zmz7/F7e3yfKCXLfhbHuk0Es9gJ+8Wmet
# 9StVwwSE/K0MpW06GKA9nPWyE4N/ADwigDIj9rsTKJR4cve9w8G1b3zohpUc/qMr
# onab+HVw5r9b3sOBnnaXC6zvrX4fovRz1ztwfWjRjILLoFG6mW/TuK5F2ICD3Rhl
# 9R2rJWWWhzTDMsQlzvX+/SaM74q3OFpIVNdhvtmWU9pKUBRAyYMijcDqKEFGSzrc
# 8JFmGMpP5TuroWF+N21d3px2eARvW1mV0DCfFAH+Og20SH/gVW6/ltQQEkH/z/Zt
# 49ARyJrgESLOQtlMCIYSoKISdkk4ml4VUS1E/PcbzXEYj3a7m6DOxcd3WuY03UT9
# cnk3p0LMN6LeLYAZgVY+HRloZlH+kW2SkOHNuWgtXvZhAgMBAAGjggF4MIIBdDAM
# BgNVHRMBAf8EAjAAMD0GA1UdHwQ2MDQwMqAwoC6GLGh0dHA6Ly9jY3NjYTIwMjEu
# Y3JsLmNlcnR1bS5wbC9jY3NjYTIwMjEuY3JsMHMGCCsGAQUFBwEBBGcwZTAsBggr
# BgEFBQcwAYYgaHR0cDovL2Njc2NhMjAyMS5vY3NwLWNlcnR1bS5jb20wNQYIKwYB
# BQUHMAKGKWh0dHA6Ly9yZXBvc2l0b3J5LmNlcnR1bS5wbC9jY3NjYTIwMjEuY2Vy
# MB8GA1UdIwQYMBaAFN10XUwA23ufoHTKsW73PMAywHDNMB0GA1UdDgQWBBTBvGyH
# MXj1dMbVnxuof9oP4PY7cDBLBgNVHSAERDBCMAgGBmeBDAEEATA2BgsqhGgBhvZ3
# AgUBBDAnMCUGCCsGAQUFBwIBFhlodHRwczovL3d3dy5jZXJ0dW0ucGwvQ1BTMBMG
# A1UdJQQMMAoGCCsGAQUFBwMDMA4GA1UdDwEB/wQEAwIHgDANBgkqhkiG9w0BAQsF
# AAOCAgEAkC165c7/r6y3VeEEpNXas9bQs6ZpKPdJffHkf9xoPi4kWvAIWGc4ocG1
# DJLb18RXiDYShDmT5sb/6I08FeipKi2DNJkuOq6yOTJ3S7+hNFIZCyd6i7WKO8ea
# P5I+FAbyJP4+5coZoMg45bUa9JoocaJCm+5n7WiCfN7/EQpClW5Trm4+ZMbp7EMN
# F1uLJgGCMnN0SsoUarp7PHlqupMxMC4Uhhe+lAP2ytcSIU81L97/qjrADbLWqICk
# 9il5iGM+aZndguq/cmfH9rJj4GRZHWnG6WwnzYD9+KgYlRI6em+/9dq+IRiXe2Bs
# thUuIaFW7U2gWw8tBTEYm1UBLHdJx+L1gjJCLs5KT/wy5wU32wZxNDqPUQejUC1x
# GvU3cKqmLgRq+yNf3gFVfzqIoggyM3U9wQvnk7nBtCkpjXS2hdx4yxzcmrlWsDHr
# z6+6/jeyQySzbYUlFmKnc4n9Mw2soHOajEua5poFARVRS5KgUBuiiQ21YFNYzWBo
# qGuECaaAI/C5rGxapMJ3dAOBnHw6NLqBet+ZXBJEccX5y89MLlOQu+jDmJP8HieR
# qDLlwfhVZnw4RY8Rus43NZBwhNlVuOUXqocHKmJhKNlekda3qsJj93eQBQOxKJcc
# w5O08q9jukpH2+yzFAj+7ll33DuNUgWptogc4Awtbx3glzcEvJQwggaCMIIEaqAD
# AgECAhAo8HfBHDa9/l90MkdwJy4DMA0GCSqGSIb3DQEBDAUAMFYxCzAJBgNVBAYT
# AlBMMSEwHwYDVQQKExhBc3NlY28gRGF0YSBTeXN0ZW1zIFMuQS4xJDAiBgNVBAMT
# G0NlcnR1bSBUaW1lc3RhbXBpbmcgMjAyMSBDQTAeFw0yNjAzMTEwNzM0NTRaFw0z
# NjAyMjcwNzM0NTRaMFAxCzAJBgNVBAYTAlBMMSEwHwYDVQQKDBhBc3NlY28gRGF0
# YSBTeXN0ZW1zIFMuQS4xHjAcBgNVBAMMFUNlcnR1bSBUaW1lc3RhbXAgMjAyNjCC
# AiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBALi9zfBw+xI12hi2F9uNQBNO
# STkTIi13fuh3DiWybI5392bBfJw6tO7zDs7UqP/rejVf4xFTU3XNOPsuOFkP5SPF
# snWwn3VJL+NhNhsuujvYsQtaQnvNo1wk46reTgws1L8OFEdKaxgyqT4zOQx81mLw
# NZSqXNU9Rrb9oAJWJF4Mydz+C1ebu0D0+vgM3tqAQ8TU203fqgKCPqIdHloaFqH9
# eyR+8tnpmy2hnnN59ZOtmsuHeRwf5iTOhlhf0d/qqquLOSIGPgGVL1Hj5XBUox2O
# E7tQRr+l/xxNbL6OuwBO4Aa2EbuxYg37cpXv5i2/lTL4NASB/duf9W4fjA3Ro+GP
# +bC54n2OYiM3Cq8z92ejx8F5HWO2e4/75eC95B9tMw/eq7109GDyW2+2ut5Bn0o8
# MXJTERkyystWbqcnUkomA97qfiDCwvpWuOMdfTOksXe9S+xDxOJaCbiyTivgPulx
# cT+tfQTRXBr5UwtPvc+INnafOEilzVHA11tv+pHcylryxBc08cUihf/ttqkZS0bm
# x3VxQay8uohaTp0//ECA9vYGW51SKdJMJhXvKyIdBLy8WYNchTt8yRITv9Adxg9J
# qGujh+TnP1bW3aQjFtlEl7SzgNeI8OVA/CJAWWUuRT3Au9ToOpHy/flrrknhN4he
# Ew7/6ILMv4HSsj0YZ4otAgMBAAGjggFQMIIBTDB1BggrBgEFBQcBAQRpMGcwOwYI
# KwYBBQUHMAKGL2h0dHA6Ly9zdWJjYS5yZXBvc2l0b3J5LmNlcnR1bS5wbC9jdHNj
# YTIwMjEuY2VyMCgGCCsGAQUFBzABhhxodHRwOi8vc3ViY2Eub2NzcC1jZXJ0dW0u
# Y29tMB8GA1UdIwQYMBaAFL5UAi+/QGxzQ86sCSVOnkNEGu7gMAwGA1UdEwEB/wQC
# MAAwOQYDVR0fBDIwMDAuoCygKoYoaHR0cDovL3N1YmNhLmNybC5jZXJ0dW0ucGwv
# Y3RzY2EyMDIxLmNybDAWBgNVHSUBAf8EDDAKBggrBgEFBQcDCDAOBgNVHQ8BAf8E
# BAMCB4AwIgYDVR0gBBswGTAIBgZngQwBBAIwDQYLKoRoAYb2dwIFAQswHQYDVR0O
# BBYEFCM5aiirmhKnpKfyH/lFmr+N/oIDMA0GCSqGSIb3DQEBDAUAA4ICAQBm/pOb
# rAveTqYFPCSthxNVwBHTW4TfuBQY2KGCdMlH6ALjVPYf2XARNGMTXScR0JQ2/c2L
# xLpoHHhRwxFf3d933DhFS165rYj6iPSQKm41rc9AMpTCjMdmai1aK9o870pOzTzL
# RhWFVIxm+5Qf+t4V5z14tPoUsAWdfRKD1Zzvzfe5AHlE9rxOZcGROiULxs3Edf7Z
# J8RZI0jzD9juj2oEX2XS7BVKuNCK1W0h1mjapv028yqo8UtZh5/1Ib48Yyp88BDY
# Shh5PSGUUuS5tYXVfcduIPBe3PdNTYRYdT5VNxkwLgjMzIcr/6SH2fejFJDjnCxj
# vXLuarErJg/IdI+hXdGIBDVOGv1zrkF4ktoQvFZIp5DIAYh0GwyUhc87qqn5UaYT
# j2+iyiBDSNh7aGCrkuAnqg7Qh7frhX+QhMGeJ9SpLpnG76qN85hLyjvYR6gRYiLT
# j8G4fxjqV400/buPw0rBo9yJZyq/8cGdCOLPOYf9qpefTcrZKeO9pg+UJHx8UABb
# aTqizRxm8sUSEycccCCFhIit3AI/3wGuSOADeiA676NcQ7+dsP04YvzvdN7pTZNy
# g672WpBDJdg0eItHl4yomvNeIOWh1qs4QqOW6TI8Ocsi4uSDTLorxLUDnBTwKQzz
# pxd1YUctEgRh32R0J9FkFkeEruJU94RljFr1uDCCBrkwggShoAMCAQICEQCZo4AK
# JlU7ZavcboSms+o5MA0GCSqGSIb3DQEBDAUAMIGAMQswCQYDVQQGEwJQTDEiMCAG
# A1UEChMZVW5pemV0byBUZWNobm9sb2dpZXMgUy5BLjEnMCUGA1UECxMeQ2VydHVt
# IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MSQwIgYDVQQDExtDZXJ0dW0gVHJ1c3Rl
# ZCBOZXR3b3JrIENBIDIwHhcNMjEwNTE5MDUzMjE4WhcNMzYwNTE4MDUzMjE4WjBW
# MQswCQYDVQQGEwJQTDEhMB8GA1UEChMYQXNzZWNvIERhdGEgU3lzdGVtcyBTLkEu
# MSQwIgYDVQQDExtDZXJ0dW0gQ29kZSBTaWduaW5nIDIwMjEgQ0EwggIiMA0GCSqG
# SIb3DQEBAQUAA4ICDwAwggIKAoICAQCdI88EMCM7wUYs5zNzPmNdenW6vlxNur3r
# Lfi+5OZ+U3iZIB+AspO+CC/bj+taJUbMbFP1gQBJUzDUCPx7BNLgid1TyztVLn52
# NKgxxu8gpyTr6EjWyGzKU/gnIu+bHAse1LCitX3CaOE13rbuHbtrxF2tPU8f253Q
# gX6eO8yTbGps1Mg+yda3DcTsOYOhSYNCJiL+5wnjZ9weoGRtvFgMHtJg6i671OPX
# IciiHO4Lwo2p9xh/tnj+JmCQEn5QU0NxzrOiRna4kjFaA9ZcwSaG7WAxeC/xoZSx
# F1oK1UPZtKVt+yrsGKqWONoK6f5EmBOAVEK2y4ATDSkb34UD7JA32f+Rm0wsr5aj
# zftDhA5mBipVZDjHpwzv8bTKzCDUSUuUmPo1govD0RwFcTtMXcfJtm1i+P2UNXad
# PyYVKRxKQATHN3imsfBiNRdN5kiVVeqP55piqgxOkyt+HkwIA4gbmSc3hD8ke66t
# 9MjlcNg73rZZlrLHsAIV/nJ0mmgSjBI/TthoGJDydekOQ2tQD2Dup/+sKQptalDl
# ui59SerVSJg8gAeV7N/ia4mrGoiez+SqV3olVfxyLFt3o/OQOnBmjhKUANoKLYlK
# mUpKEFI0PfoT8Q1W/y6s9LTI6ekbi0igEbFUIBE8KDUGfIwnisEkBw5KcBZ3XwnH
# mfznwlKo8QIDAQABo4IBVTCCAVEwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU
# 3XRdTADbe5+gdMqxbvc8wDLAcM0wHwYDVR0jBBgwFoAUtqFUOQLDoD+Oirz61Pgc
# ptE6Dv0wDgYDVR0PAQH/BAQDAgEGMBMGA1UdJQQMMAoGCCsGAQUFBwMDMDAGA1Ud
# HwQpMCcwJaAjoCGGH2h0dHA6Ly9jcmwuY2VydHVtLnBsL2N0bmNhMi5jcmwwbAYI
# KwYBBQUHAQEEYDBeMCgGCCsGAQUFBzABhhxodHRwOi8vc3ViY2Eub2NzcC1jZXJ0
# dW0uY29tMDIGCCsGAQUFBzAChiZodHRwOi8vcmVwb3NpdG9yeS5jZXJ0dW0ucGwv
# Y3RuY2EyLmNlcjA5BgNVHSAEMjAwMC4GBFUdIAAwJjAkBggrBgEFBQcCARYYaHR0
# cDovL3d3dy5jZXJ0dW0ucGwvQ1BTMA0GCSqGSIb3DQEBDAUAA4ICAQB1iFgP5Y9Q
# KJpTnxDsQ/z0O23JmoZifZdEOEmQvo/79PQg9nLF/GJe6ZiUBEyDBHMtFRK0mXj3
# Qv3gL0sYXe+PPMfwmreJHvgFGWQ7XwnfMh2YIpBrkvJnjwh8gIlNlUl4KENTK5DL
# qsYPEtRQCw7R6p4s2EtWyDDr/M58iY2UBEqfUU/ujR9NuPyKk0bEcEi62JGxauFY
# zZ/yld13fHaZskIoq2XazjaD0pQkcQiIueL0HKiohS6XgZuUtCKA7S6CHttZEsOb
# QJ1j2s0urIDdqF7xaXFVaTHKtAuMfwi0jXtF3JJphrJfc+FFILgCbX/uYBPBlbBI
# P4Ht4xxk2GmfzMn7oxPITpigQFJFWuzTMUUgdRHTxaTSKRJ/6Uh7ki/pFjf9sUAS
# WgxT69QF9Ki4JF5nBIujxZ2sOU9e1HSCJwOfK07t5nnzbs1LbHuAIGJsRJiQ6HX/
# DW1XFOlXY1rc9HufFhWU+7Uk+hFkJsfzqBz3pRO+5aI6u5abI4Qws4YaeJH7H7M8
# X/YNoaArZbV4Ql+jarKsE0+8XvC4DJB+IVcvC9Ydqahi09mjQse4fxfef0L7E3hh
# o2O3bLDM6v60rIRUCi2fJT2/IRU5ohgyTch4GuYWefSBsp5NPJh4QRTP9DC3gc5Q
# EKtbrTY0Ka87Web7/zScvLmvQBm8JDFpDjCCBrkwggShoAMCAQICEQDn/2nHOzXO
# S5Em2HR8aKWHMA0GCSqGSIb3DQEBDAUAMIGAMQswCQYDVQQGEwJQTDEiMCAGA1UE
# ChMZVW5pemV0byBUZWNobm9sb2dpZXMgUy5BLjEnMCUGA1UECxMeQ2VydHVtIENl
# cnRpZmljYXRpb24gQXV0aG9yaXR5MSQwIgYDVQQDExtDZXJ0dW0gVHJ1c3RlZCBO
# ZXR3b3JrIENBIDIwHhcNMjEwNTE5MDUzMjA3WhcNMzYwNTE4MDUzMjA3WjBWMQsw
# CQYDVQQGEwJQTDEhMB8GA1UEChMYQXNzZWNvIERhdGEgU3lzdGVtcyBTLkEuMSQw
# IgYDVQQDExtDZXJ0dW0gVGltZXN0YW1waW5nIDIwMjEgQ0EwggIiMA0GCSqGSIb3
# DQEBAQUAA4ICDwAwggIKAoICAQDpEh8ENe25XXrFppVBvoplf0530W0lddNmjtv4
# YSh/f7eDQKFaIqc7tHj7ox+u8vIsJZlroakUeMS3i3T8aJRC+eQs4FF0GqvkM6+W
# ZO8kmzZfxmZaBYmMLs8FktgFYCzywmXeQ1fEExflee2OpbHVk665eXRHjH7MYZIz
# Nnjl2m8Hy8ulB9mR8wL/W0v0pjKNT6G0sfrx1kk+3OGosFUb7yWNnVkWKU4qSxLv
# 16kJ6oVJ4BSbZ4xMak6JLeB8szrK9vwGDpvGDnKCUMYL3NuviwH1x4gZG0JAXU3x
# 2pOAz91JWKJSAmRy/l0s0l5bEYKolg+DMqVhlOANd8Yh5mkQWaMEvBRE/kAGzIqg
# WhwzN2OsKIVtO8mf5sPWSrvyplSABAYa13rMYnzwfg08nljZHghquCJYCa/xHK9a
# cev9UD7Y+usr15d7mrszzxhF1JOr1Mpup2chNSBlyOObhlSO16rwrffVrg/SzaKf
# SndS5swRhr8bnDqNJY9TNyEYvBYpgF95K7p0g4LguR4A++Z1nFIHWVY5v0fNVZmg
# zxD9uVo/gta3onGOQj3JCxgYx0KrCXu4yc9QiVwTFLWbNdHFSjBCt5/8Q9pLuRhV
# ocdCunhcHudMS1CGQ/Rn0+7P+fzMgWdRKfEOh/hjLrnQ8BdJiYrZNxvIOhM2aa3z
# EDHNwwIDAQABo4IBVTCCAVEwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUvlQC
# L79AbHNDzqwJJU6eQ0Qa7uAwHwYDVR0jBBgwFoAUtqFUOQLDoD+Oirz61PgcptE6
# Dv0wDgYDVR0PAQH/BAQDAgEGMBMGA1UdJQQMMAoGCCsGAQUFBwMIMDAGA1UdHwQp
# MCcwJaAjoCGGH2h0dHA6Ly9jcmwuY2VydHVtLnBsL2N0bmNhMi5jcmwwbAYIKwYB
# BQUHAQEEYDBeMCgGCCsGAQUFBzABhhxodHRwOi8vc3ViY2Eub2NzcC1jZXJ0dW0u
# Y29tMDIGCCsGAQUFBzAChiZodHRwOi8vcmVwb3NpdG9yeS5jZXJ0dW0ucGwvY3Ru
# Y2EyLmNlcjA5BgNVHSAEMjAwMC4GBFUdIAAwJjAkBggrBgEFBQcCARYYaHR0cDov
# L3d3dy5jZXJ0dW0ucGwvQ1BTMA0GCSqGSIb3DQEBDAUAA4ICAQC4k1l3yUwV/ZQH
# CKCneqAs8EGTnwEUJLdDpokN/dMhKjK0rR5qX8nIIHzxpQR3TAw2IRw1Uxsr2Pli
# G3bCFqSdQTUbfaTq6V3vBzEebDru9QFjqlKnxCF2h1jhLNFFplbPJiW+JSnJTh1f
# KEqEdKdxgl9rVTvlxfEJ7exOn25MGbd/wGPwuSmMxRJVO0wnqgS7kmoJjNF9zqee
# hFSDDP8ZVkWg4EZ2tIS0M3uZmByRr+1Lkwjjt8AtW83mVnZTyTsOb+FNfwJY7DS4
# FmWhkRbgcHRetreoTirPOr/ozyDKhT8MTSTf6Lttg6s6T/u08mDWw6HK04ZRDfQ9
# sb77QV8mKgO44WGP31vXnVKoWVJpFBjPvjL8/Zck/5wXX2iqjOaLStFOR/IQki+E
# hn4zlcgVm22ZVCBPF+l8nAwUUShCtKuSU7GmZLKCmmxQMkSiWILTm8EtVD6AxnJh
# oq8EnhjEEyUoflkeRF2WhFiVQOmWTwZRr44IxWGkNJC6tTorW5rl2Zl+2e9JLPYf
# 3pStAPMDoPKIjVXd6NW2+fZrNUBeDo2eOa5Fn7Brs/HLQff5Xgris5MeUbdVgDrF
# 8uxO6cLPvZPo63j62SsNg55pTWk9fUIF9iPoRbb4QurjoY/woI1RAOKtYtTic6aA
# Jq3u83RIPpGXBSJKwx4KJAOZnCDCtTGCBqIwggaeAgEBMGowVjELMAkGA1UEBhMC
# UEwxITAfBgNVBAoTGEFzc2VjbyBEYXRhIFN5c3RlbXMgUy5BLjEkMCIGA1UEAxMb
# Q2VydHVtIENvZGUgU2lnbmluZyAyMDIxIENBAhBX5gfP8LabvnxCXoItEBa/MA0G
# CWCGSAFlAwQCAQUAoIGEMBgGCisGAQQBgjcCAQwxCjAIoAKAAKECgAAwGQYJKoZI
# hvcNAQkDMQwGCisGAQQBgjcCAQQwHAYKKwYBBAGCNwIBCzEOMAwGCisGAQQBgjcC
# ARUwLwYJKoZIhvcNAQkEMSIEIACu0Gb9fAvhvN0qIfuBIJLcu9hFDXFIJTER64IL
# 2ywJMA0GCSqGSIb3DQEBAQUABIIBgKMp3IfHHcuLbMaOa9aU00byLbzH6AZ537ZJ
# LzhuzNW0GheAKhNnNYjoRHjikI0iFQQSURCTjjhmKbrRI+7mVOzXZBtvwSlhxOtS
# yoazl3vMvbD3g00lGSoCtYlkjU7ZF5SKb4CfkSejt26rTLlf6zvTGSAPHHUZzgfr
# tfxFD+XEp1rAGocZxhP+PN4KW+wXUfu4EzxlNetOonvMnpLxrjfBwx/lKu2g57Jp
# IgnuAklW4xs1OHOgU51g+J/jRKmiYEqYjdZriA23SVWQPNZtx3XdftwpFmHn70of
# v4AWjxajqY3gHUnVBWg3H+Wc1hdn/puYs4VQbd21ZDybS0OnizlXj/oCOfp3wHOB
# GForWKRTJL1k80j6uVfJYg12zt3rM6PygcDaq7rHW1Waqv1+A1nNl/8Q8gjO5HaL
# TfRqP6tZpFny2BwSBxxZI67HAk7twG/y7xnx85FHEBDS4wlAx8lTCkWsa3avsI6S
# KadQnqaPyzhtGFUQOmR85JsXQCmvb6GCBAIwggP+BgkqhkiG9w0BCQYxggPvMIID
# 6wIBATBqMFYxCzAJBgNVBAYTAlBMMSEwHwYDVQQKExhBc3NlY28gRGF0YSBTeXN0
# ZW1zIFMuQS4xJDAiBgNVBAMTG0NlcnR1bSBUaW1lc3RhbXBpbmcgMjAyMSBDQQIQ
# KPB3wRw2vf5fdDJHcCcuAzANBglghkgBZQMEAgIFAKCCAVYwGgYJKoZIhvcNAQkD
# MQ0GCyqGSIb3DQEJEAEEMBwGCSqGSIb3DQEJBTEPFw0yNjEwMjIxMTQwNDFaMDcG
# CyqGSIb3DQEJEAIvMSgwJjAkMCIEIIW+kOEK0kONfMkotq9IsJqyCBd87PiwEmxY
# 05EFJcQ8MD8GCSqGSIb3DQEJBDEyBDDhjdWVEra5AtwY4GCQ1vHqgUgA2lo0Aupx
# Fvw5/XiTHbGHcqkZjjhCS5KoCHI8HucwgZ8GCyqGSIb3DQEJEAIMMYGPMIGMMIGJ
# MIGGBBRXFGhBDKha80JO+RZKUTYQ9NONmDBuMFqkWDBWMQswCQYDVQQGEwJQTDEh
# MB8GA1UEChMYQXNzZWNvIERhdGEgU3lzdGVtcyBTLkEuMSQwIgYDVQQDExtDZXJ0
# dW0gVGltZXN0YW1waW5nIDIwMjEgQ0ECECjwd8EcNr3+X3QyR3AnLgMwDQYJKoZI
# hvcNAQEBBQAEggIAGVnX6eI1DJWh00MqLXCT5+SSutq8oeOHeogERhOesv9AHzxp
# CAjgFvrP2FRdxWEEmOWg0gZGFrPLkoDxQqqmwm7JZ0N3V/oDMFNeWrmthxsU59eB
# TWOzXHEfSPqykvMHZiwSq6lwpzHpHxFy3xV1eqbGLhQQ4eSQUDivfq3CCzq5TSov
# dyNhjeLXGzAl9JlAlwRd+eg5WXubTdHcJzJKjiDpBfN/1kn4pv3si3LiZP2ZhdGZ
# /8YBeu1QdkXh9b23ohhbPzWXNDCzYO1pZMhNVMQftGqRgvYJn7g8+4b5RemdWX2w
# XfIgdPQiJ76xmjPFG/UczxXC1ASJcvp3ZAnfkjpSsyW9k3+yKPMTs7XEg/4oJEAA
# wpa5omPcA7momwnH0/U0k5KxfzJewViJQAUJ4u3rwXb+4O5Z+Pplr4Zt4syed3ob
# 8Tr32lLWwMzQzT7mIMVKoUpzL4TG1cxvKfxkTaM6cgmJNeL7o5dHDC8Z6GaGuTms
# kj+GKv0Q88DVDJOQUZ80ZAfOK8uQuLj6Kqs7h/qE4JTyCRpE1X4hd1+D6ZvP3dlb
# 2s6HDkCtITTXJF5CFU4TGIMxU43bdc6fP1aZScLUKLyvEQUj/evDq1yXuqwmpgtv
# TIgOMuFYUGt2Y0LT075GMncARDql5HxuYOKA3yr6Z+tMGDIMZWTlegQkL3k=
# SIG # End signature block