Functions/Import-AHPolicySetDefinition.ps1
<# .Synopsis Imports an Azure Policy Initiative definition (also known as a policy set) and the associated policy definitions .DESCRIPTION Imports an Azure Policy Initiative definition (also known as a policy set) and the associated policy definitions. This will overwrite the PolicySetDefinitionFile with the proper ManagementGroupName specified instead of whatever management group is currently there from the export. This command assumes any necessary definitions are located in the same directory as the PolicySetDefinitionFile .EXAMPLE $ManagementGroupName = 'MyManagementGroup' $PolicySetDefinitionFile = '.\Initiatives\Custom General V2\Custom General V2-Policy.json' $PolicySetParameterFile = '.\Initiatives\Custom General V2\Custom General V2-Parameters.json' $PolicySetName = 'Custom General V2' Import-AHPolicySetDefinition -PolicySetDefinitionFile $PolicySetDefinitionFile -PolicySetParameterFile $PolicySetParameterFile -PolicySetName $PolicySetName -PolicySetDescription $PolicySetName -ManagementGroupName $ManagementGroupName -IncludeMissingPolicyDefinitions This example imports the policy set "Custom General V2". I stored many initiatives in subfolders of the 'initiatives' folder then looped through all of them .NOTES .PARAMETER PolicySetDefinitionFile The PolicySet definition file to import .PARAMETER PolicySetParameterFile The PolicySet parameter file to import .PARAMETER PolicySetName The name for the policy set .PARAMETER PolicySetDescription The description for the policy set .PARAMETER ManagementGroupName The name of the management group to create this policy set in .PARAMETER PolicySetCategory This category will be assigned to the policy set definition .PARAMETER IncludeMissingPolicyDefinitions Imports the policy definitions required for the policy set if they don't already exist in the new environment .PARAMETER PurgeExistingPolicyDefinitions Purges existing policies in the management group that have the same name as one to be imported. Purge will fail if the policy definition is currently in use by policy set but if you want to clean it out completely it will provide you with an error telling you which policy set it is a part of. #> function Import-AHPolicySetDefinition { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [string] [ValidateScript({ Test-Path $_ })] $PolicySetDefinitionFile, [Parameter(Mandatory = $true)] [string] [ValidateScript({ Test-Path $_ })] $PolicySetParameterFile, [switch] $IncludeMissingPolicyDefinitions, [Parameter(Mandatory = $true)] [string] $PolicySetName, [Parameter(Mandatory = $true)] [string] $PolicySetDescription, [Parameter(Mandatory = $false)] [string] $PolicySetCategory, [Parameter(Mandatory = $false)] [string] [ValidateScript({ $temp = Get-AzManagementGroup $_ -ea 0 -wa 0 $temp.gettype().Name -eq 'PSManagementGroup' -or $temp.GetType().BaseType.Name -eq 'Object' })] $ManagementGroupName, [Parameter(Mandatory = $false)] [switch] $PurgeExistingPolicyDefinitions ) begin { If ($PSVersionTable.PSVersion.Major -lt 7) { throw 'This cmdlet requires PowerShell 7 or greater' } $metadataFileName = 'metadata.txt' $builtinPolicies = Get-AzPolicyDefinition -Builtin #this is a stupid function I plan to fix later so I'll add it to private functions later, but for testing it is fine here Function FindByValue { #this function determines if there is a NoteProperty with value $value of any child at any dept of the $object. If there is it returns true, otherwise it returns false. It must be an exact match but is not case sensitive right now. [CmdletBinding()] param($object, $value, $totalResult = $false) #write-verbose "object = $object" #write-verbose "value = $value" #write-verbose '' $result = $false If ($Null -ne $object) { $keys = $object | Get-Member -MemberType NoteProperty $keys | Where-Object { $Null -ne $_ } | ForEach-Object { If ($object.$($_.Name) -eq $value) { #$totalPath + '.' + $($_.Name) $result = $true $totalResult = $result -or $totalResult } Else { $result = FindByValue -object $($object.$($_.Name)) -value $value #-totalPath $($totalPath + '.' + $($_.Name)) #| out-null $totalResult = $result -or $totalResult } } } $totalResult } } process { If ($PurgeExistingPolicyDefinitions) { $PolicyPath = Split-Path -Path $PolicySetDefinitionFile -Parent ForEach ($file in (Get-ChildItem $policyPath -Filter *.json | Where-Object { $_.Name -ne $(Split-Path $PolicySetDefinitionFile -Leaf) -and $_.Name -ne $(Split-Path $PolicySetParameterFile -Leaf) } )) { $policy = Get-Content $file -Raw | ConvertFrom-Json -Depth 99 #################################################################################################################################################### If ($Null -ne $policy.Name -and $Null -ne (Get-AzPolicyDefinition -Name $policy.Name -ManagementGroupName $ManagementGroupName -ErrorAction SilentlyContinue)) { #Check if there are any policy definitions in the same management group with the same name, if so, delete them #delete them Remove-AzPolicyDefinition -Name $policy.Name -ManagementGroupName $ManagementGroupName -Force | Out-Null } } } If ($IncludeMissingPolicyDefinitions) { #this is a dumb way, I know, maybe I'll be smarter later $PolicyPath = Split-Path -Path $PolicySetDefinitionFile -Parent ForEach ($file in (Get-ChildItem $policyPath -Filter *.json | Where-Object { $_.Name -ne $(Split-Path $PolicySetDefinitionFile -Leaf) -and $_.Name -ne $(Split-Path $PolicySetParameterFile -Leaf) } )) { $policy = Get-Content $file -Raw | ConvertFrom-Json -Depth 99 #################################################################################################################################################### If ($builtinPolicies.Name -notcontains $($policy.Name)) { #Check to see if the policy is a builtin one that already exists in the environment if it doesn't exist as a builtin policy then import the policy to the to the management group $results = Get-AzPolicyDefinition -ManagementGroupName $ManagementGroupName | Where-Object { $_.Name -eq $policy.Name } If ($results.count -eq 0) { #If the policy is not already in the environment then prepare the policy for importing ### If the policy definition file contains information about "location" data then automatically fix the location based on (get-azlocation).Location If (FindByValue -object $policy -value 'location') { Write-Warning "The Policy $($policy.Name) contains location data. Please validate location data is correct for this environment." } ### import the policy $PolicyDefinitionSplat = @{} If (![string]::IsNullOrEmpty($policy.Properties.DisplayName)) { $PolicyDefinitionSplat.Add('DisplayName', $($policy.Properties.DisplayName)) } If (![string]::IsNullOrEmpty($policy.Properties.Description)) { $PolicyDefinitionSplat.Add('Description', $($policy.Properties.Description)) } #If (![string]::IsNullOrEmpty($policy.Name)) { $PolicyDefinitionSplat.Add('Name', $policy.Name) } #I didn't use this because the policy definition must always have a name defined $result = New-AzPolicyDefinition @PolicyDefinitionSplat -Name $policy.Name -Policy $file.FullName -ManagementGroupName $ManagementGroupName #-ErrorAction Break #$result = New-AzPolicyDefinition @PolicyDefinitionSplat -Policy $file #-ErrorAction Break Write-Verbose @" Name: $($result.Name) ResourceId: $($result.ResourceId) "@ ### After the policy is imported, modify the PolicySetDefinition JSON to point to the new custom PolicyDefinitionId # The ".id" in the policy definition file will match the PolicyDefinitionId in the PolicySet definition. we want to replace it with the $newDefinitionOutput.PolicyDefinitionId $policySet = Get-Content $PolicySetDefinitionFile -Raw | ConvertFrom-Json -Depth 99 For ($i = 0; $i -lt $policySet.count; $i++) { If ($policySet[$i].policyDefinitionId -eq $policy.id) { $policySet[$i].policyDefinitionId = $result.PolicyDefinitionId } } $policySet | ConvertTo-Json -Depth 99 | Out-File $PolicySetDefinitionFile -Force #then update the Policy Defintion File $policyDefinition = Get-Content $file -Raw | ConvertFrom-Json -Depth 99 $policyDefinition.id = $result.PolicyDefinitionId $policyDefinition | ConvertTo-Json -Depth 99 | Out-File -LiteralPath $file.FullName -Force } } ##################################################################################################################################################### } } ### fix location data in the policy set parameters file - I'm not going to fix location data because it must be considered manually but I should alert ### fix location data in the policy set definition - I'm not going to fix location data because it must be considered manually but I should alert ##handle ManagementGroupName problems here - since the Policy Set's policyDefinitionId is overwritten anyway, this section is no longer needed if the policy didn't exist and needed to get imported $temp = Get-Content $PolicySetDefinitionFile -Raw | ConvertFrom-Json -Depth 99 $NewPolicy = ForEach ($i in $temp) { If ($i.PolicyDefinitionId -like '*/managementGroups/*') { #get managementGroup Name then replace it $arr = $i.PolicyDefinitionId.split('/') $managementGroupIndex = $arr.IndexOf('managementGroups') + 1 $arr[$managementGroupIndex] = $ManagementGroupName $NewPolicyDefinitionId = $arr -join ('/') $i.PolicyDefinitionId = $NewPolicyDefinitionId $i } Else { $i } } $NewPolicy | ConvertTo-Json -Depth 99 | Out-File $PolicySetDefinitionFile -Force #update the DisplayName and Description of the policy set definition to include the version number $metadataFile = $(Join-Path (Split-Path $PolicySetDefinitionFile) $metadataFileName) If (Test-Path $metadataFile) { $metadataContent = Get-Content $metadataFile | ConvertFrom-Json $PolicyVersion = $metadataContent.version if ([string]::IsNullOrEmpty($PolicyVersion)) { Write-Verbose "No version found in $metadataFile. DisplayName and Descrption will not be updated for $PolicySetName." $policySetDescription = $PolicySetDescription $PolicySetDisplayName = $PolicySetName } Else { #Set-AzPolicySetDefinition -Name $policysetname -ManagementGroupName $ManagementGroupName -Description "$PolicySetDescription-$PolicyVersion" -DisplayName "$PolicySetName-$PolicyVersion" $PolicySetDescription = "$PolicySetDescription-$PolicyVersion" $PolicySetDisplayName = "$PolicySetName-$PolicyVersion" } } #autofill policySetDescription depending on metadata? maybe not, maybe force the user to do it since it is a new environemnt... we'll see $PolicySetDefinitionSplat = @{ PolicyDefinition = $PolicySetDefinitionFile Parameter = $PolicySetParameterFile } Write-Verbose "`n`nPolicySetName = $PolicySetName" If (![string]::IsNullOrEmpty($PolicySetName)) { $PolicySetDefinitionSplat.Add('Name', $PolicySetName) } If (![string]::IsNullOrEmpty($PolicySetDisplayName)) { $PolicySetDefinitionSplat.Add('DisplayName', $PolicySetDisplayName) } If (![string]::IsNullOrEmpty($PolicySetDescription)) { $PolicySetDefinitionSplat.Add('Description', $PolicySetDescription) } If (![string]::IsNullOrEmpty($ManagementGroupName)) { $PolicySetDefinitionSplat.Add('ManagementGroupName', $ManagementGroupName) } If (![string]::IsNullOrEmpty($PolicySetCategory)) { $PolicySetDefinitionSplat.Add('Metadata', "{`"category`":`"$PolicySetCategory`"}") } # $result = New-AzPolicySetDefinition -PolicyDefinition $PolicySetDefinitionFile -Parameter $PolicySetParameterFile -Name $PolicySetName -Description $PolicySetDescription -ManagementGroupName $ManagementGroupName $result = New-AzPolicySetDefinition @PolicySetDefinitionSplat Write-Verbose @" Name: $($result.Name) ResourceId: $($result.ResourceId) "@ } end { } } |