Public/Configuration/New-EntraOpsConfigFile.ps1

<#
.SYNOPSIS
    Create config file to store required or optional parameters for executing EntraOps without end-user prompts.

.DESCRIPTION
    Creates a configuration file used by other cmdlets, for example to create the required app registration and grant permissions.
    Connect-EntraOps can also import the file so the same settings are available to interactive and automated runs.

.PARAMETER AuthenticationType
    Defines the authentication method used to request tokens for Microsoft Graph, Azure Resource Manager, and other APIs. Default is FederatedCredentials.

.PARAMETER UseInvokeRestMethodOnly
    Defines if all Invoke-EntraOps*Query cmdlets should use Invoke-RestMethod instead of the Microsoft Graph SDK (Invoke-MgGraphRequest). Default is false.
    In this mode the Microsoft Graph SDK is neither required nor installed; tokens are provided via Connect-EntraOps -MsGraphAccessToken or acquired with Get-AzAccessToken from the Az PowerShell context.
    Written as top-level setting "UseInvokeRestMethodOnly" to the config file and read by Connect-EntraOps; an explicit Connect-EntraOps -UseInvokeRestMethodOnly parameter wins over the config file.
    Note: REST-only parallel object resolution pre-warms a Graph token from the Az PowerShell context. If that token cannot be prepared, resolution falls back to sequential processing.

.PARAMETER IncludeObjectDetails
    Include display names, UPNs, and detailed object-specific errors in console output. Object IDs are always
    shown. Written as ConsoleOutput.IncludeObjectDetails and disabled by default for automation-log privacy.

.PARAMETER DevOpsPlatform
    Defines the platform where the EntraOps repository is hosted. Default and support is currently limited to GitHub.

.PARAMETER ConfigFilePath
    Location of the config file which will be created. Default is ./EntraOpsConfig.json.

.PARAMETER IngestToLogAnalytics
    Defines if the classification data should be ingested to Log Analytics or Microsoft Sentinel Workspace. Default is false.

.PARAMETER IngestToWatchLists
    Defines if the classification data should be ingested to Microsoft Sentinel WatchLists. Default is false.

.PARAMETER ApplyAutomatedControlPlaneScopeUpdate
    Defines whether role-assignment scopes containing Control Plane assets should be updated before privileges are analyzed. Default is false.
    You are able to customize in the config file the data source ("EntraOps", "PrivilegedRolesFromAzGraph", "PrivilegedEdgesFromExposureManagement") and which criticality level or role definition/scope level should be considered.

.PARAMETER ClassificationParameterScope
    Array of RBAC systems whose classification parameter files are (re)generated by Update-EntraOpsClassificationControlPlaneScope, written to the config file as AutomatedControlPlaneScopeUpdate.ClassificationParameterScope. Default is all supported RBAC systems ("Azure", "EntraID", "IdentityGovernance", "ResourceApps", "DeviceManagement", "Defender").
    This is distinct from EntraOpsScopes (RbacSystems parameter/AutomatedControlPlaneScopeUpdate.EntraOpsScopes in the config file), which selects which RBAC systems' already-classified EAM export data is read as input to discover privileged objects. ClassificationParameterScope instead selects which RBAC systems' classification template/parameter files are written as output during that same run.

.PARAMETER ApplyAutomatedClassificationUpdate
    Defines whether classification templates should be updated from "https://github.com/Cloud-Architekt/AzurePrivilegedIAM" before privileges are analyzed. Default is false.
    You are able to define which classification files should be updated in the config file later.

.PARAMETER ApplyAutomatedEntraOpsUpdate
    Defines whether the Update-EntraOps workflow and its weekly schedule are enabled. Default is false.
    Workflow definitions are excluded from the default automated target set because GITHUB_TOKEN cannot
    publish changes below .github/workflows. Update them interactively with Update-EntraOps, or explicitly
    add the workflow target after configuring the documented EntraOps update publisher GitHub App.
    When enabled with the default PullRequest publication mode, the repository setting
    "Allow GitHub Actions to create and approve pull requests" (Settings > Actions > General >
    Workflow permissions) must be turned on, otherwise pull request creation fails.

.PARAMETER UpdateRepository
    Repository name below the Cloud-Architekt organization that serves EntraOps updates. Default is
    "EntraOps" (public release channel, no credentials). "EntraOps-Insiders" is the private preview
    channel and requires the EntraOpsUpdatePat repository secret. Both are declared in the local
    EntraOpsUpdateContract.json; another repository is accepted only when it publishes a matching contract.

.PARAMETER ValidationFrequency
    Controls candidate validation in the Update-EntraOps workflow. OnChange (default) validates new
    source commits and validation-setting changes, Always validates every workflow run, and Never
    applies changed candidates without executing their validation suite.

.PARAMETER RunBrowserTests
    Defines whether update-candidate validation includes the Playwright documentation and report tests.
    Default is true. This setting has no effect when ValidationFrequency is Never.

.PARAMETER UpdatePublicationMode
    Controls how the Update-EntraOps workflow publishes an applied candidate. PullRequest (default)
    creates or updates a review branch and pull request; DirectPush commits to the current branch.

.PARAMETER ApplyPrivilegedElmCatalogProtection
    Defines if Entitlement Management catalogs with Control Plane scope should be updated to privileged catalogs (Update-EntraOpsPrivilegedUnprotectedElmCatalog) as part of the push pipeline. Default is false.
    Caution: Enabling this setting requires the elevated Microsoft Graph permission "EntitlementManagement.ReadWrite.All" which will be assigned by New-EntraOpsWorkloadIdentity only if this setting is enabled.

.PARAMETER IncludeUnprotectedDevices
    Defines if devices owned by or associated with privileged users should be added to the same tier's unprotected-objects Restricted Management Administrative Unit when they are not already protected by another RMAU. Default is false.

.PARAMETER RemovalSafetyThreshold
    Safety brake for every automated cmdlet that removes protections from classified EAM data (Conditional Access target groups, Administrative Units, Restricted Management AUs including devices, and Entitlement Management catalog privilege levels). The value is the fraction (0..1) of a target's currently protected members/objects that a single run may remove. Each target plans all eligible removals before mutation; when the plan exceeds Ceiling(current count x threshold), no removals are applied to that target, a SafetyAbort is recorded, and the command fails after its summary. Non-destructive additions or protection operations may still proceed. Example with the default 0.5: a CA group with 20 current members permits at most 10 removals; a plan for 11 aborts before group removals begin. Setting the value to 1.0 permits removal of the complete current protected set and therefore disables threshold-based SafetyAbort protection for automated runs. After reviewing the reported plan, run the affected cmdlet manually with -ForceRemovalBeyondSafetyThreshold to apply an intentional oversized reconciliation. The force switch is deliberately not configurable because bypassing the brake must remain an explicit invocation-time decision. Written to all four mutation sections of the config file. Default is 0.5.

.PARAMETER ClassifyConstrainedDelegationAlwaysAsControlPlane
    Defines if Azure RBAC role assignments with an authorization/role assignment ABAC condition (constrained delegation) should always be classified as Control Plane instead of being dynamically downgraded based on the scope and the role definitions allowed by the condition. Default is false (dynamic downgrade).

.PARAMETER UnresolvedRoleDefinitionFallbackTier
    Tier assigned to Azure RBAC role assignments whose role definition could not be resolved from Azure Resource Graph (e.g. ARG replication lag for a new custom role, deleted role definition or a transient ARG failure). Allowed values: "Unclassified" (default, consistent with the Unclassified fallback of the other RBAC systems), "ManagementPlane" or "ControlPlane" (stricter fail-closed tiers) or "None" (skip classification).

.PARAMETER DeletedPrincipalAssignmentHandling
    Controls Azure RBAC assignments whose principal is confirmed deleted by HTTP 404 responses from both Microsoft Graph identity endpoints. "Filter" (default) removes it from Azure EAM output; "Keep" preserves the unresolved assignment. Other resolution failures are never filtered.

.PARAMETER ApplyAutomatedReportingGeneration
    Defines if the EntraOps Reporting static web apps (Classification Explorer, Tier Breach Analyzer, EAM Dashboard and Access Path Map) should be regenerated automatically by the Push-EntraOpsPrivilegedReporting workflow. Default is false.
    The generated report is uploaded as a downloadable workflow artifact; it is not committed back to the repository.
    The trigger for this workflow is controlled by the config file's `WorkflowTrigger` section: by default (`PushReportingAfterPullWorkflowTrigger` set to `true`) it runs right after every successful Pull-EntraOpsPrivilegedEAM run. It can additionally/alternatively be run on a recurring schedule by enabling `PushReportingScheduledTrigger` and customizing the cron expression in `PushReportingScheduledCron` (default is weekly, every Monday at 09:00 UTC). Disable both settings to only allow manual (`workflow_dispatch`) runs.

.PARAMETER PublishReportsAsRelease
    Publishes generated reports as GitHub Releases in addition to the 30-day workflow artifact. Disabled by default because each release contains a full tenant-data snapshot.

.PARAMETER ReportingReleasesToKeep
    Number of `reporting-*` GitHub Releases to retain when release publishing is enabled. Default is 10.
    Accepts 1 to 1000, so a generated configuration always prunes. Turning pruning off is deliberately not
    offered here because every retained release holds a full tenant-data snapshot; set
    `AutomatedReportingGeneration.ReportingReleasesToKeep` to an empty value in the Configuration Wizard or
    directly in the configuration file if unbounded retention is really intended.

.PARAMETER AccessPathMapResolveObjectIdsOutsidePrivilegedEAM
    Defines if New-EntraOpsAccessPathMapData should resolve edge endpoints that reference an object id outside the Privileged EAM export (e.g. a standard, non-privileged work account or a group/application owned by a privileged principal - referenced via OwnedObjects, Owners, AssociatedPawDevice or AssociatedWorkAccount) via Microsoft Graph to their display name and object type, instead of showing them as an anonymous "unresolved" placeholder node in Access Path Map. Default is true.
    Requires an active Microsoft Graph connection with at least directory read permissions (User.Read.All, Group.Read.All, Application.Read.All or Directory.Read.All) when generating the Access Path Map data.

.PARAMETER EamDashboardResolveLinkedIdentityObjectIds
    Defines if New-EntraOpsPrivilegedEamDashboardData should resolve linked identity object IDs outside the Privileged EAM export to display names through Microsoft Graph. Default is true.
    Requires an active Microsoft Graph connection with directory read permissions when generating EAM Dashboard data.

.PARAMETER ClassificationExplorerGenerateChangeHistory
    Defines if New-EntraOpsClassificationExplorerData should derive the classification change history
    from the source repository's git log. Default is false because the derivation requires a full
    AzurePrivilegedIAM checkout and is rarely needed in deployment reporting. Written as
    ClassificationExplorer.GenerateChangeHistory; a missing configuration file or setting also skips
    history generation.

.PARAMETER ResolveGroupMembersForPrivilegedAssets
    Defines if Configuration Analyzer should resolve included Conditional Access and authentication
    method policy target groups through PIM-aware transitive membership. Default is true.

.PARAMETER AllowPartialTenantGovernanceSnapshot
    Defines if the Push-EntraOpsPrivilegedReporting workflow should generate Configuration Analyzer
    data from a partially successful Tenant Governance snapshot. Failed resource types remain marked
    as stale in the report. Default is true.

.PARAMETER PimRequestFlowExcludedRiskFlags
    Array of PIM Request Flow risk-flag IDs to exclude from the generated report. Default is empty,
    so every supported risk flag is reported. Use the Configuration Wizard to select flags by name.

.PARAMETER EnableAlternateObjectTierLevelAttributes
    Defines if User and ServicePrincipal objects should be classified by PowerShell filter expressions evaluated against their own resolved EntraOps details (section AlternateObjectTierLevelAttributes) instead of Custom Security Attributes. Default is false. The config file always includes the section with empty filter expressions so it can be filled in and enabled later; see README.md "Classify privileged objects by Alternate Tier Level Attributes" for the required syntax.

.PARAMETER EnableTenantGovernanceSnapshot
    Defines if the EntraOps Tenant Governance Snapshot feature is enabled. Default is false.
When enabled, run New-EntraOpsWorkloadIdentity afterwards to grant the workload identity the required "ConfigurationMonitoring.ReadWrite.All" Microsoft Graph permission, and to configure the read permissions of the first-party "Microsoft Tenant Configuration Management" (UTCM) service principal for the resources configured in `TenantGovernanceSnapshot.ResourcesToInclude`.
    Snapshots are captured by Save-EntraOpsTenantGovernanceSnapshotJson (and the Pull-EntraOpsTenantGovernance workflow) and stored as per-resource files under `TenantGovernance/Snapshots`; historical versions are tracked via Git history.
    Reference: https://learn.microsoft.com/en-us/graph/utcm-entra-resources

.PARAMETER TenantGovernanceResourcesToInclude
    Array of Microsoft Entra resource types (e.g. "microsoft.entra.conditionalAccessPolicy") to capture in each Tenant Governance snapshot. Defaults to the recommended tenant governance resource set.

.PARAMETER TenantGovernanceSnapshotScheduledCron
    Cron expression for the "start" scheduled trigger of the Pull-EntraOpsTenantGovernance workflow,
    which creates the snapshot job without waiting for it to complete. Default is daily at 06:00 UTC ("0 6 * * *").

.PARAMETER TenantGovernanceSnapshotScheduledCronComplete
    Cron expression for the first "collect" trigger of the Pull-EntraOpsTenantGovernance workflow,
    which checks the pending snapshot job once without polling. Default is daily at 07:00 UTC.

.PARAMETER TenantGovernanceSnapshotScheduledCronCompleteRetry1
    Cron expression for the first collection retry. Default is daily at 07:30 UTC.

.PARAMETER TenantGovernanceSnapshotScheduledCronCompleteRetry2
    Cron expression for the final collection retry. Default is daily at 08:00 UTC.

.PARAMETER TenantGovernanceSnapshotResourceFileNaming
    Controls the file name used for each persisted Tenant Governance snapshot resource file. "ResourceId"
    (default) names files after the immutable resource Id; "DisplayName" produces readable paths but changes
    them on rename. Resource-ID naming falls back to a display-name path for singleton types without an Id.

.EXAMPLE
    Create a configuration file in the default location, update classification files before analyzing privileges, and enable both Log Analytics and Sentinel WatchList ingestion.
    New-EntraOpsConfigFile -TenantName "contoso.onmicrosoft.com" -ApplyAutomatedControlPlaneScopeUpdate $true -IngestToLogAnalytics $true -IngestToWatchLists $true -ApplyAutomatedClassificationUpdate $true
 #>


function New-EntraOpsConfigFile {

    [CmdletBinding()]
    param (
        [parameter(Mandatory = $true)]
        [string]$TenantName,

        [parameter(Mandatory = $false)]
        [string]$ManagingTenantName = "",        

        [parameter(Mandatory = $false)]
        [string]$ManagingTenantId = "",

        [Parameter(Mandatory = $false)]
        [ValidateSet('UserInteractive', 'SystemAssignedMSI', 'UserAssignedMSI', 'FederatedCredentials', 'AlreadyAuthenticated', 'DeviceAuthentication')]
        [string]$AuthenticationType = "FederatedCredentials",

        [Parameter(Mandatory = $false)]
        [boolean]$UseInvokeRestMethodOnly = $false,

        [Parameter(Mandatory = $false)]
        [boolean]$IncludeObjectDetails = $false,

        [Parameter(Mandatory = $false)]
        [ValidateSet('AzureDevOps', 'GitHub', 'None')]
        [string]$DevOpsPlatform = "GitHub",

        [Parameter(Mandatory = $false)]
        [ValidateScript({ Test-Path (Split-Path $_ -Parent) })]
        [string]$ConfigFilePath = "$EntraOpsBasefolder/EntraOpsConfig.json",

        [Parameter(Mandatory = $false)]
        [boolean]$IngestToLogAnalytics = $false,

        [Parameter(Mandatory = $false)]
        [boolean]$IngestToWatchLists = $false,

        [Parameter(Mandatory = $false)]
        [boolean]$ApplyAutomatedControlPlaneScopeUpdate = $false,

        [Parameter(Mandatory = $false)]
        [boolean]$ApplyAutomatedClassificationUpdate = $false,

        [Parameter(Mandatory = $false)]
        [boolean]$FailOnContradictoryTierPair = $false,

        [Parameter(Mandatory = $false)]
        [boolean]$FailOnPrivilegedAssignmentWithoutClassification = $false,

        [Parameter(Mandatory = $false)]
        [boolean]$ApplyAutomatedEntraOpsUpdate = $false,

        [Parameter(Mandatory = $false)]
        [ValidatePattern('^[A-Za-z0-9_.-]+$')]
        [string]$UpdateRepository = 'EntraOps',

        [Parameter(Mandatory = $false)]
        [ValidateSet('OnChange', 'Always', 'Never')]
        [string]$ValidationFrequency = 'OnChange',

        [Parameter(Mandatory = $false)]
        [boolean]$RunBrowserTests = $true,

        [Parameter(Mandatory = $false)]
        [ValidateSet('PullRequest', 'DirectPush')]
        [string]$UpdatePublicationMode = 'PullRequest',

        [Parameter(Mandatory = $false)]
        [boolean]$ApplyConditionalAccessTargetGroups = $false,

        [Parameter(Mandatory = $false)]
        [boolean]$ApplyAdministrativeUnitAssignments = $false,

        [Parameter(Mandatory = $false)]
        [boolean]$ApplyRmauAssignmentsForUnprotectedObjects = $false,

        [Parameter(Mandatory = $false)]
        [boolean]$IncludeUnprotectedDevices = $false,

        [Parameter(Mandatory = $false)]
        [ValidateRange(0, 1)]
        [double]$RemovalSafetyThreshold = 0.5,

        [Parameter(Mandatory = $false)]
        [boolean]$ApplyPrivilegedElmCatalogProtection = $false,

        [Parameter(Mandatory = $false)]
        [boolean]$ClassifyConstrainedDelegationAlwaysAsControlPlane = $false,

        [Parameter(Mandatory = $false)]
        [ValidateSet("Unclassified", "ManagementPlane", "ControlPlane", "None")]
        [string]$UnresolvedRoleDefinitionFallbackTier = "Unclassified",

        [Parameter(Mandatory = $false)]
        [ValidateSet("Keep", "Filter")]
        [string]$DeletedPrincipalAssignmentHandling = "Filter",

        [Parameter(Mandatory = $false)]
        [boolean]$ApplyAutomatedReportingGeneration = $false,

        [Parameter(Mandatory = $false)]
        [boolean]$PublishReportsAsRelease = $false,

        [Parameter(Mandatory = $false)]
        [ValidateRange(1, 1000)]
        [int]$ReportingReleasesToKeep = 10,

        [Parameter(Mandatory = $false)]
        [boolean]$EnablePrivilegeHistory = $true,

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

        [Parameter(Mandatory = $false)]
        [string]$PrivilegeHistorySnapshotInterval = "P2W",

        [Parameter(Mandatory = $false)]
        [boolean]$AccessPathMapResolveObjectIdsOutsidePrivilegedEAM = $true,

        [Parameter(Mandatory = $false)]
        [boolean]$EamDashboardResolveLinkedIdentityObjectIds = $true,

        [Parameter(Mandatory = $false)]
        [boolean]$ClassificationExplorerGenerateChangeHistory = $false,

        [Parameter(Mandatory = $false)]
        [boolean]$ResolveGroupMembersForPrivilegedAssets = $true,

        [Parameter(Mandatory = $false)]
        [boolean]$AllowPartialTenantGovernanceSnapshot = $true,

        [Parameter(Mandatory = $false)]
        [ValidateSet('noMfaActivation', 'noApprovalActivation', 'approverNotEnforced', 'approverLowerTier', 'authContextMissing', 'permanentEligible', 'permanentActive', 'noMfaActiveAssign')]
        [string[]]$PimRequestFlowExcludedRiskFlags = @(),

        [Parameter(Mandatory = $false)]
        [ValidateSet('noApproval', 'broadRequestor', 'approverLowerTier', 'assignedMorePrivileged')]
        [string[]]$AccessPackageFlowExcludedRiskFlags = @(),

        [Parameter(Mandatory = $false)]
        [ValidateSet('policyNotEnforced', 'legacyAuthentication', 'allUserMfa', 'privilegedRoleControls', 'riskBasedPolicies', 'deviceControls', 'guestCoverage', 'clientAppCoverage', 'exclusionSurface')]
        [string[]]$ConditionalAccessAnalysisExcludedFindings = @(),

        [Parameter(Mandatory = $false)]
        [string[]]$EidscaExcludedFindings = @(),

        [Parameter(Mandatory = $false)]
        [boolean]$EnableAlternateObjectTierLevelAttributes = $false,        

        [Parameter(Mandatory = $false)]
        [boolean]$EnableTenantGovernanceSnapshot = $false,

        [Parameter(Mandatory = $false)]
        [Array]$TenantGovernanceResourcesToInclude,

        [Parameter(Mandatory = $false)]
        [string]$TenantGovernanceSnapshotScheduledCron = "0 6 * * *",

        [Parameter(Mandatory = $false)]
        [string]$TenantGovernanceSnapshotScheduledCronComplete = "0 7 * * *",

        [Parameter(Mandatory = $false)]
        [string]$TenantGovernanceSnapshotScheduledCronCompleteRetry1 = "30 7 * * *",

        [Parameter(Mandatory = $false)]
        [string]$TenantGovernanceSnapshotScheduledCronCompleteRetry2 = "0 8 * * *",

        [Parameter(Mandatory = $false)]
        [ValidateSet("DisplayName", "ResourceId")]
        [string]$TenantGovernanceSnapshotResourceFileNaming = "ResourceId",

        [Parameter(Mandatory = $false)]
        [ValidateSet("Azure", "EntraID", "IdentityGovernance", "DeviceManagement", "ResourceApps", "Defender")]
        [Array]$RbacSystems = ("Azure", "EntraID", "IdentityGovernance", "ResourceApps", "DeviceManagement"),

        [Parameter(Mandatory = $false)]
        [ValidateSet("Azure", "EntraID", "IdentityGovernance", "DeviceManagement", "ResourceApps", "Defender")]
        [Array]$ClassificationParameterScope = ("Azure", "EntraID", "IdentityGovernance", "ResourceApps", "DeviceManagement", "Defender"),

        [Parameter(Mandatory = $false)]
        [ValidateSet("None", "All", "VIPUsers", "HighValueAssets", "IdentityCorrelation")]
        [Array]$WatchListTemplates = "None",

        [Parameter(Mandatory = $false)]
        [ValidateSet("ManagedIdentityAssignedResourceId", "All", "WorkloadIdentityAttackPaths", "WorkloadIdentityInfo", "WorkloadIdentityRecommendations")]
        [Array]$WatchListWorkloadIdentity = "None"
    )

    $ErrorActionPreference = "Stop"

    #region Connect to Azure Resource Manager
    # Get TenantId
    try {
        $TenantId = (Invoke-RestMethod -Uri ("https://login.windows.net/$($TenantName)/.well-known/openid-configuration")).token_endpoint.split('/')[3]
    } catch {
        Write-Error "Can't find tenant with name $TenantName. Error: $_"
    }

    Write-Output "Connect to Azure Resource Manager..."
    $AzContext = Get-AzContext
    if ($AzContext.Tenant.Id -ne $TenantId) {
        Write-Verbose "Call Connect-AzAccount to $($TenantId)..."
        Connect-AzAccount -TenantId $TenantId
    } else {
        Write-Verbose "Already connected to $($AzContext.Tenant.Id)"
    }

    try {
        $TenantDetails = Get-AzTenant -TenantId $TenantId
    } catch {
        Write-Error "Failed to get Tenant details for TenantId $TenantId. Error: $_"
    }
    #endregion

    #region Create configuration file schema with default values
    $EnvConfigSchema = [ordered]@{
        TenantId                                      = $($TenantId)
        TenantName                                    = $($TenantDetails.Domains[0])
        ManagingTenantId                              = $($ManagingTenantId)
        ManagingTenantName                            = $($ManagingTenantName)
        AuthenticationType                            = $($AuthenticationType)
        UseInvokeRestMethodOnly                       = $UseInvokeRestMethodOnly
        ConsoleOutput                                 = [ordered]@{
            IncludeObjectDetails = $IncludeObjectDetails
        }
        ClientId                                      = "Use New-EntraOpsWorkloadIdentity to create a new App Registration or enter here manually"
        DevOpsPlatform                                = $($DevOpsPlatform)
        RbacSystems                                   = $($RbacSystems)
        AzureRbacClassification                       = [ordered]@{
            ClassifyConstrainedDelegationAlwaysAsControlPlane = $ClassifyConstrainedDelegationAlwaysAsControlPlane
            UnresolvedRoleDefinitionFallbackTier              = $UnresolvedRoleDefinitionFallbackTier
            DeletedPrincipalAssignmentHandling                = $DeletedPrincipalAssignmentHandling
        }
        WorkflowTrigger                               = [ordered]@{
            PullScheduledTrigger                  = $true
            PullScheduledCron                     = "30 9 * * *"
            PushAfterPullWorkflowTrigger          = $true
            PushReportingAfterPullWorkflowTrigger = $true
            PushReportingScheduledTrigger         = $false
            PushReportingScheduledCron            = "0 9 * * 1"
        }
        AutomatedControlPlaneScopeUpdate              = [ordered]@{
            ApplyAutomatedControlPlaneScopeUpdate = $ApplyAutomatedControlPlaneScopeUpdate
            PrivilegedObjectClassificationSource  = ("EntraOps", "PrivilegedRolesFromAzGraph", "PrivilegedEdgesFromExposureManagement")
            EntraOpsScopes                        = @($RbacSystems)
            ClassificationParameterScope          = $($ClassificationParameterScope)
            AzureHighPrivilegedRoles              = ("Owner", "Role Based Access Control Administrator", "User Access Administrator")
            AzureHighPrivilegedScopes             = ("/", "/providers/microsoft.management/managementgroups/$($TenantId)")
            ExposureCriticalityLevel              = "<1"
        }
        AutomatedClassificationUpdate                 = [ordered]@{
            ApplyAutomatedClassificationUpdate = $ApplyAutomatedClassificationUpdate
            Classifications                    = ("All")
        }
        GeneratedArtifactValidation                   = [ordered]@{
            # Contradictory tier pairs come from Custom Security Attribute drift on the source object,
            # so they are reported as warnings by default instead of blocking the whole collection run.
            FailOnContradictoryTierPair = $FailOnContradictoryTierPair
            FailOnPrivilegedAssignmentWithoutClassification = $FailOnPrivilegedAssignmentWithoutClassification
        }
        AutomatedEntraOpsUpdate                       = [ordered]@{
            ApplyAutomatedEntraOpsUpdate = $ApplyAutomatedEntraOpsUpdate
            UpdateScheduledTrigger       = $ApplyAutomatedEntraOpsUpdate
            UpdateScheduledCron          = "0 9 * * 3"
            ValidationFrequency          = $ValidationFrequency
            RunBrowserTests              = $RunBrowserTests
            # Distribution repositories are declared once in EntraOpsUpdateContract.json: "EntraOps"
            # (public release channel, no credentials) or "EntraOps-Insiders" (private, requires the
            # EntraOpsUpdatePat secret). A different source is accepted only when it publishes a
            # matching EntraOpsUpdateContract.json.
            Repository                   = $UpdateRepository
            # Track main by default so enabled automated updates require no release-ref maintenance.
            Branch                       = "main"
            # Workflow definitions are intentionally opt-in: the built-in GITHUB_TOKEN cannot push
            # changes below .github/workflows. Add the target explicitly only for an interactive
            # update or after configuring the dedicated publisher GitHub App documented in core.md.
            TargetUpdateFolders          = @("./.github/actions", "./.github/agents", "./.github/scripts", "./Docs", "./EntraOps", "./Parsers", "./Queries", "./Reports", "./Samples", "./Tests", "./Workbooks", "./package.json", "./package-lock.json", "./playwright.config.mjs", "./CHANGELOG.md", "./EntraOpsUpdateContract.json")
            PublicationMode              = $UpdatePublicationMode
        }
        LogAnalytics                                  = [ordered]@{
            IngestToLogAnalytics             = $IngestToLogAnalytics
            DataCollectionRuleName           = "Enter Data Collection Rule Name and run New-EntraOpsWorkloadIdentity to assign permissions"
            DataCollectionRuleSubscriptionId = "Enter Subscription Id of Data Collection Rule and run New-EntraOpsWorkloadIdentity to assign permissions"
            DataCollectionResourceGroupName  = "Enter Resource Group Name of Data Collection Rule and run New-EntraOpsWorkloadIdentity to assign permissions"
            TableName                        = "PrivilegedEAM_CL"
        }
        SentinelWatchLists                            = [ordered]@{
            IngestToWatchLists        = $IngestToWatchLists
            WatchListTemplates        = $WatchListTemplates
            WatchListWorkloadIdentity = $WatchListWorkloadIdentity
            SentinelWorkspaceName     = "Enter Log Analytics Workspace Name and run New-EntraOpsWorkloadIdentity to assign permissions"
            SentinelSubscriptionId    = "Enter Subscription Id of Log Analytics Workspace and run New-EntraOpsWorkloadIdentity to assign permissions"
            SentinelResourceGroupName = "Enter Resource Group of Log Analytics Workspace and run New-EntraOpsWorkloadIdentity to assign permissions"
            WatchListPrefix           = "EntraOps_"
        }        
        AutomatedAdministrativeUnitManagement         = [ordered]@{
            ApplyAdministrativeUnitAssignments = $ApplyAdministrativeUnitAssignments
            ApplyToAccessTierLevel             = ("ControlPlane", "ManagementPlane")
            FilterObjectType                   = ("User", "Group")
            RbacSystems                        = ("EntraID", "IdentityGovernance", "ResourceApps", "DeviceManagement")
            RestrictedAuMode                   = "selected"
            RemovalSafetyThreshold             = $RemovalSafetyThreshold
        }
        AutomatedConditionalAccessTargetGroups        = [ordered]@{
            ApplyConditionalAccessTargetGroups = $ApplyConditionalAccessTargetGroups
            AdminUnitName                      = "Tier0-ControlPlane.ConditionalAccess"
            ApplyToAccessTierLevel             = ("ControlPlane", "ManagementPlane")
            FilterObjectType                   = ("User", "Group")
            GroupPrefix                        = "sug_Entra.CA.IncludeUsers.PrivilegedAccounts."
            RbacSystems                        = ("EntraID", "IdentityGovernance", "ResourceApps", "DeviceManagement")
            RemovalSafetyThreshold             = $RemovalSafetyThreshold
        }
        AutomatedRmauAssignmentsForUnprotectedObjects = [ordered]@{
            ApplyRmauAssignmentsForUnprotectedObjects = $ApplyRmauAssignmentsForUnprotectedObjects
            ApplyToAccessTierLevel                    = ("ControlPlane", "ManagementPlane")
            FilterObjectType                          = ("User", "Group")
            RbacSystems                               = ("EntraID", "IdentityGovernance", "DeviceManagement")
            IncludeUnprotectedDevices                 = $IncludeUnprotectedDevices
            RemovalSafetyThreshold                    = $RemovalSafetyThreshold
        }
        AutomatedReportingGeneration                  = [ordered]@{
            ApplyAutomatedReportingGeneration = $ApplyAutomatedReportingGeneration
            PublishReportsAsRelease           = $PublishReportsAsRelease
            ReportingReleasesToKeep           = $ReportingReleasesToKeep
            GenerateClassificationExplorer    = $true
            GenerateTierBreachAnalyzer        = $true
            GenerateEamDashboard              = $true
            GenerateAccessPathMap             = $true
            GenerateConfigurationAnalyzer     = $true
            GenerateAccessPackageFlow         = $true
            GeneratePrivilegeHistory          = $true
            ClassificationExplorerRepository  = "Cloud-Architekt/AzurePrivilegedIAM"
        }
        ConfigurationAnalyzer                         = [ordered]@{
            ResolveGroupMembersForPrivilegedAssets = $ResolveGroupMembersForPrivilegedAssets
            AllowPartialTenantGovernanceSnapshot    = $AllowPartialTenantGovernanceSnapshot
            PimRequestFlowExcludedRiskFlags         = @($PimRequestFlowExcludedRiskFlags)
            AccessPackageFlowExcludedRiskFlags       = @($AccessPackageFlowExcludedRiskFlags)
            ConditionalAccessAnalysisExcludedFindings = @($ConditionalAccessAnalysisExcludedFindings)
            EidscaExcludedFindings                   = @($EidscaExcludedFindings)
        }
        AutomatedElmCatalogProtection                 = [ordered]@{
            ApplyPrivilegedElmCatalogProtection = $ApplyPrivilegedElmCatalogProtection
            ApplyToAccessTierLevel              = , "ControlPlane"
            RemovalSafetyThreshold              = $RemovalSafetyThreshold
        }
        CustomSecurityAttributes                      = [ordered]@{
            PrivilegedUserAttribute             = "privilegedUser"
            PrivilegedUserPawAttribute          = "associatedSecureAdminWorkstation"
            PrivilegedServicePrincipalAttribute = "privilegedWorkloadIdentity"
            UserWorkAccountAttribute            = "associatedWorkAccount"
            PrivilegedUserAdminTierLevelAttribute = "adminTierLevel"
            PrivilegedUserAdminTierLevelNameAttribute = "adminTierLevelName"
            PrivilegedServicePrincipalAdminTierLevelAttribute = "adminTierLevel"
            PrivilegedServicePrincipalAdminTierLevelNameAttribute = "adminTierLevelName"
        }        
        AlternateObjectTierLevelAttributes            = [ordered]@{
            Enabled          = $EnableAlternateObjectTierLevelAttributes
            User             = [ordered]@{
                ControlPlane    = ""
                ManagementPlane = ""
                UserAccess      = ""
            }
            ServicePrincipal = [ordered]@{
                ControlPlane    = ""
                ManagementPlane = ""
                UserAccess      = ""
            }            
        }        
        PrivilegeHistory                              = [ordered]@{
            EnablePrivilegeHistory = $EnablePrivilegeHistory
            TimeRangeInDays        = $PrivilegeHistoryTimeRangeInDays
            SnapshotInterval       = $PrivilegeHistorySnapshotInterval
        }
        AccessPathMap                                 = [ordered]@{
            ResolveObjectIdsOutsidePrivilegedEAM = $AccessPathMapResolveObjectIdsOutsidePrivilegedEAM
        }
        EamDashboard                                  = [ordered]@{
            ResolveLinkedIdentityObjectIds = $EamDashboardResolveLinkedIdentityObjectIds
        }
        ClassificationExplorer                        = [ordered]@{
            GenerateChangeHistory = $ClassificationExplorerGenerateChangeHistory
        }
        TenantGovernanceSnapshot                      = [ordered]@{
            EnableTenantGovernanceSnapshot = $EnableTenantGovernanceSnapshot
            ResourcesToInclude             = if ($TenantGovernanceResourcesToInclude) { $TenantGovernanceResourcesToInclude } else { (Get-EntraOpsTenantGovernanceResourceDefinition).DefaultResources }
            SnapshotDisplayNamePrefix      = "EntraOps TG"
            SnapshotResourceFileNaming     = $TenantGovernanceSnapshotResourceFileNaming
            SnapshotScheduledTrigger       = $EnableTenantGovernanceSnapshot
            SnapshotScheduledCron          = $TenantGovernanceSnapshotScheduledCron
            SnapshotScheduledCronComplete  = $TenantGovernanceSnapshotScheduledCronComplete
            SnapshotScheduledCronCompleteRetry1 = $TenantGovernanceSnapshotScheduledCronCompleteRetry1
            SnapshotScheduledCronCompleteRetry2 = $TenantGovernanceSnapshotScheduledCronCompleteRetry2
        }
    }
    #endregion

    #region Write configuration file to disk
    try {
        Write-Output "Writing configuration file to $($ConfigFilePath)..."
        $EnvConfigSchema | ConvertTo-Json -Depth 10 | Out-File -Path $($ConfigFilePath)
    } catch {
        Write-Error "Failed to write configuration file to $($ConfigFilePath). Error: $_"
    }
    #endregion
}

# SIG # Begin signature block
# MIIniAYJKoZIhvcNAQcCoIIneTCCJ3UCAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCAgw5OCk+XSck3u
# svVW4A2L+Dzni4/wMjLoFs8SqVIQz6CCIDwwggXJMIIEsaADAgECAhAbtY8lKt8j
# 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
# ARUwLwYJKoZIhvcNAQkEMSIEINGwoE61ziwM/mdtFdJKbg+Rjw1h1hT+Xhmu0yoo
# /F4mMA0GCSqGSIb3DQEBAQUABIIBgCijnKPvMM4bG5CayI0R0BmpR0gRnsezNWMz
# HgUi7sxLawuxWnIYU6MYQGlrrSchwbjCdjfLXPjJ4CZKbWpngnlWWhpNyBOWKSHK
# Nm1nyrcBWEoW4XEiyalUKKsZFGK/TrxN+Fyn9Es2dbqdfc/eixtnDSwQhhFrwRDc
# h0ui6w5ZyshGqHjzDT/G0OtbZPWDXocL973TNo0OrzpGrk2fcADOqbgiCf/GF38Z
# Yh2ouDY9FrU09EclQkB0djcih8TRAZlVjJZLPaNqZjhjbsEnm+Gt/FRVtDjPh2QD
# s0tQAQaxyywNxZZRTRSayU6NQJmmdyps39y4tdzX1rvn2edzGKjrlFhlinnnAFd1
# 4zo07Y0CONsf7caDxR/sW21JMLtDYfDl0vexb7eJlDPsJLWfop/tIkCSjRtqrYFU
# 7uP45wtWGxhfgchWiGqQChFjHzKJ96R0+BWU+sdVFK8QsgzgYWAg50pPWOBQ6Iaf
# s6ohE3ROepzXyG+uxNxGEF5dX8lIAKGCBAIwggP+BgkqhkiG9w0BCQYxggPvMIID
# 6wIBATBqMFYxCzAJBgNVBAYTAlBMMSEwHwYDVQQKExhBc3NlY28gRGF0YSBTeXN0
# ZW1zIFMuQS4xJDAiBgNVBAMTG0NlcnR1bSBUaW1lc3RhbXBpbmcgMjAyMSBDQQIQ
# KPB3wRw2vf5fdDJHcCcuAzANBglghkgBZQMEAgIFAKCCAVYwGgYJKoZIhvcNAQkD
# MQ0GCyqGSIb3DQEJEAEEMBwGCSqGSIb3DQEJBTEPFw0yNjEwMTMwOTQwMzJaMDcG
# CyqGSIb3DQEJEAIvMSgwJjAkMCIEIIW+kOEK0kONfMkotq9IsJqyCBd87PiwEmxY
# 05EFJcQ8MD8GCSqGSIb3DQEJBDEyBDAV3vQ4guK643SZE5MHxFks+RN2mZDnRXAL
# IRizmsmfR+TRQ68P9+blgo72bbGbV2owgZ8GCyqGSIb3DQEJEAIMMYGPMIGMMIGJ
# MIGGBBRXFGhBDKha80JO+RZKUTYQ9NONmDBuMFqkWDBWMQswCQYDVQQGEwJQTDEh
# MB8GA1UEChMYQXNzZWNvIERhdGEgU3lzdGVtcyBTLkEuMSQwIgYDVQQDExtDZXJ0
# dW0gVGltZXN0YW1waW5nIDIwMjEgQ0ECECjwd8EcNr3+X3QyR3AnLgMwDQYJKoZI
# hvcNAQEBBQAEggIABlFyWJIjev4Af+U2CQxg2Hpz/Ka5ygDTf6qg0PCIypjmLyIN
# HHFcnRFRvxvtLpY2GaLKJhxwSjKDwAQaAxgdug5g4k6rRLb9a00RYYS6bcnyhMJ7
# 0E1ZjPll5dJLTQZZsYBB0J0PLGCiS2sLCd/6mZC7fclMiPWJW85ClyfrjBAattgm
# v1ngoRB1ainQz8vXYyUjCZ7xnvwUUUE/vyDO4hXcY3VBVcBkFOwt0qjDg0rdttv5
# Y1/FszaeVZBA0WPBBKjYbxJ9J3YolFfCZIJW0uPxPd8pr2f3NYqLy4eFBuZv28+A
# BWbBTt7v8d+HpY+sHfYPKmudFiZX6oWmUdWQdUnBI2Qk6+Sq4jvonbMIdrjrHa8L
# 1b4n/DYXgnfFV0geWc8lN4L7rQOCGkShpqzVOR7ZXbAoh6W1uLl29txLK6ya/DwC
# fAenDFsrt3MJPp9YeKPx86ge6wMFthpOwvRRmkN87MiX9KYP1Uy/361/EFpMXLJt
# oQbB9p6OCRaHyGAbEIOwXQnA/DlRS1QcSB/wS8WpwReDXBxY/F0G/DviCi5AnzH/
# OL/CHJ7zFZ97XiBKfz2mlK1N7niyFEMIZ7nlpGJmAhoKRLQl5ssv8CwkJgtwyZW+
# PwtwZE3ZMdX2vJ9hKdVnCqlm3NmB1Rm6arZoytKgxA0zVIyuoGSGXYo6uBE=
# SIG # End signature block