Public/Reportings/New-EntraOpsReportingData.ps1

<#
.SYNOPSIS
    Generate (refresh) data for all EntraOps Reporting static web apps in a single call.

.DESCRIPTION
    Convenience wrapper around the Classification Explorer, Tier Breach Analyzer, EAM Dashboard,
    Privileged Assets, Privilege History, Configuration Analyzer, Access Package Flow, and Access Path Map generators. It runs all
    applicable generators in one call, so the whole
    EntraOps Reporting portal (Reports/index.html + all sub-apps) can be refreshed with a
    single cmdlet. Use Remove-EntraOpsReportingData to clear the generated data again.

.PARAMETER EntraOpsRoot
    Path to the EntraOps repository root. Used as the classification-logic source
    (Classification/Templates) for the Classification Explorer and as the PrivilegedEAM
    source root for the Tier Breach Analyzer. Defaults to the repository this module lives in.

.PARAMETER ConfigFilePath
    Configuration file used for Privilege History and Tenant Governance feature settings. Defaults
    to EntraOpsConfig.json below EntraOpsRoot.

.PARAMETER ClassificationExplorerRepoRoot
    Path to the repository that contains the classified roles and permission catalogs (the
    AzurePrivilegedIAM repository). Forwarded as -RepoRoot to New-EntraOpsClassificationExplorerData.
    When omitted, that cmdlet auto-detects the location.

.PARAMETER ClassificationExplorerAppRoot
    Path to the Classification Explorer app folder. Defaults to Reports/ClassificationExplorer
    under EntraOpsRoot.

.PARAMETER TierBreachImportPath
    Folder with the Privileged EAM export. Forwarded as -ImportPath to
    New-EntraOpsTierBreachAnalyzerData. Defaults to <EntraOpsRoot>/PrivilegedEAM.

.PARAMETER TierBreachAnalyzerAppRoot
    Path to the Tier Breach Analyzer app folder. Defaults to Reports/TierBreachAnalyzer under
    EntraOpsRoot.

.PARAMETER EamDashboardImportPath
    Folder with the Privileged EAM export. Forwarded as -ImportPath to
    New-EntraOpsPrivilegedEamDashboardData. Defaults to <EntraOpsRoot>/PrivilegedEAM.

.PARAMETER EamDashboardAppRoot
    Path to the EAM Dashboard app folder. Defaults to Reports/EamDashboard under
    EntraOpsRoot.

.PARAMETER EamDashboardResolveLinkedIdentityObjectIds
    Forwarded to New-EntraOpsPrivilegedEamDashboardData. Resolves linked identity
    IDs outside the Privileged EAM export through Microsoft Graph when enabled.
    When omitted, the EAM Dashboard generator defaults this option to `$true`.

.PARAMETER PrivilegedAssetsAppRoot
    Path to the Privileged Assets app folder. Defaults to Reports/PrivilegedAssets
    under EntraOpsRoot. The Privileged EAM export is read from the EAM Dashboard import path.

.PARAMETER PrivilegedAssetsResolveRelatedObjectIds
    Forwarded as -ResolveRelatedObjectIds to New-EntraOpsPrivilegedAssetsData. When omitted,
    the generator falls back to `PrivilegedAssets.ResolveRelatedObjectIds` in
    EntraOpsConfig.json, or $true.

.PARAMETER PrivilegedAssetsMaesterTestResultPath
    Forwarded as -MaesterTestResultPath to New-EntraOpsPrivilegedAssetsData: Maester result JSON,
    path without extension, or folder (latest TestResults JSON). Optional; the Maester integration is
    skipped when omitted or when no result file is found.

.PARAMETER SkipPrivilegedAssets
    Do not generate Privileged Assets data.

.PARAMETER AccessPathMapTenantId
    Tenant id forwarded to New-EntraOpsAccessPathMapData (used to build role node ids
    matching the BloodHound OpenGraph export). Defaults to the ObjectTenantId found in the export.

.PARAMETER AccessPathMapImportPath
    Folder with the Privileged EAM export. Forwarded as -ImportPath to
    New-EntraOpsAccessPathMapData. Defaults to <EntraOpsRoot>/PrivilegedEAM.

.PARAMETER AccessPathMapAppRoot
    Path to the Access Path Map app folder. Defaults to Reports/AccessPathMap under
    EntraOpsRoot.

.PARAMETER AccessPathMapResolveObjectIdsOutsidePrivilegedEAM
    Forwarded as -ResolveObjectIdsOutsidePrivilegedEAM to New-EntraOpsAccessPathMapData (resolves edge
    endpoints outside the Privileged EAM export via Microsoft Graph instead of showing them as an
    unresolved placeholder). When omitted, New-EntraOpsAccessPathMapData falls back to the
    `AccessPathMap.ResolveObjectIdsOutsidePrivilegedEAM` setting in EntraOpsConfig.json, or $true.

.PARAMETER SkipClassificationExplorer
    Do not generate Classification Explorer data.

.PARAMETER SkipExplorerHistory
    Do not derive the Classification Explorer change history (git log over the classification
    source files). When omitted, New-EntraOpsClassificationExplorerData falls back to the
    inverse of `ClassificationExplorer.GenerateChangeHistory` in EntraOpsConfig.json. A missing
    setting or configuration file defaults generation to $false because deriving history from the
    AzurePrivilegedIAM git log is slow and rarely meaningful. Pass -SkipExplorerHistory:$false to
    generate it for an individual invocation.

.PARAMETER SkipTierBreachAnalyzer
    Do not generate Tier Breach Analyzer data.

.PARAMETER SkipEamDashboard
    Do not generate EAM Dashboard data.

.PARAMETER SkipAccessPathMap
    Do not generate Access Path Map data.

.PARAMETER PrivilegeHistoryImportPath
    Folder with the Privileged EAM export whose git history is walked. Forwarded as
    -ImportPath to New-EntraOpsPrivilegedEamPrivilegeHistoryData. Defaults to
    <EntraOpsRoot>/PrivilegedEAM.

.PARAMETER PrivilegeHistoryAppRoot
    Path to the Privilege History app folder. Defaults to Reports/PrivilegeHistory under
    EntraOpsRoot.

.PARAMETER PrivilegeHistoryTimeRangeInDays
    Only consider commits from the last N days for the Privilege History dataset. Forwarded as
    -TimeRangeInDays to New-EntraOpsPrivilegedEamPrivilegeHistoryData. Defaults to the
    `PrivilegeHistory.TimeRangeInDays` setting in EntraOpsConfig.json, or the full git history
    ($null) when that config file/setting is not present.

.PARAMETER PrivilegeHistorySnapshotInterval
    Minimum spacing between two consecutive Privilege History snapshots, as an ISO 8601
    duration (`P1D` daily, `P1W` weekly, `P2W` bi-weekly, `P1M` monthly, `P3M` quarterly,
    `P1Y` yearly, ...). Forwarded as -SnapshotInterval to
    New-EntraOpsPrivilegedEamPrivilegeHistoryData. Defaults to the
    `PrivilegeHistory.SnapshotInterval` setting in EntraOpsConfig.json, or `P2W` (bi-weekly)
    when that config file/setting is not present.

.PARAMETER SkipPrivilegeHistory
    Do not generate the Privilege History data, regardless of the
    `PrivilegeHistory.EnablePrivilegeHistory` setting in EntraOpsConfig.json.

.PARAMETER ConfigurationAnalyzerImportPath
    Folder with the Tenant Governance snapshot export whose git history is walked.
    Forwarded as -ImportPath to New-EntraOpsTenantGovernanceConfigurationAnalyzerData.
    Defaults to <EntraOpsRoot>/TenantGovernance/Snapshots.

.PARAMETER ConfigurationAnalyzerAppRoot
    Path to the Configuration Analyzer app folder. Defaults to
    Reports/ConfigurationAnalyzer under EntraOpsRoot.

.PARAMETER ConfigurationAnalyzerTimeRangeInDays
    Only consider Tenant Governance snapshot commits from the last N days for the Configuration
    Analyzer dataset. Forwarded as -TimeRangeInDays to
    New-EntraOpsTenantGovernanceConfigurationAnalyzerData. Defaults to the
    `ConfigurationAnalyzer.TimeRangeInDays` setting in EntraOpsConfig.json (null = full history),
    or 365 when the setting is not present.

.PARAMETER ConfigurationAnalyzerSnapshotInterval
    Minimum spacing between two consecutive Configuration Analyzer snapshots, as an ISO 8601
    duration (`P1D`, `P1W`, `P1M`, ... or `None` to keep every commit). Forwarded as
    -SnapshotInterval. Defaults to the `ConfigurationAnalyzer.SnapshotInterval` setting in
    EntraOpsConfig.json, or `P1W` (weekly) when the setting is not present.

.PARAMETER ConfigurationAnalyzerMaxSnapshotAgeHours
    Maximum age of the published Tenant Governance snapshot before it is treated as stale. Forwarded
    as -MaxSnapshotAgeHours. Defaults to `ConfigurationAnalyzer.MaxTenantGovernanceSnapshotAgeHours`
    in EntraOpsConfig.json, or a value derived from `TenantGovernanceSnapshot.SnapshotScheduledCron`
    (daily 30 hours, weekly 174 hours, monthly 750 hours).

.PARAMETER SkipConfigurationAnalyzer
    Do not generate the Configuration Analyzer data. When not skipped, the data is only
    generated if the Tenant Governance snapshot folder exists (or
    `TenantGovernanceSnapshot.EnableTenantGovernanceSnapshot` is set to true in
    EntraOpsConfig.json), so repositories without the feature stay untouched.

.PARAMETER SkipAccessPackageFlow
    Do not generate Graph-backed group and resource enrichment for Access Package Flow.

.PARAMETER PassThru
    Emit the result object of each generator (one per app) to the pipeline.

.PARAMETER FailureAction
    Controls whether failures from optional generators stop the combined run or are reported as
    warnings. Continue preserves the interactive convenience behavior; Stop is intended for CI.

.PARAMETER AllowStaleTenantGovernanceSnapshot
    Allow Configuration Analyzer generation from a successful snapshot older than its maximum age.

.PARAMETER AllowPartialTenantGovernanceSnapshot
    Allow Configuration Analyzer generation from a partial snapshot with preserved stale resource types.

.PARAMETER InstallMissingReportingFolder
    Download the reporting apps (Install-EntraOpsReportingFolder) and the classification templates
    required by the Classification Explorer (Initialize-EntraOpsWorkspace -Content Classification)
    from the public EntraOps repository, main branch, when they are missing in the EntraOps working
    folder, e.g. for a module-only installation from the PowerShell Gallery. Prefer
    Initialize-EntraOpsWorkspace -Ref <release tag> beforehand, so the content matches the module version.
    Also repairs an incomplete Reports folder when a selected app is missing.
    Without this switch, missing reporting apps stop with an error and missing templates skip the
    Classification Explorer (error with -FailureAction Stop).

.EXAMPLE
    New-EntraOpsReportingData

    Regenerates the data for all reporting apps in this repository checkout.

.EXAMPLE
    New-EntraOpsReportingData -InstallMissingReportingFolder

    Downloads the Reports folder first if only the EntraOps module folder exists, then regenerates the data.

.EXAMPLE
    New-EntraOpsReportingData -ClassificationExplorerRepoRoot "C:\Repos\AzurePrivilegedIAM" -WhatIf

    Shows what would be generated for all apps without changing any files.

.EXAMPLE
    New-EntraOpsReportingData -SkipClassificationExplorer

    Regenerates only the Tier Breach Analyzer data.
#>


function New-EntraOpsReportingData {

    [CmdletBinding(SupportsShouldProcess = $true)]
    param (
        [Parameter(Mandatory = $false)]
        [System.String]$EntraOpsRoot,

        [Parameter(Mandatory = $false)]
        [System.String]$ConfigFilePath,

        [Parameter(Mandatory = $false)]
        [System.String]$ClassificationExplorerRepoRoot,

        [Parameter(Mandatory = $false)]
        [System.String]$ClassificationExplorerAppRoot,

        [Parameter(Mandatory = $false)]
        [System.String]$TierBreachImportPath,

        [Parameter(Mandatory = $false)]
        [System.String]$TierBreachAnalyzerAppRoot,

        [Parameter(Mandatory = $false)]
        [System.String]$EamDashboardImportPath,

        [Parameter(Mandatory = $false)]
        [System.String]$EamDashboardAppRoot,

        [Parameter(Mandatory = $false)]
        [System.Nullable[bool]]$EamDashboardResolveLinkedIdentityObjectIds,

        [Parameter(Mandatory = $false)]
        [System.String]$PrivilegedAssetsAppRoot,

        [Parameter(Mandatory = $false)]
        [System.Nullable[bool]]$PrivilegedAssetsResolveRelatedObjectIds,

        [Parameter(Mandatory = $false)]
        [Alias('MaesterTestResultPath')]
        [AllowEmptyString()]
        [System.String]$PrivilegedAssetsMaesterTestResultPath,

        [Parameter(Mandatory = $false)]
        [switch]$SkipPrivilegedAssets,

        [Parameter(Mandatory = $false)]
        [System.String]$AccessPathMapTenantId,

        [Parameter(Mandatory = $false)]
        [System.String]$AccessPathMapImportPath,

        [Parameter(Mandatory = $false)]
        [System.String]$AccessPathMapAppRoot,

        [Parameter(Mandatory = $false)]
        [System.Nullable[bool]]$AccessPathMapResolveObjectIdsOutsidePrivilegedEAM,

        [Parameter(Mandatory = $false)]
        [System.String]$PrivilegeHistoryImportPath,

        [Parameter(Mandatory = $false)]
        [System.String]$PrivilegeHistoryAppRoot,

        [Parameter(Mandatory = $false)]
        [System.Nullable[int]]$PrivilegeHistoryTimeRangeInDays,

        [Parameter(Mandatory = $false)]
        [System.String]$PrivilegeHistorySnapshotInterval,

        [Parameter(Mandatory = $false)]
        [switch]$SkipClassificationExplorer,

        [Parameter(Mandatory = $false)]
        [switch]$SkipExplorerHistory,

        [Parameter(Mandatory = $false)]
        [switch]$SkipTierBreachAnalyzer,

        [Parameter(Mandatory = $false)]
        [switch]$SkipEamDashboard,

        [Parameter(Mandatory = $false)]
        [switch]$SkipAccessPathMap,

        [Parameter(Mandatory = $false)]
        [switch]$SkipPrivilegeHistory,

        [Parameter(Mandatory = $false)]
        [System.String]$ConfigurationAnalyzerImportPath,

        [Parameter(Mandatory = $false)]
        [System.String]$ConfigurationAnalyzerAppRoot,

        [Parameter(Mandatory = $false)]
        [System.Nullable[int]]$ConfigurationAnalyzerTimeRangeInDays,

        [Parameter(Mandatory = $false)]
        [System.String]$ConfigurationAnalyzerSnapshotInterval,

        [Parameter(Mandatory = $false)]
        [System.Nullable[int]]$ConfigurationAnalyzerMaxSnapshotAgeHours,

        [Parameter(Mandatory = $false)]
        [switch]$SkipConfigurationAnalyzer,

        [Parameter(Mandatory = $false)]
        [switch]$SkipAccessPackageFlow,

        [Parameter(Mandatory = $false)]
        [switch]$PassThru,

        [Parameter(Mandatory = $false)]
        [ValidateSet('Continue', 'Stop')]
        [string]$FailureAction = 'Continue',

        [Parameter(Mandatory = $false)]
        [switch]$AllowStaleTenantGovernanceSnapshot,

        [Parameter(Mandatory = $false)]
        [switch]$AllowPartialTenantGovernanceSnapshot,

        [Parameter(Mandatory = $false)]
        [switch]$InstallMissingReportingFolder
    )

    # Resolve the repository root relative to the module location. Prefer the module's
    # own ModuleBase (always populated for exported module functions) over $PSScriptRoot,
    # which can be empty depending on how the function was invoked/loaded.
    $ModuleRoot = $MyInvocation.MyCommand.Module.ModuleBase
    if ([string]::IsNullOrWhiteSpace($ModuleRoot) -and -not [string]::IsNullOrWhiteSpace($PSScriptRoot)) {
        $ModuleRoot = Split-Path -Parent (Split-Path -Parent $PSScriptRoot)
    }
    if ([string]::IsNullOrWhiteSpace($ModuleRoot)) {
        throw "Unable to resolve the EntraOps module location. Import the module with 'Import-Module <path-to-EntraOps> -Force' and try again."
    }
    $RepositoryRoot = if (-not [string]::IsNullOrWhiteSpace($Global:EntraOpsBaseFolder)) { $Global:EntraOpsBaseFolder } else { Split-Path -Parent $ModuleRoot }
    if ([string]::IsNullOrWhiteSpace($EntraOpsRoot)) { $EntraOpsRoot = $RepositoryRoot }

    Write-Verbose "Using EntraOps repository root: $EntraOpsRoot"

    # Privilege History defaults come from EntraOpsConfig.json ("PrivilegeHistory": { "EnablePrivilegeHistory":
    # true|false, "TimeRangeInDays": <int>|null }) so it can be toggled without changing
    # calling scripts/workflows. Enabled by default when the config file/section is absent;
    # -SkipPrivilegeHistory always wins over the config setting.
    $EnablePrivilegeHistory = $true
    $ConfigPrivilegeHistoryTimeRangeInDays = $null
    $ConfigPrivilegeHistorySnapshotInterval = $null
    if ([string]::IsNullOrWhiteSpace($ConfigFilePath)) {
        $ConfigFilePath = Join-Path $EntraOpsRoot 'EntraOpsConfig.json'
    } else {
        $ConfigFilePath = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($ConfigFilePath)
    }
    # Assigned explicitly so a missing or unreadable config never resolves a caller's $EntraOpsConfig through dynamic scoping.
    $EntraOpsConfig = $null
    if (Test-Path -LiteralPath $ConfigFilePath -PathType Leaf) {
        try {
            $EntraOpsConfig = Get-Content -LiteralPath $ConfigFilePath -Raw | ConvertFrom-Json
            if ($null -ne $EntraOpsConfig.PrivilegeHistory) {
                if ($null -ne $EntraOpsConfig.PrivilegeHistory.EnablePrivilegeHistory) {
                    $EnablePrivilegeHistory = [bool]$EntraOpsConfig.PrivilegeHistory.EnablePrivilegeHistory
                }
                if ($null -ne $EntraOpsConfig.PrivilegeHistory.TimeRangeInDays) {
                    $ConfigPrivilegeHistoryTimeRangeInDays = [int]$EntraOpsConfig.PrivilegeHistory.TimeRangeInDays
                }
                if (-not [string]::IsNullOrWhiteSpace($EntraOpsConfig.PrivilegeHistory.SnapshotInterval)) {
                    $ConfigPrivilegeHistorySnapshotInterval = [string]$EntraOpsConfig.PrivilegeHistory.SnapshotInterval
                }
            }
        } catch {
            Write-Warning "Failed to read ${ConfigFilePath}: $($_.Exception.Message). Using defaults for PrivilegeHistory (enabled, full history, bi-weekly) and ConfigurationAnalyzer (365 days, weekly, 30-hour snapshot age limit)."
        }
    }
    if (-not $PSBoundParameters.ContainsKey('PrivilegeHistoryTimeRangeInDays')) {
        $PrivilegeHistoryTimeRangeInDays = $ConfigPrivilegeHistoryTimeRangeInDays
    }
    if (-not $PSBoundParameters.ContainsKey('PrivilegeHistorySnapshotInterval')) {
        $PrivilegeHistorySnapshotInterval = $ConfigPrivilegeHistorySnapshotInterval
    }

    # Configuration Analyzer history is bounded by default (365 days, weekly) because every snapshot adds
    # its full resource index to the dataset. An explicit null/'None' in the config keeps the full history.
    # Resolved and validated only when the Configuration Analyzer is generated, so an invalid setting never
    # blocks the other reports, and checked before any report so it never fails the run late.
    if (-not $SkipConfigurationAnalyzer) {
        $ConfigurationAnalyzerConfig = if ($null -ne $EntraOpsConfig) { $EntraOpsConfig.ConfigurationAnalyzer } else { $null }
        if (-not $PSBoundParameters.ContainsKey('ConfigurationAnalyzerTimeRangeInDays')) {
            $ConfigurationAnalyzerTimeRangeInDays = 365
            if ($null -ne $ConfigurationAnalyzerConfig -and $null -ne $ConfigurationAnalyzerConfig.PSObject.Properties['TimeRangeInDays']) {
                $ConfiguredTimeRange = $ConfigurationAnalyzerConfig.TimeRangeInDays
                if ($null -eq $ConfiguredTimeRange -or [string]::IsNullOrWhiteSpace("$ConfiguredTimeRange")) {
                    $ConfigurationAnalyzerTimeRangeInDays = $null
                } else {
                    $ParsedTimeRange = 0
                    if (-not [int]::TryParse("$ConfiguredTimeRange", [ref]$ParsedTimeRange) -or $ParsedTimeRange -lt 1) {
                        throw "ConfigurationAnalyzer.TimeRangeInDays in $ConfigFilePath must be a whole number of at least 1 (or null for the full history), but is '$ConfiguredTimeRange'."
                    }
                    $ConfigurationAnalyzerTimeRangeInDays = $ParsedTimeRange
                }
            }
        }
        if (-not $PSBoundParameters.ContainsKey('ConfigurationAnalyzerSnapshotInterval')) {
            $ConfigurationAnalyzerSnapshotInterval = 'P1W'
            if ($null -ne $ConfigurationAnalyzerConfig -and $null -ne $ConfigurationAnalyzerConfig.PSObject.Properties['SnapshotInterval']) {
                $ConfigurationAnalyzerSnapshotInterval = if ([string]::IsNullOrWhiteSpace("$($ConfigurationAnalyzerConfig.SnapshotInterval)")) { 'None' } else { [string]$ConfigurationAnalyzerConfig.SnapshotInterval }
            }
        }
        # Same accepted values as New-EntraOpsTenantGovernanceConfigurationAnalyzerData -SnapshotInterval.
        if (-not [string]::IsNullOrWhiteSpace($ConfigurationAnalyzerSnapshotInterval) -and
            $ConfigurationAnalyzerSnapshotInterval -notin @('P0D', 'PT0S', 'None', 'none') -and
            $ConfigurationAnalyzerSnapshotInterval -notmatch '^P(?:\d+Y)?(?:\d+M)?(?:\d+W)?(?:\d+D)?$') {
            throw "ConfigurationAnalyzer.SnapshotInterval must be an ISO 8601 duration such as 'P1D', 'P1W' or 'P1M' (or 'None' to keep every snapshot), but is '$ConfigurationAnalyzerSnapshotInterval'."
        }
        # Validated here instead of [ValidateRange] because that attribute rejects an explicit $null (derive from schedule).
        if ($null -ne $ConfigurationAnalyzerMaxSnapshotAgeHours -and ($ConfigurationAnalyzerMaxSnapshotAgeHours -lt 1 -or $ConfigurationAnalyzerMaxSnapshotAgeHours -gt 8760)) {
            throw "ConfigurationAnalyzerMaxSnapshotAgeHours must be between 1 and 8760 (or null to derive it from the snapshot schedule), but is $ConfigurationAnalyzerMaxSnapshotAgeHours."
        }
        if ($null -eq $ConfigurationAnalyzerMaxSnapshotAgeHours) {
            $ConfigurationAnalyzerMaxSnapshotAgeHours = Resolve-EntraOpsTenantGovernanceSnapshotMaxAge -Config $EntraOpsConfig
        }
    }

    # Module-only installations (e.g. PowerShell Gallery) ship neither the reporting apps nor the classification templates.
    # Same root the generators write to (RepoRoot = $EntraOpsRoot), so an explicit -EntraOpsRoot is checked and installed there.
    $ReportingFolder = Join-Path $EntraOpsRoot 'Reports'
    $ClassificationTemplatesFolder = Join-Path $EntraOpsRoot 'Classification/Templates'
    $HasCustomAppRoot = @($PSBoundParameters.Keys | Where-Object { $_ -like '*AppRoot' }).Count -gt 0
    # Check each selected app, even when Reports/index.html exists or another app uses a custom path.
    $TgSnapshotFolder = if (-not [string]::IsNullOrWhiteSpace($ConfigurationAnalyzerImportPath)) { $ConfigurationAnalyzerImportPath } else { Join-Path $EntraOpsRoot 'TenantGovernance/Snapshots' }
    $HasTenantGovernance = (Test-Path -LiteralPath $TgSnapshotFolder -PathType Container) -or $EntraOpsConfig.TenantGovernanceSnapshot.EnableTenantGovernanceSnapshot -eq $true
    $ReportApps = @(
        @{ Name = 'TierBreachAnalyzer'; Root = $TierBreachAnalyzerAppRoot; Skip = $SkipTierBreachAnalyzer }
        @{ Name = 'EamDashboard'; Root = $EamDashboardAppRoot; Skip = $SkipEamDashboard }
        @{ Name = 'PrivilegedAssets'; Root = $PrivilegedAssetsAppRoot; Skip = $SkipPrivilegedAssets }
        @{ Name = 'AccessPathMap'; Root = $AccessPathMapAppRoot; Skip = $SkipAccessPathMap }
        @{ Name = 'ClassificationExplorer'; Root = $ClassificationExplorerAppRoot; Skip = $SkipClassificationExplorer }
        @{ Name = 'PrivilegeHistory'; Root = $PrivilegeHistoryAppRoot; Skip = ($SkipPrivilegeHistory -or -not $EnablePrivilegeHistory) }
        @{ Name = 'ConfigurationAnalyzer'; Root = $ConfigurationAnalyzerAppRoot; Skip = ($SkipConfigurationAnalyzer -or -not $HasTenantGovernance) }
        @{ Name = 'AccessPackageFlow'; Root = $(Join-Path $EntraOpsRoot 'Reports/AccessPackageFlow'); Skip = ($SkipAccessPackageFlow -or -not $HasTenantGovernance) }
    )
    $MissingAppPaths = @(
        foreach ($App in $ReportApps) {
            if ($App.Skip) { continue }
            $AppPath = if ([string]::IsNullOrWhiteSpace($App.Root)) { Join-Path $ReportingFolder $App.Name } else { $App.Root }
            if (-not (Test-Path -LiteralPath (Join-Path $AppPath 'index.html') -PathType Leaf)) { $AppPath }
        }
    )
    $MissingReports = $MissingAppPaths.Count -gt 0 -or (-not $HasCustomAppRoot -and -not (Test-Path -LiteralPath (Join-Path $ReportingFolder 'index.html') -PathType Leaf))
    $MissingTemplates = -not $SkipClassificationExplorer -and -not (Test-Path -LiteralPath $ClassificationTemplatesFolder -PathType Container)

    if ($MissingReports -or $MissingTemplates) {
        $MissingItems = @(
            if ($MissingReports) { "reporting apps in '$ReportingFolder'$(if ($MissingAppPaths.Count) { " (missing: $($MissingAppPaths -join ', '))" })" }
            if ($MissingTemplates) { "classification templates folder '$ClassificationTemplatesFolder'" }
        ) -join ' and '
        $ModuleVersion = $MyInvocation.MyCommand.Module.Version
        $MissingContentMessage = "Missing EntraOps content: $MissingItems. Run Initialize-EntraOpsWorkspace -Ref <release tag of EntraOps $ModuleVersion> to install the content that matches your module$(if ($MissingTemplates -and -not $MissingReports) { ', or run Invoke-EntraOpsPrivilegedEAM -KeepClassificationFiles to keep the classification files used for the export' }). To repair an existing Reports folder, run Initialize-EntraOpsWorkspace -Content Reports -Force or Install-EntraOpsReportingFolder -Force. These installers preserve generated report data. -InstallMissingReportingFolder downloads missing content from the main branch instead."

        if ($InstallMissingReportingFolder) {
            if ($MissingReports) {
                Install-EntraOpsReportingFolder -DestinationPath $ReportingFolder -Force:(Test-Path -LiteralPath $ReportingFolder) | Out-Null
                if (-not $WhatIfPreference) {
                    $StillMissing = @($MissingAppPaths | Where-Object { -not (Test-Path -LiteralPath (Join-Path $_ 'index.html') -PathType Leaf) })
                    if ($StillMissing.Count) {
                        throw "Reporting apps are still missing after installation: $($StillMissing -join ', '). Check custom AppRoot paths or install a repository/ref that contains these apps."
                    }
                }
            }
            if ($MissingTemplates) {
                Initialize-EntraOpsWorkspace -Path $EntraOpsRoot -Content Classification -KeepWorkingFolder | Out-Null
            }
        } elseif ($WhatIfPreference) {
            Write-Warning $MissingContentMessage
            return
        } elseif ($MissingReports -or $FailureAction -eq 'Stop') {
            throw $MissingContentMessage
        } else {
            Write-Warning "$MissingContentMessage Skipping Classification Explorer data."
            $SkipClassificationExplorer = [switch]$true
        }
    }

    $results = [System.Collections.Generic.List[object]]::new()

    Write-Verbose "Starting reporting data generation. Skipped apps: TierBreachAnalyzer=$SkipTierBreachAnalyzer; EamDashboard=$SkipEamDashboard; PrivilegedAssets=$SkipPrivilegedAssets; PrivilegeHistory=$SkipPrivilegeHistory; ConfigurationAnalyzer=$SkipConfigurationAnalyzer; AccessPackageFlow=$SkipAccessPackageFlow; AccessPathMap=$SkipAccessPathMap; ClassificationExplorer=$SkipClassificationExplorer."

    if (-not $SkipTierBreachAnalyzer) {
        $TierBreachParams = @{
            RepoRoot = $EntraOpsRoot
            Verbose  = $VerbosePreference -eq 'Continue'
            WhatIf   = $WhatIfPreference
            Confirm  = $false
        }
        if (-not [string]::IsNullOrWhiteSpace($TierBreachImportPath)) { $TierBreachParams.ImportPath = $TierBreachImportPath }
        if (-not [string]::IsNullOrWhiteSpace($TierBreachAnalyzerAppRoot)) { $TierBreachParams.AppRoot = $TierBreachAnalyzerAppRoot }
        if ($PassThru) { $TierBreachParams.PassThru = $true }

        Write-Verbose "Generating Tier Breach Analyzer data..."
        $TierBreachResult = New-EntraOpsTierBreachAnalyzerData @TierBreachParams
        $results.Add([pscustomobject]@{ App = 'TierBreachAnalyzer'; Result = $TierBreachResult })
        Write-Verbose "Completed Tier Breach Analyzer data generation."
    }

    if (-not $SkipEamDashboard) {
        $EamDashboardParams = @{
            RepoRoot = $EntraOpsRoot
            Verbose  = $VerbosePreference -eq 'Continue'
            WhatIf   = $WhatIfPreference
            Confirm  = $false
        }
        if (-not [string]::IsNullOrWhiteSpace($EamDashboardImportPath)) { $EamDashboardParams.ImportPath = $EamDashboardImportPath }
        if (-not [string]::IsNullOrWhiteSpace($EamDashboardAppRoot)) { $EamDashboardParams.AppRoot = $EamDashboardAppRoot }
        if ($null -ne $EamDashboardResolveLinkedIdentityObjectIds) { $EamDashboardParams.ResolveLinkedIdentityObjectIds = $EamDashboardResolveLinkedIdentityObjectIds }
        if ($PassThru) { $EamDashboardParams.PassThru = $true }

        Write-Verbose "Generating EAM Dashboard data..."
        $EamDashboardResult = New-EntraOpsPrivilegedEamDashboardData @EamDashboardParams
        $results.Add([pscustomobject]@{ App = 'EamDashboard'; Result = $EamDashboardResult })
        Write-Verbose "Completed EAM Dashboard data generation."
    }

    if (-not $SkipPrivilegedAssets) {
        $PrivilegedAssetsParams = @{
            RepoRoot       = $EntraOpsRoot
            ConfigFilePath = $ConfigFilePath
            Verbose        = $VerbosePreference -eq 'Continue'
            WhatIf         = $WhatIfPreference
            Confirm        = $false
        }
        if (-not [string]::IsNullOrWhiteSpace($EamDashboardImportPath)) { $PrivilegedAssetsParams.ImportPath = $EamDashboardImportPath }
        if (-not [string]::IsNullOrWhiteSpace($PrivilegedAssetsAppRoot)) { $PrivilegedAssetsParams.AppRoot = $PrivilegedAssetsAppRoot }
        if ($null -ne $PrivilegedAssetsResolveRelatedObjectIds) { $PrivilegedAssetsParams.ResolveRelatedObjectIds = $PrivilegedAssetsResolveRelatedObjectIds }
        if ($PSBoundParameters.ContainsKey('PrivilegedAssetsMaesterTestResultPath')) { $PrivilegedAssetsParams.MaesterTestResultPath = $PrivilegedAssetsMaesterTestResultPath }
        if ($PassThru) { $PrivilegedAssetsParams.PassThru = $true }

        Write-Verbose "Generating Privileged Assets data..."
        $PrivilegedAssetsResult = New-EntraOpsPrivilegedAssetsData @PrivilegedAssetsParams
        $results.Add([pscustomobject]@{ App = 'PrivilegedAssets'; Result = $PrivilegedAssetsResult })
        Write-Verbose "Completed Privileged Assets data generation."
    }

    if (-not $SkipPrivilegeHistory -and $EnablePrivilegeHistory) {
        $PrivilegeHistoryParams = @{
            RepoRoot = $EntraOpsRoot
            Verbose  = $VerbosePreference -eq 'Continue'
            WhatIf   = $WhatIfPreference
            Confirm  = $false
        }
        if (-not [string]::IsNullOrWhiteSpace($PrivilegeHistoryImportPath)) { $PrivilegeHistoryParams.ImportPath = $PrivilegeHistoryImportPath }
        if (-not [string]::IsNullOrWhiteSpace($PrivilegeHistoryAppRoot)) { $PrivilegeHistoryParams.AppRoot = $PrivilegeHistoryAppRoot }
        if ($null -ne $PrivilegeHistoryTimeRangeInDays) { $PrivilegeHistoryParams.TimeRangeInDays = $PrivilegeHistoryTimeRangeInDays }
        if (-not [string]::IsNullOrWhiteSpace($PrivilegeHistorySnapshotInterval)) { $PrivilegeHistoryParams.SnapshotInterval = $PrivilegeHistorySnapshotInterval }
        if ($PassThru) { $PrivilegeHistoryParams.PassThru = $true }

        Write-Verbose "Generating Privilege History data..."
        try {
            $PrivilegeHistoryResult = New-EntraOpsPrivilegedEamPrivilegeHistoryData @PrivilegeHistoryParams
            $results.Add([pscustomobject]@{ App = 'PrivilegeHistory'; Result = $PrivilegeHistoryResult })
        } catch {
            if ($FailureAction -eq 'Stop') { throw }
            Write-Warning "Skipped Privilege History data: $($_.Exception.Message)"
        }
    } elseif (-not $SkipPrivilegeHistory -and -not $EnablePrivilegeHistory) {
        Write-Verbose "Skipping Privilege History data (PrivilegeHistory.EnablePrivilegeHistory is false in $ConfigFilePath)."
    }

    # Only generate the snapshot-based apps (Configuration Analyzer, Access Package Flow) when
    # the Tenant Governance Snapshot feature is in use: the snapshot folder exists (working
    # tree) or the feature is enabled in EntraOpsConfig.json. Resolved outside the skip blocks
    # because both apps share these checks (for example -SkipConfigurationAnalyzer alone must
    # not disable the Access Package Flow enrichment).
    $TgSnapshotFolder = if (-not [string]::IsNullOrWhiteSpace($ConfigurationAnalyzerImportPath)) { $ConfigurationAnalyzerImportPath } else { Join-Path $EntraOpsRoot 'TenantGovernance/Snapshots' }
    $TgEnabledInConfig = $false
    try {
        if ($null -ne $EntraOpsConfig.TenantGovernanceSnapshot.EnableTenantGovernanceSnapshot) {
            $TgEnabledInConfig = [bool]$EntraOpsConfig.TenantGovernanceSnapshot.EnableTenantGovernanceSnapshot
        }
    } catch { $TgEnabledInConfig = $false }

    if (-not $SkipConfigurationAnalyzer) {
        if ((Test-Path -LiteralPath $TgSnapshotFolder -PathType Container) -or $TgEnabledInConfig) {
            $ConfigurationAnalyzerParams = @{
                RepoRoot = $EntraOpsRoot
                Verbose  = $VerbosePreference -eq 'Continue'
                WhatIf   = $WhatIfPreference
                Confirm  = $false
            }
            if (-not [string]::IsNullOrWhiteSpace($ConfigurationAnalyzerImportPath)) { $ConfigurationAnalyzerParams.ImportPath = $ConfigurationAnalyzerImportPath }
            if (-not [string]::IsNullOrWhiteSpace($ConfigurationAnalyzerAppRoot)) { $ConfigurationAnalyzerParams.AppRoot = $ConfigurationAnalyzerAppRoot }
            if ($AllowStaleTenantGovernanceSnapshot) { $ConfigurationAnalyzerParams.AllowStaleSnapshot = $true }
            if ($AllowPartialTenantGovernanceSnapshot) { $ConfigurationAnalyzerParams.AllowPartialSnapshot = $true }
            if ($null -ne $ConfigurationAnalyzerTimeRangeInDays) { $ConfigurationAnalyzerParams.TimeRangeInDays = $ConfigurationAnalyzerTimeRangeInDays }
            if (-not [string]::IsNullOrWhiteSpace($ConfigurationAnalyzerSnapshotInterval)) { $ConfigurationAnalyzerParams.SnapshotInterval = $ConfigurationAnalyzerSnapshotInterval }
            if ($null -ne $ConfigurationAnalyzerMaxSnapshotAgeHours) { $ConfigurationAnalyzerParams.MaxSnapshotAgeHours = $ConfigurationAnalyzerMaxSnapshotAgeHours }
            if ($PassThru) { $ConfigurationAnalyzerParams.PassThru = $true }

            Write-Verbose "Generating Configuration Analyzer data..."
            try {
                $ConfigurationAnalyzerResult = New-EntraOpsTenantGovernanceConfigurationAnalyzerData @ConfigurationAnalyzerParams
                $results.Add([pscustomobject]@{ App = 'ConfigurationAnalyzer'; Result = $ConfigurationAnalyzerResult })
            } catch {
                if ($FailureAction -eq 'Stop') { throw }
                Write-Warning "Skipped Configuration Analyzer data: $($_.Exception.Message)"
            }
        } else {
            Write-Verbose "Skipping Configuration Analyzer data (no Tenant Governance snapshot folder at $TgSnapshotFolder and TenantGovernanceSnapshot.EnableTenantGovernanceSnapshot is not enabled)."
        }
    }

    if (-not $SkipAccessPathMap) {
        $AccessPathParams = @{
            RepoRoot = $EntraOpsRoot
            Verbose  = $VerbosePreference -eq 'Continue'
            WhatIf   = $WhatIfPreference
            Confirm  = $false
        }
        if (-not [string]::IsNullOrWhiteSpace($AccessPathMapTenantId)) { $AccessPathParams.TenantId = $AccessPathMapTenantId }
        if (-not [string]::IsNullOrWhiteSpace($AccessPathMapImportPath)) { $AccessPathParams.ImportPath = $AccessPathMapImportPath }
        if (-not [string]::IsNullOrWhiteSpace($AccessPathMapAppRoot)) { $AccessPathParams.AppRoot = $AccessPathMapAppRoot }
        if ($null -ne $AccessPathMapResolveObjectIdsOutsidePrivilegedEAM) { $AccessPathParams.ResolveObjectIdsOutsidePrivilegedEAM = $AccessPathMapResolveObjectIdsOutsidePrivilegedEAM }
        if ($PassThru) { $AccessPathParams.PassThru = $true }

        Write-Verbose "Generating Access Path Map data..."
        $AccessPathResult = New-EntraOpsAccessPathMapData @AccessPathParams
        $results.Add([pscustomobject]@{ App = 'AccessPathMap'; Result = $AccessPathResult })
    }

    if (-not $SkipAccessPackageFlow -and ((Test-Path -LiteralPath $TgSnapshotFolder -PathType Container) -or $TgEnabledInConfig)) {
        $AccessPackageFlowParams = @{
            RepoRoot = $EntraOpsRoot
            Verbose  = $VerbosePreference -eq 'Continue'
            WhatIf   = $WhatIfPreference
            Confirm  = $false
        }
        if (-not [string]::IsNullOrWhiteSpace($ConfigurationAnalyzerImportPath)) { $AccessPackageFlowParams.SnapshotPath = $ConfigurationAnalyzerImportPath }
        if ($PassThru) { $AccessPackageFlowParams.PassThru = $true }

        Write-Verbose "Generating Access Package Flow enrichment data..."
        try {
            $AccessPackageFlowResult = New-EntraOpsAccessPackageFlowData @AccessPackageFlowParams
            $results.Add([pscustomobject]@{ App = 'AccessPackageFlow'; Result = $AccessPackageFlowResult })
        } catch {
            if ($FailureAction -eq 'Stop') { throw }
            Write-Warning "Skipped Access Package Flow enrichment: $($_.Exception.Message)"
        }
    }

    if (-not $SkipClassificationExplorer) {
        $ClassificationExplorerParams = @{
            EntraOpsRoot = $EntraOpsRoot
            Verbose      = $VerbosePreference -eq 'Continue'
            WhatIf       = $WhatIfPreference
            Confirm      = $false
        }
        if (-not [string]::IsNullOrWhiteSpace($ClassificationExplorerRepoRoot)) { $ClassificationExplorerParams.RepoRoot = $ClassificationExplorerRepoRoot }
        if (-not [string]::IsNullOrWhiteSpace($ClassificationExplorerAppRoot)) { $ClassificationExplorerParams.AppRoot = $ClassificationExplorerAppRoot }
        if ($PSBoundParameters.ContainsKey('SkipExplorerHistory')) { $ClassificationExplorerParams.SkipHistory = $SkipExplorerHistory }
        if ($PassThru) { $ClassificationExplorerParams.PassThru = $true }

        Write-Verbose "Generating Classification Explorer data..."
        $ClassificationExplorerResult = New-EntraOpsClassificationExplorerData @ClassificationExplorerParams
        $results.Add([pscustomobject]@{ App = 'ClassificationExplorer'; Result = $ClassificationExplorerResult })
    }

    if ($results.Count -eq 0) {
        Write-Host "No EntraOps Reporting data was generated (all apps skipped)."
    } else {
        Write-Host "Generated EntraOps Reporting data for: $(($results.App) -join ', ')."
        $ReportingPortal = Join-Path $EntraOpsRoot 'Reports/index.html'
        if (Test-Path -LiteralPath $ReportingPortal -PathType Leaf) { Write-Host "Open the reporting apps: $ReportingPortal" }
    }

    if ($PassThru) { $results }
}

# SIG # Begin signature block
# MIIniAYJKoZIhvcNAQcCoIIneTCCJ3UCAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCCKe+ctwEHSXgUw
# pB5oaIpF7tU3tmiA/99sa19Mqjuf8KCCIDwwggXJMIIEsaADAgECAhAbtY8lKt8j
# 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
# ARUwLwYJKoZIhvcNAQkEMSIEIGWLnJ7bSMvJQZF+5CnpYJR5Hlixb7Fk3dzqFB7J
# JKKjMA0GCSqGSIb3DQEBAQUABIIBgCszz21eyd2QCgM6cjlO3msBr7BBkhvKrx3n
# NjQ3estEJduDov9wMC9FfNXJamXEVzKDMXej7BYatT3vYJ7utXxrCZComPdu5VGB
# 9wfrPGZtPvz3MrP7Ccl3nxJG1Arg9HvkPZmooXmhoV/dYHUUb47wp366r+y/908N
# jvVthLqWKpjS+l0ij/Q3kgkQwaWNTKnlXjJRsu3eKCh/XuE6wLVvdwaQGOeSU3pc
# +V4pQ66e7HJY6CY+7fIdUkbccB+T52tpr2MHBQvq/CYPvUL7oNV+RE7z46NtJhb+
# 6qQFihptL1BBBwG2ZdCi/qLu2KpxTNzlzt3u4bCBSt29R3U7x0nqJKAeIJYMfMwt
# 8MKrVvNuUVqK6EA/J0tvxwaysJ6uad4JaWte7lcXzcD3vkJizRueEJLRIbUBi+vd
# D9YoThx3BxQfCFSi6t6PYIn7vfBomppKaUtE9b3/0w5eACGFp8kGsaTmjkdSm5bQ
# bjg9xBw9xEAm9r2lkD4MtyI8DdI3L6GCBAIwggP+BgkqhkiG9w0BCQYxggPvMIID
# 6wIBATBqMFYxCzAJBgNVBAYTAlBMMSEwHwYDVQQKExhBc3NlY28gRGF0YSBTeXN0
# ZW1zIFMuQS4xJDAiBgNVBAMTG0NlcnR1bSBUaW1lc3RhbXBpbmcgMjAyMSBDQQIQ
# KPB3wRw2vf5fdDJHcCcuAzANBglghkgBZQMEAgIFAKCCAVYwGgYJKoZIhvcNAQkD
# MQ0GCyqGSIb3DQEJEAEEMBwGCSqGSIb3DQEJBTEPFw0yNjEwMjIxMTQwMjNaMDcG
# CyqGSIb3DQEJEAIvMSgwJjAkMCIEIIW+kOEK0kONfMkotq9IsJqyCBd87PiwEmxY
# 05EFJcQ8MD8GCSqGSIb3DQEJBDEyBDCWcUtLBc185NsJdRuAaS8pBe1UZLzWVwIi
# +cy0Ongsi/xeUXoyWm+Bn3/R0WDzgj0wgZ8GCyqGSIb3DQEJEAIMMYGPMIGMMIGJ
# MIGGBBRXFGhBDKha80JO+RZKUTYQ9NONmDBuMFqkWDBWMQswCQYDVQQGEwJQTDEh
# MB8GA1UEChMYQXNzZWNvIERhdGEgU3lzdGVtcyBTLkEuMSQwIgYDVQQDExtDZXJ0
# dW0gVGltZXN0YW1waW5nIDIwMjEgQ0ECECjwd8EcNr3+X3QyR3AnLgMwDQYJKoZI
# hvcNAQEBBQAEggIAiVV87k5zUKzeNCGp5iPw30+Tj1Yn7mWfDx1EYYdoIKrAbPhw
# Izjrp0wKHoSyeooPw/xSczPanq6Gx0JDjEwvrgNI1IZ5JiDJosXkIpBLGwoPnj9J
# PwShZDmNmrZGFeW+A/ZSp0sBNGoGNBDXBflN4ESItlo0X0LZcvypV0k+zwBU3nDp
# jztDkrQaIwaHT4r1N0+cAibZdevCrSv/14sfnnDqO+AC8yI301EoXMBjzXPVgZF7
# q8SI7jtTE9HJQUuS79yP8dCii9sgU7DAGQSLnyG4FH9O+7nGufJpKiOVUb4o4iyr
# IbVjWpZIzNFg7ApfRfGARk5M3qy8i3ZPu1uPo7y3MAqr10MnhySucZ68S5VwK6FO
# Oi72cy9VDT1YIjaFUx+13Unm0lQV0pZ+03HJCMznk7dwX5d9QgRwnw6rel6bl/fH
# SH+mAWlXXPkP/NevjR+ZBz+uc+us1FBGKDVkm2c9Zq4e38kNhVlSCN8ofk8qB7od
# OOzOZFNNojQ8oo6waDO/YdEo5T00rvfl2UPkqKZmu5kKS5f7tG3zE1DnLseecppB
# IyaZKB+/seBozu0rHrWLFT6AQjpWH906Mu0aQX6fskd6vprAKiwNzmzHPxIjxOii
# UVDJHgnE1/zaKKSpDRoW0wDyVmHCSbGBGvVJ0K/Pl+HNmsv/rcebl1H1BKQ=
# SIG # End signature block