internal/Get-MsGraphResults.ps1

<#
.SYNOPSIS
    Query Microsoft Graph API
.EXAMPLE
    PS C:\>Get-MsGraphResults 'users'
    Return query results for first page of users.
.EXAMPLE
    PS C:\>Get-MsGraphResults 'users' -ApiVersion beta
    Return query results for all users using the beta API.
.EXAMPLE
    PS C:\>Get-MsGraphResults 'users' -UniqueId 'user1@domain.com','user2@domain.com' -Select id,userPrincipalName,displayName
    Return id, userPrincipalName, and displayName for user1@domain.com and user2@domain.com.
#>

function Get-MsGraphResults {
    [CmdletBinding()]
    [OutputType([PSCustomObject])]
    param (
        # Graph endpoint such as "users".
        [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true)]
        [uri[]] $RelativeUri,
        # Specifies unique Id(s) for the URI endpoint. For example, users endpoint accepts Id or UPN.
        [Parameter(Mandatory = $false, Position = 1, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
        [Alias('Id')]
        #[ValidateNotNullOrEmpty()]
        [string[]] $UniqueId,
        # Filters properties (columns).
        [Parameter(Mandatory = $false)]
        [string[]] $Select,
        # Filters results (rows). https://docs.microsoft.com/en-us/graph/query-parameters#filter-parameter
        [Parameter(Mandatory = $false)]
        [string] $Filter,
        # Specifies the page size of the result set.
        [Parameter(Mandatory = $false)]
        [int] $Top,
        # Include a count of the total number of items in a collection
        [Parameter(Mandatory = $false)]
        [switch] $Count,
        # Parameters such as "$orderby".
        [Parameter(Mandatory = $false)]
        [hashtable] $QueryParameters,
        # API Version.
        [Parameter(Mandatory = $false)]
        [ValidateSet('v1.0', 'beta')]
        [string] $ApiVersion = 'v1.0',
        # Specifies consistency level.
        [Parameter(Mandatory = $false)]
        [string] $ConsistencyLevel = "eventual",
        # Total requests to calcuate progress bar when using pipeline.
        [Parameter(Mandatory = $false)]
        [int] $TotalRequests,
        # Copy OData Context to each result value.
        [Parameter(Mandatory = $false)]
        [switch] $KeepODataContext,
        # Add OData Type to each result value.
        [Parameter(Mandatory = $false)]
        [switch] $AddODataType,
        # Incapsulate member and owner reference calls with a parent object.
        [Parameter(Mandatory = $false)]
        [switch] $IncapsulateReferenceListInParentObject,
        # Group results in array by request.
        [Parameter(Mandatory = $false)]
        [switch] $GroupOutputByRequest,
        # Disable deduplication of UniqueId values.
        [Parameter(Mandatory = $false)]
        [switch] $DisableUniqueIdDeduplication,
        # Only return first page of results.
        [Parameter(Mandatory = $false)]
        [switch] $DisablePaging,
        # Disable consolidating uniqueIds using getByIds endpoint
        [Parameter(Mandatory = $false)]
        [switch] $DisableGetByIdsBatching,
        # Specify GetByIds Batch size.
        [Parameter(Mandatory = $false)]
        [int] $GetByIdsBatchSize = 1000,
        # Enables in filter by in on ids for requried uniqueIds; $filter={previous filter} and id in ({csv with ids})
        # Should be more flexible than GetByIds, scalability to be tested to eventually replace getbyids
        [Parameter(Mandatory = $false)]
        [switch] $EnableInFilter,
        [Parameter(Mandatory = $false)]
        [int] $InFilterBatchSize = 15,
        # Force individual requests to MS Graph.
        [Parameter(Mandatory = $false)]
        [switch] $DisableBatching,
        # Specify Batch size.
        [Parameter(Mandatory = $false)]
        [int] $BatchSize = 20,
        # Base URL for Microsoft Graph API.
        [Parameter(Mandatory = $false)]
        [uri] $GraphBaseUri = $script:mapMgEnvironmentToMgEndpoint[$script:ConnectState.CloudEnvironment]
    )

    begin {
        [uri] $uriGraphVersionBase = [IO.Path]::Combine($GraphBaseUri.AbsoluteUri, $ApiVersion)
        $listRequests = New-Object 'System.Collections.Generic.Dictionary[string,System.Collections.Generic.List[pscustomobject]]'
        $listRequests.Add($uriGraphVersionBase.AbsoluteUri, (New-Object 'System.Collections.Generic.List[pscustomobject]'))
        [System.Collections.Generic.List[guid]] $listIds = New-Object 'System.Collections.Generic.List[guid]'
        [System.Collections.Generic.HashSet[uri]] $hashUri = New-Object 'System.Collections.Generic.HashSet[uri]'
        $ProgressState = Start-Progress -Activity 'Microsoft Graph Requests' -Total $TotalRequests

        function Catch-MsGraphError {
            [CmdletBinding()]
            param (
                [Parameter(Mandatory = $true)]
                [System.Management.Automation.ErrorRecord] $ErrorRecord
            )

            # throw error record directly if no response is found on the exception
            if (!$_.Exception.psobject.Properties.Name.Contains('Response')) {
                throw $ErrorRecord
            }

            ## Get Response Body
            if ($_.ErrorDetails) {
                $Response = '{0} {1} HTTP/{2}' -f $_.Exception.Response.StatusCode.value__, $_.Exception.Response.ReasonPhrase, $_.Exception.Response.Version
                $ContentType = $_.Exception.Response.Content.Headers.ContentType.ToString()
                $ResponseContent = ConvertFrom-Json $_.ErrorDetails.Message
            }
            elseif ($_.Exception -is [System.Net.WebException]) {
                if ($_.Exception.Response) {
                    $Response = '{0} {1} HTTP/{2}' -f $_.Exception.Response.StatusCode.value__, $_.Exception.Response.StatusDescription, $_.Exception.Response.ProtocolVersion
                    $ContentType = $_.Exception.Response.Headers.GetValues('Content-Type') -join '; '

                    $StreamReader = New-Object System.IO.StreamReader -ArgumentList $_.Exception.Response.GetResponseStream()
                    try { $ResponseContent = ConvertFrom-Json $StreamReader.ReadToEnd() }
                    finally { $StreamReader.Close() }
                }
            }

            Write-Debug -Message (ConvertTo-Json ([PSCustomObject]@{
                        'Request'                             = '{0} {1}' -f $_.TargetObject.Method, $_.TargetObject.RequestUri.AbsoluteUri
                        'Response'                            = $Response
                        'Response.Content-Type'               = $ContentType
                        'Response.Content'                    = $ResponseContent
                        'Response.Header.Date'                = $_.Exception.Response.Headers.GetValues('Date')[0]
                        'Response.Header.request-id'          = $_.Exception.Response.Headers.GetValues('request-id')[0]
                        'Response.Header.client-request-id'   = $_.Exception.Response.Headers.GetValues('client-request-id')[0]
                        'Response.Header.x-ms-ags-diagnostic' = $_.Exception.Response.Headers.GetValues('x-ms-ags-diagnostic')[0] | ConvertFrom-Json
                    }) -Depth 3)

            if ($ResponseContent) {
                ## Write Custom Error
                if ($ResponseContent.error.code -eq 'Authentication_ExpiredToken' -or $ResponseContent.error.code -eq 'Service_ServiceUnavailable' -or $ResponseContent.error.code -eq 'Request_UnsupportedQuery') {
                    #Write-AppInsightsException $_.Exception
                    Write-Error -Exception $_.Exception -Message $ResponseContent.error.message -ErrorId $ResponseContent.error.code -Category $_.CategoryInfo.Category -CategoryActivity $_.CategoryInfo.Activity -CategoryReason $_.CategoryInfo.Reason -CategoryTargetName $_.CategoryInfo.TargetName -CategoryTargetType $_.CategoryInfo.TargetType -TargetObject $_.TargetObject -ErrorAction Stop
                }
                else {
                    if ($ResponseContent.error.code -eq 'Request_ResourceNotFound') {
                        Write-Error -Exception $_.Exception -Message $ResponseContent.error.message -ErrorId $ResponseContent.error.code -Category $_.CategoryInfo.Category -CategoryActivity $_.CategoryInfo.Activity -CategoryReason $_.CategoryInfo.Reason -CategoryTargetName $_.CategoryInfo.TargetName -CategoryTargetType $_.CategoryInfo.TargetType -TargetObject $_.TargetObject -ErrorVariable cmdError -ErrorAction SilentlyContinue
                        Write-Warning $ResponseContent.error.message
                    }
                    else {
                        Write-Error -Exception $_.Exception -Message $ResponseContent.error.message -ErrorId $ResponseContent.error.code -Category $_.CategoryInfo.Category -CategoryActivity $_.CategoryInfo.Activity -CategoryReason $_.CategoryInfo.Reason -CategoryTargetName $_.CategoryInfo.TargetName -CategoryTargetType $_.CategoryInfo.TargetType -TargetObject $_.TargetObject -ErrorVariable cmdError
                    }
                    Write-AppInsightsException $cmdError.Exception
                }
            }
            else { throw $ErrorRecord }
        }

        function Test-MsGraphBatchError ($BatchResponse) {
            if ($BatchResponse.status -ne '200') {
                Write-Debug -Message (ConvertTo-Json $BatchResponse -Depth 3)

                if ($BatchResponse.body.error.code -eq 'Authentication_ExpiredToken' -or $BatchResponse.body.error.code -eq 'Service_ServiceUnavailable' -or $BatchResponse.body.error.code -eq 'Request_UnsupportedQuery') {
                    Write-Error -Message $BatchResponse.body.error.message -ErrorId $BatchResponse.body.error.code -ErrorAction Stop
                }
                else {
                    if ($BatchResponse.body.error.code -eq 'Request_ResourceNotFound') {
                        Write-Error -Message $BatchResponse.body.error.message -ErrorId $BatchResponse.body.error.code -ErrorVariable cmdError -ErrorAction SilentlyContinue
                        Write-Warning $BatchResponse.body.error.message
                    }
                    else {
                        Write-Error -Message $BatchResponse.body.error.message -ErrorId $BatchResponse.body.error.code -ErrorVariable cmdError
                    }
                    Write-AppInsightsException $cmdError.Exception
                }
                return $true
            }
            return $false
        }

        function Add-MsGraphRequest {
            param (
                # A collection of request objects.
                [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true)]
                [object[]] $Requests,
                # Base URL for Microsoft Graph API.
                [Parameter(Mandatory = $false)]
                [uri] $GraphBaseUri = 'https://graph.microsoft.com/'
            )

            process {
                foreach ($Request in $Requests) {
                    if ($DisableBatching) {
                        if ($ProgressState) { Update-Progress $ProgressState -CurrentOperation ('{0} {1}' -f $Request.method.ToUpper(), $Request.url) -IncrementBy 1 }
                        Invoke-MsGraphRequest $Request -GraphBaseUri $GraphBaseUri
                    }
                    else {
                        $listRequests[$GraphBaseUri].Add($Request)
                        ## Invoke when there are enough for a batch
                        while ($listRequests[$GraphBaseUri].Count -ge $BatchSize) {
                            Invoke-MsGraphBatchRequest $listRequests[$GraphBaseUri][0..($BatchSize - 1)] -BatchSize $BatchSize -ProgressState $ProgressState -GraphBaseUri $GraphBaseUri
                            $listRequests[$GraphBaseUri].RemoveRange(0, $BatchSize)
                        }
                    }
                }
            }
        }

        function Invoke-MsGraphBatchRequest {
            param (
                # A collection of request objects.
                [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true)]
                [object[]] $Requests,
                # Specify Batch size.
                [Parameter(Mandatory = $false)]
                [int] $BatchSize = 20,
                # Use external progress object.
                [Parameter(Mandatory = $false)]
                [psobject] $ProgressState,
                # Base URL for Microsoft Graph API.
                [Parameter(Mandatory = $false)]
                [uri] $GraphBaseUri = 'https://graph.microsoft.com/'
            )

            begin {
                [bool] $ExternalProgress = $false
                if ($ProgressState) { $ExternalProgress = $true }
                else {
                    $ProgressState = Start-Progress -Activity 'Microsoft Graph Requests - Batched' -Total $Requests.Count
                    $Stopwatch = [System.Diagnostics.Stopwatch]::StartNew()
                }
                [uri] $uriEndpoint = [IO.Path]::Combine($GraphBaseUri.AbsoluteUri, '$batch')
                $listRequests = New-Object 'System.Collections.Generic.List[pscustomobject]'
            }

            process {
                foreach ($Request in $Requests) {
                    $listRequests.Add($Request)
                }
            }

            end {
                [array] $BatchRequests = New-MsGraphBatchRequest $listRequests -BatchSize $BatchSize
                for ($iRequest = 0; $iRequest -lt $BatchRequests.Count; $iRequest++) {
                    if ($ProgressState.Total -gt $BatchSize) {
                        Update-Progress $ProgressState -CurrentOperation ('{0} {1}' -f $BatchRequests[$iRequest].method.ToUpper(), $BatchRequests[$iRequest].url) -IncrementBy $BatchRequests[$iRequest].body.requests.Count
                    }
                    $resultsBatch = Invoke-MsGraphRequest $BatchRequests[$iRequest] -NoAppInsights -GraphBaseUri $GraphBaseUri

                    [array] $resultsBatch = $resultsBatch.responses | Sort-Object -Property { [int]$_.id }

                    $throttledRequests = @()
                    [double]$maxRetryAfter = 1.0
                    foreach ($results in ($resultsBatch)) {
                        # check if batch result failed and call the endpoint or throw
                        if ($results.status -eq "429") {
                            # request was throttled
                            $throttledRequests += $listRequests[$results.id]
                            # check if a retry after was recieved
                            if ($results.psobject.Properties.Name.Contains('headers')) {
                                if ($results.headers.psobject.Properties.Name.Contains('Retry-After')) {
                                    $RetryAfter = [double]$results.headers.'Retry-After'
                                    if ($RetryAfter -gt $maxRetryAfter) {
                                        $maxRetryAfter = $RetryAfter
                                    }
                                }
                            }
                            continue
                        }
                        if (!(Test-MsGraphBatchError $results)) {
                            if ($IncapsulateReferenceListInParentObject -and $listRequests[$results.id].url -match '.*/(.+)/(.+)/((?:transitive)?members|owners)') {
                                [PSCustomObject]@{
                                    id            = $Matches[2]
                                    '@odata.type' = '#{0}' -f (Get-MsGraphEntityType $GraphBaseUri.AbsoluteUri -EntityName $Matches[1])
                                    $Matches[3]   = Complete-MsGraphResult $results.body -DisablePaging:$DisablePaging -KeepODataContext:$KeepODataContext -AddODataType:$AddODataType -GroupOutputByRequest -Request $listRequests[$results.id] -GraphBaseUri $GraphBaseUri
                                }
                            }
                            else {
                                Complete-MsGraphResult $results.body -DisablePaging:$DisablePaging -KeepODataContext:$KeepODataContext -AddODataType:$AddODataType -GroupOutputByRequest:$GroupOutputByRequest -Request $listRequests[$results.id] -GraphBaseUri $GraphBaseUri
                            }
                        }
                    }
                    if ($throttledRequests.Count -gt 0) {
                        Write-Warning "$($throttledRequests.Count) requests have been throttled; Retrying after $($maxRetryAfter)s"
                        Start-Sleep -Seconds $maxRetryAfter
                        foreach($request in $throttledRequests) {
                            Invoke-MsGraphRequest $request -NoAppInsights -GraphBaseUri $GraphBaseUri -RetryAfter $RetryAfter
                        }
                    }
                }

                if (!$ExternalProgress) {
                    $Stopwatch.Stop()
                    Write-AppInsightsDependency ('{0} {1}' -f 'POST', $uriEndpoint.AbsolutePath) -Type 'MS Graph' -Data ("{0} {1}`r`n`r`n{2}" -f 'POST', $uriEndpoint.AbsoluteUri, ('{{"requests":[...{0}...]}}' -f $listRequests.Count)) -Duration $Stopwatch.Elapsed -Success ($null -ne $resultsBatch)
                    Stop-Progress $ProgressState
                }
            }
        }

        function Invoke-MsGraphRequest {
            param (
                # A collection of request objects.
                [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true)]
                [psobject] $Request,
                # Do not write application insights dependency.
                [Parameter(Mandatory = $false)]
                [switch] $NoAppInsights,
                # Base URL for Microsoft Graph API.
                [Parameter(Mandatory = $false)]
                [uri] $GraphBaseUri = 'https://graph.microsoft.com/',
                # Number of retries in case of throttling
                [Parameter(Mandatory = $false)]
                [int] $MaxRetries = 5,
                [Parameter(Mandatory = $false)]
                [double] $RetryAfter = 1
            )

            process {
                [uri] $uriEndpoint = $Request.url
                if (!$uriEndpoint.IsAbsoluteUri) {
                    $uriEndpoint = [IO.Path]::Combine($GraphBaseUri.AbsoluteUri, $Request.url.TrimStart('/'))
                }
                #if ($uriEndpoint.Segments -contains 'directoryObjects/') { $NoAppInsights = $true }

                [hashtable] $paramInvokeRestMethod = @{
                    Method = $Request.method
                    Uri    = $uriEndpoint
                }
                if ($Request.psobject.Properties.Name -contains 'headers') { $paramInvokeRestMethod.Add('Headers', $Request.headers) }
                if ($Request.psobject.Properties.Name -contains 'body') {
                    $paramInvokeRestMethod.Add('Body', ($Request.body | ConvertTo-Json -Depth 10 -Compress))
                    $paramInvokeRestMethod.Add('ContentType', 'application/json')
                }

                ## Get results
                $results = $null
                $MsGraphSession = Confirm-ModuleAuthentication -MsGraphSession -ErrorAction Stop
                if (!$NoAppInsights) { $Stopwatch = [System.Diagnostics.Stopwatch]::StartNew() }
                try {
                    for($Retries = 0; $Retries -lt $MaxRetries; $Retries++) {
                        try {
                            $results = Invoke-RestMethod -WebSession $MsGraphSession -UseBasicParsing @paramInvokeRestMethod -ErrorAction Stop
                            # break the loop if no error was raised
                            break
                        }
                        catch {
                            ## error while invoking graph
                            if ($Retries -eq $MaxRetries-1) {
                                # catch error if it was the last try
                                Catch-MsGraphError $_ 
                            }
                            ## check if throttling happened
                            if ($_.Exception.PSobject.Properties.Name.Contains("Response") -and $_.Exception.Response.StatusCode.value__ -eq 429) {
                                # Get the retry after header
                                try {
                                    $RetryAfter = [double]($_.Exception.Response.Headers.GetValues('Retry-After')[0])
                                } catch {
                                    Write-Verbose "Request throttled but Retry-After not provided ($(Request.url)) using exponential backoff ($(RetryAfter)s)"
                                }
                            }
                            # request had an error and has not reached maximum retries
                            Write-Warning "$($paramInvokeRestMethod['Method']) $($paramInvokeRestMethod['Uri']); error $($_.Exception.Message); attempt $($Retries+1) out of $MaxRetries. Retrying after $($RetryAfter)s"
                            Start-Sleep -Seconds $RetryAfter
                            $RetryAfter = $RetryAfter * 2
                        }                 
                    }
                    if ($results) {
                        if ($IncapsulateReferenceListInParentObject -and $Request.url -match '.*/(.+)/(.+)/((?:transitive)?members|owners)') {
                            [PSCustomObject]@{
                                id            = $Matches[2]
                                '@odata.type' = '#{0}' -f (Get-MsGraphEntityType $GraphBaseUri.AbsoluteUri -EntityName $Matches[1])
                                $Matches[3]   = Complete-MsGraphResult $results -DisablePaging:$DisablePaging -KeepODataContext:$KeepODataContext -AddODataType:$AddODataType -GroupOutputByRequest -Request $Request -GraphBaseUri $GraphBaseUri
                            }
                        }
                        else {
                            Complete-MsGraphResult $results -DisablePaging:$DisablePaging -KeepODataContext:$KeepODataContext -AddODataType:$AddODataType -GroupOutputByRequest:$GroupOutputByRequest -Request $Request -GraphBaseUri $GraphBaseUri
                        }       
                    }
                }
                finally {
                    if (!$NoAppInsights) {
                        $Stopwatch.Stop()
                        Write-AppInsightsDependency ('{0} {1}' -f $Request.method.ToUpper(), $uriEndpoint.AbsolutePath) -Type 'MS Graph' -Data ('{0} {1}' -f $Request.method.ToUpper(), $uriEndpoint.AbsoluteUri) -Duration $Stopwatch.Elapsed -Success ($null -ne $results)
                    }
                }
            }
        }

        function Complete-MsGraphResult {
            param (
                # Results from MS Graph API.
                [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true)]
                [object[]] $Results,
                # Only return first page of results.
                [Parameter(Mandatory = $false)]
                [switch] $DisablePaging,
                # Copy ODataContext to each result value.
                [Parameter(Mandatory = $false)]
                [switch] $KeepODataContext,
                # Add ODataType to each result value.
                [Parameter(Mandatory = $false)]
                [switch] $AddODataType,
                # Group results in array by request.
                [Parameter(Mandatory = $false)]
                [switch] $GroupOutputByRequest,
                # MS Graph request object.
                [Parameter(Mandatory = $false)]
                [psobject] $Request,
                # Base URL for Microsoft Graph API.
                [Parameter(Mandatory = $false)]
                [uri] $GraphBaseUri = 'https://graph.microsoft.com/'
            )

            begin {
                [System.Collections.Generic.List[object]] $listOutput = New-Object 'System.Collections.Generic.List[object]'
            }

            process {
                foreach ($Result in $Results) {
                    $Output = Expand-MsGraphResult $Result -RawOutput:$DisablePaging -KeepODataContext:$KeepODataContext -AddODataType:$AddODataType
                    if ($GroupOutputByRequest -and $Output) { $listOutput.AddRange([array]$Output) }
                    else { $Output }

                    if (!$DisablePaging -and $Result) {
                        if (Get-ObjectPropertyValue $Result '@odata.nextLink') {
                            [uri] $uriEndpoint = [IO.Path]::Combine($GraphBaseUri.AbsoluteUri, $Request.url.TrimStart('/'))
                            [int] $Total = Get-MsGraphResultsCount $uriEndpoint -GraphBaseUri $GraphBaseUri
                            $Activity = ('Microsoft Graph Request - {0} {1}' -f $Request.method.ToUpper(), $uriEndpoint.AbsolutePath)
                            $ProgressState = Start-Progress -Activity $Activity -Total $Total
                            $ProgressState.CurrentIteration = $Result.value.Count
                            $MaxRetries = 5
                            try {
                                while (Get-ObjectPropertyValue $Result '@odata.nextLink') {
                                    Update-Progress $ProgressState -IncrementBy $Result.value.Count
                                    $nextLink = $Result.'@odata.nextLink'
                                    $MsGraphSession = Confirm-ModuleAuthentication -MsGraphSession -ErrorAction Stop
                                    $Result = $null
                                    [double]$RetryAfter = 1.0
                                    for($Retries = 0; $Retries -lt $MaxRetries; $Retries++) {
                                        try {
                                            $Result = Invoke-RestMethod -WebSession $MsGraphSession -UseBasicParsing -Method Get -Uri $nextLink -Headers $Request.headers -ErrorAction Stop
                                            # break the loop if no error was raised
                                            break
                                        }
                                        catch {
                                            ## error while invoking graph
                                            if ($Retries -eq $MaxRetries-1) {
                                                # catch error if it was the last try
                                                Catch-MsGraphError $_ 
                                            }
                                            # update retry after if throttling
                                            if ($_.Exception.PSobject.Properties.Name.Contains("Response") -and $_.Exception.Response.StatusCode.value__ -eq 429) {
                                                # Get the retry after header
                                                try {
                                                    $RetryAfter = [double]($_.Exception.Response.Headers.GetValues('Retry-After')[0])
                                                } catch {
                                                    Write-Verbose "Request throttled but Retry-After not provided ($nextLink) using exponential backoff ($(RetryAfter)s)"
                                                }
                                            }
                                            # request has encountered and error and has not hit the maximum retires
                                            Write-Warning "GET $nextLink; error '$($_.Exception.Message); attempt $($Retries+1) out of $MaxRetries. Retrying after $($RetryAfter)s"
                                            Start-Sleep -Seconds $RetryAfter
                                            $RetryAfter = $RetryAfter * 2
                                        }
                                    }
                                    if ($Result) {
                                        $Output = Expand-MsGraphResult $Result -RawOutput:$DisablePaging -KeepODataContext:$KeepODataContext -AddODataType:$AddODataType
                                        if ($GroupOutputByRequest -and $Output) { $listOutput.AddRange([array]$Output) }
                                        else { $Output }
                                    }
                                }
                            }
                            finally {
                                Stop-Progress $ProgressState
                            }
                        }
                    }
                }
            }

            end {
                if ($GroupOutputByRequest) { Write-Output $listOutput.ToArray() -NoEnumerate }
            }
        }
    }

    process {
        ## Initialize
        if ($PSBoundParameters.ContainsKey('UniqueId') -and !$UniqueId) { return }
        if ($RelativeUri.OriginalString -eq $UniqueId) { $UniqueId = $null }  # Pipeline string/uri input binds to both parameters so default to just uri

        ## Process Each RelativeUri
        foreach ($uri in $RelativeUri) {
            [string] $BaseUri = $uriGraphVersionBase.AbsoluteUri
            if ($uri.IsAbsoluteUri) {
                if ($uri.AbsoluteUri -match '^https://(.+?)/(v1.0|beta)?') { $BaseUri = $Matches[0] }
                if (!$listRequests.ContainsKey($BaseUri)) { $listRequests.Add($BaseUri, (New-Object 'System.Collections.Generic.List[pscustomobject]')) }
                $uriQueryEndpoint = New-Object System.UriBuilder -ArgumentList $uri
            }
            else { $uriQueryEndpoint = New-Object System.UriBuilder -ArgumentList ([IO.Path]::Combine($BaseUri, $uri)) }

            ## Combine query parameters from URI and cmdlet parameters
            [hashtable] $QueryParametersFinal = @{ }
            if ($uriQueryEndpoint.Query) {
                $QueryParametersFinal = ConvertFrom-QueryString $uriQueryEndpoint.Query -AsHashtable
                if ($QueryParameters) {
                    foreach ($ParameterName in $QueryParameters.Keys) {
                        $QueryParametersFinal[$ParameterName] = $QueryParameters[$ParameterName]
                    }
                }
            }
            elseif ($QueryParameters) { $QueryParametersFinal = $QueryParameters }
            if ($Select) { $QueryParametersFinal['$select'] = $Select -join ',' }
            if ($Filter) { $QueryParametersFinal['$filter'] = $Filter }
            if ($Top) { $QueryParametersFinal['$top'] = $Top }
            if ($PSBoundParameters.ContainsKey('Count')) { $QueryParametersFinal['$count'] = ([string]$Count).ToLower() }
            $uriQueryEndpoint.Query = ConvertTo-QueryString $QueryParametersFinal

            ## Expand with UniqueIds
            if ($UniqueId) {
                foreach ($id in $UniqueId) {
                    if ($id) {
                        ## If the URI contains '{0}', then replace it with Unique Id.
                        if ($uriQueryEndpoint.Uri.AbsoluteUri.Contains('%7B0%7D')) {
                            $uriQueryEndpointUniqueId = New-Object System.UriBuilder -ArgumentList ([System.Net.WebUtility]::UrlDecode($uriQueryEndpoint.Uri.AbsoluteUri) -f $id)
                        }
                        else {
                            $uriQueryEndpointUniqueId = New-Object System.UriBuilder -ArgumentList $uriQueryEndpoint.Uri
                            $uriQueryEndpointUniqueId.Path = ([IO.Path]::Combine($uriQueryEndpointUniqueId.Path, $id))
                        }
                        if ($DisableUniqueIdDeduplication -or $hashUri.Add($uriQueryEndpointUniqueId.Uri)) {
                            if ($EnableInFilter -and $id -match '^[{]?[0-9a-fA-F]{8}-([0-9a-fA-F]{4}-){3}[0-9a-fA-F]{12}[}]?$') {
                                $listIds.Add($id)
                                while($listIds.Count -ge $InFilterBatchSize) {
                                    # go back to initial uri (without appending id)
                                    $uriQueryEndpointUniqueId = New-Object System.UriBuilder -ArgumentList $uriQueryEndpoint.Uri
                                    # get the query parameters
                                    $QueryParametersInIds = ConvertFrom-QueryString $uriQueryEndpoint.Query -AsHashtable
                                    # get the ids to query
                                    $filterids = $listIds[0..($InFilterBatchSize - 1)]
                                    # append them to "$filter"
                                    if ($QueryParametersInIds.ContainsKey('$filter')) {
                                        $QueryParametersInIds['$filter'] = "($($QueryParametersInIds['$filter'])) and id in ('$($filterids -join "','")')"
                                    } else  {
                                        $QueryParametersInIds['$filter'] = "id in ('$($filterids -join "','")')"
                                    }
                                    # update query
                                    $uriQueryEndpointUniqueId.Query = ConvertTo-QueryString $QueryParametersInIds
                                    # add new batch request
                                    New-MsGraphRequest $uriQueryEndpointUniqueId.Uri -Headers @{ ConsistencyLevel = $ConsistencyLevel } | Add-MsGraphRequest -GraphBaseUri $BaseUri
                                    # remove ids from ids to request
                                    $listIds.RemoveRange(0, $InFilterBatchSize)
                                    # update progress
                                    if ($ProgressState) { $ProgressState.CurrentIteration += $InFilterBatchSize - 1 }
                                }
                            }
                            elseif (!$DisableGetByIdsBatching -and $id -match '^[{]?[0-9a-fA-F]{8}-([0-9a-fA-F]{4}-){3}[0-9a-fA-F]{12}[}]?$' -and $uriQueryEndpoint.Uri.Segments.Count -eq 3 -and $uriQueryEndpoint.Uri.Segments[2] -in ('directoryObjects', 'users', 'groups', 'devices', 'servicePrincipals', 'applications') -and ($QueryParametersFinal.Count -eq 0 -or ($QueryParametersFinal.Count -eq 1 -and $QueryParametersFinal.ContainsKey('$select')))) {
                                $listIds.Add($id)
                                while ($listIds.Count -ge $GetByIdsBatchSize) {
                                    New-MsGraphGetByIdsRequest $listIds[0..($GetByIdsBatchSize - 1)] -Types $uriQueryEndpoint.Uri.Segments[2].TrimEnd('s') -Select $QueryParametersFinal['$select'] -BatchSize $GetByIdsBatchSize | Add-MsGraphRequest -GraphBaseUri $BaseUri
                                    $listIds.RemoveRange(0, $GetByIdsBatchSize)
                                    if ($ProgressState) { $ProgressState.CurrentIteration += $GetByIdsBatchSize - 1 }
                                }
                            }
                            else {
                                New-MsGraphRequest $uriQueryEndpointUniqueId.Uri -Headers @{ ConsistencyLevel = $ConsistencyLevel } | Add-MsGraphRequest -GraphBaseUri $BaseUri
                            }
                        }
                        elseif ($ProgressState) { $ProgressState.Total -= 1 }
                    }
                    elseif ($ProgressState) { $ProgressState.Total -= 1 }
                }
            }
            else {
                New-MsGraphRequest $uriQueryEndpoint.Uri -Headers @{ ConsistencyLevel = $ConsistencyLevel } | Add-MsGraphRequest -GraphBaseUri $BaseUri
            }
        }
    }

    end {
        ## Complete Remaining Ids
        if ($listIds.Count -gt 0) {
            New-MsGraphGetByIdsRequest $listIds -Types $uriQueryEndpoint.Uri.Segments[2].TrimEnd('s') -Select $QueryParametersFinal['$select'] -BatchSize $GetByIdsBatchSize | Add-MsGraphRequest -GraphBaseUri $BaseUri
            if ($ProgressState) { $ProgressState.CurrentIteration += $listIds.Count - 1 }
        }
        ## Finish requests
        foreach ($BaseUri in $listRequests.Keys) {
            if ($listRequests[$BaseUri].Count -eq 1) {
                Invoke-MSGraphRequest $listRequests[$BaseUri][0] -GraphBaseUri $BaseUri
            }
            elseif ($listRequests[$BaseUri].Count -gt 0) {
                Invoke-MsGraphBatchRequest $listRequests[$BaseUri] -BatchSize $BatchSize -ProgressState $ProgressState -GraphBaseUri $BaseUri
            }
            if (!$DisableBatching -and $ProgressState -and $ProgressState.CurrentIteration -gt 1) {
                [uri] $uriEndpoint = [IO.Path]::Combine($BaseUri, '$batch')
                Write-AppInsightsDependency ('{0} {1}' -f 'POST', $uriEndpoint.AbsolutePath) -Type 'MS Graph' -Data ("{0} {1}`r`n`r`n{2}" -f 'POST', $uriEndpoint.AbsoluteUri, ('{{"requests":[...{0}...]}}' -f $ProgressState.CurrentIteration)) -Duration $ProgressState.Stopwatch.Elapsed -Success $?
            }
        }
        ## Clean-up
        if ($ProgressState) { Stop-Progress $ProgressState }
    }
}



<#
.SYNOPSIS
    New request object containing Microsoft Graph API details.
.EXAMPLE
    PS C:\>New-MsGraphRequest 'users'
    Return request object for GET /users.
.EXAMPLE
    PS C:\>New-MsGraphRequest -Method Get -Uri 'https://graph.microsoft.com/v1.0/users'
    Return request object for GET /users.
.EXAMPLE
    PS C:\>New-MsGraphRequest -Method Patch -Uri 'users/{id}' -Body ([PsCustomObject]{ displayName = "Joe Cool" }
    Return request object for PATCH /users/{id} with a body payload to update the displayName.
#>

function New-MsGraphRequest {
    [CmdletBinding()]
    param (
        # Specifies the method used for the web request.
        [Parameter(Mandatory = $false, ValueFromPipelineByPropertyName = $true)]
        [Alias('Id')]
        [int] $RequestId = 0,
        # Specifies the method used for the web request.
        [Parameter(Mandatory = $false, ValueFromPipelineByPropertyName = $true)]
        [ValidateSet('Get', 'Head', 'Post', 'Put', 'Delete', 'Trace', 'Options', 'Merge', 'Patch')]
        [string] $Method = 'Get',
        # Specifies the Uniform Resource Identifier (URI) of the Internet resource to which the web request is sent.
        [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
        [uri[]] $Uri,
        # Specifies the headers of the web request.
        [Parameter(Mandatory = $false, ValueFromPipelineByPropertyName = $true)]
        [hashtable] $Headers,
        # Specifies the body of the request.
        [Parameter(Mandatory = $false, ValueFromPipelineByPropertyName = $true)]
        [pscustomobject] $Body
    )

    process {
        if (!$Headers) { $Headers = @{} }
        for ($iRequest = 0; $iRequest -lt $Uri.Count; $iRequest++) {
            if ($Body) {
                if (!$Headers.ContainsKey('Content-Type')) { $Headers.Add('Content-Type', 'application/json') }
            }
            [string] $url = $Uri[$iRequest].PathAndQuery
            if (!$url) { $url = $Uri[$iRequest].ToString() }
            [pscustomobject]@{
                id      = $RequestId + $iRequest
                method  = $Method.ToUpper()
                url     = $url -replace '^(https://.+?/)?/?(v1.0/|beta/)?', '/'
                headers = $Headers
                body    = $Body
            }
        }
    }
}

function New-MsGraphGetByIdsRequest {
    [CmdletBinding()]
    param (
        # A collection of IDs for which to return objects.
        [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true)]
        [guid[]] $Ids,
        # A collection of resource types that specifies the set of resource collections to search.
        [Parameter(Mandatory = $false)]
        [string[]] $Types,
        # Filters properties (columns).
        [Parameter(Mandatory = $false)]
        [string[]] $Select,
        # Specify Batch size.
        [Parameter(Mandatory = $false)]
        [int] $BatchSize = 1000
    )

    begin {
        $Types = $Types | Where-Object { $_ -ne 'directoryObject' }
        if (!$Select) { $Select = "*" }
        $listIds = New-Object 'System.Collections.Generic.List[guid]'
    }

    process {
        foreach ($Id in $Ids) {
            $listIds.Add($Id)

            ## Process IDs when a full batch is reached
            while ($listIds.Count -ge $BatchSize) {
                New-MsGraphRequest ('/directoryObjects/getByIds?$select={0}' -f ($Select -join ',')) -Method Post -Headers @{ 'Content-Type' = 'application/json' } -Body ([PSCustomObject]@{
                        ids   = $listIds[0..($BatchSize - 1)]
                        types = $Types
                    })
                $listIds.RemoveRange(0, $BatchSize)
            }
        }
    }

    end {
        ## Process any remaining IDs
        if ($listIds.Count -gt 0) {
            New-MsGraphRequest ('/directoryObjects/getByIds?$select={0}' -f ($Select -join ',')) -Method Post -Headers @{ 'Content-Type' = 'application/json' } -Body ([PSCustomObject]@{
                    ids   = $listIds
                    types = $Types
                })
        }
    }
}

function New-MsGraphBatchRequest {
    [CmdletBinding()]
    param (
        # A collection of request objects.
        [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true)]
        [object[]] $Requests,
        # Specify Batch size.
        [Parameter(Mandatory = $false)]
        [int] $BatchSize = 20,
        # Specify depth of nested batches. MS Graph does not currently support batch nesting.
        [Parameter(Mandatory = $false)]
        [int] $Depth = 1
    )

    process {
        for ($iRequest = 0; $iRequest -lt $Requests.Count; $iRequest += [System.Math]::Pow($BatchSize, $Depth)) {
            $indexEnd = [System.Math]::Min($iRequest + [System.Math]::Pow($BatchSize, $Depth) - 1, $Requests.Count - 1)

            ## Reset ID Order
            for ($iId = $iRequest; $iId -le $indexEnd; $iId++) {
                $Requests[$iId].id = $iId
            }

            ## Generate Batch Request
            if ($Depth -gt 1) {
                $BatchRequest = New-MsGraphBatchRequest $Requests[$iRequest..$indexEnd] -Depth ($Depth - 1)
            }
            else {
                $BatchRequest = $Requests[$iRequest..$indexEnd]
            }

            New-MsGraphRequest -RequestId $iRequest -Method Post -Uri '/$batch' -Headers @{ 'Content-Type' = 'application/json' } -Body ([PSCustomObject]@{
                    requests = $BatchRequest
                })
        }
    }
}

function Get-MsGraphMetadata {
    param (
        # Metadata URL for Microsoft Graph API.
        [Parameter(Mandatory = $false, Position = 0, ValueFromPipeline = $true)]
        [uri] $Uri = 'https://graph.microsoft.com/v1.0/$metadata',
        # Force a refresh of metadata.
        [Parameter(Mandatory = $false)]
        [switch] $ForceRefresh
    )

    if (!(Get-Variable MsGraphMetadataCache -Scope Script -ErrorAction SilentlyContinue)) { New-Variable -Name MsGraphMetadataCache -Scope Script -Value (New-Object 'System.Collections.Generic.Dictionary[string,xml]') }
    if (!$Uri.AbsolutePath.EndsWith('$metadata')) { $Uri = ([IO.Path]::Combine($Uri.AbsoluteUri, '$metadata')) }
    [string] $BaseUri = $Uri.AbsoluteUri
    if ($Uri.AbsoluteUri -match ('^.+{0}' -f ([regex]::Escape($Uri.AbsolutePath)))) { $BaseUri = $Matches[0] }

    if ($ForceRefresh -or !$script:MsGraphMetadataCache.ContainsKey($BaseUri)) {
        #$MsGraphSession = Confirm-ModuleAuthentication -MsGraphSession -ErrorAction Stop
        try {
            $script:MsGraphMetadataCache[$BaseUri] = Invoke-RestMethod -UseBasicParsing -Method Get -Uri $Uri -ErrorAction Ignore
        }
        catch {}
    }
    return $script:MsGraphMetadataCache[$BaseUri]
}

function Get-MsGraphEntityType {
    param (
        # Metadata URL for Microsoft Graph API.
        [Parameter(Mandatory = $false, Position = 0, ValueFromPipeline = $true)]
        [uri] $Uri = 'https://graph.microsoft.com/v1.0/$metadata',
        # Name of endpoint.
        [Parameter(Mandatory = $false)]
        [string] $EntityName
    )

    process {
        $MsGraphMetadata = Get-MSGraphMetadata $Uri

        if (!$EntityName -and $Uri.Fragment -match '^#(.+?)(\(.+\))?(/\$entity)?$') { $EntityName = $Matches[1] }

        foreach ($Schema in $MsGraphMetadata.Edmx.DataServices.Schema) {
            foreach ($EntitySet in $Schema.EntityContainer.EntitySet) {
                if ($EntitySet.Name -eq $EntityName) {
                    return $EntitySet.EntityType
                }
            }
        }
    }
}

function Expand-MsGraphResult {
    param (
        # Results from MS Graph API.
        [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true)]
        [object[]] $Results,
        # Do not expand result values
        [Parameter(Mandatory = $false)]
        [switch] $RawOutput,
        # Copy ODataContext to each result value
        [Parameter(Mandatory = $false)]
        [switch] $KeepODataContext,
        # Add ODataType to each result value
        [Parameter(Mandatory = $false)]
        [switch] $AddODataType
    )

    process {
        foreach ($Result in $Results) {
            if (!$RawOutput -and (Get-ObjectPropertyValue $Result.psobject.Properties 'Name') -contains 'value') {
                foreach ($ResultValue in $Result.value) {
                    if ($AddODataType) {
                        $ODataType = Get-ObjectPropertyValue $Result '@odata.context' | Get-MsGraphEntityType
                        if ($ODataType) { $ODataType = '#' + $ODataType }
                        if ($ResultValue -is [hashtable] -and !$ResultValue.ContainsKey('@odata.type')) {
                            $ResultValue.Add('@odata.type', $ODataType)
                        }
                        elseif ($ResultValue.psobject.Properties.Name -notcontains '@odata.type') {
                            $ResultValue | Add-Member -MemberType NoteProperty -Name '@odata.type' -Value $ODataType
                        }
                    }
                    if ($KeepODataContext) {
                        if ($ResultValue -is [hashtable]) {
                            $ResultValue.Add('@odata.context', ('{0}/$entity' -f $Result.'@odata.context'))
                        }
                        else {
                            $ResultValue | Add-Member -MemberType NoteProperty -Name '@odata.context' -Value ('{0}/$entity' -f $Result.'@odata.context')
                        }
                    }
                    Write-Output $ResultValue
                }
            }
            else { Write-Output $Result }
        }
    }
}

function Get-MsGraphResultsCount {
    [CmdletBinding()]
    param (
        # Graph endpoint such as "users".
        [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true)]
        [uri] $Uri,
        # Base URL for Microsoft Graph API.
        [Parameter(Mandatory = $false)]
        [uri] $GraphBaseUri = 'https://graph.microsoft.com/'
    )

    process {
        if ($Uri.IsAbsoluteUri) {
            $uriEndpointCount = New-Object System.UriBuilder -ArgumentList $Uri -ErrorAction Stop
        }
        else {
            $uriEndpointCount = New-Object System.UriBuilder -ArgumentList $GraphBaseUri -ErrorAction Stop
        }
        ## Remove $ref from path
        $uriEndpointCount.Path = $uriEndpointCount.Path -replace '/\$ref$', ''
        ## Add $count segment to path
        $uriEndpointCount.Path = ([IO.Path]::Combine($uriEndpointCount.Path, '$count'))
        ## $count is not supported with $expand parameter so remove it.
        [hashtable] $QueryParametersUpdated = ConvertFrom-QueryString $uriEndpointCount.Query -AsHashtable
        if ($QueryParametersUpdated.ContainsKey('$expand')) { $QueryParametersUpdated.Remove('$expand') }
        $uriEndpointCount.Query = ConvertTo-QueryString $QueryParametersUpdated
        $MsGraphSession = Confirm-ModuleAuthentication -MsGraphSession -ErrorAction Stop
        [int] $Count = $null
        try {
            $Count = Invoke-RestMethod -WebSession $MsGraphSession -UseBasicParsing -Method Get -Uri $uriEndpointCount.Uri -Headers @{ ConsistencyLevel = 'eventual' } -ErrorAction Ignore
        }
        catch {}
        return $Count
    }
}

# SIG # Begin signature block
# MIInuwYJKoZIhvcNAQcCoIInrDCCJ6gCAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCCVyQ2Dabavt36b
# WvieSObiQq63mBqxyLhMr3ZTboUBT6CCDYUwggYDMIID66ADAgECAhMzAAACU+OD
# 3pbexW7MAAAAAAJTMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD
# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p
# bmcgUENBIDIwMTEwHhcNMjEwOTAyMTgzMzAwWhcNMjIwOTAxMTgzMzAwWjB0MQsw
# CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u
# ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
# AQDLhxHwq3OhH+4J+SX4qS/VQG8HybccH7tnG+BUqrXubfGuDFYPZ29uCuHfQlO1
# lygLgMpJ4Geh6/6poQ5VkDKfVssn6aA1PCzIh8iOPMQ9Mju3sLF9Sn+Pzuaie4BN
# rp0MuZLDEXgVYx2WNjmzqcxC7dY9SC3znOh5qUy2vnmWygC7b9kj0d3JrGtjc5q5
# 0WfV3WLXAQHkeRROsJFBZfXFGoSvRljFFUAjU/zdhP92P+1JiRRRikVy/sqIhMDY
# +7tVdzlE2fwnKOv9LShgKeyEevgMl0B1Fq7E2YeBZKF6KlhmYi9CE1350cnTUoU4
# YpQSnZo0YAnaenREDLfFGKTdAgMBAAGjggGCMIIBfjAfBgNVHSUEGDAWBgorBgEE
# AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQUlZpLWIccXoxessA/DRbe26glhEMw
# VAYDVR0RBE0wS6RJMEcxLTArBgNVBAsTJE1pY3Jvc29mdCBJcmVsYW5kIE9wZXJh
# dGlvbnMgTGltaXRlZDEWMBQGA1UEBRMNMjMwMDEyKzQ2NzU5ODAfBgNVHSMEGDAW
# gBRIbmTlUAXTgqoXNzcitW2oynUClTBUBgNVHR8ETTBLMEmgR6BFhkNodHRwOi8v
# d3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NybC9NaWNDb2RTaWdQQ0EyMDExXzIw
# MTEtMDctMDguY3JsMGEGCCsGAQUFBwEBBFUwUzBRBggrBgEFBQcwAoZFaHR0cDov
# L3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9jZXJ0cy9NaWNDb2RTaWdQQ0EyMDEx
# XzIwMTEtMDctMDguY3J0MAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADggIB
# AKVY+yKcJVVxf9W2vNkL5ufjOpqcvVOOOdVyjy1dmsO4O8khWhqrecdVZp09adOZ
# 8kcMtQ0U+oKx484Jg11cc4Ck0FyOBnp+YIFbOxYCqzaqMcaRAgy48n1tbz/EFYiF
# zJmMiGnlgWFCStONPvQOBD2y/Ej3qBRnGy9EZS1EDlRN/8l5Rs3HX2lZhd9WuukR
# bUk83U99TPJyo12cU0Mb3n1HJv/JZpwSyqb3O0o4HExVJSkwN1m42fSVIVtXVVSa
# YZiVpv32GoD/dyAS/gyplfR6FI3RnCOomzlycSqoz0zBCPFiCMhVhQ6qn+J0GhgR
# BJvGKizw+5lTfnBFoqKZJDROz+uGDl9tw6JvnVqAZKGrWv/CsYaegaPePFrAVSxA
# yUwOFTkAqtNC8uAee+rv2V5xLw8FfpKJ5yKiMKnCKrIaFQDr5AZ7f2ejGGDf+8Tz
# OiK1AgBvOW3iTEEa/at8Z4+s1CmnEAkAi0cLjB72CJedU1LAswdOCWM2MDIZVo9j
# 0T74OkJLTjPd3WNEyw0rBXTyhlbYQsYt7ElT2l2TTlF5EmpVixGtj4ChNjWoKr9y
# TAqtadd2Ym5FNB792GzwNwa631BPCgBJmcRpFKXt0VEQq7UXVNYBiBRd+x4yvjqq
# 5aF7XC5nXCgjbCk7IXwmOphNuNDNiRq83Ejjnc7mxrJGMIIHejCCBWKgAwIBAgIK
# 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/Xmfwb1tbWrJUnMTDXpQzTGCGYwwghmIAgEBMIGVMH4x
# CzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRt
# b25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01p
# Y3Jvc29mdCBDb2RlIFNpZ25pbmcgUENBIDIwMTECEzMAAAJT44Pelt7FbswAAAAA
# AlMwDQYJYIZIAWUDBAIBBQCgga4wGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQw
# HAYKKwYBBAGCNwIBCzEOMAwGCisGAQQBgjcCARUwLwYJKoZIhvcNAQkEMSIEIIgk
# NkjfUUwAio6o8/Cziuom+Ag7dUxfQJYrcbFLDF/rMEIGCisGAQQBgjcCAQwxNDAy
# oBSAEgBNAGkAYwByAG8AcwBvAGYAdKEagBhodHRwOi8vd3d3Lm1pY3Jvc29mdC5j
# b20wDQYJKoZIhvcNAQEBBQAEggEAGF0ZDbd9KesmaqioesU5tzPn02CTYc++XkZW
# TP+WpbkML18upUPMZ/iXKzU7dZaKh4WbbwtBERHm7ogpwlBxjOXyprrGS4R+VM32
# M98MMmrNuWfYqei85QLtvJGivSD6ERc/ETTFEy6yjsGCFI581SGORoYdcDKFT2fd
# kSgs4nYJ0G0znPd18Odlvreu8PLozGhlCpgEPLUxulcnlD5/TmIzK+CkpGkNzUGZ
# R8eSOHvWOhs50laUvN8mzliMbaVfUp+2XZNxHICKCkN4lwBxyfdbzApeDBOO5IYx
# XoR3qK2sUMxO7qi6w9mKqX0MYfxbZV1FtDsjZyQ0odtxZqx2FqGCFxYwghcSBgor
# BgEEAYI3AwMBMYIXAjCCFv4GCSqGSIb3DQEHAqCCFu8wghbrAgEDMQ8wDQYJYIZI
# AWUDBAIBBQAwggFZBgsqhkiG9w0BCRABBKCCAUgEggFEMIIBQAIBAQYKKwYBBAGE
# WQoDATAxMA0GCWCGSAFlAwQCAQUABCAlTJ/9vS9O+skYfzmfRbFlOjunRvXWfiCG
# FGr5GPTJKwIGYgivzlZcGBMyMDIyMDQyMTEyMDM0Ny4zMThaMASAAgH0oIHYpIHV
# MIHSMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMH
# UmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMS0wKwYDVQQL
# EyRNaWNyb3NvZnQgSXJlbGFuZCBPcGVyYXRpb25zIExpbWl0ZWQxJjAkBgNVBAsT
# HVRoYWxlcyBUU1MgRVNOOjg2REYtNEJCQy05MzM1MSUwIwYDVQQDExxNaWNyb3Nv
# ZnQgVGltZS1TdGFtcCBTZXJ2aWNloIIRZTCCBxQwggT8oAMCAQICEzMAAAGMAZdi
# RzZ2ZjsAAQAAAYwwDQYJKoZIhvcNAQELBQAwfDELMAkGA1UEBhMCVVMxEzARBgNV
# BAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jv
# c29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAg
# UENBIDIwMTAwHhcNMjExMDI4MTkyNzQ0WhcNMjMwMTI2MTkyNzQ0WjCB0jELMAkG
# A1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQx
# HjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEtMCsGA1UECxMkTWljcm9z
# b2Z0IElyZWxhbmQgT3BlcmF0aW9ucyBMaW1pdGVkMSYwJAYDVQQLEx1UaGFsZXMg
# VFNTIEVTTjo4NkRGLTRCQkMtOTMzNTElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUt
# U3RhbXAgU2VydmljZTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANNI
# aEyhE/khrGssPvQRXZvrmfpLxDxi3ebBfF5U91MuGBqk/Ovg6/Bt5Oqv5UWoIsUS
# r5/oNgBUS/Vbmagtbk72u3WTfQoYqLRxxZsskGT2pV3SUwvJyiK24EzFwMIf5m4Z
# 5qGsbCPYxpYr2IIuRjThO7uk1eFDrZ1T/IqIU1HzTCoWWiXc5lg44Vguy4z1yIWp
# vUIUZFc65MXySnOfQLGhg9z74kZIB6BsX6XVhzz2lvIohB43ODw5gipbltyfiHVN
# /B/jJCj5npAuxrUUy1ygQrlil0vE42WP8JDXM1jRKPpeSdzmXR3lYoMacwp3rJGX
# 3B18awl9obnu6ib1q5LBUrZGWzhuyGJmn2DEK2RrpZe9j50taCHUHWJ0ef54HL0k
# G9dRkNJDTA84irEnfuYn1GmGyS2dFxMTVeKi1wkuuQ4/vBcoAo7Tb5A4geR7PSOy
# vc8WbFG+3yikhhGfcgNCYE1m3ADwmD7bgB1SfFCmk/eu6SZu/q94YHHt/FVN/bKX
# nhx4GgkuL163pUy4lDAJdDrZOZ3CkCnNpBp77sD9kQkt5BBBQMaJ8C5/Kcnncq3m
# U2wTEAan9aN5I9IpTie/3/z93Na52mDtNRgyaJr+6LaW+c/tYa0qCLPLvunq7iSg
# k4oXdIv/G3OuwChe+sKVrr1vQYW1DE7FpMMOK+NnAgMBAAGjggE2MIIBMjAdBgNV
# HQ4EFgQUls5ThqmCIWCIeVadPojK3UCLUiMwHwYDVR0jBBgwFoAUn6cVXQBeYl2D
# 9OXSZacbUzUZ6XIwXwYDVR0fBFgwVjBUoFKgUIZOaHR0cDovL3d3dy5taWNyb3Nv
# ZnQuY29tL3BraW9wcy9jcmwvTWljcm9zb2Z0JTIwVGltZS1TdGFtcCUyMFBDQSUy
# MDIwMTAoMSkuY3JsMGwGCCsGAQUFBwEBBGAwXjBcBggrBgEFBQcwAoZQaHR0cDov
# L3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9jZXJ0cy9NaWNyb3NvZnQlMjBUaW1l
# LVN0YW1wJTIwUENBJTIwMjAxMCgxKS5jcnQwDAYDVR0TAQH/BAIwADATBgNVHSUE
# DDAKBggrBgEFBQcDCDANBgkqhkiG9w0BAQsFAAOCAgEA12jFRVjCCanW5UGSuqJL
# O3HQlMHjwJphCHnbMrrIFDCEJUKmo3wj/YhufMjhUkcdpOfY9oQAUmQcRZm5FY8I
# WBAtciT0JveOuIFM+RvrjludYvLnnngd4dovg5qFjSjUrpSDcn0hoFujwgwokajt
# 6p/CmFcy86Hpnz4q/1FceQgIFXBAwDLcW0a0x1wQAV8gmumkN/o7pFgeWkMy8Oqo
# R4c+xyDlPav0PWNjZ1QSj38yJcD429ja0Bn0J107LHxQ/fDqUR6tO2VMdtYOKbPF
# d94UkpCdrg8IbaeVbRRpxfgMcxQZQr3N9yz05l7HM5cuvskIAEcJjR3jQNutlqiy
# yTPOCM/DktVXxNTesmApC44PNfsxl7I7zBpowZYssWcF1hliZrKLwek+odRq35rz
# CrnThPdg+u0kd809w3QOScC/UwM1/FIYtGhmLZ+bjVAxW8SKMyETKS1aT/2Di54P
# q9r/LPJclr9Gn48GWBwSeuDFlTcR3GjbY85GLUI3WeW4cpGunV/g7UA/W4d844tE
# pa31QyC8RG+jo8qrXxo+4lmbya2+AKiFYB0Gg84LosREvYnrRYpB33+qfewuaqG0
# 02ysDdABD96ubXsiPTSDlZSZdIIuSG3efB4n9ySzur6fuch146Ei/zJYRZrxrWmJ
# kMA+ys05vbgAxeAcz/5sdr8wggdxMIIFWaADAgECAhMzAAAAFcXna54Cm0mZAAAA
# AAAVMA0GCSqGSIb3DQEBCwUAMIGIMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2Fz
# aGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENv
# cnBvcmF0aW9uMTIwMAYDVQQDEylNaWNyb3NvZnQgUm9vdCBDZXJ0aWZpY2F0ZSBB
# dXRob3JpdHkgMjAxMDAeFw0yMTA5MzAxODIyMjVaFw0zMDA5MzAxODMyMjVaMHwx
# CzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRt
# b25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1p
# Y3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwMIICIjANBgkqhkiG9w0BAQEFAAOC
# Ag8AMIICCgKCAgEA5OGmTOe0ciELeaLL1yR5vQ7VgtP97pwHB9KpbE51yMo1V/YB
# f2xK4OK9uT4XYDP/XE/HZveVU3Fa4n5KWv64NmeFRiMMtY0Tz3cywBAY6GB9alKD
# RLemjkZrBxTzxXb1hlDcwUTIcVxRMTegCjhuje3XD9gmU3w5YQJ6xKr9cmmvHaus
# 9ja+NSZk2pg7uhp7M62AW36MEBydUv626GIl3GoPz130/o5Tz9bshVZN7928jaTj
# kY+yOSxRnOlwaQ3KNi1wjjHINSi947SHJMPgyY9+tVSP3PoFVZhtaDuaRr3tpK56
# KTesy+uDRedGbsoy1cCGMFxPLOJiss254o2I5JasAUq7vnGpF1tnYN74kpEeHT39
# IM9zfUGaRnXNxF803RKJ1v2lIH1+/NmeRd+2ci/bfV+AutuqfjbsNkz2K26oElHo
# vwUDo9Fzpk03dJQcNIIP8BDyt0cY7afomXw/TNuvXsLz1dhzPUNOwTM5TI4CvEJo
# LhDqhFFG4tG9ahhaYQFzymeiXtcodgLiMxhy16cg8ML6EgrXY28MyTZki1ugpoMh
# XV8wdJGUlNi5UPkLiWHzNgY1GIRH29wb0f2y1BzFa/ZcUlFdEtsluq9QBXpsxREd
# cu+N+VLEhReTwDwV2xo3xwgVGD94q0W29R6HXtqPnhZyacaue7e3PmriLq0CAwEA
# AaOCAd0wggHZMBIGCSsGAQQBgjcVAQQFAgMBAAEwIwYJKwYBBAGCNxUCBBYEFCqn
# Uv5kxJq+gpE8RjUpzxD/LwTuMB0GA1UdDgQWBBSfpxVdAF5iXYP05dJlpxtTNRnp
# cjBcBgNVHSAEVTBTMFEGDCsGAQQBgjdMg30BATBBMD8GCCsGAQUFBwIBFjNodHRw
# Oi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL0RvY3MvUmVwb3NpdG9yeS5odG0w
# EwYDVR0lBAwwCgYIKwYBBQUHAwgwGQYJKwYBBAGCNxQCBAweCgBTAHUAYgBDAEEw
# CwYDVR0PBAQDAgGGMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAU1fZWy4/o
# olxiaNE9lJBb186aGMQwVgYDVR0fBE8wTTBLoEmgR4ZFaHR0cDovL2NybC5taWNy
# b3NvZnQuY29tL3BraS9jcmwvcHJvZHVjdHMvTWljUm9vQ2VyQXV0XzIwMTAtMDYt
# MjMuY3JsMFoGCCsGAQUFBwEBBE4wTDBKBggrBgEFBQcwAoY+aHR0cDovL3d3dy5t
# aWNyb3NvZnQuY29tL3BraS9jZXJ0cy9NaWNSb29DZXJBdXRfMjAxMC0wNi0yMy5j
# cnQwDQYJKoZIhvcNAQELBQADggIBAJ1VffwqreEsH2cBMSRb4Z5yS/ypb+pcFLY+
# TkdkeLEGk5c9MTO1OdfCcTY/2mRsfNB1OW27DzHkwo/7bNGhlBgi7ulmZzpTTd2Y
# urYeeNg2LpypglYAA7AFvonoaeC6Ce5732pvvinLbtg/SHUB2RjebYIM9W0jVOR4
# U3UkV7ndn/OOPcbzaN9l9qRWqveVtihVJ9AkvUCgvxm2EhIRXT0n4ECWOKz3+SmJ
# w7wXsFSFQrP8DJ6LGYnn8AtqgcKBGUIZUnWKNsIdw2FzLixre24/LAl4FOmRsqlb
# 30mjdAy87JGA0j3mSj5mO0+7hvoyGtmW9I/2kQH2zsZ0/fZMcm8Qq3UwxTSwethQ
# /gpY3UA8x1RtnWN0SCyxTkctwRQEcb9k+SS+c23Kjgm9swFXSVRk2XPXfx5bRAGO
# WhmRaw2fpCjcZxkoJLo4S5pu+yFUa2pFEUep8beuyOiJXk+d0tBMdrVXVAmxaQFE
# fnyhYWxz/gq77EFmPWn9y8FBSX5+k77L+DvktxW/tM4+pTFRhLy/AsGConsXHRWJ
# jXD+57XQKBqJC4822rpM+Zv/Cuk0+CQ1ZyvgDbjmjJnW4SLq8CdCPSWU5nR0W2rR
# nj7tfqAxM328y+l7vzhwRNGQ8cirOoo6CGJ/2XBjU02N7oJtpQUQwXEGahC0HVUz
# WLOhcGbyoYIC1DCCAj0CAQEwggEAoYHYpIHVMIHSMQswCQYDVQQGEwJVUzETMBEG
# A1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWlj
# cm9zb2Z0IENvcnBvcmF0aW9uMS0wKwYDVQQLEyRNaWNyb3NvZnQgSXJlbGFuZCBP
# cGVyYXRpb25zIExpbWl0ZWQxJjAkBgNVBAsTHVRoYWxlcyBUU1MgRVNOOjg2REYt
# NEJCQy05MzM1MSUwIwYDVQQDExxNaWNyb3NvZnQgVGltZS1TdGFtcCBTZXJ2aWNl
# oiMKAQEwBwYFKw4DAhoDFQA0ovIU66v0PKKacHhsrmSzRCav1aCBgzCBgKR+MHwx
# CzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRt
# b25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1p
# Y3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwMA0GCSqGSIb3DQEBBQUAAgUA5gt/
# DjAiGA8yMDIyMDQyMTE0NTgyMloYDzIwMjIwNDIyMTQ1ODIyWjB0MDoGCisGAQQB
# hFkKBAExLDAqMAoCBQDmC38OAgEAMAcCAQACAgn9MAcCAQACAhFpMAoCBQDmDNCO
# AgEAMDYGCisGAQQBhFkKBAIxKDAmMAwGCisGAQQBhFkKAwKgCjAIAgEAAgMHoSCh
# CjAIAgEAAgMBhqAwDQYJKoZIhvcNAQEFBQADgYEAks/ZgMX+jGfC1EWyNdx2nvj0
# hW8iwNY2zO5y7sLaW4jyqxuWxF42PsUWszU+5OrGI3jAHq8n8ft3f2Nyi2FfOdtG
# UtEotOBOpUXkk9WmyNrULUF7ftdB0vHn9UwSim+FAI+5WuhYJgLW1+uo9DUTyh2z
# sBxiCu4p+g3qr9iRnAsxggQNMIIECQIBATCBkzB8MQswCQYDVQQGEwJVUzETMBEG
# A1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWlj
# cm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFt
# cCBQQ0EgMjAxMAITMwAAAYwBl2JHNnZmOwABAAABjDANBglghkgBZQMEAgEFAKCC
# AUowGgYJKoZIhvcNAQkDMQ0GCyqGSIb3DQEJEAEEMC8GCSqGSIb3DQEJBDEiBCCI
# obnCQFtgRn3CT2omdcI6iDgRrJ/ijxeO5YhDhcL1CjCB+gYLKoZIhvcNAQkQAi8x
# geowgecwgeQwgb0EINWti/gVKpDPBn/E5iEFnYHik062FyMDqHzriYgYmGmeMIGY
# MIGApH4wfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNV
# BAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQG
# A1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIwMTACEzMAAAGMAZdiRzZ2
# ZjsAAQAAAYwwIgQgpdF2N4mEhZ6yisryWdFie0WYGW0rlwyPw2hqo1TwPeAwDQYJ
# KoZIhvcNAQELBQAEggIAbdOtrazbTDZTSlzeUOR2AxhbQFkG/hnIWnQ0IRKTzdxB
# xXnFs7aJFswAsGghuBM8w0EtiyK3mzJIOV6upVV/RgOQpEjqtOZoXNWdNrJ13uNA
# TudCSqi95psyHyPXoReWQTaeYtY47H76QOEdtBuD7dPokHvARy80GCaGvqEiaodT
# tmJK2dLeYCJQopzytl9/iwo1UXB+wEp2NonSbLNPF6H45V9MyNv7l0zhdTDIs/bU
# HHSAcfzVE2iPvuiCeunpr4Jgdkjv/D0Kj7n1KNt2fkHSjhDRZGCVozTs+rslKS6C
# jz0ujV2C1s1pl1ngOnj+NPBvSN9Llia1ynVdFNtiMOGnhKjb2gJ/xJrsBkG4HrM0
# UUqKRppi2gBGimeGezAMPqGPigcax+v0f0ei5q51D0lvMUzvioSTx6nv/jaoFMMD
# 574kifc89Dpf+cQiHPt2y23XUaV8CyOC3P2ZNpB397sPHC2mOI8ccLVCEzaGKcEi
# 7zKO4rhBESztLGgoeI3iALdG70Mo4/KzXWWHeIGdPRC1BHrNL5I9MGxu4ZFjC7KI
# JjO5TV77fwNxoj9X9Ue78ibSBlM165A5OhmapT8UecRuPUSJr6pdTO/pHUYHKPtQ
# nSV0YkDVC6i8WOBqTDHHmDs7CHvjt6gHcMkjP7lSjQOZQchQ80nubmzinQaGvjI=
# SIG # End signature block