functions/New-HydrationCaf3Hierarchy.ps1


<#
.SYNOPSIS
    This function creates a new hierarchy of management groups based on the CAF 3.0 model.
 
.DESCRIPTION
    The New-HydrationCaf3Hierarchy function takes a prefix and a suffix, and creates a new hierarchy of management groups based on the CAF 3.0 model.
 
.PARAMETER Prefix
    The prefix to be used in the naming of the new hierarchy. This is not generally recommended as it adds complexity with little RoI, but is an available option.
 
.PARAMETER Suffix
    The suffix to be used in the naming of the new hierarchy. This is not generally recommended as it adds complexity with little RoI, but is an available option.
 
.EXAMPLE
    New-HydrationCaf3Hierarchy -Prefix "epacdev-" -Suffix "-dev"
 
    This will create a new hierarchy of management groups based on the CAF 3.0 model, using "epacdev-" as the prefix and "-dev" as the suffix.
 
.LINK
    https://aka.ms/epac
    https://github.com/Azure/enterprise-azure-policy-as-code/tree/main/Docs/start-hydration-kit.md
     
#>

function New-HydrationCaf3Hierarchy {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true, HelpMessage = "The name of the destination root management group. This parameter is mandatory.")]
        [string]
        $DestinationRootName,
        [Parameter(Mandatory = $false, HelpMessage = "The prefix to be used in the naming of the new hierarchy. This is not generally recommended as it adds complexity with little RoI, but is an available option.")]
        [string]
        $Prefix,
        [Parameter(Mandatory = $false, HelpMessage = "The suffix to be used in the naming of the new hierarchy. This is not generally recommended as it adds complexity with little RoI, but is an available option.")]
        [string]
        $Suffix
    )
    $InformationPreference = "Continue"
    $updatedDestinationRootName = $( -join ($Prefix, $DestinationRootName, $Suffix))
    $mgLists = [ordered]@{
        $updatedDestinationRootName = @("Platform", "LandingZones", "Decommissioned", "Sandbox")
        Platform             = @("Identity", "Management", "Connectivity", "Security")
        LandingZones         = @("Corp", "Online")
    }
    if(!(Get-AzManagementGroup -GroupName $updatedDestinationRootName -ErrorAction SilentlyContinue)){
        Write-Host "Creating root Management Group $updatedDestinationRootName" -ForegroundColor Yellow
        $null = New-AzManagementGroup -GroupName $updatedDestinationRootName -DisplayName $updatedDestinationRootName -ErrorAction Stop
    }else{
        Write-Host "Root Management Group $updatedDestinationRootName already exists." -ForegroundColor Green
    }
    foreach ($listName in $mgLists.Keys) {
        if ($updatedDestinationRootName -eq $listName) {
            $parentName = $listName
        }
        else {
            $parentName = $( -join ($Prefix, $listName, $Suffix))
        }
        $rootGroupId = $( -join ("/providers/Microsoft.Management/managementGroups/", $parentName))
        foreach ($t in $mgLists.($listName)) {
            $i = 0
            $name = $( -join ($Prefix, $t, $Suffix))
            Remove-Variable repeat -ErrorAction SilentlyContinue
            do {
                $null = Remove-variable testResult -ErrorAction SilentlyContinue
                $null = Remove-variable complete -ErrorAction SilentlyContinue
                # try {
                    # Can move to Get-AzManagementGroupRestMethod if we change the default behavior from forcing a stop on the tests.
                $null = $testResult = Get-AzManagementGroup -GroupName $name -ErrorAction SilentlyContinue
                # }
                # catch {
                # $complete = $false
                # }
                if ($testResult.name) {
                    # This exists for several reasons:
                    # First, timeout errors on response to new-azmanagementgroup are addressed this way.
                    # Second, this avoids collisions, and notifies of the location if one occurs.
                    # Third, this accelerates a retry if the first attempt is interrupted.
                    if( $testResult.ParentId -eq $rootGroupId) {
                        $complete = $true                    
                        Write-Information "Management Group $name confirmed in $($testResult.ParentId)."
                    }
                    elseif(!($testResult.ParentId -eq $rootGroupId)) {
                        Write-Warning "Management Group $name already exists in $($testResult.ParentId), expected $rootGroupId. Please resolve this collision before proceeding."
                        return
                    }
                }
                if (!($complete -eq $true)) {
                    try {
                        $null = $newMg = New-AzManagementGroup -GroupName $name -DisplayName $name -ParentId $rootGroupId -ErrorAction SilentlyContinue
                    }
                    catch {
                        $null = $newMg = Get-AzManagementGroup -GroupName $name -ErrorAction SilentlyContinue
                        Write-Error $_.Exception.Message
                    }
                }
                if (!($newMg)) {
                    if ($i -gt 0) {
                        Write-Warning "Failed to Create Management Group $name, this is generally caused by a timeout on the API call, and will automatically retry $(10-$i) more times..."
                    }                    
                    $i++
                }
            }until($newMg -or $complete -or $i -eq 10)
            if ($i -eq 3) {
                Write-Error "Failed to create $name Management Group"
                return
            }
            Write-Information "Verified $name Management Group in $rootGroupId"
        }
    }
}