Functions/Export-AHPolicySetDefinition.ps1

<#
.Synopsis
   Exports an Azure Policy Initiative definition (also known as a policy set) and the associated policy definitions
.DESCRIPTION
   Exports an Azure Policy Initiative definition (also known as a policy set) and the associated policy definitions
.EXAMPLE
   Export-AHPolicySetDefinition -PolicySetDefinitionId '/providers/Microsoft.Management/managementGroups/TestManagementGroup0/providers/Microsoft.Authorization/policySetDefinitions/General Policies V2' -Force
.EXAMPLE
    $MyPolicySets = Get-AzPolicySetDefinition | select @{N='DisplayName';E={$_.Properties.DisplayName}}, @{N='Description';E={$_.Properties.Description}}, resourceId | ogv -PassThru
   ($MyPolicySets).ResourceId | %{Export-AHPolicySetDefinition -PolicySetDefinitionId $_ -OutputDir 'C:\myPolicies'}

   I'm lazy, so if I want to export a ton of policy sets but I don't want to type out a bunch of stuff I use this
.NOTES
   You can use the following one-liner to help you find the policy set definition ID you want
   $MyPolicySets = Get-AzPolicySetDefinition | select @{N='DisplayName';E={$_.Properties.DisplayName}}, @{N='Description';E={$_.Properties.Description}}, resourceId | ogv -PassThru
   ($MyPolicySets).ResourceId | %{Export-AHPolicySetDefinition -PolicySetDefinitionId $_ -OutputDir 'C:\myPolicies'}
.PARAMETER PolicySetDefinitionId
   The PolicySet definition Id to export
.PARAMETER OutputDir
   The directory to write the policy set to - by default it writes to the current working directory
.PARAMETER Force
   Forces the overwriting of an existing policy set even if the folder already exists
#>

function Export-AHPolicySetDefinition {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [string]
        [ValidateScript({
                $result = Get-AzPolicySetDefinition -Id $_
                If ($result.GetType().Name -eq 'PsPolicySetDefinition' -or $result.GetType().BaseType.Name -eq 'Object') { $true }Else { $false }
            })]
        $PolicySetDefinitionId,
        [Parameter(Mandatory = $false)]
        [string]
        [ValidateScript({
                Test-Path $_
            })]
        $OutputDir = '.', #set to . by default and validate it is valid
        [Parameter(Mandatory = $false)]
        [int]
        [ValidateRange(10, 100)]
        $NumChars = 40,
        [switch]
        $Force #include to overwrite directory
        
    )
    
    begin {
        If ($PSVersionTable.PSVersion.Major -lt 7) {
            throw 'This cmdlet requires PowerShell 7 or greater'
        }
        $numchars = 40 #number of characters to use of the display name before truncating - we don't want 300 character file names
        #define helper function
        function Copy-Property {
            [CmdletBinding()]
            param([Parameter(ValueFromPipeline = $true)]$InputObject,
                $SourceObject,
                [string[]]$Property,
                [switch]$Passthru,
                [switch]$ForceLowercaseKeys)
               
            $passthruHash = @{Passthru = $passthru.IsPresent }
            
            $propHash = @{}
            $property | ForEach-Object {
                If ($ForceLowercaseKeys) {
                    $propHash += @{$_.ToLower() = $SourceObject.$_ }
                }
                Else {
                    $propHash += @{$_ = $SourceObject.$_ }
                }
            }
            $inputObject | Add-Member -NotePropertyMembers $propHash @passthruHash
        }
    }
    process {
        $policySets = Get-AzPolicySetDefinition -Id $PolicySetDefinitionId
        ForEach ($policySet in $policySets) {
            #Region Create folder for the initiative
            #If ([string]::IsNullOrEmpty($policySet.Properties.DisplayName)) {
            # $folderName = $policySet.Name
            #}
            #Else {
            $folderName = If ($policySet.ResourceName.Length -le $numchars) { $policySet.ResourceName }else { $policySet.ResourceName.Substring(0, $numchars - 1) }
            #}
            #$folderName += $policySet.Name
            $folderPath = Join-Path $OutputDir $folderName
            If (Test-Path $folderPath) {
                If ($Force) {
                    Remove-Item $folderPath -Force -Confirm:$false -Recurse
                }
                Else {
                    throw 'folder already exists'
                    return 1
                }
            }
            New-Item -Path $folderPath -ItemType Directory | Out-Null
            #EndRegion

            #Region Export policy set definition
            $policySet.Properties.PolicyDefinitions | ConvertTo-Json -Depth 99 | Out-File "$folderPath\$folderName-Policy.json" #export policy
            #EndRegion
            #Region Export Policy set parameters
            $policySet.Properties.Parameters | ConvertTo-Json -Depth 99 | Out-File "$folderPath\$folderName-Parameters.json" # export parameters
            #EndRegion

            #Region Export each policy definition
            #$nonce = 0 #introduce a nonce in case the first $numChars of a policy description are identical
            ForEach ($item in $policySet.Properties.PolicyDefinitions) {
                
                #$fileName = If ($item.policyDefinitionReferenceId.Length -le $numchars) { $item.policyDefinitionReferenceId }else { $item.policyDefinitionReferenceId.Substring(0, $numchars - 1) }
                #$policy = Get-AzPolicyDefinition -id $item.policyDefinitionId
                #$proposedName = $policy.Properties.DisplayName# + $nonce
                #$fileName = If ($proposedName.Length + $($nonce.tostring().length) -le $numchars) { $proposedName + $nonce.tostring() }else { $proposedName.Substring(0, $numchars - 1 - $($nonce.ToString().Length)) + $nonce.ToString() }
                $fileName = If ($item.policyDefinitionReferenceId.Length -le $numchars) { $item.policyDefinitionReferenceId }else { $item.policyDefinitionReferenceId.Substring(0, $numchars - 1) }
                $fileName += $item.policyDefinitionId.split('/')[-1] # just in case the first $numChars of the policyDefinitionReferenceId are the same on a bunch of policies in a policy set
                $filename = Remove-InvalidFileNameChars $filename
                Export-AHPolicyDefinition -PolicyDefinitionId $item.policyDefinitionId | Out-File -LiteralPath "$folderPath\$filename.json"
                #$nonce += 1
            }
            #EndRegion
        }
    }
    
    end {
        
    }
}