Public/TenantGovernance/Get-EntraOpsTenantGovernanceSnapshot.ps1
|
<# .SYNOPSIS Create and retrieve a Microsoft Entra Tenant Configuration Management (UTCM) snapshot. .DESCRIPTION Uses the Microsoft Graph Tenant Configuration Management (also known as Universal Tenant Configuration Management, UTCM) API to create a point-in-time snapshot of the configured Microsoft Entra resources (e.g. Conditional Access policies, authentication methods, cross-tenant access policy, authorization policy, ...), waits for the snapshot job to complete and returns the captured resource configuration. Requires the "ConfigurationMonitoring.ReadWrite.All" Microsoft Graph application permission on the calling identity (the default permission granted by New-EntraOpsWorkloadIdentity) - this is Microsoft's documented least-privileged permission for the createSnapshot API, and "ConfigurationMonitoring.Read.All" alone is not sufficient to create a snapshot job. It also requires read permissions on the first-party "Microsoft Tenant Configuration Management" service principal for the requested resource types. Both prerequisites are configured by Register-EntraOpsTenantGovernanceServicePrincipal (called automatically by New-EntraOpsWorkloadIdentity when TenantGovernanceSnapshot.EnableTenantGovernanceSnapshot is set to $true in the config file), and are validated automatically before the snapshot job is created via Test-EntraOpsTenantGovernancePrerequisite. If an existing workload identity was set up with an older EntraOps version that only granted "ConfigurationMonitoring.Read.All", re-apply the required permission via Register-EntraOpsTenantGovernanceServicePrincipal -GrantConfigurationMonitoringReadWrite. Reference: https://learn.microsoft.com/en-us/graph/api/configurationbaseline-createsnapshot .NOTES UTCM enforces service limits on captured resources per tenant and keeps snapshot results server-side only for a limited period. The exact figures are set by the service and change independently of EntraOps, so verify them against https://learn.microsoft.com/en-us/graph/api/resources/unified-tenant-configuration-management-api-overview?view=graph-rest-1.0#api-limits before rollout. Daily or weekly scheduling (see TenantGovernanceSnapshot.SnapshotScheduledCron in the config file) suits a default ResourcesToInclude set. If ResourcesToInclude is extended with high-cardinality resource types (e.g. "microsoft.entra.user", "group", "servicePrincipal", "application"), prefer a weekly (or less frequent) cadence to stay within the resource limit. .PARAMETER ResourcesToInclude Array of Microsoft Entra resource types (e.g. "microsoft.entra.conditionalAccessPolicy") to include in the snapshot. Defaults to the recommended tenant governance resource set. .PARAMETER SnapshotDisplayName Display name of the snapshot. Default is "EntraOps TG <yyyyMMddHHmmss>". The Microsoft Graph UTCM API only allows alphabets, numbers, and spaces in this value - any other characters (e.g. hyphens) are stripped automatically. .PARAMETER SnapshotDescription Description of the snapshot. .PARAMETER TimeoutInSeconds Maximum time (in seconds) to wait for the snapshot job to complete. Default is 900 (15 minutes). When the timeout is reached, the job is not cancelled: a pending result object (Status is the last known job status) is returned so the job can be completed later with -SnapshotJobId. .PARAMETER PollIntervalInSeconds Interval (in seconds) between polling attempts for the snapshot job status. Default is 10. .PARAMETER SnapshotJobId Resume an existing snapshot job instead of creating a new one (e.g. a job Id returned by a previous call made with -SkipWaitForCompletion). When supplied, ResourcesToInclude, SnapshotDisplayName and SnapshotDescription are ignored for job creation purposes. .PARAMETER SkipWaitForCompletion Return immediately after creating (or checking) the snapshot job instead of polling until it completes. Useful to split long-running snapshots (UTCM jobs for large resource sets can take up to an hour) across two separate runs: an initial run that starts the job with this switch, and a later run that completes it by passing the returned SnapshotId to -SnapshotJobId. The returned object's Status property indicates whether the job is still pending or has completed. .PARAMETER SkipPrerequisiteCheck Skip the automatic prerequisite validation (Test-EntraOpsTenantGovernancePrerequisite) before creating the snapshot job. Default is $false. .EXAMPLE Create a snapshot of the default tenant governance resources and wait for completion. Get-EntraOpsTenantGovernanceSnapshot .EXAMPLE Create a snapshot of only Conditional Access policies and Named Locations. Get-EntraOpsTenantGovernanceSnapshot -ResourcesToInclude @("microsoft.entra.conditionalAccessPolicy","microsoft.entra.namedLocationPolicy") .EXAMPLE Start a snapshot job without waiting for it to complete, then complete it in a later run. $Job = Get-EntraOpsTenantGovernanceSnapshot -SkipWaitForCompletion # ... in a separate, later run ... Get-EntraOpsTenantGovernanceSnapshot -SnapshotJobId $Job.SnapshotId #> function Get-EntraOpsTenantGovernanceSnapshot { [CmdletBinding()] param ( [Parameter(Mandatory = $false)] [Array]$ResourcesToInclude = (Get-EntraOpsTenantGovernanceResourceDefinition).DefaultResources , [Parameter(Mandatory = $false)] [System.String]$SnapshotDisplayName = "EntraOps TG $((Get-Date).ToUniversalTime().ToString('yyyyMMddHHmmss'))" , [Parameter(Mandatory = $false)] [System.String]$SnapshotDescription = "EntraOps Tenant Governance snapshot of Microsoft Entra configuration" , [Parameter(Mandatory = $false)] [System.Int32]$TimeoutInSeconds = 900 , [Parameter(Mandatory = $false)] [System.Int32]$PollIntervalInSeconds = 10 , [Parameter(Mandatory = $false)] [System.String]$SnapshotJobId , [Parameter(Mandatory = $false)] [switch]$SkipWaitForCompletion , [Parameter(Mandatory = $false)] [switch]$SkipPrerequisiteCheck ) $ErrorActionPreference = "Stop" # Filter out any null values resulting from PowerShell array wrapping of $null $ResourcesToInclude = @($ResourcesToInclude) | Where-Object { $null -ne $_ } if ($ResourcesToInclude.Count -eq 0) { throw "ResourcesToInclude can not be empty. Provide at least one Microsoft Entra resource type (e.g. 'microsoft.entra.conditionalAccessPolicy')." } # The UTCM API only allows alphabets, numbers, and spaces in the snapshot displayName, # and enforces a length between 8 and 32 characters. # Sanitize defensively in case a caller-supplied name contains hyphens, other punctuation, or is too long. $SnapshotDisplayName = ($SnapshotDisplayName -replace '[^a-zA-Z0-9 ]', ' ') -replace '\s+', ' ' $SnapshotDisplayName = $SnapshotDisplayName.Trim() if ($SnapshotDisplayName.Length -gt 32) { $SnapshotDisplayName = $SnapshotDisplayName.Substring(0, 32).TrimEnd() } if ($SnapshotDisplayName.Length -lt 8) { $SnapshotDisplayName = $SnapshotDisplayName.PadRight(8, '0') } #region Validate prerequisites (UTCM service principal + permissions) if (-not $SkipPrerequisiteCheck) { Write-Verbose "Validating Tenant Governance Snapshot prerequisites..." Test-EntraOpsTenantGovernancePrerequisite -ResourcesToInclude $ResourcesToInclude -ThrowOnFailure | Out-Null } #endregion #region Create the configuration snapshot job (or reuse an existing one via -SnapshotJobId) $CreatedNewSnapshotJob = [string]::IsNullOrEmpty($SnapshotJobId) if (-not [string]::IsNullOrEmpty($SnapshotJobId)) { Write-Verbose "Resuming existing Tenant Governance snapshot job $SnapshotJobId" } else { Write-Verbose "Creating Tenant Governance snapshot '$SnapshotDisplayName' for $(@($ResourcesToInclude).Count) resource type(s)..." $Body = @{ displayName = $SnapshotDisplayName description = $SnapshotDescription resources = @($ResourcesToInclude) } | ConvertTo-Json -Depth 10 try { $CreatedSnapshotJob = Invoke-EntraOpsMsGraphQuery -Method "POST" -Body $Body -Uri "/beta/admin/configurationManagement/configurationSnapshots/createSnapshot" -OutputType PSObject -DisableCache -ThrowOnFailure } catch { throw "Failed to create Tenant Governance snapshot job. Error: $_" } $SnapshotJobId = $CreatedSnapshotJob.id if ([string]::IsNullOrEmpty($SnapshotJobId)) { throw "Failed to retrieve the snapshot job Id from the createSnapshot response." } Write-Verbose "Snapshot job created with Id $SnapshotJobId" } #endregion #region Wait for the snapshot job to complete (or return immediately if -SkipWaitForCompletion is set) $SnapshotJob = $null $LastPollingError = $null $SnapshotJobUri = "/beta/admin/configurationManagement/configurationSnapshotJobs/$($SnapshotJobId)?`$select=id,displayName,description,status,resources,errorDetails,createdDateTime,completedDateTime,resourceLocation,tenantId" if ($SkipWaitForCompletion -and $CreatedNewSnapshotJob) { $InitialStatus = if ([string]::IsNullOrWhiteSpace($CreatedSnapshotJob.status)) { 'notStarted' } else { $CreatedSnapshotJob.status } Write-Verbose "Snapshot job $SnapshotJobId was created (status: $InitialStatus). Complete it later with: Get-EntraOpsTenantGovernanceSnapshot -SnapshotJobId $SnapshotJobId" return [PSCustomObject]@{ SnapshotId = $SnapshotJobId Status = $InitialStatus DisplayName = $CreatedSnapshotJob.displayName Description = $CreatedSnapshotJob.description CreatedDateTime = if ($CreatedSnapshotJob.createdDateTime) { $CreatedSnapshotJob.createdDateTime } else { (Get-Date).ToUniversalTime().ToString("o") } ResourcesToInclude = @($ResourcesToInclude) ResourceCount = $null Resources = $null PollingError = $null } } # Preserve the creation response as the last known state until the first status poll succeeds. # This retains useful metadata if every polling request fails. if ($CreatedNewSnapshotJob) { $SnapshotJob = $CreatedSnapshotJob } if ($SkipWaitForCompletion) { Write-Verbose "Checking status of snapshot job $SnapshotJobId (not waiting for completion)..." try { $SnapshotJob = Invoke-EntraOpsMsGraphQuery -Method "GET" -Uri $SnapshotJobUri -OutputType PSObject -DisableCache -ThrowOnFailure $LastPollingError = $null } catch { throw "Failed to retrieve status of snapshot job $SnapshotJobId. Error: $_" } if ($SnapshotJob.status -eq "failed") { throw "Tenant Governance snapshot job $SnapshotJobId failed. Error: $($SnapshotJob.error | Out-String)" } if ($SnapshotJob.status -notin @("succeeded", "completed", "partiallySuccessful")) { Write-Verbose "Snapshot job $SnapshotJobId has not completed yet (status: $($SnapshotJob.status)). Complete it later with: Get-EntraOpsTenantGovernanceSnapshot -SnapshotJobId $SnapshotJobId" return [PSCustomObject]@{ SnapshotId = $SnapshotJobId Status = $SnapshotJob.status DisplayName = $SnapshotJob.displayName Description = $SnapshotJob.description CreatedDateTime = if ($SnapshotJob.createdDateTime) { $SnapshotJob.createdDateTime } else { (Get-Date).ToUniversalTime().ToString("o") } ResourcesToInclude = @($ResourcesToInclude) ResourceCount = $null Resources = $null PollingError = $null } } if ($SnapshotJob.status -eq "partiallySuccessful") { Write-Warning "Snapshot job $SnapshotJobId completed as 'partiallySuccessful' - one or more resource types may have failed to capture. Check the snapshot job details in the Microsoft Entra admin center for more information." } } else { Write-Verbose "Waiting for snapshot job to complete (timeout: $($TimeoutInSeconds)s)..." $StopWatch = [System.Diagnostics.Stopwatch]::StartNew() do { Start-Sleep -Seconds $PollIntervalInSeconds try { $SnapshotJob = Invoke-EntraOpsMsGraphQuery -Method "GET" -Uri $SnapshotJobUri -OutputType PSObject -DisableCache -ThrowOnFailure $LastPollingError = $null } catch { $LastPollingError = $_ Write-Warning "Failed to poll snapshot job status. Retrying... Error: $_" } $ElapsedSeconds = [math]::Floor($StopWatch.Elapsed.TotalSeconds) $SnapshotStatus = if ([string]::IsNullOrWhiteSpace($SnapshotJob.status)) { 'unknown' } else { $SnapshotJob.status } Write-Verbose "Snapshot job $SnapshotJobId status: $SnapshotStatus (elapsed: $ElapsedSeconds/$TimeoutInSeconds seconds)" } while ( $SnapshotJob.status -notin @("succeeded", "completed", "partiallySuccessful", "failed") ` -and $StopWatch.Elapsed.TotalSeconds -lt $TimeoutInSeconds ) $StopWatch.Stop() if ($SnapshotJob.status -eq "failed") { throw "Tenant Governance snapshot job $SnapshotJobId failed. Error: $($SnapshotJob.error | Out-String)" } if ($SnapshotJob.status -notin @("succeeded", "completed", "partiallySuccessful")) { # The local wait does not cancel the Graph job, so return a resumable pending result # instead of throwing - otherwise the job Id is lost and no state can be persisted. $LastKnownStatus = if ([string]::IsNullOrEmpty($SnapshotJob.status)) { "unknown" } else { $SnapshotJob.status } $PollingErrorMessage = if ($LastPollingError) { $LastPollingError.ToString() } else { $null } if ($PollingErrorMessage) { Write-Warning "Tenant Governance snapshot job $SnapshotJobId did not complete within $($TimeoutInSeconds) seconds and its completion could not be confirmed (last known status: $LastKnownStatus; last polling error: $PollingErrorMessage). The job was not cancelled - check it later with: Get-EntraOpsTenantGovernanceSnapshot -SnapshotJobId $SnapshotJobId" } else { Write-Warning "Tenant Governance snapshot job $SnapshotJobId did not complete within $($TimeoutInSeconds) seconds (last status: $LastKnownStatus). The job was not cancelled - check it later with: Get-EntraOpsTenantGovernanceSnapshot -SnapshotJobId $SnapshotJobId" } return [PSCustomObject]@{ SnapshotId = $SnapshotJobId Status = $LastKnownStatus DisplayName = $SnapshotJob.displayName Description = $SnapshotJob.description CreatedDateTime = if ($SnapshotJob.createdDateTime) { $SnapshotJob.createdDateTime } elseif ($CreatedSnapshotJob.createdDateTime) { $CreatedSnapshotJob.createdDateTime } else { (Get-Date).ToUniversalTime().ToString("o") } ResourcesToInclude = @($ResourcesToInclude) ResourceCount = $null Resources = $null PollingError = $PollingErrorMessage } } if ($SnapshotJob.status -eq "partiallySuccessful") { Write-Warning "Snapshot job $SnapshotJobId completed as 'partiallySuccessful' - one or more resource types may have failed to capture. Check the snapshot job details in the Microsoft Entra admin center for more information." } } #endregion #region Retrieve the captured resources from the snapshot $ResourceLocation = $SnapshotJob.resourceLocation if ([string]::IsNullOrEmpty($ResourceLocation)) { throw "Snapshot job $SnapshotJobId completed but no resourceLocation was returned." } # resourceLocation can be returned as a fully qualified Graph URL or a relative path. if ($ResourceLocation -notlike "https://graph.microsoft.com/*" -and $ResourceLocation -notlike "/beta/*" -and $ResourceLocation -notlike "/v1.0/*") { $ResourceLocation = "/beta/" + $ResourceLocation.TrimStart('/') } try { $SnapshotResult = Invoke-EntraOpsMsGraphQuery -Method "GET" -Uri $ResourceLocation -OutputType PSObject -DisableCache -ThrowOnFailure } catch { throw "Failed to retrieve snapshot resources from $ResourceLocation. Error: $_" } $SnapshotResources = @($SnapshotResult.resources) if ($null -eq $SnapshotResult -or $SnapshotResources.Count -eq 0) { throw "Snapshot job $SnapshotJobId completed, but its resourceLocation returned no snapshot resources. Refusing to publish an empty snapshot." } #endregion [PSCustomObject]@{ SnapshotId = $SnapshotJobId Status = "Completed" SnapshotJobStatus = $SnapshotJob.status DisplayName = $SnapshotResult.displayName Description = $SnapshotResult.description CreatedDateTime = (Get-Date).ToUniversalTime().ToString("o") ResourcesToInclude = @($ResourcesToInclude) ResourceCount = $SnapshotResources.Count Resources = $SnapshotResources ErrorDetails = @($SnapshotJob.errorDetails) PollingError = $null } } # SIG # Begin signature block # MIIniAYJKoZIhvcNAQcCoIIneTCCJ3UCAQExDzANBglghkgBZQMEAgEFADB5Bgor # BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG # KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCD0iId81+EMZWzS # liTP0W5CGFCH4P+tUVFFy5tanLA4VaCCIDwwggXJMIIEsaADAgECAhAbtY8lKt8j # 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 # ARUwLwYJKoZIhvcNAQkEMSIEIGW7O/ILgb/27xGkcMYm9cLQEHrQesxjClwiXfy6 # 0jMuMA0GCSqGSIb3DQEBAQUABIIBgHj4EVlqYcUd2qxACljX32QPkXmhaVsYI6wo # Drsau+3uOQZNO2EFA+6Yq/S94nsScrJ+U0yTZDCkqobfInDb5WiC9ktgOj6mCGfh # iZ37Ifmgr7TRxP/huEmCwNHgwZ3KT2qqn0Uw787tgy+EJkaf9lHFIYyWjTV2buCB # OAkC0hZ7RuxynI9V592qr2c+ckwQ2iq+Bb8v2ZBqLKDY4hIFU/ONjHWAP6nmbhsa # E2p0MRbeZ0VMhFYnfH7TzbCqCp+KgIq1krTQpA7CVhewQNoVEGDu4u3oF4Fbbkg+ # XG1PNgN1dnVSgLwTqYNc8K5nPUG2W7+d3ycxPYKb2g/mFys1DXUlvYunMvWw1SvM # tWTqvD0i1+wHBfCTtnGAIXZ2brObMk/LpjOA2Y5K44x3hlgUTxQDWbPhwUMySLbE # PeEF6btjZvKyB4cW4XpmBWcS5Yob/iUOsAbk6tf3GT5O6sTa2nZP1a9qzHM1YNaO # 7ugv8BXQCUB43qwejD2RUgyotIeFbaGCBAIwggP+BgkqhkiG9w0BCQYxggPvMIID # 6wIBATBqMFYxCzAJBgNVBAYTAlBMMSEwHwYDVQQKExhBc3NlY28gRGF0YSBTeXN0 # ZW1zIFMuQS4xJDAiBgNVBAMTG0NlcnR1bSBUaW1lc3RhbXBpbmcgMjAyMSBDQQIQ # KPB3wRw2vf5fdDJHcCcuAzANBglghkgBZQMEAgIFAKCCAVYwGgYJKoZIhvcNAQkD # MQ0GCyqGSIb3DQEJEAEEMBwGCSqGSIb3DQEJBTEPFw0yNjEwMTMwOTQyMDhaMDcG # CyqGSIb3DQEJEAIvMSgwJjAkMCIEIIW+kOEK0kONfMkotq9IsJqyCBd87PiwEmxY # 05EFJcQ8MD8GCSqGSIb3DQEJBDEyBDDL0UTsByPqN51b3yvJemcDrsyhggL5Alny # XhjUxso07iW5yWRkVOlcmuwXzjs61vMwgZ8GCyqGSIb3DQEJEAIMMYGPMIGMMIGJ # MIGGBBRXFGhBDKha80JO+RZKUTYQ9NONmDBuMFqkWDBWMQswCQYDVQQGEwJQTDEh # MB8GA1UEChMYQXNzZWNvIERhdGEgU3lzdGVtcyBTLkEuMSQwIgYDVQQDExtDZXJ0 # dW0gVGltZXN0YW1waW5nIDIwMjEgQ0ECECjwd8EcNr3+X3QyR3AnLgMwDQYJKoZI # hvcNAQEBBQAEggIAdZ6wp2eBmDP9CopuP2YetDVKsstoRHSRYaSfrq2l5fvyvaR8 # UKJKzMi7qrC3YMiFNnDSMIYypgl997YcfaTwhrH32K7FV5YF76nnbx+EMqMWURXs # +oGI2feuCvOix89waTcF0tAt7XLm338OEKYnlx5hcoMuPdJqf4kwKuTU4wfxFfvi # BA8MPwLnWCFSBeeKf9WtqTnELd7t9YNf+qQBVC6mTnvNrzAaIeY0StW7LOSoZE7k # kooLm4FFJPwOoHeS56p/WMQdtAYLD2H/lp9z8i4TdnRRxjQxvKgVs2qN/V7KOryM # 8lEKNOyNFBxr/HSj4j7LplySXzSwG0UI5xCeiylQeW2mmfBGNLqdvnL/UOGkPcO5 # MsLS4d3huMWtBVfFMnUTFvNFkReKQYdDH0hOxn63lAuUuEn55iOQVqQvIvc1aQhR # wvEbRW9eOa2Uu/N292RvXPoksmP/dtY/PPBSac6YHnonEPf3HNpHiQntHsCiMZJS # 4Pf9wgz6Cl1D4YIQq3aRXkcZqAkfRh890e0RTER98Zd563bb5E9pl3dkPd0zJjM4 # Gwkhqnas8ggiV4hmcJyuDqroEtHjEY641vTvOxNOeAZTb5/4P596K3rzVg73BZV7 # ftmGvLLGlxtEeNW+gDEIBox7GIIRE3ZsVuwJMxxfjICqpQKA52MXf47wOKc= # SIG # End signature block |