Public/Remove-PlatformLandingZone.ps1

function Remove-PlatformLandingZone {
    <#
    .SYNOPSIS
        Removes Azure Landing Zone platform resources including management groups and all resource groups within subscriptions.
 
    .DESCRIPTION
        The Remove-PlatformLandingZone function performs a comprehensive cleanup of Azure Landing Zone platform resources.
        It can delete management group hierarchies, remove subscriptions from management groups, and delete all resource
        groups within the affected subscriptions. This function is primarily designed for testing and cleanup scenarios.
 
        The function operates in the following sequence:
        1. Validates provided management groups and subscriptions (if any) exist in Azure
        2. Prompts for confirmation (unless bypassed or in plan mode)
        3. Processes each specified management group, recursively discovering child management groups
        4. Removes subscriptions from management groups and optionally moves them to a target management group
        5. Discovers subscriptions from management groups (if not explicitly provided)
        6. Deletes management groups in reverse depth order (children before parents)
        7. Deletes management group-level deployments from target management groups (if not being deleted)
        8. Deletes orphaned role assignments from target management groups (if not being deleted)
        9. Deletes custom role assignments and definitions from target management groups (if not being deleted)
        10. Deletes all resource groups in the discovered/specified subscriptions (excluding retention patterns)
        11. Resets Microsoft Defender for Cloud plans to Free tier
        12. Deletes all subscription-level deployments
        13. Deletes orphaned role assignments from subscriptions
 
        CRITICAL WARNING: This is a highly destructive operation that will permanently delete Azure resources.
        By default, ALL resource groups in the subscriptions will be deleted unless they match retention patterns.
        Use with extreme caution and ensure you have appropriate backups and authorization before executing.
 
    .PARAMETER ManagementGroups
        An array of management group IDs or names to process. By default, the function deletes child management groups
        one level below these target groups (not the target groups themselves). Use -DeleteTargetManagementGroups to
        delete the target groups as well. Subscriptions under these management groups will be discovered unless
        subscriptions are explicitly provided via the -Subscriptions parameter.
 
    .PARAMETER DeleteTargetManagementGroups
        A switch parameter that causes the target management groups specified in -ManagementGroups to be deleted along
        with all their children. By default, only management groups one level below the targets are deleted, preserving
        the target management groups themselves.
        Default: $false (preserve target management groups)
 
    .PARAMETER SubscriptionsTargetManagementGroup
        The management group ID or name where subscriptions should be moved after being removed from their current
        management groups. If not specified, subscriptions are removed from management groups without being reassigned.
        This is useful for maintaining subscription organization during cleanup operations.
        Default: $null (subscriptions are not reassigned)
 
    .PARAMETER Subscriptions
        An optional array of subscription IDs or names to process for resource group deletion. If provided, the
        function will only delete resource groups from these specific subscriptions and will not discover additional
        subscriptions from management groups. If omitted, subscriptions will be discovered from the management groups
        being processed. Accepts both subscription IDs (GUIDs) and subscription names.
        Default: Empty array (discover from management groups)
 
    .PARAMETER ResourceGroupsToRetainNamePatterns
        An array of regex patterns for resource group names that should be retained (not deleted). Resource groups
        matching any of these patterns will be skipped during the deletion process. This is useful for preserving
        critical infrastructure or billing-related resource groups.
        Default: @("VisualStudioOnline-") - Retains Azure DevOps billing resource groups
 
    .PARAMETER BypassConfirmation
        A switch parameter that bypasses the interactive confirmation prompts. When specified, the function waits
        for the duration specified in -BypassConfirmationTimeoutSeconds before proceeding, allowing time to cancel.
        During this timeout, pressing any key will cancel the operation.
        WARNING: Use this parameter with extreme caution as it reduces safety checks.
        Default: $false (confirmation required)
 
    .PARAMETER BypassConfirmationTimeoutSeconds
        The number of seconds to wait before proceeding when -BypassConfirmation is used. During this timeout,
        pressing any key will cancel the operation. This provides a safety window to prevent accidental deletions.
        Default: 30 seconds
 
    .PARAMETER ThrottleLimit
        The maximum number of parallel operations to execute simultaneously. This controls the degree of parallelism
        when processing management groups and resource groups. Higher values may improve performance but increase
        API throttling risk and resource consumption.
        Default: 11 "These go to eleven."
 
    .PARAMETER PlanMode
        A switch parameter that enables "dry run" mode. When specified, the function displays what actions would be
        taken without actually making any changes. This is useful for validating the scope of operations before
        executing the actual cleanup.
        Default: $false (execute actual deletions)
 
    .PARAMETER SkipDefenderPlanReset
        A switch parameter that skips the Microsoft Defender for Cloud plan reset operation. When specified, the
        function will not attempt to reset Defender plans to Free tier. This is useful when you want to preserve
        existing Defender configurations or when you don't have the necessary permissions.
        Default: $false (reset Defender plans)
 
    .PARAMETER SkipDeploymentDeletion
        A switch parameter that skips deployment deletion operations at both the management group and subscription
        levels. When specified, the function will not delete deployment history records from management groups or
        subscriptions. This is useful when you want to preserve deployment records for audit or compliance purposes.
        Default: $false (delete deployments)
 
    .PARAMETER SkipOrphanedRoleAssignmentDeletion
        A switch parameter that skips orphaned role assignment deletion operations at both the management group and
        subscription levels. When specified, the function will not delete role assignments where the principal no
        longer exists. This is useful when you want to preserve role assignment records or lack the necessary permissions.
        Default: $false (delete orphaned role assignments)
 
    .PARAMETER SkipCustomRoleDefinitionDeletion
        A switch parameter that skips custom role definition deletion operations on target management groups that are
        not being deleted. When specified, the function will not delete custom role definitions or their assignments.
        This is useful when you want to preserve custom role definitions or lack the necessary permissions to delete them.
        Default: $false (delete custom role definitions)
 
    .EXAMPLE
        Remove-PlatformLandingZone -ManagementGroups @("alz-platform", "alz-landingzones")
 
        Removes all child management groups one level below "alz-platform" and "alz-landingzones", discovers
        subscriptions from those management groups, prompts for confirmation, then deletes all resource groups
        in the discovered subscriptions (except those matching retention patterns).
 
    .EXAMPLE
        Remove-PlatformLandingZone -ManagementGroups @("alz-test") -DeleteTargetManagementGroups
 
        Deletes the "alz-test" management group itself along with all its children, rather than just deleting
        one level below it.
 
    .EXAMPLE
        Remove-PlatformLandingZone -ManagementGroups @("mg-dev") -Subscriptions @("Sub-Dev-001", "Sub-Dev-002")
 
        Processes the "mg-dev" management group hierarchy and deletes resource groups only from the two explicitly
        specified subscriptions. No additional subscriptions will be discovered from the management group.
 
    .EXAMPLE
        Remove-PlatformLandingZone -ManagementGroups @("alz-test") -SubscriptionsTargetManagementGroup "mg-tenant-root"
 
        Removes child management groups and moves all discovered subscriptions to the "mg-tenant-root" management
        group instead of leaving them orphaned.
 
    .EXAMPLE
        Remove-PlatformLandingZone -ManagementGroups @("alz-dev") -PlanMode
 
        Runs in plan mode (dry run) to show what would be deleted without making any actual changes. Useful for
        validating the scope before executing.
 
    .EXAMPLE
        Remove-PlatformLandingZone -ManagementGroups @("alz-test") -BypassConfirmation -BypassConfirmationTimeoutSeconds 60
 
        Bypasses interactive confirmation prompts but waits 60 seconds before proceeding, allowing time to cancel
        by pressing any key. USE WITH EXTREME CAUTION!
 
    .EXAMPLE
        Remove-PlatformLandingZone -ManagementGroups @("alz-prod") -ResourceGroupsToRetainNamePatterns @("VisualStudioOnline-", "RG-Critical-", "NetworkWatcherRG")
 
        Removes management group hierarchy but retains resource groups matching any of the specified patterns.
        This example preserves Azure DevOps billing resources, critical resource groups, and Network Watcher resource groups.
 
    .EXAMPLE
        $subs = @("12345678-1234-1234-1234-123456789012", "87654321-4321-4321-4321-210987654321")
        Remove-PlatformLandingZone -ManagementGroups @("alz-test") -Subscriptions $subs -ThrottleLimit 5
 
        Processes the management group hierarchy and only the specified subscriptions (by GUID) with reduced
        parallelism to minimize API throttling.
 
    .EXAMPLE
        Remove-PlatformLandingZone -Subscriptions @("Sub-Test-001")
 
        Skips management group processing entirely and only deletes resource groups from the specified subscription.
        This is useful when you want to clean subscriptions without touching the management group structure.
 
    .EXAMPLE
        Remove-PlatformLandingZone -ManagementGroups @("alz-test") -SkipDefenderPlanReset -SkipDeploymentDeletion
 
        Removes management groups and resource groups but skips resetting Microsoft Defender plans and deleting
        deployment history. Useful for faster cleanup when Defender configuration and audit trails should be preserved.
 
    .EXAMPLE
        Remove-PlatformLandingZone -Subscriptions @("Sub-Test-001") -SkipOrphanedRoleAssignmentDeletion
 
        Cleans up the subscription but skips orphaned role assignment deletion. Useful when you want to preserve
        role assignments for review or lack the necessary permissions to delete them.
 
    .EXAMPLE
        Remove-PlatformLandingZone -ManagementGroups @("alz-test") -SkipCustomRoleDefinitionDeletion
 
        Removes management groups and resource groups but skips custom role definition deletion. Useful when you want
        to preserve custom role definitions or lack the necessary permissions to delete them.
 
    .NOTES
        This function uses Azure CLI commands and requires:
        - Azure CLI to be installed and available in the system path
        - User to be authenticated to Azure (az login)
        - Appropriate RBAC permissions:
            * Management Group Contributor or Owner at the management group scope
            * Contributor or Owner at the subscription scope for resource group deletions
            * Security Admin for resetting Microsoft Defender for Cloud plans
 
        The function supports PowerShell's ShouldProcess pattern and respects -WhatIf and -Confirm parameters.
 
        The function uses parallel processing with ForEach-Object -Parallel to improve performance when handling
        multiple management groups, subscriptions, and resource groups. The default throttle limit is 11.
 
        Resource group deletions include retry logic to handle dependencies between resources. If a resource group
        fails to delete (e.g., due to locks or dependencies), it will be retried after other resource groups in
        the same subscription have completed their deletion attempts.
 
        The function automatically resets Microsoft Defender for Cloud plans to the Free tier for all processed
        subscriptions. Plans that don't support the Free tier will be set to Standard tier instead.
 
        Management group deletion behavior:
        - By default: Deletes management groups one level below the specified targets
        - With -deleteTargetManagementGroups: Deletes the target management groups and all their children
 
        Subscription discovery behavior:
        - If -subscriptions is provided: Only those subscriptions are processed; no discovery occurs
        - If -subscriptions is empty: Subscriptions are discovered from management groups during cleanup
        - If -subscriptionsTargetManagementGroup is specified: Subscriptions are moved to that management group
 
        Plan mode behavior:
        - All Azure CLI commands are displayed but not executed
        - Useful for validating scope and understanding impact before actual execution
        - Combine with -bypassConfirmation for fully automated dry runs
 
    .LINK
        https://learn.microsoft.com/azure/cloud-adoption-framework/ready/landing-zone/
 
    .LINK
        https://learn.microsoft.com/cli/azure/account/management-group
 
    .LINK
        https://learn.microsoft.com/azure/defender-for-cloud/
    #>

    [CmdletBinding(SupportsShouldProcess = $true)]
    param (
        [string[]]$ManagementGroups,
        [switch]$DeleteTargetManagementGroups,
        [string]$SubscriptionsTargetManagementGroup = $null,
        [string[]]$Subscriptions = @(),
        [string[]]$ResourceGroupsToRetainNamePatterns = @(
            "VisualStudioOnline-" # By default retain Visual Studio Online resource groups created for Azure DevOps billing purposes
        ),
        [switch]$BypassConfirmation,
        [int]$BypassConfirmationTimeoutSeconds = 30,
        [int]$ThrottleLimit = 11,
        [switch]$PlanMode,
        [switch]$SkipDefenderPlanReset,
        [switch]$SkipDeploymentDeletion,
        [switch]$SkipOrphanedRoleAssignmentDeletion,
        [switch]$SkipCustomRoleDefinitionDeletion
    )

    function Write-ToConsoleLog {
        param (
            [string[]]$Messages,
            [string]$Level = "INFO",
            [System.ConsoleColor]$Color = [System.ConsoleColor]::Blue,
            [switch]$NoNewLine,
            [switch]$Overwrite,
            [switch]$IsError,
            [switch]$IsWarning,
            [switch]$IsSuccess,
            [switch]$IsPlan,
            [switch]$WriteToFile,
            [string]$LogFilePath = $null
        )

        $isDefaultColor = $Color -eq [System.ConsoleColor]::Blue

        if($IsError) {
            $Level = "ERROR"
        } elseif ($IsWarning) {
            $Level = "WARNING"
        } elseif ($IsSuccess) {
            $Level = "SUCCESS"
        } elseif ($IsPlan) {
            $Level = "PLAN"
            $WriteToFile = $true
            $NoNewLine = $true
        }

        if($isDefaultColor) {
            if($Level -eq "ERROR") {
                $Color = [System.ConsoleColor]::Red
            } elseif ($Level -eq "WARNING") {
                $Color = [System.ConsoleColor]::Yellow
            } elseif ($Level -eq "SUCCESS") {
                $Color = [System.ConsoleColor]::Green
            } elseif ($Level -eq "PLAN") {
                $Color = [System.ConsoleColor]::Gray
            }
        }

        $timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss.fff"
        $prefix = ""

        if ($Overwrite) {
            $prefix = "`r"
        } else {
            if (-not $NoNewLine) {
                $prefix = [System.Environment]::NewLine
            }
        }

        $finalMessages = @()
        foreach ($Message in $Messages) {
            $finalMessages += "$prefix[$timestamp] [$Level] $Message"
        }

        if($finalMessages.Count -gt 1) {
            $finalMessages = $finalMessages -join "`n"
        }

        Write-Host $finalMessages -ForegroundColor $Color -NoNewline:$Overwrite.IsPresent
        if($WriteToFile -and $LogFilePath) {
            Add-Content -Path $LogFilePath -Value $finalMessages
        }
    }

    function Test-RequiredTooling {
        Write-ToConsoleLog "Checking the software requirements for the Accelerator..."

        $checkResults = @()
        $hasFailure = $false

        # Check if Azure CLI is installed
        Write-Verbose "Checking Azure CLI installation"
        $azCliPath = Get-Command az -ErrorAction SilentlyContinue
        if ($azCliPath) {
            $checkResults += @{
                message = "Azure CLI is installed."
                result  = "Success"
            }
        } else {
            $checkResults += @{
                message = "Azure CLI is not installed. Follow the instructions here: https://learn.microsoft.com/en-us/cli/azure/install-azure-cli"
                result  = "Failure"
            }
            $hasFailure = $true
        }

        # Check if Azure CLI is logged in
        Write-Verbose "Checking Azure CLI login status"
        $azCliAccount = $(az account show -o json) | ConvertFrom-Json
        if ($azCliAccount) {
            $checkResults += @{
                message = "Azure CLI is logged in. Tenant ID: $($azCliAccount.tenantId), Subscription: $($azCliAccount.name) ($($azCliAccount.id))"
                result  = "Success"
            }
        } else {
            $checkResults += @{
                message = "Azure CLI is not logged in. Please login to Azure CLI using 'az login -t `"00000000-0000-0000-0000-000000000000}`"', replacing the empty GUID with your tenant ID."
                result  = "Failure"
            }
            $hasFailure = $true
        }

        Write-Verbose "Showing check results"
        Write-Verbose $(ConvertTo-Json $checkResults -Depth 100)
        $checkResults | ForEach-Object {[PSCustomObject]$_} | Format-Table -Property @{
            Label = "Check Result"; Expression = {
                switch ($_.result) {
                    'Success' { $color = "92"; break }
                    'Failure' { $color = "91"; break }
                    'Warning' { $color = "93"; break }
                    default { $color = "0" }
                }
                $e = [char]27
                "$e[${color}m$($_.result)${e}[0m"
            }
        }, @{ Label = "Check Details"; Expression = {$_.message} }  -AutoSize -Wrap

        if($hasFailure) {
            Write-ToConsoleLog "Software requirements have no been met, please review and install the missing software." -IsError
            Write-ToConsoleLog "Cannot continue with Deployment..." -IsError
            throw "Software requirements have no been met, please review and install the missing software."
        }

        Write-ToConsoleLog "All software requirements have been met." -IsSuccess
    }

    function Get-ManagementGroupChildrenRecursive {
        param (
            [object[]]$ManagementGroups,
            [int]$Depth = 0,
            [hashtable]$ManagementGroupsFound = @{}
        )

        $ManagementGroups = $ManagementGroups | Where-Object { $_.type -eq "Microsoft.Management/managementGroups" }

        foreach($managementGroup in $ManagementGroups) {
            if(!$ManagementGroupsFound.ContainsKey($Depth)) {
                $ManagementGroupsFound[$Depth] = @()
            }

            $ManagementGroupsFound[$Depth] += $managementGroup.name

            $children = $managementGroup.children | Where-Object { $_.type -eq "Microsoft.Management/managementGroups" }

            if ($children -and $children.Count -gt 0) {
                Write-ToConsoleLog "Management group has children: $($managementGroup.name)" -NoNewLine
                if(!$ManagementGroupsFound.ContainsKey($Depth + 1)) {
                    $ManagementGroupsFound[$Depth + 1] = @()
                }
                Get-ManagementGroupChildrenRecursive -ManagementGroups $children -Depth ($Depth + 1) -ManagementGroupsFound $ManagementGroupsFound
            } else {
                Write-ToConsoleLog "Management group has no children: $($managementGroup.name)" -NoNewLine
            }
        }

        if($Depth -eq 0) {
            return $ManagementGroupsFound
        }
    }

    function Test-IsGuid {
        [OutputType([bool])]
        param (
            [Parameter(Mandatory = $true)]
            [string]$StringGuid
        )

        $ObjectGuid = [System.Guid]::empty
        return [System.Guid]::TryParse($StringGuid,[System.Management.Automation.PSReference]$ObjectGuid)
    }

    function Invoke-PromptForConfirmation {
        param (
            [string]$Message,
            [string]$InitialConfirmationText,
            [string]$FinalConfirmationText = "YES I CONFIRM"
        )

        Write-ToConsoleLog "$Message" -IsWarning
        Write-ToConsoleLog "If you wish to proceed, type '$InitialConfirmationText' to confirm." -IsWarning
        $confirmation = Read-Host "Enter the confirmation text"
        if ($confirmation -ne $InitialConfirmationText) {
            Write-ToConsoleLog "Confirmation not received. Exiting without making any changes." -IsError
            return $false
        }
        Write-ToConsoleLog "Initial confirmation received." -IsSuccess
        Write-ToConsoleLog "This operation is permanent and cannot be reversed!" -IsWarning
        Write-ToConsoleLog "Are you sure you want to proceed? Type '$FinalConfirmationText' to perform the highly destructive operation..." -IsWarning
        $confirmation = Read-Host "Enter the final confirmation text"
        if ($confirmation -ne $FinalConfirmationText) {
            Write-ToConsoleLog "Final confirmation not received. Exiting without making any changes." -IsError
            return $false
        }
        Write-ToConsoleLog "Final confirmation received. Proceeding with destructive operation..." -IsSuccess
        return $true
    }

    function Remove-OrphanedRoleAssignmentsForScope {
        [CmdletBinding(SupportsShouldProcess = $true)]
        param (
            [string]$ScopeType,
            [string]$ScopeNameForLogs,
            [string]$ScopeId,
            [int]$ThrottleLimit,
            [switch]$PlanMode,
            [string]$TempLogFileForPlan
        )

        if(-not $PSCmdlet.ShouldProcess("Delete Orphaned Role Assignments", "delete")) {
            return
        }

        $funcWriteToConsoleLog = ${function:Write-ToConsoleLog}.ToString()
        $isSubscriptionScope = $ScopeType -eq "subscription"
        Write-ToConsoleLog "Checking for orphaned role assignments to delete in $($ScopeType): $ScopeNameForLogs" -NoNewLine
        $scopePrefix = $isSubscriptionScope ? "/subscriptions" : "/providers/Microsoft.Management/managementGroups"
        $roleAssignments = (az role assignment list --scope "$scopePrefix/$ScopeId" --query "[?principalName==''].{id:id,principalId:principalId,roleDefinitionName:roleDefinitionName}" -o json) | ConvertFrom-Json

        if ($roleAssignments -and $roleAssignments.Count -gt 0) {
            Write-ToConsoleLog "Found $($roleAssignments.Count) orphaned role assignment(s) in $($ScopeType): $ScopeNameForLogs" -NoNewLine

            $roleAssignments | ForEach-Object -Parallel {
                $roleAssignment = $_
                $ScopeType = $using:ScopeType
                $ScopeNameForLogs = $using:ScopeNameForLogs
                $funcWriteToConsoleLog = $using:funcWriteToConsoleLog
                ${function:Write-ToConsoleLog} = $funcWriteToConsoleLog

                Write-ToConsoleLog "Deleting orphaned role assignment: $($roleAssignment.roleDefinitionName) for principal: $($roleAssignment.principalId) from $($ScopeType): $ScopeNameForLogs" -NoNewLine
                $result = $null
                if($using:PlanMode) {
                    Write-ToConsoleLog `
                        "Deleting orphaned role assignment: $($roleAssignment.roleDefinitionName) for principal: $($roleAssignment.principalId) from $($ScopeType): $ScopeNameForLogs", `
                        "Would run: az role assignment delete --ids $($roleAssignment.id)" `
                        -IsPlan -LogFilePath $using:TempLogFileForPlan
                } else {
                    $result = az role assignment delete --ids $roleAssignment.id 2>&1
                }

                if (!$result) {
                    Write-ToConsoleLog "Deleted orphaned role assignment: $($roleAssignment.roleDefinitionName) from $($ScopeType): $ScopeNameForLogs" -NoNewLine
                } else {
                    Write-ToConsoleLog "Failed to delete orphaned role assignment: $($roleAssignment.roleDefinitionName) from $($ScopeType): $ScopeNameForLogs" -IsWarning -NoNewLine
                }
            } -ThrottleLimit $using:ThrottleLimit

            Write-ToConsoleLog "All orphaned role assignments processed in $($ScopeType): $ScopeNameForLogs" -NoNewLine
        } else {
            Write-ToConsoleLog "No orphaned role assignments found in $($ScopeType): $ScopeNameForLogs, skipping." -NoNewLine
        }
    }

    function Remove-DeploymentsForScope {
        [CmdletBinding(SupportsShouldProcess = $true)]
        param (
            [string]$ScopeType,
            [string]$ScopeNameForLogs,
            [string]$ScopeId,
            [int]$ThrottleLimit,
            [switch]$PlanMode,
            [string]$TempLogFileForPlan
        )

        if(-not $PSCmdlet.ShouldProcess("Delete Deployments", "delete")) {
            return
        }

        $funcWriteToConsoleLog = ${function:Write-ToConsoleLog}.ToString()
        $isSubscriptionScope = $ScopeType -eq "subscription"

        Write-ToConsoleLog "Checking for deployments to delete in $($ScopeType): $ScopeNameForLogs" -NoNewLine

        $deployments = @()
        if ($isSubscriptionScope) {
            $deployments = (az deployment sub list --subscription $ScopeId --query "[].name" -o json) | ConvertFrom-Json
        } else {
            $deployments = (az deployment mg list --management-group-id $ScopeId --query "[].name" -o json) | ConvertFrom-Json
        }

        if ($deployments -and $deployments.Count -gt 0) {
            Write-ToConsoleLog "Found $($deployments.Count) deployment(s) in $($ScopeType): $scopeNameForLogs" -NoNewLine

            $deployments | ForEach-Object -Parallel {
                $deploymentName = $_
                $scopeId = $using:ScopeId
                $scopeNameForLogs = $using:ScopeNameForLogs
                $funcWriteToConsoleLog = $using:funcWriteToConsoleLog
                ${function:Write-ToConsoleLog} = $funcWriteToConsoleLog

                Write-ToConsoleLog "Deleting deployment: $deploymentName from $($scopeType): $scopeNameForLogs" -NoNewLine
                $result = $null
                if($isSubscriptionScope) {
                    if($using:PlanMode) {
                        Write-ToConsoleLog `
                            "Deleting deployment: $deploymentName from $($scopeType): $scopeNameForLogs", `
                            "Would run: az deployment sub delete --subscription $scopeId --name $deploymentName" `
                            -IsPlan -LogFilePath $using:TempLogFileForPlan
                    } else {
                        $result = az deployment sub delete --subscription $scopeId --name $deploymentName 2>&1
                    }
                } else {
                    if($using:PlanMode) {
                        Write-ToConsoleLog `
                            "Deleting deployment: $deploymentName from $($scopeType): $scopeNameForLogs", `
                            "Would run: az deployment mg delete --management-group-id $scopeId --name $deploymentName" `
                            -IsPlan -LogFilePath $using:TempLogFileForPlan
                    } else {
                        $result = az deployment mg delete --management-group-id $scopeId --name $deploymentName 2>&1
                    }
                }

                if (!$result) {
                    Write-ToConsoleLog "Deleted deployment: $deploymentName from $($scopeType): $scopeNameForLogs" -NoNewLine
                } else {
                    Write-ToConsoleLog "Failed to delete deployment: $deploymentName from $($scopeType): $scopeNameForLogs" -IsWarning -NoNewLine
                }
            } -ThrottleLimit $using:ThrottleLimit

            Write-ToConsoleLog "All deployments processed in $($scopeType): $scopeNameForLogs" -NoNewLine
        } else {
            Write-ToConsoleLog "No deployments found in $($scopeType): $scopeNameForLogs, skipping." -NoNewLine
        }
    }

    function Remove-CustomRoleDefinitionsForScope {
        [CmdletBinding(SupportsShouldProcess = $true)]
        param (
            [string]$ManagementGroupId,
            [string]$ManagementGroupDisplayName,
            [array]$Subscriptions,
            [int]$ThrottleLimit,
            [switch]$PlanMode,
            [string]$TempLogFileForPlan
        )

        if(-not $PSCmdlet.ShouldProcess("Delete Custom Role Definitions", "delete")) {
            return
        }

        $funcWriteToConsoleLog = ${function:Write-ToConsoleLog}.ToString()

        Write-ToConsoleLog "Checking for custom role definitions on management group: $ManagementGroupId ($ManagementGroupDisplayName)" -NoNewLine

        # Get all custom role definitions scoped to this management group
        $customRoleDefinitions = (az role definition list --custom-role-only true --scope "/providers/Microsoft.Management/managementGroups/$ManagementGroupId" --query "[].{name:name,roleName:roleName,id:id,assignableScopes:assignableScopes}" -o json) | ConvertFrom-Json

        $customRoleDefinitions = $customRoleDefinitions | Where-Object {
            $_.assignableScopes -contains "/providers/Microsoft.Management/managementGroups/$ManagementGroupId"
        }

        if (-not $customRoleDefinitions -or $customRoleDefinitions.Count -eq 0) {
            Write-ToConsoleLog "No custom role definitions found on management group: $ManagementGroupId ($ManagementGroupDisplayName), skipping." -NoNewLine
            return
        }

        Write-ToConsoleLog "Found $($customRoleDefinitions.Count) custom role definition(s) on management group: $ManagementGroupId ($ManagementGroupDisplayName)" -NoNewLine

        # For each custom role definition, find and delete all assignments first
        foreach ($roleDefinition in $customRoleDefinitions) {
            Write-ToConsoleLog "Processing custom role definition: $($roleDefinition.roleName) (ID: $($roleDefinition.name))" -NoNewLine

            # Find all role assignments for this custom role on the management group
            Write-ToConsoleLog "Checking for role assignments of custom role '$($roleDefinition.roleName)' on management group: $ManagementGroupId ($ManagementGroupDisplayName)" -NoNewLine
            $mgRoleAssignments = (az role assignment list --role $roleDefinition.roleName --scope "/providers/Microsoft.Management/managementGroups/$ManagementGroupId" --query "[].{id:id,principalName:principalName,principalId:principalId}" -o json) | ConvertFrom-Json

            if ($mgRoleAssignments -and $mgRoleAssignments.Count -gt 0) {
                Write-ToConsoleLog "Found $($mgRoleAssignments.Count) role assignment(s) of custom role '$($roleDefinition.roleName)' on management group: $ManagementGroupId ($ManagementGroupDisplayName)" -NoNewLine

                $mgRoleAssignments | ForEach-Object -Parallel {
                    $assignment = $_
                    $roleDefinitionName = $using:roleDefinition.roleName
                    $managementGroupId = $using:ManagementGroupId
                    $managementGroupDisplayName = $using:ManagementGroupDisplayName
                    $funcWriteToConsoleLog = $using:funcWriteToConsoleLog
                    ${function:Write-ToConsoleLog} = $funcWriteToConsoleLog

                    Write-ToConsoleLog "Deleting role assignment of custom role '$roleDefinitionName' for principal: $($assignment.principalName) ($($assignment.principalId)) from management group: $managementGroupId ($managementGroupDisplayName)" -NoNewLine

                    if($using:PlanMode) {
                        Write-ToConsoleLog `
                            "Deleting role assignment of custom role '$roleDefinitionName' for principal: $($assignment.principalName) ($($assignment.principalId)) from management group: $managementGroupId ($managementGroupDisplayName)", `
                            "Would run: az role assignment delete --ids $($assignment.id)" `
                            -IsPlan -LogFilePath $using:TempLogFileForPlan
                    } else {
                        $result = az role assignment delete --ids $assignment.id 2>&1
                        if (!$result) {
                            Write-ToConsoleLog "Deleted role assignment of custom role '$roleDefinitionName' from management group: $managementGroupId ($managementGroupDisplayName)" -NoNewLine
                        } else {
                            Write-ToConsoleLog "Failed to delete role assignment of custom role '$roleDefinitionName' from management group: $managementGroupId ($managementGroupDisplayName)" -IsWarning -NoNewLine
                        }
                    }
                } -ThrottleLimit $using:ThrottleLimit
            } else {
                Write-ToConsoleLog "No role assignments found for custom role '$($roleDefinition.roleName)' on management group: $ManagementGroupId ($ManagementGroupDisplayName)" -NoNewLine
            }

            # Find all role assignments for this custom role on subscriptions under the management group
            if ($Subscriptions -and $Subscriptions.Count -gt 0) {
                Write-ToConsoleLog "Checking for role assignments of custom role '$($roleDefinition.roleName)' on subscriptions under management group: $ManagementGroupId ($ManagementGroupDisplayName)" -NoNewLine

                $Subscriptions | ForEach-Object -Parallel {
                    $subscription = $_
                    $roleDefinition = $using:roleDefinition
                    $managementGroupId = $using:ManagementGroupId
                    $managementGroupDisplayName = $using:ManagementGroupDisplayName
                    $funcWriteToConsoleLog = $using:funcWriteToConsoleLog
                    ${function:Write-ToConsoleLog} = $funcWriteToConsoleLog

                    Write-ToConsoleLog "Checking for role assignments of custom role '$($roleDefinition.roleName)' on subscription: $($subscription.Name) (ID: $($subscription.Id))" -NoNewLine

                    $subRoleAssignments = (az role assignment list --role $roleDefinition.roleName --subscription $subscription.Id --query "[].{id:id,principalName:principalName,principalId:principalId}" -o json) | ConvertFrom-Json

                    if ($subRoleAssignments -and $subRoleAssignments.Count -gt 0) {
                        Write-ToConsoleLog "Found $($subRoleAssignments.Count) role assignment(s) of custom role '$($roleDefinition.roleName)' on subscription: $($subscription.Name) (ID: $($subscription.Id))" -NoNewLine

                        foreach ($assignment in $subRoleAssignments) {
                            Write-ToConsoleLog "Deleting role assignment of custom role '$($roleDefinition.roleName)' for principal: $($assignment.principalName) ($($assignment.principalId)) from subscription: $($subscription.Name) (ID: $($subscription.Id))" -NoNewLine

                            if($using:PlanMode) {
                                Write-ToConsoleLog `
                                    "Deleting role assignment of custom role '$($roleDefinition.roleName)' for principal: $($assignment.principalName) ($($assignment.principalId)) from subscription: $($subscription.Name) (ID: $($subscription.Id))", `
                                    "Would run: az role assignment delete --ids $($assignment.id)" `
                                    -IsPlan -LogFilePath $using:TempLogFileForPlan
                            } else {
                                $result = az role assignment delete --ids $assignment.id 2>&1
                                if (!$result) {
                                    Write-ToConsoleLog "Deleted role assignment of custom role '$($roleDefinition.roleName)' from subscription: $($subscription.Name) (ID: $($subscription.Id))" -NoNewLine
                                } else {
                                    Write-ToConsoleLog "Failed to delete role assignment of custom role '$($roleDefinition.roleName)' from subscription: $($subscription.Name) (ID: $($subscription.Id))" -IsWarning -NoNewLine
                                }
                            }
                        }
                    } else {
                        Write-ToConsoleLog "No role assignments found for custom role '$($roleDefinition.roleName)' on subscription: $($subscription.Name) (ID: $($subscription.Id))" -NoNewLine
                    }
                } -ThrottleLimit $using:ThrottleLimit
            }

            # Now delete the custom role definition itself
            Write-ToConsoleLog "Deleting custom role definition: $($roleDefinition.roleName) (ID: $($roleDefinition.name))" -NoNewLine

            if($PlanMode) {
                Write-ToConsoleLog `
                    "Deleting custom role definition: $($roleDefinition.roleName) (ID: $($roleDefinition.name))", `
                    "Would run: az role definition delete --name $($roleDefinition.name) --scope `"/providers/Microsoft.Management/managementGroups/$ManagementGroupId`"" `
                    -IsPlan -LogFilePath $using:TempLogFileForPlan
            } else {
                $result = az role definition delete --name $roleDefinition.name --scope "/providers/Microsoft.Management/managementGroups/$ManagementGroupId" 2>&1
                if (!$result) {
                    Write-ToConsoleLog "Deleted custom role definition: $($roleDefinition.roleName) (ID: $($roleDefinition.name))" -NoNewLine
                } else {
                    Write-ToConsoleLog "Failed to delete custom role definition: $($roleDefinition.roleName) (ID: $($roleDefinition.name))" -IsWarning -NoNewLine
                }
            }
        }

        Write-ToConsoleLog "All custom role definitions processed for management group: $ManagementGroupId ($ManagementGroupDisplayName)" -NoNewLine
    }

    # Main execution starts here
    if ($PSCmdlet.ShouldProcess("Delete Management Groups and Clean Subscriptions", "delete")) {

        Test-RequiredTooling

        $TempLogFileForPlan = ""
        if($PlanMode) {
            Write-ToConsoleLog "Plan Mode enabled, no changes will be made. All actions will be logged as what would be performed." -IsWarning
            $TempLogFileForPlan = (New-TemporaryFile).FullName
        }

        $funcWriteToConsoleLog = ${function:Write-ToConsoleLog}.ToString()
        $funcRemoveOrphanedRoleAssignmentsForScope = ${function:Remove-OrphanedRoleAssignmentsForScope}.ToString()
        $funcRemoveDeploymentsForScope = ${function:Remove-DeploymentsForScope}.ToString()
        $funcRemoveCustomRoleDefinitionsForScope = ${function:Remove-CustomRoleDefinitionsForScope}.ToString()

        if($BypassConfirmation) {
            Write-ToConsoleLog "Bypass confirmation enabled, proceeding without prompts..." -IsWarning
            Write-ToConsoleLog "This is a highly destructive operation that will permanently delete Azure resources!" -IsWarning
            Write-ToConsoleLog "We are waiting $BypassConfirmationTimeoutSeconds seconds to allow for cancellation. Press any key to cancel..." -IsWarning

            $keyPressed = $false
            $secondsRunning = 0

            while((-not $keyPressed) -and ($secondsRunning -lt $BypassConfirmationTimeoutSeconds)){
                $keyPressed = [Console]::KeyAvailable
                Write-ToConsoleLog ("Waiting for: $($BypassConfirmationTimeoutSeconds-$secondsRunning) seconds. Press any key to cancel...") -IsWarning -Overwrite
                Start-Sleep -Seconds 1
                $secondsRunning++
            }

            if($keyPressed) {
                Write-ToConsoleLog "Cancellation key pressed, exiting without making any changes..." -IsError
                return
            }
        }

        Write-ToConsoleLog "Thanks for providing the inputs, getting started..." -IsSuccess

        $managementGroupsProvided = $ManagementGroups.Count -gt 0
        $subscriptionsProvided = $Subscriptions.Count -gt 0

        if(-not $subscriptionsProvided -and -not $managementGroupsProvided) {
            Write-ToConsoleLog "No management groups or subscriptions provided, nothing to do. Exiting..." -IsError
            return
        }

        if(-not $managementGroupsProvided) {
            Write-ToConsoleLog "No management groups provided, skipping..." -IsWarning
        }

        $subscriptionsFound = [System.Collections.Concurrent.ConcurrentBag[hashtable]]::new()

        if($managementGroupsProvided) {
            $managementGroupsFound = @()

            if($SubscriptionsTargetManagementGroup) {
                Write-ToConsoleLog "Validating target management group for subscriptions: $SubscriptionsTargetManagementGroup"

                $managementGroupObject = (az account management-group show --name $SubscriptionsTargetManagementGroup) | ConvertFrom-Json
                if($null -eq $managementGroupObject) {
                    Write-ToConsoleLog "Target management group for subscriptions not found: $SubscriptionsTargetManagementGroup" -IsError
                    return
                }

                Write-ToConsoleLog "Subscriptions removed from management groups will be moved to target management group: $($managementGroupObject.name) ($($managementGroupObject.displayName))"
            }

            Write-ToConsoleLog "Validating provided management groups..."
            foreach($managementGroup in $ManagementGroups) {
                $managementGroupObject = (az account management-group show --name $managementGroup) | ConvertFrom-Json

                if($null -eq $managementGroupObject) {
                    Write-ToConsoleLog "Management group not found: $managementGroup" -IsWarning
                    continue
                }

                $managementGroupsFound += @{
                    Name        = $managementGroupObject.name
                    DisplayName = $managementGroupObject.displayName
                }
            }

            if($managementGroupsFound.Count -eq 0) {
                Write-ToConsoleLog "No valid management groups found from the provided list, exiting..." -IsError
                return
            }

            if(-not $BypassConfirmation) {
                Write-ToConsoleLog "The following Management Groups will be processed for removal:"
                $managementGroupsFound | ForEach-Object { Write-ToConsoleLog "Management Group: $($_.Name) ($($_.DisplayName))" -NoNewLine }
                $warningMessage = "ALL THE MANAGEMENT GROUP STRUCTURES ONE LEVEL BELOW THE LISTED MANAGEMENT GROUPS WILL BE PERMANENTLY DELETED"
                $confirmationText = "I CONFIRM I UNDERSTAND ALL THE MANAGEMENT GROUP STRUCTURES ONE LEVEL BELOW THE LISTED MANAGEMENT GROUPS WILL BE PERMANENTLY DELETED"
                if($DeleteTargetManagementGroups) {
                    $warningMessage = "ALL THE LISTED MANAGEMENTS GROUPS AND THEIR CHILDREN WILL BE PERMANENTLY DELETED"
                    $confirmationText = "I CONFIRM I UNDERSTAND ALL THE MANAGEMENT GROUPS AND THEIR CHILDREN WILL BE PERMANENTLY DELETED"
                }
                $continue = Invoke-PromptForConfirmation `
                    -message $warningMessage `
                    -initialConfirmationText $confirmationText
                if(-not $continue) {
                    Write-ToConsoleLog "Exiting..."
                    return
                }
            }

            $funcGetManagementGroupChildrenRecursive = ${function:Get-ManagementGroupChildrenRecursive}.ToString()

            if(-not $subscriptionsProvided) {
                Write-ToConsoleLog "No subscriptions provided, they will be discovered from the target management group hierarchy..."
            }

            if($managementGroupsFound.Count -ne 0) {
                $managementGroupsFound | ForEach-Object -Parallel {
                    $subscriptionsProvided = $using:subscriptionsProvided
                    $subscriptionsFound = $using:subscriptionsFound
                    $subscriptionsTargetManagementGroup = $using:SubscriptionsTargetManagementGroup
                    $deleteTargetManagementGroups = $using:DeleteTargetManagementGroups
                    $funcWriteToConsoleLog = $using:funcWriteToConsoleLog
                    ${function:Write-ToConsoleLog} = $funcWriteToConsoleLog
                    $TempLogFileForPlan = $using:TempLogFileForPlan

                    $managementGroupId = $_.Name
                    $managementGroupDisplayName = $_.DisplayName

                    Write-ToConsoleLog "Finding management group: $managementGroupId ($managementGroupDisplayName)" -NoNewLine
                    $topLevelManagementGroup = (az account management-group show --name $managementGroupId --expand --recurse) | ConvertFrom-Json

                    $hasChildren = $topLevelManagementGroup.children -and $topLevelManagementGroup.children.Count -gt 0

                    $managementGroupsToDelete = @{}

                    $targetManagementGroups = $deleteTargetManagementGroups ? @($topLevelManagementGroup) : @($topLevelManagementGroup.children)

                    if($hasChildren -or $deleteTargetManagementGroups) {
                        ${function:Get-ManagementGroupChildrenRecursive} = $using:funcGetManagementGroupChildrenRecursive
                        $managementGroupsToDelete = Get-ManagementGroupChildrenRecursive -ManagementGroups @($targetManagementGroups)
                    } else {
                        Write-ToConsoleLog "Management group has no children: $managementGroupId ($managementGroupDisplayName)" -NoNewLine
                    }

                    $reverseKeys = $managementGroupsToDelete.Keys | Sort-Object -Descending

                    $throttleLimit = $using:ThrottleLimit
                    $planMode = $using:PlanMode

                    foreach($depth in $reverseKeys) {
                        $managementGroups = $managementGroupsToDelete[$depth]

                        Write-ToConsoleLog "Deleting management groups at depth: $depth" -NoNewLine

                        $managementGroups | ForEach-Object -Parallel {
                            $subscriptionsFound = $using:subscriptionsFound
                            $subscriptionsTargetManagementGroup = $using:SubscriptionsTargetManagementGroup
                            $funcWriteToConsoleLog = $using:funcWriteToConsoleLog
                            ${function:Write-ToConsoleLog} = $funcWriteToConsoleLog

                            $subscriptions = (az account management-group subscription show-sub-under-mg --name $_) | ConvertFrom-Json
                            if ($subscriptions.Count -gt 0) {
                                Write-ToConsoleLog "Management group has subscriptions: $_" -NoNewLine
                                foreach ($subscription in $subscriptions) {
                                    Write-ToConsoleLog "Removing subscription from management group: $_, subscription: $($subscription.displayName)" -NoNewLine
                                    if(-not $subscriptionsProvided) {
                                        $subscriptionsFound.Add(
                                            @{
                                                Id   = $subscription.name
                                                Name = $subscription.displayName
                                            }
                                        )
                                    }

                                    if($subscriptionsTargetManagementGroup) {
                                        Write-ToConsoleLog "Moving subscription from management group $_ to target management group: $($subscriptionsTargetManagementGroup), subscription: $($subscription.displayName)" -NoNewLine
                                        if($using:PlanMode) {
                                            Write-ToConsoleLog `
                                                "Moving subscription from management group $_ to target management group: $($subscriptionsTargetManagementGroup), subscription: $($subscription.displayName)", `
                                                "Would run: az account management-group subscription add --name $($subscriptionsTargetManagementGroup) --subscription $($subscription.name)" `
                                                -IsPlan -LogFilePath $using:TempLogFileForPlan
                                        } else {
                                            $result = (az account management-group subscription add --name $subscriptionsTargetManagementGroup --subscription $subscription.name 2>&1)
                                            if($result) {
                                                Write-ToConsoleLog "Failed to move subscription to target management group: $($subscriptionsTargetManagementGroup), subscription: $($subscription.displayName)" -IsWarning -NoNewLine
                                            } else {
                                                Write-ToConsoleLog "Moved subscription to target management group: $($subscriptionsTargetManagementGroup), subscription: $($subscription.displayName)" -NoNewLine
                                            }
                                        }
                                    } else {
                                        Write-ToConsoleLog "Removing subscription from management group $($_): $($subscription.displayName)" -NoNewLine
                                        if($using:PlanMode) {
                                            Write-ToConsoleLog `
                                                "Removing subscription from management group $($_): $($subscription.displayName)", `
                                                "Would run: az account management-group subscription remove --name $_ --subscription $($subscription.name)" `
                                                -IsPlan -LogFilePath $using:TempLogFileForPlan
                                        } else {
                                            $result = (az account management-group subscription remove --name $_ --subscription $subscription.name 2>&1)
                                            if($result) {
                                                Write-ToConsoleLog "Failed to remove subscription from management group: $_, subscription: $($subscription.displayName)" -IsWarning -NoNewLine
                                            } else {
                                                Write-ToConsoleLog "Removed subscription from management group: $_, subscription: $($subscription.displayName)" -NoNewLine
                                            }
                                        }
                                    }
                                }
                            } else {
                                Write-ToConsoleLog "Management group has no subscriptions: $_" -NoNewline
                            }

                            Write-ToConsoleLog "Deleting management group: $_" -NoNewline
                            if($using:PlanMode) {
                                Write-ToConsoleLog `
                                    "Deleting management group: $_", `
                                    "Would run: az account management-group delete --name $_" `
                                    -IsPlan -LogFilePath $using:TempLogFileForPlan
                            } else {
                                $result = (az account management-group delete --name $_ 2>&1)
                                if($result -like "*Error*") {
                                    Write-ToConsoleLog "Failed to delete management group: $_" -IsWarning -NoNewline
                                } else {
                                    Write-ToConsoleLog "Deleted management group: $_" -NoNewline
                                }
                            }
                        } -ThrottleLimit $using:ThrottleLimit
                    }
                } -ThrottleLimit $ThrottleLimit
            }

            # Delete deployments from target management groups that are not being deleted
            if($managementGroupsFound.Count -ne 0 -and -not $SkipDeploymentDeletion -and -not $DeleteTargetManagementGroups) {
                $managementGroupsFound | ForEach-Object -Parallel {
                    $managementGroupId = $_.Name
                    $managementGroupDisplayName = $_.DisplayName
                    $deleteTargetManagementGroups = $using:DeleteTargetManagementGroups
                    $funcWriteToConsoleLog = $using:funcWriteToConsoleLog
                    ${function:Write-ToConsoleLog} = $funcWriteToConsoleLog
                    $funcRemoveDeploymentsForScope = $using:funcRemoveDeploymentsForScope
                    ${function:Remove-DeploymentsForScope} = $funcRemoveDeploymentsForScope

                    Remove-DeploymentsForScope `
                        -ScopeType "management group" `
                        -ScopeNameForLogs "$managementGroupId ($managementGroupDisplayName)" `
                        -ScopeId $managementGroupId `
                        -ThrottleLimit $using:ThrottleLimit `
                        -PlanMode:$using:PlanMode `
                        -TempLogFileForPlan $using:TempLogFileForPlan

                } -ThrottleLimit $ThrottleLimit
            } else {
                Write-ToConsoleLog "Skipping deployment deletion for management groups" -NoNewLine
            }

            # Delete orphaned role assignments from target management groups that are not being deleted
            if($managementGroupsFound.Count -ne 0 -and -not $SkipOrphanedRoleAssignmentDeletion -and -not $DeleteTargetManagementGroups) {
                $managementGroupsFound | ForEach-Object -Parallel {
                    $managementGroupId = $_.Name
                    $managementGroupDisplayName = $_.DisplayName
                    $deleteTargetManagementGroups = $using:DeleteTargetManagementGroups
                    $funcWriteToConsoleLog = $using:funcWriteToConsoleLog
                    ${function:Write-ToConsoleLog} = $funcWriteToConsoleLog
                    $funcRemoveOrphanedRoleAssignmentsForScope = $using:funcRemoveOrphanedRoleAssignmentsForScope
                    ${function:Remove-OrphanedRoleAssignmentsForScope} = $funcRemoveOrphanedRoleAssignmentsForScope

                    Remove-OrphanedRoleAssignmentsForScope `
                        -ScopeType "management group" `
                        -ScopeNameForLogs "$managementGroupId ($managementGroupDisplayName)" `
                        -ScopeId $managementGroupId `
                        -ThrottleLimit $using:ThrottleLimit `
                        -PlanMode:$using:PlanMode `
                        -TempLogFileForPlan $using:TempLogFileForPlan

                } -ThrottleLimit $ThrottleLimit
            } else {
                Write-ToConsoleLog "Skipping orphaned role assignment deletion for management groups" -NoNewLine
            }
        }

        if($subscriptionsProvided) {
            Write-ToConsoleLog "Checking the provided subscriptions exist..."

            foreach($subscription in $Subscriptions) {
                $subscriptionObject = @{
                    Id   = (Test-IsGuid -StringGuid $subscription) ? $subscription : (az account list --all --query "[?name=='$subscription'].id" -o tsv)
                    Name = (Test-IsGuid -StringGuid $subscription) ? (az account list --all --query "[?id=='$subscription'].name" -o tsv) : $subscription
                }
                if(-not $subscriptionObject.Id -or -not $subscriptionObject.Name) {
                    Write-ToConsoleLog "Subscription not found, skipping: $($subscription.Name) (ID: $($subscription.Id))" -IsWarning
                    continue
                }
                $subscriptionsFound.Add($subscriptionObject)
            }
        }

        $subscriptionsFinal = $subscriptionsFound.ToArray() | Sort-Object -Property name -Unique

        if($subscriptionsFinal.Count -eq 0) {
            Write-ToConsoleLog "No subscriptions provided or found, skipping resource group deletion..." -IsWarning
        } else {
            if(-not $BypassConfirmation) {
                Write-ToConsoleLog "The following Subscriptions were provided or discovered during management group cleanup:"
                $subscriptionsFinal | ForEach-Object { Write-ToConsoleLog "Name: $($_.Name), ID: $($_.Id)" -NoNewline }
                $continue = Invoke-PromptForConfirmation `
                    -Message "ALL RESOURCE GROUPS IN THE LISTED SUBSCRIPTIONS WILL BE PERMANENTLY DELETED UNLESS THEY MATCH RETENTION PATTERNS" `
                    -InitialConfirmationText "I CONFIRM I UNDERSTAND ALL SELECTED RESOURCE GROUPS IN THE NAMED SUBSCRIPTIONS WILL BE PERMANENTLY DELETED"
                if(-not $continue) {
                    Write-ToConsoleLog "Exiting..."
                    return
                }
            }
        }

        if($subscriptionsFinal.Count -ne 0) {
            $subscriptionsFinal | ForEach-Object -Parallel {
                $funcWriteToConsoleLog = $using:funcWriteToConsoleLog
                ${function:Write-ToConsoleLog} = $funcWriteToConsoleLog
                $funcRemoveOrphanedRoleAssignmentsForScope = $using:funcRemoveOrphanedRoleAssignmentsForScope
                ${function:Remove-OrphanedRoleAssignmentsForScope} = $funcRemoveOrphanedRoleAssignmentsForScope
                $funcRemoveDeploymentsForScope = $using:funcRemoveDeploymentsForScope
                ${function:Remove-DeploymentsForScope} = $funcRemoveDeploymentsForScope
                $TempLogFileForPlan = $using:TempLogFileForPlan
                $throttleLimit = $using:ThrottleLimit
                $planMode = $using:PlanMode

                $subscription = $_
                Write-ToConsoleLog "Finding resource groups for subscription: $($subscription.Name) (ID: $($subscription.Id))" -NoNewline

                $resourceGroups = (az group list --subscription $subscription.Id) | ConvertFrom-Json

                if ($resourceGroups.Count -eq 0) {
                    Write-ToConsoleLog "No resource groups found for subscription: $($subscription.Name) (ID: $($subscription.Id)), skipping." -NoNewline
                } else {
                    Write-ToConsoleLog "Found resource groups for subscription: $($subscription.Name) (ID: $($subscription.Id)), count: $($resourceGroups.Count)" -NoNewline

                    $resourceGroupsToDelete = @()
                    $resourceGroupsToRetainNamePatterns = $using:ResourceGroupsToRetainNamePatterns

                    foreach ($resourceGroup in $resourceGroups) {
                        $foundMatch = $false

                        foreach ($pattern in $resourceGroupsToRetainNamePatterns) {
                            if ($resourceGroup.name -match $pattern) {
                                Write-ToConsoleLog "Retaining resource group as it matches the pattern '$pattern': $($resourceGroup.name) in subscription: $($subscription.Name) (ID: $($subscription.Id))" -NoNewLine
                                $foundMatch = $true
                                break
                            }
                        }

                        if($foundMatch) {
                            continue
                        }

                        $resourceGroupsToDelete += @{
                            ResourceGroupName = $resourceGroup.name
                            Subscription      = $subscription
                        }
                    }

                    $shouldRetry = $true

                    while($shouldRetry) {
                        $shouldRetry = $false
                        $resourceGroupsToRetry = [System.Collections.Concurrent.ConcurrentBag[hashtable]]::new()
                        $resourceGroupsToDelete | ForEach-Object -Parallel {
                            $funcWriteToConsoleLog = $using:funcWriteToConsoleLog
                            ${function:Write-ToConsoleLog} = $funcWriteToConsoleLog
                            $resourceGroupName = $_.ResourceGroupName
                            $subscription = $_.Subscription

                            Write-ToConsoleLog "Deleting resource group for subscription: $($subscription.Name) (ID: $($subscription.Id)), resource group: $($ResourceGroupName)" -NoNewLine
                            $result = $null
                            if($using:PlanMode) {
                                Write-ToConsoleLog `
                                    "Deleting resource group for subscription: $($subscription.Name) (ID: $($subscription.Id)), resource group: $($ResourceGroupName)", `
                                    "Would run: az group delete --name $ResourceGroupName --subscription $($subscription.Id) --yes" `
                                    -IsPlan -LogFilePath $using:TempLogFileForPlan
                            } else {
                                $result = az group delete --name $ResourceGroupName --subscription $subscription.Id --yes 2>&1
                            }

                            if (!$result) {
                                Write-ToConsoleLog "Deleted resource group for subscription: $($subscription.Name) (ID: $($subscription.Id)), resource group: $($ResourceGroupName)" -NoNewLine
                            } else {
                                Write-ToConsoleLog "Delete resource group failed for subscription: $($subscription.Name) (ID: $($subscription.Id)), resource group: $($ResourceGroupName)" -NoNewLine
                                Write-ToConsoleLog "It will be retried once the other resource groups in the subscription have reported their status." -NoNewLine
                                $retries = $using:resourceGroupsToRetry
                                $retries.Add($_)
                            }
                        } -ThrottleLimit $using:ThrottleLimit

                        if($resourceGroupsToRetry.Count -gt 0) {
                            Write-ToConsoleLog "Some resource groups failed to delete and will be retried in subscription: $($subscription.Name) (ID: $($subscription.Id))" -NoNewLine
                            $shouldRetry = $true
                            $resourceGroupsToDelete = $resourceGroupsToRetry.ToArray()
                        } else {
                            Write-ToConsoleLog "All resource groups deleted successfully in subscription: $($subscription.Name) (ID: $($subscription.Id))." -NoNewLine
                        }
                    }
                }

                if(-not $using:SkipDefenderPlanReset) {
                    Write-ToConsoleLog "Checking for Microsoft Defender for Cloud Plans to reset in subscription: $($subscription.Name) (ID: $($subscription.Id))"
                    $defenderPlans = (az security pricing list --subscription $subscription.Id) | ConvertFrom-Json

                    $defenderPlans.value | Where-Object { -not $_.deprecated } | ForEach-Object -Parallel {
                        $subscription = $using:subscription
                        $funcWriteToConsoleLog = $using:funcWriteToConsoleLog
                        ${function:Write-ToConsoleLog} = $funcWriteToConsoleLog

                        if ($_.pricingTier -ne "Free") {
                            Write-ToConsoleLog "Resetting Microsoft Defender for Cloud Plan to Free for plan: $($_.name) in subscription: $($subscription.Name) (ID: $($subscription.Id))" -NoNewLine
                            $result = $null
                            if($using:PlanMode) {
                                Write-ToConsoleLog `
                                    "Resetting Microsoft Defender for Cloud Plan to Free for plan: $($_.name) in subscription: $($subscription.Name) (ID: $($subscription.Id))", `
                                    "Would run: az security pricing create --name $($_.name) --tier `"Free`" --subscription $($subscription.Id)" `
                                    -IsPlan -LogFilePath $using:TempLogFileForPlan
                            } else {
                                $result = (az security pricing create --name $_.name --tier "Free" --subscription $subscription.Id 2>&1)
                            }
                            if ($result -like "*must be 'Standard'*") {
                                Write-ToConsoleLog "Resetting Microsoft Defender for Cloud Plan to Standard as Free is not supported for plan: $($_.name) in subscription: $($subscription.Name) (ID: $($subscription.Id))" -NoNewLine
                                if($using:PlanMode) {
                                    Write-ToConsoleLog `
                                        "Resetting Microsoft Defender for Cloud Plan to Standard for plan: $($_.name) in subscription: $($subscription.Name) (ID: $($subscription.Id))", `
                                        "Would run: az security pricing create --name $($_.name) --tier `"Standard`" --subscription $($subscription.Id)" `
                                        -IsPlan -LogFilePath $using:TempLogFileForPlan
                                } else {
                                    $result = az security pricing create --name $_.name --tier "Standard" --subscription $subscription.Id
                                }
                            }
                            Write-ToConsoleLog "Microsoft Defender for Cloud Plan reset for plan: $($_.name) in subscription: $($subscription.Name) (ID: $($subscription.Id))" -NoNewLine
                        } else {
                            Write-ToConsoleLog "Microsoft Defender for Cloud Plan is already set to Free for plan: $($_.name) in subscription: $($subscription.Name) (ID: $($subscription.Id)), skipping." -NoNewLine
                        }
                    } -ThrottleLimit $using:ThrottleLimit
                } else {
                    Write-ToConsoleLog "Skipping Microsoft Defender for Cloud Plans reset in subscription: $($subscription.Name) (ID: $($subscription.Id))" -NoNewLine
                }

                if(-not $using:SkipDeploymentDeletion) {
                    Remove-DeploymentsForScope `
                        -ScopeType "subscription" `
                        -ScopeNameForLogs "$($subscription.Name) (ID: $($subscription.Id))" `
                        -ScopeId $subscription.Id `
                        -ThrottleLimit $using:ThrottleLimit `
                        -PlanMode:$using:PlanMode `
                        -TempLogFileForPlan $using:TempLogFileForPlan
                } else {
                    Write-ToConsoleLog "Skipping subscription level deployment deletion in subscription: $($subscription.Name) (ID: $($subscription.Id))" -NoNewLine
                }

                if(-not $using:SkipOrphanedRoleAssignmentDeletion) {
                    Remove-OrphanedRoleAssignmentsForScope `
                        -ScopeType "subscription" `
                        -ScopeNameForLogs "$($subscription.Name) (ID: $($subscription.Id))" `
                        -ScopeId $subscription.Id `
                        -ThrottleLimit $using:ThrottleLimit `
                        -PlanMode:$using:PlanMode `
                        -TempLogFileForPlan $using:TempLogFileForPlan
                } else {
                    Write-ToConsoleLog "Skipping orphaned role assignment deletion in subscription: $($subscription.Name) (ID: $($subscription.Id))" -NoNewLine
                }

            } -ThrottleLimit $ThrottleLimit
        }

        # Delete custom role definitions from target management groups that are not being deleted
        if($managementGroupsFound.Count -ne 0 -and -not $SkipCustomRoleDefinitionDeletion -and -not $DeleteTargetManagementGroups) {
            $managementGroupsFound | ForEach-Object -Parallel {
                $managementGroupId = $_.Name
                $managementGroupDisplayName = $_.DisplayName
                $funcWriteToConsoleLog = $using:funcWriteToConsoleLog
                ${function:Write-ToConsoleLog} = $funcWriteToConsoleLog
                $funcRemoveCustomRoleDefinitionsForScope = $using:funcRemoveCustomRoleDefinitionsForScope
                ${function:Remove-CustomRoleDefinitionsForScope} = $funcRemoveCustomRoleDefinitionsForScope

                Remove-CustomRoleDefinitionsForScope `
                    -ManagementGroupId $managementGroupId `
                    -ManagementGroupDisplayName $managementGroupDisplayName `
                    -Subscriptions $using:subscriptionsFinal `
                    -ThrottleLimit $using:ThrottleLimit `
                    -PlanMode:$using:PlanMode `
                    -TempLogFileForPlan $using:TempLogFileForPlan

            } -ThrottleLimit $ThrottleLimit
        } else {
            Write-ToConsoleLog "Skipping custom role definition deletion for management groups" -NoNewLine
        }

        Write-ToConsoleLog "Cleanup completed." -IsSuccess

        if($PlanMode) {
            Write-ToConsoleLog "Plan mode enabled, no changes were made." -IsWarning
            $planLogContents = Get-Content -Path $TempLogFileForPlan -Raw
            Write-ToConsoleLog "Plan mode log contents:`n$planLogContents" -Color Gray
            Remove-Item -Path $TempLogFileForPlan -Force
        }
    }
}

# SIG # Begin signature block
# MIIoLwYJKoZIhvcNAQcCoIIoIDCCKBwCAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCCgYyKkbokY/DYf
# j/0anNnWv/b0rwQGfebptN6k3Iy4MaCCDXYwggX0MIID3KADAgECAhMzAAAEhV6Z
# 7A5ZL83XAAAAAASFMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD
# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p
# bmcgUENBIDIwMTEwHhcNMjUwNjE5MTgyMTM3WhcNMjYwNjE3MTgyMTM3WjB0MQsw
# CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u
# ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
# AQDASkh1cpvuUqfbqxele7LCSHEamVNBfFE4uY1FkGsAdUF/vnjpE1dnAD9vMOqy
# 5ZO49ILhP4jiP/P2Pn9ao+5TDtKmcQ+pZdzbG7t43yRXJC3nXvTGQroodPi9USQi
# 9rI+0gwuXRKBII7L+k3kMkKLmFrsWUjzgXVCLYa6ZH7BCALAcJWZTwWPoiT4HpqQ
# hJcYLB7pfetAVCeBEVZD8itKQ6QA5/LQR+9X6dlSj4Vxta4JnpxvgSrkjXCz+tlJ
# 67ABZ551lw23RWU1uyfgCfEFhBfiyPR2WSjskPl9ap6qrf8fNQ1sGYun2p4JdXxe
# UAKf1hVa/3TQXjvPTiRXCnJPAgMBAAGjggFzMIIBbzAfBgNVHSUEGDAWBgorBgEE
# AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQUuCZyGiCuLYE0aU7j5TFqY05kko0w
# RQYDVR0RBD4wPKQ6MDgxHjAcBgNVBAsTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEW
# MBQGA1UEBRMNMjMwMDEyKzUwNTM1OTAfBgNVHSMEGDAWgBRIbmTlUAXTgqoXNzci
# tW2oynUClTBUBgNVHR8ETTBLMEmgR6BFhkNodHRwOi8vd3d3Lm1pY3Jvc29mdC5j
# b20vcGtpb3BzL2NybC9NaWNDb2RTaWdQQ0EyMDExXzIwMTEtMDctMDguY3JsMGEG
# CCsGAQUFBwEBBFUwUzBRBggrBgEFBQcwAoZFaHR0cDovL3d3dy5taWNyb3NvZnQu
# Y29tL3BraW9wcy9jZXJ0cy9NaWNDb2RTaWdQQ0EyMDExXzIwMTEtMDctMDguY3J0
# MAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADggIBACjmqAp2Ci4sTHZci+qk
# tEAKsFk5HNVGKyWR2rFGXsd7cggZ04H5U4SV0fAL6fOE9dLvt4I7HBHLhpGdE5Uj
# Ly4NxLTG2bDAkeAVmxmd2uKWVGKym1aarDxXfv3GCN4mRX+Pn4c+py3S/6Kkt5eS
# DAIIsrzKw3Kh2SW1hCwXX/k1v4b+NH1Fjl+i/xPJspXCFuZB4aC5FLT5fgbRKqns
# WeAdn8DsrYQhT3QXLt6Nv3/dMzv7G/Cdpbdcoul8FYl+t3dmXM+SIClC3l2ae0wO
# lNrQ42yQEycuPU5OoqLT85jsZ7+4CaScfFINlO7l7Y7r/xauqHbSPQ1r3oIC+e71
# 5s2G3ClZa3y99aYx2lnXYe1srcrIx8NAXTViiypXVn9ZGmEkfNcfDiqGQwkml5z9
# nm3pWiBZ69adaBBbAFEjyJG4y0a76bel/4sDCVvaZzLM3TFbxVO9BQrjZRtbJZbk
# C3XArpLqZSfx53SuYdddxPX8pvcqFuEu8wcUeD05t9xNbJ4TtdAECJlEi0vvBxlm
# M5tzFXy2qZeqPMXHSQYqPgZ9jvScZ6NwznFD0+33kbzyhOSz/WuGbAu4cHZG8gKn
# lQVT4uA2Diex9DMs2WHiokNknYlLoUeWXW1QrJLpqO82TLyKTbBM/oZHAdIc0kzo
# STro9b3+vjn2809D0+SOOCVZMIIHejCCBWKgAwIBAgIKYQ6Q0gAAAAAAAzANBgkq
# hkiG9w0BAQsFADCBiDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24x
# EDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlv
# bjEyMDAGA1UEAxMpTWljcm9zb2Z0IFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5
# IDIwMTEwHhcNMTEwNzA4MjA1OTA5WhcNMjYwNzA4MjEwOTA5WjB+MQswCQYDVQQG
# EwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwG
# A1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSgwJgYDVQQDEx9NaWNyb3NvZnQg
# Q29kZSBTaWduaW5nIFBDQSAyMDExMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIIC
# CgKCAgEAq/D6chAcLq3YbqqCEE00uvK2WCGfQhsqa+laUKq4BjgaBEm6f8MMHt03
# a8YS2AvwOMKZBrDIOdUBFDFC04kNeWSHfpRgJGyvnkmc6Whe0t+bU7IKLMOv2akr
# rnoJr9eWWcpgGgXpZnboMlImEi/nqwhQz7NEt13YxC4Ddato88tt8zpcoRb0Rrrg
# OGSsbmQ1eKagYw8t00CT+OPeBw3VXHmlSSnnDb6gE3e+lD3v++MrWhAfTVYoonpy
# 4BI6t0le2O3tQ5GD2Xuye4Yb2T6xjF3oiU+EGvKhL1nkkDstrjNYxbc+/jLTswM9
# sbKvkjh+0p2ALPVOVpEhNSXDOW5kf1O6nA+tGSOEy/S6A4aN91/w0FK/jJSHvMAh
# dCVfGCi2zCcoOCWYOUo2z3yxkq4cI6epZuxhH2rhKEmdX4jiJV3TIUs+UsS1Vz8k
# A/DRelsv1SPjcF0PUUZ3s/gA4bysAoJf28AVs70b1FVL5zmhD+kjSbwYuER8ReTB
# w3J64HLnJN+/RpnF78IcV9uDjexNSTCnq47f7Fufr/zdsGbiwZeBe+3W7UvnSSmn
# Eyimp31ngOaKYnhfsi+E11ecXL93KCjx7W3DKI8sj0A3T8HhhUSJxAlMxdSlQy90
# lfdu+HggWCwTXWCVmj5PM4TasIgX3p5O9JawvEagbJjS4NaIjAsCAwEAAaOCAe0w
# ggHpMBAGCSsGAQQBgjcVAQQDAgEAMB0GA1UdDgQWBBRIbmTlUAXTgqoXNzcitW2o
# ynUClTAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTALBgNVHQ8EBAMCAYYwDwYD
# VR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBRyLToCMZBDuRQFTuHqp8cx0SOJNDBa
# BgNVHR8EUzBRME+gTaBLhklodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20vcGtpL2Ny
# bC9wcm9kdWN0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFfMDNfMjIuY3JsMF4GCCsG
# AQUFBwEBBFIwUDBOBggrBgEFBQcwAoZCaHR0cDovL3d3dy5taWNyb3NvZnQuY29t
# L3BraS9jZXJ0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFfMDNfMjIuY3J0MIGfBgNV
# HSAEgZcwgZQwgZEGCSsGAQQBgjcuAzCBgzA/BggrBgEFBQcCARYzaHR0cDovL3d3
# dy5taWNyb3NvZnQuY29tL3BraW9wcy9kb2NzL3ByaW1hcnljcHMuaHRtMEAGCCsG
# AQUFBwICMDQeMiAdAEwAZQBnAGEAbABfAHAAbwBsAGkAYwB5AF8AcwB0AGEAdABl
# AG0AZQBuAHQALiAdMA0GCSqGSIb3DQEBCwUAA4ICAQBn8oalmOBUeRou09h0ZyKb
# C5YR4WOSmUKWfdJ5DJDBZV8uLD74w3LRbYP+vj/oCso7v0epo/Np22O/IjWll11l
# hJB9i0ZQVdgMknzSGksc8zxCi1LQsP1r4z4HLimb5j0bpdS1HXeUOeLpZMlEPXh6
# I/MTfaaQdION9MsmAkYqwooQu6SpBQyb7Wj6aC6VoCo/KmtYSWMfCWluWpiW5IP0
# wI/zRive/DvQvTXvbiWu5a8n7dDd8w6vmSiXmE0OPQvyCInWH8MyGOLwxS3OW560
# STkKxgrCxq2u5bLZ2xWIUUVYODJxJxp/sfQn+N4sOiBpmLJZiWhub6e3dMNABQam
# ASooPoI/E01mC8CzTfXhj38cbxV9Rad25UAqZaPDXVJihsMdYzaXht/a8/jyFqGa
# J+HNpZfQ7l1jQeNbB5yHPgZ3BtEGsXUfFL5hYbXw3MYbBL7fQccOKO7eZS/sl/ah
# XJbYANahRr1Z85elCUtIEJmAH9AAKcWxm6U/RXceNcbSoqKfenoi+kiVH6v7RyOA
# 9Z74v2u3S5fi63V4GuzqN5l5GEv/1rMjaHXmr/r8i+sLgOppO6/8MO0ETI7f33Vt
# Y5E90Z1WTk+/gFcioXgRMiF670EKsT/7qMykXcGhiJtXcVZOSEXAQsmbdlsKgEhr
# /Xmfwb1tbWrJUnMTDXpQzTGCGg8wghoLAgEBMIGVMH4xCzAJBgNVBAYTAlVTMRMw
# EQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVN
# aWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNp
# Z25pbmcgUENBIDIwMTECEzMAAASFXpnsDlkvzdcAAAAABIUwDQYJYIZIAWUDBAIB
# BQCggbAwGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQwHAYKKwYBBAGCNwIBCzEO
# MAwGCisGAQQBgjcCARUwLwYJKoZIhvcNAQkEMSIEIMJYNZY3FGKS5q5SqaYtQbne
# ONQvVUTUvuS34QsypmYnMEQGCisGAQQBgjcCAQwxNjA0oBSAEgBNAGkAYwByAG8A
# cwBvAGYAdKEcgBpodHRwczovL3d3dy5taWNyb3NvZnQuY29tIDANBgkqhkiG9w0B
# AQEFAASCAQARf5YMPwOznFERihxnqmW9qgTURdgJAyMRDGNyJKOzrJeWFZhKUr/v
# /F13HJWT9mkGYzqna/FXdcULXt/wMe53ZL60ucnDWEHpJTu/Ge+bAqfwlU5wloQu
# BSgbybqd8OvIbipyePTTHa/JbMxFpETYushdYzQ3pMTkNjGSF3AjD79LP7Sw8q2Q
# 0xy+2n0aLEz0F3HNDqOD9fXNFI3xd7UfOqN+eWPk6JvYN8f6EH2fAedo4eRIbkPo
# B37qLlQbKFImOoIh+HX+1HC9ukbXMvENabrTFwivtpU2tGxt9COSGA04ewyO66Wq
# 7/AjsAd2kaB3jreqHNd04iMpmdW/HDSxoYIXlzCCF5MGCisGAQQBgjcDAwExgheD
# MIIXfwYJKoZIhvcNAQcCoIIXcDCCF2wCAQMxDzANBglghkgBZQMEAgEFADCCAVIG
# CyqGSIb3DQEJEAEEoIIBQQSCAT0wggE5AgEBBgorBgEEAYRZCgMBMDEwDQYJYIZI
# AWUDBAIBBQAEIJRaF+MiTR1yCGJOikLqIYD9HngNbfbHnf+YZGVE+AiwAgZpJzpM
# 2D0YEzIwMjUxMjAyMTczMzExLjgwOFowBIACAfSggdGkgc4wgcsxCzAJBgNVBAYT
# AlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYD
# VQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJTAjBgNVBAsTHE1pY3Jvc29mdCBB
# bWVyaWNhIE9wZXJhdGlvbnMxJzAlBgNVBAsTHm5TaGllbGQgVFNTIEVTTjo4NjAz
# LTA1RTAtRDk0NzElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2Vydmlj
# ZaCCEe0wggcgMIIFCKADAgECAhMzAAACBywROYnNhfvFAAEAAAIHMA0GCSqGSIb3
# DQEBCwUAMHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYD
# VQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJjAk
# BgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwMB4XDTI1MDEzMDE5
# NDI1MloXDTI2MDQyMjE5NDI1MlowgcsxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpX
# YXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQg
# Q29ycG9yYXRpb24xJTAjBgNVBAsTHE1pY3Jvc29mdCBBbWVyaWNhIE9wZXJhdGlv
# bnMxJzAlBgNVBAsTHm5TaGllbGQgVFNTIEVTTjo4NjAzLTA1RTAtRDk0NzElMCMG
# A1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2VydmljZTCCAiIwDQYJKoZIhvcN
# AQEBBQADggIPADCCAgoCggIBAMU//3p0+Zx+A4N7f+e4W964Gy38mZLFKQ6fz1kX
# K0dCbfjiIug+qRXCz4KJR6NBpsp/79zspTWerACaa2I+cbzObhKX35EllpDgPHeq
# 0D2Z1B1LsKF/phRs/hn77yVo1tNCKAmhcKbOVXfi+YLjOkWsRPgoABONdI8rSxC4
# WEqvuW01owUZyVdKciFydJyP1BQNUtCkCwm2wofIc3tw3vhoRcukUZzUj5ZgVHFp
# OCpI+oZF8R+5DbIasBtaMlg5e555MDUxUqFbzPNISl+Mp4r+3Ze4rKSkJRoqfmzy
# yo1sjdse3+sT+k3PBacArP484FFsnEiSYv6f8QxWKvm7y7JY+XW3zwwrnnUAZWH7
# YfjOJHXhgPHPIIb3biBqicqOJxidZQE61euc8roBL8s3pj7wrGHbprq8psVvNqpZ
# cCPMSJDwRj0r2lgj8oLKCLGMPAd9SBVJYLJPwrDuYYHJRmZE8/Fc42W4x78/wK0E
# kym6HwIFbKO8V8WY5I1ErwRORSaVNQBHUIg5p4GosbCxxKEV/K8NCtsKGaFeJvid
# ExflT1iv13tVxgefp5kmyDLOHlAqUhsJAL9i+EUrjZx4IEMxtz463lHpP8zBx7mN
# XJUKapdXFY5pBzisDadXuicw5kLpS8IbwsYVJkGePWWgMMtaj8j5G5GiTaP9DjNw
# yfCRAgMBAAGjggFJMIIBRTAdBgNVHQ4EFgQUcrVSYsK9etAK9H3wkGrXz/jOjR4w
# HwYDVR0jBBgwFoAUn6cVXQBeYl2D9OXSZacbUzUZ6XIwXwYDVR0fBFgwVjBUoFKg
# UIZOaHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9jcmwvTWljcm9zb2Z0
# JTIwVGltZS1TdGFtcCUyMFBDQSUyMDIwMTAoMSkuY3JsMGwGCCsGAQUFBwEBBGAw
# XjBcBggrBgEFBQcwAoZQaHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9j
# ZXJ0cy9NaWNyb3NvZnQlMjBUaW1lLVN0YW1wJTIwUENBJTIwMjAxMCgxKS5jcnQw
# DAYDVR0TAQH/BAIwADAWBgNVHSUBAf8EDDAKBggrBgEFBQcDCDAOBgNVHQ8BAf8E
# BAMCB4AwDQYJKoZIhvcNAQELBQADggIBAOO7Sq49ueLHSyUSMPuPbbbilg48ZOZ0
# O87T5s1EI2RmpS/Ts/Tid/Uh/dj+IkSZRpTvDXYWbnzYiakP8rDYKVes0os9ME7q
# d/G848a1qWkCXjCqgaBnG+nFvbIS6cbjJlDoRA6mDV0T245ejN7eAPgeO1xzvmRx
# rzKK+jAQj6uFe5VRYHu+iDhMZTEp2cO+mTkZIZec6E8OF0h36DqFHJd1mLCARr6r
# 0z1dy3PhMaEOA4oWxjEWFc0lmj0pG4arp6+G3I125iuTOMO1ZLqBbxqRHn1SG4sa
# xWr7gCCoRjxaVeNAYzY5OTIGeVAukHyoPvH2NGljYKrQ5ZaUrTB8f/XN5+tY3n5t
# 7ztLDZM9wi50gmff1tsMbtrAoxVgMd+w8nxm/GBaRm5/plkCSmHR5gaHchXzjm1o
# uR0s4K/Dj1bGqFrkOaLY6OHwaNrm/2TJjcpMXJfdPgLaxzF+Cn/rFF34MY6E1U+9
# U9r/fJFSpjmzlRinLwOdumlXudA7ax7ce8JJutv7I/J6hvWRR8xhr18TZsSygxs5
# odGAaOLxk+38l3Zs991CgEdxQ6o/CMcFQhxJzvF0lliNFvibzWrGOZrcMuO44WWM
# xlNii9GIa8Qwv3FmPakdFTK/6zm/tUbBwzquM1gzirNlAzoDZEZgkZTvzQZAbRA7
# 3zD6y5y5NWt9MIIHcTCCBVmgAwIBAgITMwAAABXF52ueAptJmQAAAAAAFTANBgkq
# hkiG9w0BAQsFADCBiDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24x
# EDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlv
# bjEyMDAGA1UEAxMpTWljcm9zb2Z0IFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5
# IDIwMTAwHhcNMjEwOTMwMTgyMjI1WhcNMzAwOTMwMTgzMjI1WjB8MQswCQYDVQQG
# EwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwG
# A1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQg
# VGltZS1TdGFtcCBQQ0EgMjAxMDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoC
# ggIBAOThpkzntHIhC3miy9ckeb0O1YLT/e6cBwfSqWxOdcjKNVf2AX9sSuDivbk+
# F2Az/1xPx2b3lVNxWuJ+Slr+uDZnhUYjDLWNE893MsAQGOhgfWpSg0S3po5GawcU
# 88V29YZQ3MFEyHFcUTE3oAo4bo3t1w/YJlN8OWECesSq/XJprx2rrPY2vjUmZNqY
# O7oaezOtgFt+jBAcnVL+tuhiJdxqD89d9P6OU8/W7IVWTe/dvI2k45GPsjksUZzp
# cGkNyjYtcI4xyDUoveO0hyTD4MmPfrVUj9z6BVWYbWg7mka97aSueik3rMvrg0Xn
# Rm7KMtXAhjBcTyziYrLNueKNiOSWrAFKu75xqRdbZ2De+JKRHh09/SDPc31BmkZ1
# zcRfNN0Sidb9pSB9fvzZnkXftnIv231fgLrbqn427DZM9ituqBJR6L8FA6PRc6ZN
# N3SUHDSCD/AQ8rdHGO2n6Jl8P0zbr17C89XYcz1DTsEzOUyOArxCaC4Q6oRRRuLR
# vWoYWmEBc8pnol7XKHYC4jMYctenIPDC+hIK12NvDMk2ZItboKaDIV1fMHSRlJTY
# uVD5C4lh8zYGNRiER9vcG9H9stQcxWv2XFJRXRLbJbqvUAV6bMURHXLvjflSxIUX
# k8A8FdsaN8cIFRg/eKtFtvUeh17aj54WcmnGrnu3tz5q4i6tAgMBAAGjggHdMIIB
# 2TASBgkrBgEEAYI3FQEEBQIDAQABMCMGCSsGAQQBgjcVAgQWBBQqp1L+ZMSavoKR
# PEY1Kc8Q/y8E7jAdBgNVHQ4EFgQUn6cVXQBeYl2D9OXSZacbUzUZ6XIwXAYDVR0g
# BFUwUzBRBgwrBgEEAYI3TIN9AQEwQTA/BggrBgEFBQcCARYzaHR0cDovL3d3dy5t
# aWNyb3NvZnQuY29tL3BraW9wcy9Eb2NzL1JlcG9zaXRvcnkuaHRtMBMGA1UdJQQM
# MAoGCCsGAQUFBwMIMBkGCSsGAQQBgjcUAgQMHgoAUwB1AGIAQwBBMAsGA1UdDwQE
# AwIBhjAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFNX2VsuP6KJcYmjRPZSQ
# W9fOmhjEMFYGA1UdHwRPME0wS6BJoEeGRWh0dHA6Ly9jcmwubWljcm9zb2Z0LmNv
# bS9wa2kvY3JsL3Byb2R1Y3RzL01pY1Jvb0NlckF1dF8yMDEwLTA2LTIzLmNybDBa
# BggrBgEFBQcBAQROMEwwSgYIKwYBBQUHMAKGPmh0dHA6Ly93d3cubWljcm9zb2Z0
# LmNvbS9wa2kvY2VydHMvTWljUm9vQ2VyQXV0XzIwMTAtMDYtMjMuY3J0MA0GCSqG
# SIb3DQEBCwUAA4ICAQCdVX38Kq3hLB9nATEkW+Geckv8qW/qXBS2Pk5HZHixBpOX
# PTEztTnXwnE2P9pkbHzQdTltuw8x5MKP+2zRoZQYIu7pZmc6U03dmLq2HnjYNi6c
# qYJWAAOwBb6J6Gngugnue99qb74py27YP0h1AdkY3m2CDPVtI1TkeFN1JFe53Z/z
# jj3G82jfZfakVqr3lbYoVSfQJL1AoL8ZthISEV09J+BAljis9/kpicO8F7BUhUKz
# /AyeixmJ5/ALaoHCgRlCGVJ1ijbCHcNhcy4sa3tuPywJeBTpkbKpW99Jo3QMvOyR
# gNI95ko+ZjtPu4b6MhrZlvSP9pEB9s7GdP32THJvEKt1MMU0sHrYUP4KWN1APMdU
# bZ1jdEgssU5HLcEUBHG/ZPkkvnNtyo4JvbMBV0lUZNlz138eW0QBjloZkWsNn6Qo
# 3GcZKCS6OEuabvshVGtqRRFHqfG3rsjoiV5PndLQTHa1V1QJsWkBRH58oWFsc/4K
# u+xBZj1p/cvBQUl+fpO+y/g75LcVv7TOPqUxUYS8vwLBgqJ7Fx0ViY1w/ue10Cga
# iQuPNtq6TPmb/wrpNPgkNWcr4A245oyZ1uEi6vAnQj0llOZ0dFtq0Z4+7X6gMTN9
# vMvpe784cETRkPHIqzqKOghif9lwY1NNje6CbaUFEMFxBmoQtB1VM1izoXBm8qGC
# A1AwggI4AgEBMIH5oYHRpIHOMIHLMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2Fz
# aGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENv
# cnBvcmF0aW9uMSUwIwYDVQQLExxNaWNyb3NvZnQgQW1lcmljYSBPcGVyYXRpb25z
# MScwJQYDVQQLEx5uU2hpZWxkIFRTUyBFU046ODYwMy0wNUUwLUQ5NDcxJTAjBgNV
# BAMTHE1pY3Jvc29mdCBUaW1lLVN0YW1wIFNlcnZpY2WiIwoBATAHBgUrDgMCGgMV
# ANO9VT9iP2VRLJ4MJqInYNrmFSJLoIGDMIGApH4wfDELMAkGA1UEBhMCVVMxEzAR
# BgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1p
# Y3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3Rh
# bXAgUENBIDIwMTAwDQYJKoZIhvcNAQELBQACBQDs2aE5MCIYDzIwMjUxMjAyMTcz
# MjQxWhgPMjAyNTEyMDMxNzMyNDFaMHcwPQYKKwYBBAGEWQoEATEvMC0wCgIFAOzZ
# oTkCAQAwCgIBAAICG8wCAf8wBwIBAAICExUwCgIFAOza8rkCAQAwNgYKKwYBBAGE
# WQoEAjEoMCYwDAYKKwYBBAGEWQoDAqAKMAgCAQACAwehIKEKMAgCAQACAwGGoDAN
# BgkqhkiG9w0BAQsFAAOCAQEAOdQdZ7ABVZScBOahj4jbpzFyytbtPzTHbSLzPWNa
# XNkot4orp3kbcKLMikjHxGN3LL6eVgivTVRoNJrZJhaQ/2e9DaQTOjOnYhi0taDd
# WwhB4Aj/K/nBX5vAFrAEwtOdBuyqJm4MxAH+yxAEynruEHmRlzrlCMkC5WACIyRS
# woc6D0Dj8onj9oyKfCCQDmbB3+awNC/cM6f1NJlWWd5AaBmZbetLzDTdpSurONLS
# hbgHmSZATqiqsVJzqRVmISvGvMrkAcLIVy4YQ7Q/AAlgpnBIETV65rqNwy1tYOrR
# J2Gs/EqPwZWYL7SYis3ZSXp5mewIyARkUUdab+okSGfOCDGCBA0wggQJAgEBMIGT
# MHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdS
# ZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMT
# HU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwAhMzAAACBywROYnNhfvFAAEA
# AAIHMA0GCWCGSAFlAwQCAQUAoIIBSjAaBgkqhkiG9w0BCQMxDQYLKoZIhvcNAQkQ
# AQQwLwYJKoZIhvcNAQkEMSIEIF6M2FHkOCCpAAdfRxcydoRD2BxCgLvIJRSY/Ihp
# JZhvMIH6BgsqhkiG9w0BCRACLzGB6jCB5zCB5DCBvQQgL/fU0dB2zUhnmw+e/n2n
# 6/oGMEOCgM8jCICafQ8ep7MwgZgwgYCkfjB8MQswCQYDVQQGEwJVUzETMBEGA1UE
# CBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9z
# b2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQ
# Q0EgMjAxMAITMwAAAgcsETmJzYX7xQABAAACBzAiBCAqRosu8MvPH+lqYEJVzFQu
# Vw0hoaGxwjpaBVWRZvgH3jANBgkqhkiG9w0BAQsFAASCAgCkz2M333FlTOeeYw7y
# VjVE5Yezb0OYgX9lqy0nBvR3MJCik6ZbomsTubWa2Dn5MRd24EklG7esE9jJXcdZ
# QmhlQ3g+l1hwaMPK+GqTqR1zSw75+JeIrr7WoGVxgKXgfBqD3deCLlWYvNe2M+O3
# cVRRktr6QkxTyeTpCqIy42lzCfzLnBtc6DQPm9JtTngyXo/7MR9kOb1iwclAMTtA
# dIPBXlypx9TQ6ko9uvMQHfFWBHNr983+z0EMdwVvkn0GZBBVf2aRbSZ+w6u0EkjK
# 19sCYCFt1ozxPAstKJVsJfX0YGCxGZPgBGXHYEoUQ+Pp7DN4ywYqDR/Lpx9v3Cw8
# XCiVEgNliQ5/3ilkTAweiExBEcHsjLoeRfLTMBKSj+laMhcx3I/LZ6sJEZivrLbM
# K3h29mcHodrPw0gBxyj62BbBC4JnZi/G2qus1r9jWD9dOoWHNWnQ/LOy4FAij/JP
# nC7qPNRppP2hPyJ5fCnlxShs1YgGpy4Sntv2xe/CkZoslasX/n2IxzTz+WF5w5BT
# dqWC877mWgiIlnDq/Qdg7qR++bTJ6rtfVGg42M/Cr9u24N87RajGGmBfkUbAeRg/
# NlWkqCXhZIJgCbHLBDg+/gR0D/stv7oUtC805E8JdiAQYtKu5SWLchKStF/uPu8e
# nxzIf6GGIIcD7fQqzI1o6AR5Mw==
# SIG # End signature block