functions/New-HydrationAssignmentPacSelector.ps1
<#
.SYNOPSIS This function creates a new PAC selector block in an EPAC assignment configuration file based on a source PAC selector assignment. .DESCRIPTION The New-HydrationAssignmentPacSelector function is used to create a new EPAC assignment PAC selector. It takes as input the source PAC selector, the new PAC selector, and optionally the definitions, output, management group hierarchy prefix, and management group hierarchy suffix. It then processes JSON and JSONC files in the specified definitions directory, creating new directories and files as needed. .PARAMETER SourcePacSelector The source PAC selector. The scope will be duplicated in the NewPacSelector scope block. This parameter is mandatory. .PARAMETER NewPacSelector The NewPacSelector to be created. This should already exist in your global-settings.ini file. This parameter is mandatory. .PARAMETER Definitions The directory containing the definitions for your EPAC repo. Defaults to "./Definitions". .PARAMETER Output The directory where the output will be stored for review prior to import into your EPAC repo. Defaults to "./Output". .PARAMETER MGHierarchyPrefix The prefix for the management group hierarchy. This is commonly used when copying to a PacSelector within the existing tenant to avoid naming collisions, such as when setting up the EPAC-Dev DevOps Pipeline Testing deployment hierarchy. This parameter is optional. .PARAMETER MGHierarchySuffix The suffix for the management group hierarchy. This is commonly used when copying to a PacSelector within the existing tenant to avoid naming collisions, such as when setting up the EPAC-Dev DevOps Pipeline Testing deployment hierarchy. This parameter is optional. .EXAMPLE New-HydrationAssignmentPacSelector -SourcePacSelector "Prod" -NewPacSelector "EpacDev" -MGHierarchyPrefix "epac-" This will create a new PAC selector named "EpacDev" based on the "Prod" PacSelector in your global-settings.ini file. .NOTES Assignments to Subscriptions and Resource Group Scopes will not be duplicated, as these cannot be valid in the new environment due to SubscriptionId GUID uniqueness requirements. .LINK https://aka.ms/epac https://github.com/Azure/enterprise-azure-policy-as-code/tree/main/Docs/start-hydration-kit.md #> function New-HydrationAssignmentPacSelector { [CmdletBinding()] param ( [Parameter(Mandatory = $true, HelpMessage = "The source PAC selector. The scope will be duplicated in the NewPacSelector scope block. This parameter is mandatory.")] [string] $SourcePacSelector, [Parameter(Mandatory = $true, HelpMessage = "The NewPacSelector to be created. This should already exist in your global-settings.ini file. This parameter is mandatory.")] [string] $NewPacSelector, [Parameter(Mandatory = $false, HelpMessage = "The directory containing the definitions for your EPAC repo. Defaults to './Definitions'.")] [string] $Definitions = "./Definitions", [Parameter(Mandatory = $false, HelpMessage = "The directory where the output will be stored for review prior to import into your EPAC repo. Defaults to './Output'.")] [string] $Output = "./Output", [Parameter(Mandatory = $false, HelpMessage = "The prefix for the management group hierarchy. This is commonly used when copying to a PacSelector within the existing tenant to avoid naming collisions, such as when setting up the EPAC-Dev DevOps Pipeline Testing deployment hierarchy. This parameter is optional.")] [string] $MGHierarchyPrefix, [Parameter(Mandatory = $false, HelpMessage = "The suffix for the management group hierarchy. This is commonly used when copying to a PacSelector within the existing tenant to avoid naming collisions, such as when setting up the EPAC-Dev DevOps Pipeline Testing deployment hierarchy. This parameter is optional.")] [string] $MGHierarchySuffix ) $sourcePath = Join-Path $Definitions "policyAssignments" $InformationPreference = "Continue" foreach ($s in @($sourcePath, $Definitions)) { if (!(Test-Path -Path $s)) { Write-Error "Path $s does not exist." return } } if (!(Test-Path -Path $Output)) { Write-Information "Creating directory $Output..." $null = New-Item -Path $Output -ItemType Directory -Force } $fileList = Get-ChildItem -Path $sourcePath -Recurse -File -Include "*.json", "*.jsonc" $regex = "(policyAssignments.*)" foreach ($f in $fileList) { $relativePath = (($f.FullName | Select-String -Pattern "(?<=${regex}).*").matches[0].value).Replace('\', '/') $outputFile = Join-Path $Output "UpdatedAssignments" $relativePath $outputParent = Split-Path $outputFile -Parent Write-Debug " relativePath: $relativePath" Write-Debug " outputFile: $outputFile" Write-Debug " NewPacSelector: $newPacSelector" Write-Debug " NewPacSelector Exists: $($json.scope.($NewPacSelector))" $json = Get-Content -Path $f.FullName -Raw | ConvertFrom-Json -Depth 10 if ($json.scope.($NewPacSelector)) { if ($DebugPreference -eq "Continue") { $json.scope.($NewPacSelector) | convertto-json -depth 10 } Write-Warning " Scope $($NewPacSelector) already exists in $($f.FullName), copying file in current state..." if (!(Test-Path -Path $outputParent)) { Write-Debug " Creating directory $outputParent..." $null = New-Item -Path $outputParent -ItemType Directory -Force } $json | ConvertTo-Json -Depth 100 | Set-Content -Path $outputFile continue } else { $scopeList = $json.scope.($SourcePacSelector) if ($json.children) { $i = 0 foreach ($c in $json.children) { $c.scope if ($c.scope.($NewPacSelector)) { $i++ } } } if (!($scopeList -and !($i -gt 0))) { Write-Debug " No scope found for $SourcePacSelector in $($f.FullName), reviewing children..." foreach ($c in $json.children) { Write-Debug " Processing child $($c.nodeName)..." $c.scope.($SourcePacSelector) if ($c.scope.($SourcePacSelector)) { $childScope = @() foreach ($scope in $($c.scope.($SourcePacSelector))) { if ($scope -like "/subscriptions/*") { Write-Warning "$($json.assignment.name): $($json.assignment.displayName) is assigned to subscription $scope, this cannot be duplicated without a specific subscription in the environment $NewPacSelector" } else { $childScope += "/providers/Microsoft.Management/managementGroups/" + $MGHierarchyPrefix + $(Split-Path $scope -Leaf) + $MGHierarchySuffix Write-Debug " Added Child Scope: $($childScope[-1])" Write-Debug " New Scope Name: $($c.nodeName)" } } $c.scope | Add-Member -MemberType NoteProperty -Name $NewPacSelector -Value $childScope } else { Write-Debug " No scope found for $($SourcePacSelector) in $($c.nodeName), skipping..." # $c.scope } } } elseif ($i -gt 0) { Write-Warning " Scope $($NewPacSelector) already exists in $($f.FullName), copying file in current state..." } else { $newScope = @() foreach ($scope in $scopeList) { if ($scope -like "/subscriptions/*") { Write-Warning "$($c.assignment.name): $($c.assignment.displayName) is assigned to subscription $scope, this cannot be duplicated without a specific subscription in the environment $NewPacSelector" } else { $newScope += "/providers/Microsoft.Management/managementGroups/" + $MGHierarchyPrefix + $(Split-Path $scope -Leaf) + $MGHierarchySuffix $json.scope | Add-Member -MemberType NoteProperty -Name $NewPacSelector -Value $newScope } } } if ($json.scope.$($NewPacSelector) -or $json.children.scope.$($NewPacSelector)) { Write-Debug " New Scope Name: $NewPacSelector" Write-Debug " Updated Scope: $(($json.scope.$($NewPacSelector)) -join ",")" } else { Write-Warning "No scope found for $($NewPacSelector) in $($f.FullName), skipping..." continue } if (!(Test-Path -Path $outputParent)) { Write-Information "Creating directory $outputParent..." $null = New-Item -Path $outputParent -ItemType Directory -Force } $json | ConvertTo-Json -Depth 100 | Set-Content -Path $outputFile } } } |