Get-AADAssessRoleAssignmentReport.ps1

<#
.SYNOPSIS
    Gets a report of all role assignments
.DESCRIPTION
    This function returns a list of role assignments
.EXAMPLE
    PS C:\> Get-AADAssessRoleAssignmentReport | Export-Csv -Path ".\RoleAssignmentReport.csv"
#>

function Get-AADAssessRoleAssignmentReport {
    [CmdletBinding()]
    param (
        # Role Assignments
        [Parameter(Mandatory = $false)]
        [psobject] $RoleAssignmentsData,
        # Role Assignment Schedule Instance Data
        [Parameter(Mandatory = $false)]
        [psobject] $RoleAssignmentScheduleInstancesData,
        # Role Eligible Schedule Instance Data
        [Parameter(Mandatory = $false)]
        [psobject] $RoleEligibilityScheduleInstancesData,
        # Organization Data
        [Parameter(Mandatory = $false)]
        [psobject] $OrganizationData,
        # Administrative Unit Data
        [Parameter(Mandatory = $false)]
        [psobject] $AdministrativeUnitsData,
        # User Data
        [Parameter(Mandatory = $false)]
        [psobject] $UsersData,
        # Group Data
        [Parameter(Mandatory = $false)]
        [psobject] $GroupsData,
        # Application Data
        [Parameter(Mandatory = $false)]
        [psobject] $ApplicationsData,
        # Service Principal Data
        [Parameter(Mandatory = $false)]
        [psobject] $ServicePrincipalsData,
        # Generate Report Offline, only using the data passed in parameters
        [Parameter(Mandatory = $false)]
        [switch] $Offline
    )

    Start-AppInsightsRequest $MyInvocation.MyCommand.Name
    try {

        # there may be no elegibile roles so it isn't counted to check for offline but collection will be prevented
        # role assignement should have some members if at least for one global administrator
        if ($Offline -and (!($PSBoundParameters['RoleAssignmentScheduleInstancesData'] -or $PSBoundParameters['RoleEligibilityScheduleInstancesData']) -and !$PSBoundParameters['roleAssignmentsData'])) {
            Write-Error -Exception (New-Object System.Management.Automation.ItemNotFoundException -ArgumentList 'Use of the offline parameter requires that all data be provided using the data parameters.') -ErrorId 'DataParametersRequired' -Category ObjectNotFound
            return
        }

        function Process-RoleAssignment {
            param (
                #
                [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true)]
                [psobject] $InputObject,
                #
                [Parameter(Mandatory = $true)]
                [psobject] $LookupCache,
                #
                [Parameter(Mandatory = $false)]
                [switch] $UseLookupCacheOnly
            )

            process {
                $RoleScheduleInstances = $InputObject
                foreach ($RoleScheduleInstance in $RoleScheduleInstances) {

                    # get details of directory scope
                    if ($RoleScheduleInstance.directoryScopeId -match '/(?:(.+)s/)?([0-9a-fA-F]{8}-(?:[0-9a-fA-F]{4}-){3}[0-9a-fA-F]{12})') {
                        $ObjectId = $Matches[2]
                        $directoryScopeType = $Matches[1]
                        if ($directoryScopeType) {
                            $directoryScope = Get-AadObjectById $ObjectId -Type $directoryScopeType -LookupCache $LookupCache -UseLookupCacheOnly:$UseLookupCacheOnly
                        }
                        else {
                            $directoryScope = Get-AadObjectById $ObjectId -Type servicePrincipal -LookupCache $LookupCache -UseLookupCacheOnly:$UseLookupCacheOnly
                            if ($directoryScope) { $directoryScopeType = 'servicePrincipal' }
                            else {
                                $directoryScope = Get-AadObjectById $ObjectId -Type application -LookupCache $LookupCache -UseLookupCacheOnly:$UseLookupCacheOnly
                                if ($directoryScope) { $directoryScopeType = 'application' }
                            }
                        }
                    }
                    else {
                        $directoryScopeType = "tenant"
                        $directoryScope = @{
                            id          = $OrganizationData.id
                            displayName = $OrganizationData.displayName
                        }
                    }

                    # get details of principal
                    $principalType = 'user'
                    $principal = Get-AadObjectById $RoleScheduleInstance.principalId -Type $principalType -LookupCache $LookupCache -UseLookupCacheOnly:$UseLookupCacheOnly -Properties 'id,displayName,mail,otherMails'
                    if (!$principal) {
                        $principalType = 'group'
                        $principal = Get-AadObjectById $RoleScheduleInstance.principalId -Type $principalType -LookupCache $LookupCache -UseLookupCacheOnly:$UseLookupCacheOnly -Properties 'id,displayName,mail'
                    } 
                    if (!$principal) {
                        $principalType = 'servicePrincipal'
                        $principal = Get-AadObjectById $RoleScheduleInstance.principalId -Type $principalType -LookupCache $LookupCache -UseLookupCacheOnly:$UseLookupCacheOnly -Properties 'id,displayName'
                    }
                    if (!$principal) {
                        $principalType = 'unknown'
                    }

                    $OutputObject = [PSCustomObject]@{
                        id                        = $RoleScheduleInstance.id
                        directoryScopeId          = $RoleScheduleInstance.directoryScopeId
                        directoryScopeObjectId    = if ($directoryScope) { $directoryScope.id } else { $null }
                        directoryScopeDisplayName = if ($directoryScope) { $directoryScope.displayName } else { $null }
                        directoryScopeType        = $directoryScopeType
                        roleDefinitionId          = $RoleScheduleInstance.roleDefinition.id
                        roleDefinitionTemplateId  = $RoleScheduleInstance.roleDefinition.templateId
                        roleDefinitionDisplayName = $RoleScheduleInstance.roleDefinition.displayName
                        principalId               = $RoleScheduleInstance.principalId
                        principalDisplayName      = if ($principal) { $principal.displayName } else { $null }
                        principalType             = $principalType
                        principalMail             = if ($principal) { Get-ObjectPropertyValue $principal mail } else { $null }
                        principalOtherMails       = if ($principal) { Get-ObjectPropertyValue $principal otherMails } else { $null }
                        memberType                = $RoleScheduleInstance.memberType
                        assignmentType            = $RoleScheduleInstance.assignmentType
                        startDateTime             = if ($RoleScheduleInstance.psobject.Properties.Name.Contains('startDateTime')) { $RoleScheduleInstance.startDateTime } else { $null }
                        endDateTime               = if ($RoleScheduleInstance.psobject.Properties.Name.Contains('endDateTime')) { $RoleScheduleInstance.endDateTime } else { $null }
                    }
                    $OutputObject

                    if ($principalType -eq 'group') {
                        $OutputObject.memberType = 'Group'

                        if ($UseLookupCacheOnly) {
                            Expand-GroupTransitiveMembership $RoleScheduleInstance.principalId -LookupCache $LookupCache `
                            | ForEach-Object {
                                $principalType = $_.'@odata.type' -replace '#microsoft.graph.', ''
                                $principal = Get-AadObjectById $_.id -Type $principalType -LookupCache $LookupCache -UseLookupCacheOnly:$UseLookupCacheOnly
                                $OutputObject.principalId = $_.id
                                $OutputObject.principalDisplayName = if ($principal) { $principal.displayName } else { $null }
                                $OutputObject.principalType = $principalType
                                $OutputObject.principalMail = if ($principal) { Get-ObjectPropertyValue $principal mail } else { $null }
                                $OutputObject.principalOtherMails = if ($principal) { Get-ObjectPropertyValue $principal otherMails } else { $null }
                                $OutputObject
                            }
                        }
                        else {
                            Get-MsGraphResults 'groups/{0}/transitiveMembers' -UniqueId $RoleScheduleInstance.principalId -Select id, displayName, mail, otherMails -Top 999 -DisableUniqueIdDeduplication `
                            | ForEach-Object {
                                $OutputObject.principalId = $_.id
                                $OutputObject.principalDisplayName = $_.displayName
                                $OutputObject.principalType = $_.'@odata.type' -replace '#microsoft.graph.', ''
                                $OutputObject.principalMail = if ($principal) { Get-ObjectPropertyValue $principal mail } else { $null }
                                $OutputObject.principalOtherMails = if ($principal) { Get-ObjectPropertyValue $principal otherMails } else { $null }
                                $OutputObject
                            }
                        }
                    }
                }
            }
        }

        if (!$OrganizationData) {
            $OrganizationData = Get-MsGraphResults 'organization?$select=id,displayName'
        }

        $LookupCache = New-LookupCache
        if ($AdministrativeUnitsData) {
            if ($AdministrativeUnitsData -is [System.Collections.Generic.Dictionary[guid, pscustomobject]]) {
                $LookupCache.administrativeUnit = $AdministrativeUnitsData
            }
            else {
                $AdministrativeUnitsData | Add-AadObjectToLookupCache -Type administrativeUnit -LookupCache $LookupCache
            }
        }

        if ($UsersData) {
            if ($UsersData -is [System.Collections.Generic.Dictionary[guid, pscustomobject]]) {
                $LookupCache.user = $UsersData
            }
            else {
                $UsersData | Add-AadObjectToLookupCache -Type user -LookupCache $LookupCache
            }
        }

        if ($GroupsData) {
            if ($GroupsData -is [System.Collections.Generic.Dictionary[guid, pscustomobject]]) {
                $LookupCache.group = $GroupsData
            }
            else {
                $GroupsData | Add-AadObjectToLookupCache -Type group -LookupCache $LookupCache
            }
        }

        if ($ApplicationsData) {
            if ($ApplicationsData -is [System.Collections.Generic.Dictionary[guid, pscustomobject]]) {
                $LookupCache.application = $ApplicationsData
            }
            else {
                $ApplicationsData | Add-AadObjectToLookupCache -Type application -LookupCache $LookupCache
            }
        }

        if ($ServicePrincipalsData) {
            if ($ServicePrincipalsData -is [System.Collections.Generic.Dictionary[guid, pscustomobject]]) {
                $LookupCache.servicePrincipal = $ServicePrincipalsData
            }
            else {
                $ServicePrincipalsData | Add-AadObjectToLookupCache -Type servicePrincipal -LookupCache $LookupCache
            }
        }

        ## Get Role Assignments
        [bool] $isAadP2Tenant = $true
        if ($RoleAssignmentScheduleInstancesData) {
            $isAadP2Tenant = $true
            $RoleAssignmentScheduleInstancesData | Process-RoleAssignment -LookupCache $LookupCache -UseLookupCacheOnly:$Offline
        }
        elseif ($RoleAssignmentsData) {
            $isAadP2Tenant = $false
            $RoleAssignmentsData | Select-Object -Property *, @{Name = "memberType"; Expression = { "Direct" } }, @{Name = "assignmentType"; Expression = { "Assigned" } } `
            | Process-RoleAssignment -LookupCache $LookupCache -UseLookupCacheOnly:$Offline
        }
        elseif (!$Offline) {
            try { Get-MsGraphResults 'https://graph.microsoft.com/beta/roleManagement/directory/roleAssignmentScheduleInstances?$top=1' -ApiVersion beta -DisablePaging -ErrorAction Stop | Out-Null }
            catch { $isAadP2Tenant = $false }

            if ($isAadP2Tenant) {
                Write-Verbose "Getting roleAssignmentScheduleInstances..."
                #Get-MsGraphResults 'roleManagement/directory/roleAssignmentSchedules' -Select 'id,directoryScopeId,memberType,scheduleInfo,status,assignmentType' -Filter "status eq 'Provisioned' and assignmentType eq 'Assigned'" -QueryParameters @{ '$expand' = 'principal($select=id),roleDefinition($select=id,templateId,displayName)' } -ApiVersion 'beta' `
                Get-MsGraphResults 'roleManagement/directory/roleAssignmentScheduleInstances' -Select 'id,directoryScopeId,assignmentType,memberType,principalId,startDateTime,endDateTime' -QueryParameters @{ '$expand' = 'principal($select=id),roleDefinition($select=id,templateId,displayName)' } `
                | Process-RoleAssignment -LookupCache $LookupCache
            }
            else {
                Write-Verbose "Getting roleAssignments..."
                Get-MsGraphResults 'roleManagement/directory/roleAssignments' -Select 'id,directoryScopeId,principalId' -QueryParameters @{ '$expand' = 'roleDefinition($select=id,templateId,displayName)' } `
                | Select-Object -Property *, @{Name = "memberType"; Expression = { "Direct" } }, @{Name = "assignmentType"; Expression = { "Assigned" } } `
                | Process-RoleAssignment -LookupCache $LookupCache
            }
        }

        if ($RoleEligibilityScheduleInstancesData) {
            $RoleEligibilityScheduleInstancesData | Select-Object -Property *, @{Name = "assignmentType"; Expression = { "Eligible" } } `
            | Process-RoleAssignment -LookupCache $LookupCache -UseLookupCacheOnly:$Offline
        }
        elseif (!$Offline -and $isAadP2Tenant) {
            Write-Verbose "Getting roleEligibleScheduleInstances..."
            #Get-MsGraphResults 'roleManagement/directory/roleEligibilitySchedules' -Select 'id,directoryScopeId,memberType,scheduleInfo,status' -Filter "status eq 'Provisioned'" -QueryParameters @{ '$expand' = 'principal($select=id),roleDefinition($select=id,templateId,displayName)' } -ApiVersion 'beta' `
            Get-MsGraphResults 'roleManagement/directory/roleEligibilityScheduleInstances' -Select 'id,directoryScopeId,memberType,principalId,startDateTime,endDateTime' -QueryParameters @{ '$expand' = 'principal($select=id),roleDefinition($select=id,templateId,displayName)' } `
            | Select-Object -Property *, @{Name = "assignmentType"; Expression = { "Eligible" } } `
            | Process-RoleAssignment -LookupCache $LookupCache
        }

    }
    catch { if ($MyInvocation.CommandOrigin -eq 'Runspace') { Write-AppInsightsException -ErrorRecord $_ -IncludeProcessStatistics }; throw }
    finally { Complete-AppInsightsRequest $MyInvocation.MyCommand.Name -Success $? }
}

# SIG # Begin signature block
# MIInpQYJKoZIhvcNAQcCoIInljCCJ5ICAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCDj+eYXSgZb2agD
# yHoaHwzl2tcmgw7CxybIYrVCdRip5KCCDYUwggYDMIID66ADAgECAhMzAAACzfNk
# v/jUTF1RAAAAAALNMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD
# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p
# bmcgUENBIDIwMTEwHhcNMjIwNTEyMjA0NjAyWhcNMjMwNTExMjA0NjAyWjB0MQsw
# CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u
# ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
# AQDrIzsY62MmKrzergm7Ucnu+DuSHdgzRZVCIGi9CalFrhwtiK+3FIDzlOYbs/zz
# HwuLC3hir55wVgHoaC4liQwQ60wVyR17EZPa4BQ28C5ARlxqftdp3H8RrXWbVyvQ
# aUnBQVZM73XDyGV1oUPZGHGWtgdqtBUd60VjnFPICSf8pnFiit6hvSxH5IVWI0iO
# nfqdXYoPWUtVUMmVqW1yBX0NtbQlSHIU6hlPvo9/uqKvkjFUFA2LbC9AWQbJmH+1
# uM0l4nDSKfCqccvdI5l3zjEk9yUSUmh1IQhDFn+5SL2JmnCF0jZEZ4f5HE7ykDP+
# oiA3Q+fhKCseg+0aEHi+DRPZAgMBAAGjggGCMIIBfjAfBgNVHSUEGDAWBgorBgEE
# AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQU0WymH4CP7s1+yQktEwbcLQuR9Zww
# VAYDVR0RBE0wS6RJMEcxLTArBgNVBAsTJE1pY3Jvc29mdCBJcmVsYW5kIE9wZXJh
# dGlvbnMgTGltaXRlZDEWMBQGA1UEBRMNMjMwMDEyKzQ3MDUzMDAfBgNVHSMEGDAW
# gBRIbmTlUAXTgqoXNzcitW2oynUClTBUBgNVHR8ETTBLMEmgR6BFhkNodHRwOi8v
# d3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NybC9NaWNDb2RTaWdQQ0EyMDExXzIw
# MTEtMDctMDguY3JsMGEGCCsGAQUFBwEBBFUwUzBRBggrBgEFBQcwAoZFaHR0cDov
# L3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9jZXJ0cy9NaWNDb2RTaWdQQ0EyMDEx
# XzIwMTEtMDctMDguY3J0MAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADggIB
# AE7LSuuNObCBWYuttxJAgilXJ92GpyV/fTiyXHZ/9LbzXs/MfKnPwRydlmA2ak0r
# GWLDFh89zAWHFI8t9JLwpd/VRoVE3+WyzTIskdbBnHbf1yjo/+0tpHlnroFJdcDS
# MIsH+T7z3ClY+6WnjSTetpg1Y/pLOLXZpZjYeXQiFwo9G5lzUcSd8YVQNPQAGICl
# 2JRSaCNlzAdIFCF5PNKoXbJtEqDcPZ8oDrM9KdO7TqUE5VqeBe6DggY1sZYnQD+/
# LWlz5D0wCriNgGQ/TWWexMwwnEqlIwfkIcNFxo0QND/6Ya9DTAUykk2SKGSPt0kL
# tHxNEn2GJvcNtfohVY/b0tuyF05eXE3cdtYZbeGoU1xQixPZAlTdtLmeFNly82uB
# VbybAZ4Ut18F//UrugVQ9UUdK1uYmc+2SdRQQCccKwXGOuYgZ1ULW2u5PyfWxzo4
# BR++53OB/tZXQpz4OkgBZeqs9YaYLFfKRlQHVtmQghFHzB5v/WFonxDVlvPxy2go
# a0u9Z+ZlIpvooZRvm6OtXxdAjMBcWBAsnBRr/Oj5s356EDdf2l/sLwLFYE61t+ME
# iNYdy0pXL6gN3DxTVf2qjJxXFkFfjjTisndudHsguEMk8mEtnvwo9fOSKT6oRHhM
# 9sZ4HTg/TTMjUljmN3mBYWAWI5ExdC1inuog0xrKmOWVMIIHejCCBWKgAwIBAgIK
# YQ6Q0gAAAAAAAzANBgkqhkiG9w0BAQsFADCBiDELMAkGA1UEBhMCVVMxEzARBgNV
# BAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jv
# c29mdCBDb3Jwb3JhdGlvbjEyMDAGA1UEAxMpTWljcm9zb2Z0IFJvb3QgQ2VydGlm
# aWNhdGUgQXV0aG9yaXR5IDIwMTEwHhcNMTEwNzA4MjA1OTA5WhcNMjYwNzA4MjEw
# OTA5WjB+MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UE
# BxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSgwJgYD
# VQQDEx9NaWNyb3NvZnQgQ29kZSBTaWduaW5nIFBDQSAyMDExMIICIjANBgkqhkiG
# 9w0BAQEFAAOCAg8AMIICCgKCAgEAq/D6chAcLq3YbqqCEE00uvK2WCGfQhsqa+la
# UKq4BjgaBEm6f8MMHt03a8YS2AvwOMKZBrDIOdUBFDFC04kNeWSHfpRgJGyvnkmc
# 6Whe0t+bU7IKLMOv2akrrnoJr9eWWcpgGgXpZnboMlImEi/nqwhQz7NEt13YxC4D
# dato88tt8zpcoRb0RrrgOGSsbmQ1eKagYw8t00CT+OPeBw3VXHmlSSnnDb6gE3e+
# lD3v++MrWhAfTVYoonpy4BI6t0le2O3tQ5GD2Xuye4Yb2T6xjF3oiU+EGvKhL1nk
# kDstrjNYxbc+/jLTswM9sbKvkjh+0p2ALPVOVpEhNSXDOW5kf1O6nA+tGSOEy/S6
# A4aN91/w0FK/jJSHvMAhdCVfGCi2zCcoOCWYOUo2z3yxkq4cI6epZuxhH2rhKEmd
# X4jiJV3TIUs+UsS1Vz8kA/DRelsv1SPjcF0PUUZ3s/gA4bysAoJf28AVs70b1FVL
# 5zmhD+kjSbwYuER8ReTBw3J64HLnJN+/RpnF78IcV9uDjexNSTCnq47f7Fufr/zd
# sGbiwZeBe+3W7UvnSSmnEyimp31ngOaKYnhfsi+E11ecXL93KCjx7W3DKI8sj0A3
# T8HhhUSJxAlMxdSlQy90lfdu+HggWCwTXWCVmj5PM4TasIgX3p5O9JawvEagbJjS
# 4NaIjAsCAwEAAaOCAe0wggHpMBAGCSsGAQQBgjcVAQQDAgEAMB0GA1UdDgQWBBRI
# bmTlUAXTgqoXNzcitW2oynUClTAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTAL
# BgNVHQ8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBRyLToCMZBD
# uRQFTuHqp8cx0SOJNDBaBgNVHR8EUzBRME+gTaBLhklodHRwOi8vY3JsLm1pY3Jv
# c29mdC5jb20vcGtpL2NybC9wcm9kdWN0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFf
# MDNfMjIuY3JsMF4GCCsGAQUFBwEBBFIwUDBOBggrBgEFBQcwAoZCaHR0cDovL3d3
# dy5taWNyb3NvZnQuY29tL3BraS9jZXJ0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFf
# MDNfMjIuY3J0MIGfBgNVHSAEgZcwgZQwgZEGCSsGAQQBgjcuAzCBgzA/BggrBgEF
# BQcCARYzaHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9kb2NzL3ByaW1h
# cnljcHMuaHRtMEAGCCsGAQUFBwICMDQeMiAdAEwAZQBnAGEAbABfAHAAbwBsAGkA
# YwB5AF8AcwB0AGEAdABlAG0AZQBuAHQALiAdMA0GCSqGSIb3DQEBCwUAA4ICAQBn
# 8oalmOBUeRou09h0ZyKbC5YR4WOSmUKWfdJ5DJDBZV8uLD74w3LRbYP+vj/oCso7
# v0epo/Np22O/IjWll11lhJB9i0ZQVdgMknzSGksc8zxCi1LQsP1r4z4HLimb5j0b
# pdS1HXeUOeLpZMlEPXh6I/MTfaaQdION9MsmAkYqwooQu6SpBQyb7Wj6aC6VoCo/
# KmtYSWMfCWluWpiW5IP0wI/zRive/DvQvTXvbiWu5a8n7dDd8w6vmSiXmE0OPQvy
# CInWH8MyGOLwxS3OW560STkKxgrCxq2u5bLZ2xWIUUVYODJxJxp/sfQn+N4sOiBp
# mLJZiWhub6e3dMNABQamASooPoI/E01mC8CzTfXhj38cbxV9Rad25UAqZaPDXVJi
# hsMdYzaXht/a8/jyFqGaJ+HNpZfQ7l1jQeNbB5yHPgZ3BtEGsXUfFL5hYbXw3MYb
# BL7fQccOKO7eZS/sl/ahXJbYANahRr1Z85elCUtIEJmAH9AAKcWxm6U/RXceNcbS
# oqKfenoi+kiVH6v7RyOA9Z74v2u3S5fi63V4GuzqN5l5GEv/1rMjaHXmr/r8i+sL
# gOppO6/8MO0ETI7f33VtY5E90Z1WTk+/gFcioXgRMiF670EKsT/7qMykXcGhiJtX
# cVZOSEXAQsmbdlsKgEhr/Xmfwb1tbWrJUnMTDXpQzTGCGXYwghlyAgEBMIGVMH4x
# CzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRt
# b25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01p
# Y3Jvc29mdCBDb2RlIFNpZ25pbmcgUENBIDIwMTECEzMAAALN82S/+NRMXVEAAAAA
# As0wDQYJYIZIAWUDBAIBBQCgga4wGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQw
# HAYKKwYBBAGCNwIBCzEOMAwGCisGAQQBgjcCARUwLwYJKoZIhvcNAQkEMSIEINxF
# gOBR9TSqK+jKy/tHhgBXKjCk6SN8EQuBFzbF2R5bMEIGCisGAQQBgjcCAQwxNDAy
# oBSAEgBNAGkAYwByAG8AcwBvAGYAdKEagBhodHRwOi8vd3d3Lm1pY3Jvc29mdC5j
# b20wDQYJKoZIhvcNAQEBBQAEggEAR4sUJWV23UIMmpvvsFpycjITY9AXvs0NeaFf
# S/MLTVBIf2vw40VtUPHfcK7l1scVxuNx13OPrBpE4aiLEs4hTUac10LUqD0KJhE8
# r9czKGDhguPdXlKD/zkSdc8GcrX5ZCqto4WNwq3Y6jNPVb1emyFoydkWW0PkHlGj
# ofmkBR4/+l1EHnp/ve7q9oUPpoG7m4GSJNBnh99cOyUMjDN5yA9IPHGp0nS0Yp4y
# GmD1mscIEBvjSWOSb+OIWg46Q8R6zjn0tfxaCbjwuhKnTRihfaCRF0u4B6rlIoqA
# jzldh74i63bj0xrrmomEkApAZUGt8hXu7wA+aEGgYgTWzmw9paGCFwAwghb8Bgor
# BgEEAYI3AwMBMYIW7DCCFugGCSqGSIb3DQEHAqCCFtkwghbVAgEDMQ8wDQYJYIZI
# AWUDBAIBBQAwggFRBgsqhkiG9w0BCRABBKCCAUAEggE8MIIBOAIBAQYKKwYBBAGE
# WQoDATAxMA0GCWCGSAFlAwQCAQUABCABCZ7+0psxjXqwRYxwAbrb69l8YEIMi4Cz
# odOCpLXKzwIGYxEaNGZOGBMyMDIyMDkwMjAzMzIxMC4xNTRaMASAAgH0oIHQpIHN
# MIHKMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMH
# UmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSUwIwYDVQQL
# ExxNaWNyb3NvZnQgQW1lcmljYSBPcGVyYXRpb25zMSYwJAYDVQQLEx1UaGFsZXMg
# VFNTIEVTTjozQkJELUUzMzgtRTlBMTElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUt
# U3RhbXAgU2VydmljZaCCEVcwggcMMIIE9KADAgECAhMzAAABnf6J5fl7u0zAAAEA
# AAGdMA0GCSqGSIb3DQEBCwUAMHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNo
# aW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29y
# cG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEw
# MB4XDTIxMTIwMjE5MDUxOVoXDTIzMDIyODE5MDUxOVowgcoxCzAJBgNVBAYTAlVT
# MRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQK
# ExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJTAjBgNVBAsTHE1pY3Jvc29mdCBBbWVy
# aWNhIE9wZXJhdGlvbnMxJjAkBgNVBAsTHVRoYWxlcyBUU1MgRVNOOjNCQkQtRTMz
# OC1FOUExMSUwIwYDVQQDExxNaWNyb3NvZnQgVGltZS1TdGFtcCBTZXJ2aWNlMIIC
# IjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA4BFoetAcSRbkfppRbhQrRt5k
# dlxzQnzzE13KdOmHsALUtW2xsyu4QwtSUTHrYXJoijP3m3QnBur+pOJUMi5Umh5h
# 5CFAuaTRLZbrc6bFTfo6PjW3crvcNATIeCs33bi3cW3WtVc7Sg0l4xN3hJeTn/uY
# WN6R6CV/TYf+a+LXODb6rfbb1XGHiMaXIWfNQCt/lM6QPoXEeh0uu1wPPnEFAdXx
# skaFYdbArtPqH3VQ3Fjhv7XEirSXIfnkQFQ3uyEZvOHz7D0xc9nSCUSQPz1GmUft
# 8VuBXdWWyzWv8DG6t72mLSqIGkmYogslMVO5sKAN9osk3Bhwu4R/DIGVlThAbuwn
# zbzMq7EfELr7t5vdQ6rHOYGRzMb4HgDvSy4mmlPpJWJhVkLy16YpUtYg12//Nvk4
# 8BrcRJ3PMfxLy5JO1JI+LimeifMp5YD4T0ano3KCpR7CoMVpg98MxF1tWtPXdS9i
# KNNOMNNYt9UADAfxi4YJpjCBOWeFSNiZYfbF9p5MoeUDp2Ds5xApOM2vXCHTCj/r
# HXtp4b73Enaxl0PH1jv/jdZo09EiJF+NV2Y5vkDBkdN7Wut6X7I94NxOWta9KPWh
# 4HLqYVtJcK2wQP0xjKOcwvGeViPkcKa9DAIg2RsuYdvrjMf9vvxeGYsimril6ne6
# yJdbHgGEGMSkOEnpgdECAwEAAaOCATYwggEyMB0GA1UdDgQWBBTW3xCN4SrozJxe
# EYHb2suK2RtV5DAfBgNVHSMEGDAWgBSfpxVdAF5iXYP05dJlpxtTNRnpcjBfBgNV
# HR8EWDBWMFSgUqBQhk5odHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2Ny
# bC9NaWNyb3NvZnQlMjBUaW1lLVN0YW1wJTIwUENBJTIwMjAxMCgxKS5jcmwwbAYI
# KwYBBQUHAQEEYDBeMFwGCCsGAQUFBzAChlBodHRwOi8vd3d3Lm1pY3Jvc29mdC5j
# b20vcGtpb3BzL2NlcnRzL01pY3Jvc29mdCUyMFRpbWUtU3RhbXAlMjBQQ0ElMjAy
# MDEwKDEpLmNydDAMBgNVHRMBAf8EAjAAMBMGA1UdJQQMMAoGCCsGAQUFBwMIMA0G
# CSqGSIb3DQEBCwUAA4ICAQCGx3KcS7dodCzSd9+IgjWWLLWthGp7fSVHoqcHmxSM
# EDIo+0iEPN4ieGODi+WMJaacLxedegIXB3r2DEnGOjQF0gBoaecWLZBHJHleoDW3
# KdwuCN8qLVrw3fSkAoey3SIaYmAnXtEheDeSUWVB1yDHM7zYXvPnRLogp0tbFohj
# 1BczSX7AHDbNWgZByLWksTNAFR4GX4CvEdooVZLYq8cGBtE/aNYdAZESdz37iRfp
# zo7dfnhWGIKvmzi0LP0nYGOA21FX67S3RINk20vrImcOHaszPrS3cnuVxsiZSgV+
# 2iLOMX9R4dcpjF9uGCZZvL8l/luMfMsrT5s4jR7bIcX+5SndT5u7fu1FM4vEAYW8
# tL2K3656FtSsLPs9+LqHNvg9lRcax9/cav/Cm3ngabi+fvgoqaGULHeUDuTYvNZw
# ZcIv+9SbGQxKJrKP5OCYqMr1QDNvNfAoJ7DSZzGWUsTOZMnkXWIfSG4xnT3XT1XL
# 9JDyqzWpT7UTuIodBlZC/2uAXxAbVw3hFCLDOANNEHoQ/hEldyNFG1vm+DU5m0XI
# 51RZObep7ivxaBUHOTeF7nmADsNpjA4QKBRPtsbUx5NnsHfcRpGxQ5dm+rvxW4bF
# G+fl6D9D3SdxUzT+hv+1a+LCDsWq+D6WOu+GkAojEssMLSFjqKkUvzj/60oxfRP8
# XzCCB3EwggVZoAMCAQICEzMAAAAVxedrngKbSZkAAAAAABUwDQYJKoZIhvcNAQEL
# BQAwgYgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQH
# EwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xMjAwBgNV
# BAMTKU1pY3Jvc29mdCBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eSAyMDEwMB4X
# DTIxMDkzMDE4MjIyNVoXDTMwMDkzMDE4MzIyNVowfDELMAkGA1UEBhMCVVMxEzAR
# BgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1p
# Y3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3Rh
# bXAgUENBIDIwMTAwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDk4aZM
# 57RyIQt5osvXJHm9DtWC0/3unAcH0qlsTnXIyjVX9gF/bErg4r25PhdgM/9cT8dm
# 95VTcVrifkpa/rg2Z4VGIwy1jRPPdzLAEBjoYH1qUoNEt6aORmsHFPPFdvWGUNzB
# RMhxXFExN6AKOG6N7dcP2CZTfDlhAnrEqv1yaa8dq6z2Nr41JmTamDu6GnszrYBb
# fowQHJ1S/rboYiXcag/PXfT+jlPP1uyFVk3v3byNpOORj7I5LFGc6XBpDco2LXCO
# Mcg1KL3jtIckw+DJj361VI/c+gVVmG1oO5pGve2krnopN6zL64NF50ZuyjLVwIYw
# XE8s4mKyzbnijYjklqwBSru+cakXW2dg3viSkR4dPf0gz3N9QZpGdc3EXzTdEonW
# /aUgfX782Z5F37ZyL9t9X4C626p+Nuw2TPYrbqgSUei/BQOj0XOmTTd0lBw0gg/w
# EPK3Rxjtp+iZfD9M269ewvPV2HM9Q07BMzlMjgK8QmguEOqEUUbi0b1qGFphAXPK
# Z6Je1yh2AuIzGHLXpyDwwvoSCtdjbwzJNmSLW6CmgyFdXzB0kZSU2LlQ+QuJYfM2
# BjUYhEfb3BvR/bLUHMVr9lxSUV0S2yW6r1AFemzFER1y7435UsSFF5PAPBXbGjfH
# CBUYP3irRbb1Hode2o+eFnJpxq57t7c+auIurQIDAQABo4IB3TCCAdkwEgYJKwYB
# BAGCNxUBBAUCAwEAATAjBgkrBgEEAYI3FQIEFgQUKqdS/mTEmr6CkTxGNSnPEP8v
# BO4wHQYDVR0OBBYEFJ+nFV0AXmJdg/Tl0mWnG1M1GelyMFwGA1UdIARVMFMwUQYM
# KwYBBAGCN0yDfQEBMEEwPwYIKwYBBQUHAgEWM2h0dHA6Ly93d3cubWljcm9zb2Z0
# LmNvbS9wa2lvcHMvRG9jcy9SZXBvc2l0b3J5Lmh0bTATBgNVHSUEDDAKBggrBgEF
# BQcDCDAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTALBgNVHQ8EBAMCAYYwDwYD
# VR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBTV9lbLj+iiXGJo0T2UkFvXzpoYxDBW
# BgNVHR8ETzBNMEugSaBHhkVodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20vcGtpL2Ny
# bC9wcm9kdWN0cy9NaWNSb29DZXJBdXRfMjAxMC0wNi0yMy5jcmwwWgYIKwYBBQUH
# AQEETjBMMEoGCCsGAQUFBzAChj5odHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtp
# L2NlcnRzL01pY1Jvb0NlckF1dF8yMDEwLTA2LTIzLmNydDANBgkqhkiG9w0BAQsF
# AAOCAgEAnVV9/Cqt4SwfZwExJFvhnnJL/Klv6lwUtj5OR2R4sQaTlz0xM7U518Jx
# Nj/aZGx80HU5bbsPMeTCj/ts0aGUGCLu6WZnOlNN3Zi6th542DYunKmCVgADsAW+
# iehp4LoJ7nvfam++Kctu2D9IdQHZGN5tggz1bSNU5HhTdSRXud2f8449xvNo32X2
# pFaq95W2KFUn0CS9QKC/GbYSEhFdPSfgQJY4rPf5KYnDvBewVIVCs/wMnosZiefw
# C2qBwoEZQhlSdYo2wh3DYXMuLGt7bj8sCXgU6ZGyqVvfSaN0DLzskYDSPeZKPmY7
# T7uG+jIa2Zb0j/aRAfbOxnT99kxybxCrdTDFNLB62FD+CljdQDzHVG2dY3RILLFO
# Ry3BFARxv2T5JL5zbcqOCb2zAVdJVGTZc9d/HltEAY5aGZFrDZ+kKNxnGSgkujhL
# mm77IVRrakURR6nxt67I6IleT53S0Ex2tVdUCbFpAUR+fKFhbHP+CrvsQWY9af3L
# wUFJfn6Tvsv4O+S3Fb+0zj6lMVGEvL8CwYKiexcdFYmNcP7ntdAoGokLjzbaukz5
# m/8K6TT4JDVnK+ANuOaMmdbhIurwJ0I9JZTmdHRbatGePu1+oDEzfbzL6Xu/OHBE
# 0ZDxyKs6ijoIYn/ZcGNTTY3ugm2lBRDBcQZqELQdVTNYs6FwZvKhggLOMIICNwIB
# ATCB+KGB0KSBzTCByjELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24x
# EDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlv
# bjElMCMGA1UECxMcTWljcm9zb2Z0IEFtZXJpY2EgT3BlcmF0aW9uczEmMCQGA1UE
# CxMdVGhhbGVzIFRTUyBFU046M0JCRC1FMzM4LUU5QTExJTAjBgNVBAMTHE1pY3Jv
# c29mdCBUaW1lLVN0YW1wIFNlcnZpY2WiIwoBATAHBgUrDgMCGgMVALfpQ0kV/dih
# coo+9SZ9tAqEssrgoIGDMIGApH4wfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldh
# c2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBD
# b3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIw
# MTAwDQYJKoZIhvcNAQEFBQACBQDmu5izMCIYDzIwMjIwOTAyMDQ0NjQzWhgPMjAy
# MjA5MDMwNDQ2NDNaMHcwPQYKKwYBBAGEWQoEATEvMC0wCgIFAOa7mLMCAQAwCgIB
# AAICPN0CAf8wBwIBAAICEbEwCgIFAOa86jMCAQAwNgYKKwYBBAGEWQoEAjEoMCYw
# DAYKKwYBBAGEWQoDAqAKMAgCAQACAwehIKEKMAgCAQACAwGGoDANBgkqhkiG9w0B
# AQUFAAOBgQARHT8H7s0ECT1EGwDNxAhWE9n2NN/6+quGAZanDhSFs0nI4wyRN1Fg
# hQQ7QCLQRRETHVY+XrRAumTdgC+9ueFAncH0c0sDN9R0uK19wzEeUGHnjPkB5lHb
# 1+Onjnv07Jy87UGk8JWqXP+Jvd8slehMmntzn6ZIekfUyErqJky7ijGCBA0wggQJ
# AgEBMIGTMHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYD
# VQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJjAk
# BgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwAhMzAAABnf6J5fl7
# u0zAAAEAAAGdMA0GCWCGSAFlAwQCAQUAoIIBSjAaBgkqhkiG9w0BCQMxDQYLKoZI
# hvcNAQkQAQQwLwYJKoZIhvcNAQkEMSIEID3twWcolbsJtZZpd6hS6YUlOI1rtstS
# qXjTLWB79qC9MIH6BgsqhkiG9w0BCRACLzGB6jCB5zCB5DCBvQQg9R5jreCKoE+A
# NJ+CW5V/38xS8hLuDgQzm/pqTJWHRaEwgZgwgYCkfjB8MQswCQYDVQQGEwJVUzET
# MBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMV
# TWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1T
# dGFtcCBQQ0EgMjAxMAITMwAAAZ3+ieX5e7tMwAABAAABnTAiBCBB4HukuxmF8e6F
# o/4MKzVoumrYV9pbtENGdp38F8EKWzANBgkqhkiG9w0BAQsFAASCAgCkCqvZCgUO
# ts59dr8CZrb//hsIBJMF36F4I16W3SedJ8DWsTD2ArqPi2I/N8XRwwVYXIZHX3mG
# 0VW+YdWFM+ymw6ulnyadVPv5pn1JfmgCjuvGMQAa76JA1ni23oIJM8FOQgO0Kcxn
# tq2Yvc1J5bRG98/fvjMuCkyu/T4dSRn4OvLGnq9c4K65oNXFKYRDGzUhn2zHGaAP
# hGe86orCylNtujwJtH1a0DGYMkcQXwZzFrhsY8NrspAE3K/Z9EV3m1CNEo1VzW5D
# 9K1rd8QPGUbboQBChMKIVfhXfbxTvD0fBghr5VwFJknJ8Cm2J79WBU6Sm3YWNUXa
# BiFXI65hh+UAwaaejhyvVf+p/DSx1mqZLd1Qy1yLiroj21WuhBlOtALMz8qVJoQB
# oDi9YNN5uDps7/p3bawvxZcbaxK6KWOooeYtRBhz00kj65beu42QaZPiqditL/Fs
# +WG/L7NBms+dZuccStVxcuyhF1HPMX2E0tPENhEXGoyn8ofDc+ezyyD8Nep6CVTY
# StBu0IHM+XtjOAiQHrhoT2MKm4U9aYMYxn2qoLCU+JoYhbm/l9yMVAPt/vajQcJc
# CjqB/5hiTiLn6F99AmZF16mwj0H8oQ4ETuOx/3wYnhTCmbqaSTFhs+w3yVWT8Ixk
# tcBDp8JLeH6cmex22dllwbBckZVSNoX3Ig==
# SIG # End signature block