functions/Deploy-PolicyPlan.ps1

function Deploy-PolicyPlan {

<#
    Deploys Policy resources from a plan file.
 
    Defines which Policy as Code (PAC) environment we are using, if omitted, the script prompts for a value. The values are read from `$DefinitionsRootFolder/global-settings.jsonc.
 
    Definitions folder path. Defaults to environment variable `$env:PAC_DEFINITIONS_FOLDER or './Definitions'.
 
    Input folder path for plan files. Defaults to environment variable `$env:PAC_INPUT_FOLDER, `$env:PAC_OUTPUT_FOLDER or './Output'.
 
    Use switch to indicate interactive use
 
    Number of virtual cores available to deploy Policy objects in parallel. Defaults to 4.
 
    Deploy-PolicyPlan.ps1 -PacEnvironmentSelector "dev" -DefinitionsRootFolder "C:\git\policy-as-code\Definitions" -InputFolder "C:\git\policy-as-code\Output" -Interactive
    Deploys Policy resources from a plan file.
 
    Deploy-PolicyPlan.ps1 -PacEnvironmentSelector "dev" -Interactive
    Deploys Policy resources from a plan file.
 
    https://azure.github.io/enterprise-azure-policy-as-code/#deployment-scripts
 
#>


[CmdletBinding()]
param (
    [parameter(
        HelpMessage = "Defines which Policy as Code (PAC) environment we are using, if omitted, the script prompts for a value. The values are read from `$DefinitionsRootFolder/global-settings.jsonc.",
        Position = 0
    )]
    [string] $PacEnvironmentSelector,

    [Parameter(HelpMessage = "Definitions folder path. Defaults to environment variable `$env:PAC_DEFINITIONS_FOLDER or './Definitions'.")]
    [string]$DefinitionsRootFolder,

    [Parameter(HelpMessage = "Input folder path for plan files. Defaults to environment variable `$env:PAC_INPUT_FOLDER, `$env:PAC_OUTPUT_FOLDER or './Output'.")]
    [string] $InputFolder,

    [Parameter(HelpMessage = "Use switch to indicate interactive use")]
    [switch] $Interactive,

    [Parameter(HelpMessage = "Number of virtual cores available to deploy Policy objects in parallel. Defaults to 4.")]
    [Int16] $VirtualCores = 4
)

$PSDefaultParameterValues = @{
    "Write-Information:InformationVariable" = "+global:epacInfoStream"
}

Clear-Variable -Name epacInfoStream -Scope global -Force -ErrorAction SilentlyContinue

# Dot Source Helper Scripts

$InformationPreference = "Continue"
$pacEnvironment = Select-PacEnvironment $PacEnvironmentSelector -DefinitionsRootFolder $DefinitionsRootFolder -InputFolder $InputFolder -Interactive $Interactive
$null = Set-AzCloudTenantSubscription -Cloud $pacEnvironment.cloud -TenantId $pacEnvironment.tenantId -Interactive $pacEnvironment.interactive
$throttleLimit = $VirtualCores * 2

# Telemetry
if ($pacEnvironment.telemetryEnabled) {
    Write-Information "Telemetry is enabled"
    [Microsoft.Azure.Common.Authentication.AzureSession]::ClientFactory.AddUserAgent("pid-fe9ff1e8-5521-4b9d-ab1d-84e15447565e") 
}
else {
    Write-Information "Telemetry is disabled"
}
Write-Information ""

$planFile = $pacEnvironment.policyPlanInputFile
$plan = Get-DeploymentPlan -PlanFile $planFile
if ($null -eq $plan) {
    Write-Warning "***************************************************************************************************"
    Write-Warning "Plan file '$planFile' does not exist, skipping Policy resource deployment."
    Write-Warning "***************************************************************************************************"
    Write-Warning ""
}
else {

    Write-Information "***************************************************************************************************"
    Write-Information "Deploy Policy resources from plan in file '$planFile'"
    Write-Information "Plan created on $($plan.createdOn)."
    Write-Information "***************************************************************************************************"

    #region delete exemptions, assignment, definitions

    $table = ConvertTo-HashTable $plan.exemptions.delete
    $table += ConvertTo-HashTable $plan.exemptions.replace
    if ($table.psbase.Count -gt 0) {
        Write-Information ""
        Write-Information "==================================================================================================="
        Write-Information "Delete orphaned, deleted, expired and replaced Exemptions ($($table.psbase.Count))"
        Write-Information "---------------------------------------------------------------------------------------------------"
        $chunks = Split-HashtableIntoChunks -Table $table -NumberOfChunks $throttleLimit -MinChunkingSize 10
        if ($chunks.psbase.Count -eq 1) {
            $chunk = $chunks[0]
            foreach ($id in $chunk.Keys) {
                $entry = $chunk.$id
                Write-Information "$($entry.displayName) - $($id)"
                Remove-AzResourceByIdRestMethod -Id $id -ApiVersion $pacEnvironment.apiVersions.policyExemptions
            }
        }
        else {
            $funcRemoveAzResourceByIdRestMethod = ${function:Remove-AzResourceByIdRestMethod}.ToString()
            $chunks | ForEach-Object -ThrottleLimit $throttleLimit -Parallel {
                if ($null -eq ${function:Remove-AzResourceByIdRestMethod}) {
                    ${function:Remove-AzResourceByIdRestMethod} = $using:funcRemoveAzResourceByIdRestMethod
                }
                $pacEnvironment = $using:pacEnvironment
                foreach ($id in $_.Keys) {
                    $entry = $_.$id
                    Write-Information "$($entry.displayName) - $($id)"
                    Remove-AzResourceByIdRestMethod -Id $id -ApiVersion $pacEnvironment.apiVersions.policyExemptions
                }
            }
        }
    }

    $table = ConvertTo-HashTable $plan.assignments.delete
    $table += ConvertTo-HashTable $plan.assignments.replace
    if ($table.psbase.Count -gt 0) {
        Write-Information ""
        Write-Information "==================================================================================================="
        Write-Information "Delete removed and replaced Assignments ($($table.psbase.Count))"
        Write-Information "---------------------------------------------------------------------------------------------------"
        $chunks = Split-HashtableIntoChunks -Table $table -NumberOfChunks $throttleLimit -MinChunkingSize 10
        if ($chunks.psbase.Count -eq 1) {
            $chunk = $chunks[0]
            foreach ($id in $chunk.Keys) {
                $entry = $chunk.$id
                Write-Information "$($entry.displayName) - $($id)"
                Remove-AzResourceByIdRestMethod -Id $id -ApiVersion $pacEnvironment.apiVersions.policyAssignments
            }
        }
        else {
            $funcRemoveAzResourceByIdRestMethod = ${function:Remove-AzResourceByIdRestMethod}.ToString()
            $chunks | ForEach-Object -ThrottleLimit $throttleLimit -Parallel {
                if ($null -eq ${function:Remove-AzResourceByIdRestMethod}) {
                    ${function:Remove-AzResourceByIdRestMethod} = $using:funcRemoveAzResourceByIdRestMethod
                }
                $pacEnvironment = $using:pacEnvironment
                foreach ($id in $_.Keys) {
                    $entry = $_.$id
                    Write-Information "$($entry.displayName) - $($id)"
                    Remove-AzResourceByIdRestMethod -Id $id -ApiVersion $pacEnvironment.apiVersions.policyAssignments
                }
            }
        }
    }

    $table = ConvertTo-HashTable $plan.policySetDefinitions.delete
    $table += ConvertTo-HashTable $plan.policySetDefinitions.replace
    if ($table.psbase.Count -gt 0) {
        Write-Information ""
        Write-Information "==================================================================================================="
        Write-Information "Delete removed and replaced Policy Sets ($($table.psbase.Count))"
        Write-Information "---------------------------------------------------------------------------------------------------"
        $chunks = Split-HashtableIntoChunks -Table $table -NumberOfChunks $throttleLimit -MinChunkingSize 10
        if ($chunks.psbase.Count -eq 1) {
            $chunk = $chunks[0]
            foreach ($id in $chunk.Keys) {
                $entry = $chunk.$id
                Write-Information "$($entry.displayName) - $($id)"
                Remove-AzResourceByIdRestMethod -Id $id -ApiVersion $pacEnvironment.apiVersions.policySetDefinitions
            }
        }
        else {
            $funcRemoveAzResourceByIdRestMethod = ${function:Remove-AzResourceByIdRestMethod}.ToString()
            $chunks | ForEach-Object -ThrottleLimit $throttleLimit -Parallel {
                if ($null -eq ${function:Remove-AzResourceByIdRestMethod}) {
                    ${function:Remove-AzResourceByIdRestMethod} = $using:funcRemoveAzResourceByIdRestMethod
                }
                $pacEnvironment = $using:pacEnvironment
                foreach ($id in $_.Keys) {
                    $entry = $_.$id
                    Write-Information "$($entry.displayName) - $($id)"
                    Remove-AzResourceByIdRestMethod -Id $id -ApiVersion $pacEnvironment.apiVersions.policySetDefinitions
                }
            }
        }
    }

    $table = ConvertTo-HashTable $plan.policyDefinitions.replace
    if ($table.psbase.Count -gt 0) {
        Write-Information ""
        Write-Information "==================================================================================================="
        Write-Information "Delete replaced Policies ($($table.psbase.Count))"
        Write-Information "---------------------------------------------------------------------------------------------------"
        $chunks = Split-HashtableIntoChunks -Table $table -NumberOfChunks $throttleLimit -MinChunkingSize 10
        if ($chunks.psbase.Count -eq 1) {
            $chunk = $chunks[0]
            foreach ($id in $chunk.Keys) {
                $entry = $chunk.$id
                Write-Information "$($entry.displayName) - $($id)"
                Remove-AzResourceByIdRestMethod -Id $id -ApiVersion $pacEnvironment.apiVersions.policyDefinitions
            }
        }
        else {
            $funcRemoveAzResourceByIdRestMethod = ${function:Remove-AzResourceByIdRestMethod}.ToString()
            $chunks | ForEach-Object -ThrottleLimit $throttleLimit -Parallel {
                if ($null -eq ${function:Remove-AzResourceByIdRestMethod}) {
                    ${function:Remove-AzResourceByIdRestMethod} = $using:funcRemoveAzResourceByIdRestMethod
                }
                $pacEnvironment = $using:pacEnvironment
                foreach ($id in $_.Keys) {
                    $entry = $_.$id
                    Write-Information "$($entry.displayName) - $($id)"
                    Remove-AzResourceByIdRestMethod -Id $id -ApiVersion $pacEnvironment.apiVersions.policyDefinitions
                }
            }
        }
    }

    #endregion

    $table = ConvertTo-HashTable $plan.policyDefinitions.new
    $table += ConvertTo-HashTable $plan.policyDefinitions.replace
    $table += ConvertTo-HashTable $plan.policyDefinitions.update
    if ($table.psbase.Count -gt 0) {
        Write-Information ""
        Write-Information "==================================================================================================="
        Write-Information "Create and update Policies ($($table.psbase.Count))"
        Write-Information "---------------------------------------------------------------------------------------------------"
        $chunks = Split-HashtableIntoChunks -Table $table -NumberOfChunks $throttleLimit -MinChunkingSize 10
        if ($chunks.psbase.Count -eq 1) {
            $chunk = $chunks[0]
            foreach ($id in $chunk.Keys) {
                $entry = $chunk.$id
                Set-AzPolicyDefinitionRestMethod -Definition $entry -ApiVersion $pacEnvironment.apiVersions.policyDefinitions
            }
        }
        else {
            $funcSetAzPolicyDefinitionRestMethod = ${function:Set-AzPolicyDefinitionRestMethod}.ToString()
            $funcRemoveNullFields = ${function:Remove-NullFields}.ToString()
            $chunks | ForEach-Object -ThrottleLimit $throttleLimit -Parallel {
                if ($null -eq ${function:Set-AzPolicyDefinitionRestMethod}) {
                    ${function:Set-AzPolicyDefinitionRestMethod} = $using:funcSetAzPolicyDefinitionRestMethod
                    ${function:Remove-NullFields} = $using:funcRemoveNullFields
                }
                $pacEnvironment = $using:pacEnvironment
                foreach ($id in $_.Keys) {
                    $entry = $_.$id
                    Set-AzPolicyDefinitionRestMethod -Definition $entry -ApiVersion $pacEnvironment.apiVersions.policyDefinitions
                }
            }
        }
    }

    $table = ConvertTo-HashTable $plan.policySetDefinitions.new
    $table += ConvertTo-HashTable $plan.policySetDefinitions.replace
    $table += ConvertTo-HashTable $plan.policySetDefinitions.update
    if ($table.psbase.Count -gt 0) {
        Write-Information ""
        Write-Information "==================================================================================================="
        Write-Information "Create and update Policy Sets ($($table.psbase.Count))"
        Write-Information "---------------------------------------------------------------------------------------------------"
        $chunks = Split-HashtableIntoChunks -Table $table -NumberOfChunks $throttleLimit -MinChunkingSize 10
        if ($chunks.psbase.Count -eq 1) {
            $chunk = $chunks[0]
            foreach ($id in $chunk.Keys) {
                $entry = $chunk.$id
                Set-AzPolicySetDefinitionRestMethod -Definition $entry -ApiVersion $pacEnvironment.apiVersions.policySetDefinitions
            }
        }
        else {
            $funcSetAzPolicySetDefinitionRestMethod = ${function:Set-AzPolicySetDefinitionRestMethod}.ToString()
            $funcRemoveNullFields = ${function:Remove-NullFields}.ToString()
            $chunks | ForEach-Object -ThrottleLimit $throttleLimit -Parallel {
                if ($null -eq ${function:Set-AzPolicySetDefinitionRestMethod}) {
                    ${function:Set-AzPolicySetDefinitionRestMethod} = $using:funcSetAzPolicySetDefinitionRestMethod
                    ${function:Remove-NullFields} = $using:funcRemoveNullFields
                }
                $pacEnvironment = $using:pacEnvironment
                foreach ($id in $_.Keys) {
                    $entry = $_.$id
                    Set-AzPolicySetDefinitionRestMethod -Definition $entry -ApiVersion $pacEnvironment.apiVersions.policySetDefinitions
                }
            }
        }
    }

    # Policy Sets are updated, can now delete the obsolete Policies
    $table = ConvertTo-HashTable $plan.policyDefinitions.delete
    if ($table.psbase.Count -gt 0) {
        Write-Information ""
        Write-Information "==================================================================================================="
        Write-Information "Delete Policies ($($table.psbase.Count))"
        Write-Information "---------------------------------------------------------------------------------------------------"
        $chunks = Split-HashtableIntoChunks -Table $table -NumberOfChunks $throttleLimit -MinChunkingSize 10
        if ($chunks.psbase.Count -eq 1) {
            $chunk = $chunks[0]
            foreach ($id in $chunk.Keys) {
                $entry = $chunk.$id
                Write-Information $entry.displayName
                Remove-AzResourceByIdRestMethod -Id $id -ApiVersion $pacEnvironment.apiVersions.policyDefinitions
            }
        }
        else {
            $funcRemoveAzResourceByIdRestMethod = ${function:Remove-AzResourceByIdRestMethod}.ToString()
            $chunks | ForEach-Object -ThrottleLimit $throttleLimit -Parallel {
                if ($null -eq ${function:Remove-AzResourceByIdRestMethod}) {
                    ${function:Remove-AzResourceByIdRestMethod} = $using:funcRemoveAzResourceByIdRestMethod
                }
                $pacEnvironment = $using:pacEnvironment
                foreach ($id in $_.Keys) {
                    $entry = $_.$id
                    Write-Information "$($entry.displayName) - $($id)"
                    Remove-AzResourceByIdRestMethod -Id $id -ApiVersion $pacEnvironment.apiVersions.policyDefinitions
                }
            }
        }
    }

    $table = ConvertTo-HashTable $plan.assignments.new
    $table += ConvertTo-HashTable $plan.assignments.replace
    $table += ConvertTo-HashTable $plan.assignments.update
    if ($table.psbase.Count -gt 0) {
        Write-Information ""
        Write-Information "==================================================================================================="
        Write-Information "Create and update Assignments ($($table.psbase.Count))"
        Write-Information "---------------------------------------------------------------------------------------------------"
        $chunks = Split-HashtableIntoChunks -Table $table -NumberOfChunks $throttleLimit -MinChunkingSize 10
        if ($chunks.psbase.Count -eq 1) {
            $chunk = $chunks[0]
            foreach ($id in $chunk.Keys) {
                $entry = $chunk.$id
                Set-AzPolicyAssignmentRestMethod -Assignment $entry -ApiVersion $pacEnvironment.apiVersions.policyAssignments
            }
        }
        else {
            $funcSetAzPolicyAssignmentRestMethod = ${function:Set-AzPolicyAssignmentRestMethod}.ToString()
            $funcGetDeepClone = ${function:Get-ClonedObject}.ToString()
            $chunks | ForEach-Object -ThrottleLimit $throttleLimit -Parallel {
                if ($null -eq ${function:Set-AzPolicyAssignmentRestMethod}) {
                    ${function:Set-AzPolicyAssignmentRestMethod} = $using:funcSetAzPolicyAssignmentRestMethod
                    ${function:Get-ClonedObject} = $using:funcGetDeepClone
                }
                $pacEnvironment = $using:pacEnvironment
                foreach ($id in $_.Keys) {
                    $entry = $_.$id
                    Set-AzPolicyAssignmentRestMethod -Assignment $entry -ApiVersion $pacEnvironment.apiVersions.policyAssignments
                }
            }
        }
    }

    $table = ConvertTo-HashTable $plan.exemptions.new
    $table += ConvertTo-HashTable $plan.exemptions.replace
    $table += ConvertTo-HashTable $plan.exemptions.update
    if ($table.psbase.Count -gt 0) {
        Write-Information ""
        Write-Information "==================================================================================================="
        Write-Information "Create and update Exemptions ($($table.psbase.Count))"
        Write-Information "---------------------------------------------------------------------------------------------------"
        $chunks = Split-HashtableIntoChunks -Table $table -NumberOfChunks $throttleLimit -MinChunkingSize 10
        if ($chunks.psbase.Count -eq 1) {
            $chunk = $chunks[0]
            foreach ($exemptionId in $chunk.Keys) {
                $entry = $chunk.$exemptionId
                Set-AzPolicyExemptionRestMethod -ExemptionObj $entry -ApiVersion $pacEnvironment.apiVersions.policyExemptions
            }
        }
        else {
            $funcSetAzPolicyExemptionRestMethod = ${function:Set-AzPolicyExemptionRestMethod}.ToString()
            $funcRemoveNullFields = ${function:Remove-NullFields}.ToString()
            $chunks | ForEach-Object -ThrottleLimit $throttleLimit -Parallel {
                if ($null -eq ${function:Set-AzPolicyExemptionRestMethod}) {
                    ${function:Set-AzPolicyExemptionRestMethod} = $using:funcSetAzPolicyExemptionRestMethod
                    ${function:Remove-NullFields} = $using:funcRemoveNullFields
                }
                $pacEnvironment = $using:pacEnvironment
                foreach ($exemptionId in $_.Keys) {
                    $entry = $_.$exemptionId
                    Set-AzPolicyExemptionRestMethod -ExemptionObj $entry -ApiVersion $pacEnvironment.apiVersions.policyExemptions
                }
            }
        }
    }
    Write-Information ""
    Write-Information "***************************************************************************************************"
    Write-Information "Policy resources deployed from plan in file '$planFile'"
    Write-Information "***************************************************************************************************"
}
}