functions/New-HydrationPolicyDocumentationSourceFile.ps1

<#
.SYNOPSIS
Generates policy documentation source files based on provided parameters and schema.
 
.DESCRIPTION
The New-PolicyDocumentationSourceFiles function generates policy documentation source files using the current defined schema online along with these inputs. It processes policy assignments and creates a new documentation template with assignment information that can be used to generate documentation for all Assignments, and supporting Definitions, specified in the Definitions folder.
 
.PARAMETER PacSelector
Specifies the PAC environment selector. This parameter is mandatory.
 
.PARAMETER OutputPath
Specifies the output path for the generated documentation files. The default value is "./Output".
 
.PARAMETER DefinitionsPath
Specifies the path to the definitions. The default value is "./Definitions".
 
.PARAMETER EnvironmentGroups
Specifies the environment groups as a hashtable, and can be used to override the Management Group based categories by specifying a list of k/v pairs that follow this format: ManagementGroupID: EnvironmentGroup. This will populate the environmentOverrides property. The default value is an empty hashtable.
 
.PARAMETER MaxParameterLength
Specifies the maximum length of parameters for Assignments in the documentation. The default value is 42.
 
.PARAMETER ReportTitle
Specifies the title of the report, and is an arbitrary value. The default value is "Azure Policy Effects".
 
.PARAMETER FileNameStem
Specifies the stem for the file name, and is an arbitrary value that needs to conform to the naming rules of the local filesystem. The default value is "PrimaryTenant".
 
.PARAMETER IncludeComplianceGroupNames
Includes compliance group names as headers in the documentation if specified.
 
.PARAMETER NoEmbeddedHtml
Excludes embedded HTML in the documentation if specified. This is appropriate for wikis that do not support embedded HTML, such as SharePoint.
 
.PARAMETER AddToc
Adds a table of contents to the Markdown documentation if specified.
 
.PARAMETER AdoOrganization
Specifies the Azure DevOps organization. This parameter is mandatory if any ADO parameters are set.
 
.PARAMETER AdoProject
Specifies the Azure DevOps project. This parameter is mandatory if any ADO parameters are set.
 
.PARAMETER AdoWiki
Specifies the Azure DevOps wiki. This parameter is mandatory if any ADO parameters are set.
 
.EXAMPLE
New-PolicyDocumentationSourceFiles -PacSelector "Production" -OutputPath "./Output" -DefinitionsPath "./Definitions" -EnvironmentGroups @{ "Group1" = "Location1" } -MaxParameterLength 50 -ReportTitle "Policy Report" -FileNameStem "tenant01" -IncludeComplianceGroupNames -NoEmbeddedHtml -AddToc -AdoOrganization "MyOrg" -AdoProject "MyProject" -AdoWiki "MyWiki"
 
#>

function New-HydrationPolicyDocumentationSourceFile {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true, HelpMessage = "Specifies the PAC environment selector. This parameter is mandatory.")]
        [string]
        $PacEnvironmentSelector,
        [Parameter(Mandatory = $false, HelpMessage = "Specifies the output path for the generated documentation files. The default value is './Output'.")]
        [string]
        $OutputPath = "./Output",
        [Parameter(Mandatory = $false, HelpMessage = "Specifies the path to the definitions. The default value is './Definitions'.")]
        [string]
        $DefinitionsPath = "./Definitions",
        [Parameter(Mandatory = $false, HelpMessage = "Specifies the environment groups as a hashtable. This will populate the environmentOverrides property. The default value is an empty hashtable.")]
        [hashtable]
        $EnvironmentGroups = @{},
        [Parameter(Mandatory = $false, HelpMessage = "Specifies the maximum length of parameters for Assignments in the documentation. The default value is 42.")]
        [int]
        $MaxParameterLength = 42,
        [Parameter(Mandatory = $false, HelpMessage = "Specifies the title of the report. The default value is 'Azure Policy Effects'.")]
        [string]
        $ReportTitle = "Azure Policy Effects",
        [Parameter(Mandatory = $false, HelpMessage = "Specifies the stem for the file name. The default value is 'PrimaryTenant'.")]
        [string]
        $FileNameStem = "PrimaryTenant",
        [Parameter(Mandatory = $false, HelpMessage = "Includes compliance group names as headers in the documentation if specified.")]
        [switch]
        $IncludeComplianceGroupNames,
        [Parameter(Mandatory = $false, HelpMessage = "Excludes embedded HTML in the documentation if specified. This is appropriate for wikis that do not support embedded HTML, such as SharePoint.")]
        [switch]
        $NoEmbeddedHtml,
        [Parameter(Mandatory = $false, HelpMessage = "Adds a table of contents to the Markdown documentation if specified.")]
        [switch]
        $AddToc,
        [Parameter(Mandatory = $false, ParameterSetName = 'AdoSet', HelpMessage = "Specifies the Azure DevOps organization. This parameter is mandatory if any ADO parameters are set.")]
        [string]
        $AdoOrganization,
        [Parameter(Mandatory = $false, ParameterSetName = 'AdoSet', HelpMessage = "Specifies the Azure DevOps project. This parameter is mandatory if any ADO parameters are set.")]
        [string]
        $AdoProject,
        [Parameter(Mandatory = $false, ParameterSetName = 'AdoSet', HelpMessage = "Specifies the Azure DevOps wiki. This parameter is mandatory if any ADO parameters are set.")]
        [string]
        $AdoWiki
    )
    $InformationPreference = "Continue"
    $policySets = @()
    $JsonSchemaUri = "https://raw.githubusercontent.com/Azure/enterprise-azure-policy-as-code/main/Schemas/policy-documentation-schema.json"
    $paths = @{
        policyAssignmentsPath = $(Join-Path $DefinitionsPath policyAssignments)
        outputPath            = $(Join-Path $OutputPath (Get-Date -Format "yyyy-MM-dd") policyDocumentations)
    }
    foreach ($path in $paths.Values) {
        if (!(Test-Path -Path $path)) {
            $null = New-Item -Path $path -ItemType Directory -Force
        }
    }
    $documentationObject = New-SchemaJsonTemplate  -JsonSchemaUri $JsonSchemaUri -Output $OutputPath # -Definitions $DefinitionsPath
    $documentationHashtable = $documentationObject | ConvertTo-Json -Depth 100 | ConvertFrom-Json -Depth 100 -AsHashtable

    Write-Information "Gathering Assignment Information..."
    $fileList = Get-ChildItem -Path $paths.policyAssignmentsPath -Recurse -File -Include "*.json", "*.jsonc"

    Write-Information "Updating documentAssignments\documentAllAssignments section in the template with assignment information..."
    $documentationHashtable.documentAssignments.documentAllAssignments[0].pacEnvironment = $PacEnvironmentSelector
    if ($documentationHashtable.documentAssignments.documentAllAssignments[0].contains("enabled")) {
        $documentationHashtable.documentAssignments.documentAllAssignments[0].Remove("enabled")
    }
    $documentationHashtable.documentAssignments.documentAllAssignments[0].overrideEnvironmentCategory = @{}
    if ($EnvironmentGroups.Keys.count -gt 0) {
        # Set overrides
        Write-Information "Applying Override Environment Categories..."
        $groupList = $EnvironmentGroups.GetEnumerator() | Select-Object -ExpandProperty Value -Unique
        foreach ($group in $groupList) {
            $groupContentList = @()
            foreach ($managementGroup in $EnvironmentGroups.keys) {
                if ($EnvironmentGroups[$managementGroup] -eq $group) {
                    $groupContentList += $managementGroup
                }
            }
            $documentationHashtable.documentAssignments.documentAllAssignments[0].overrideEnvironmentCategory.Add($group, $groupContentList)
        }
    }
    Write-Information "Updating documentAssignments\documentationSpecifications section in the template with assignment information..."
    $documentationHashtable.documentAssignments.documentationSpecifications[0].fileNameStem = $FileNameStem
    $documentationHashtable.documentAssignments.documentationSpecifications[0].environmentCategories = @() # Not used in document all option as it is overridden
    $documentationHashtable.documentAssignments.documentationSpecifications[0].title = $ReportTitle
    $documentationHashtable.documentAssignments.documentationSpecifications[0].markdownIncludeComplianceGroupNames = $IncludeComplianceGroupNames
    if ($SuppressParameterSection) {
        $documentationHashtable.documentAssignments.documentationSpecifications[0].markdownSuppressParameterSection = $true
    }
    else {
        $documentationHashtable.documentAssignments.documentationSpecifications[0].markdownSuppressParameterSection = $false
    }
    if ($IncludeComplianceGroupNames) {
        $documentationHashtable.documentAssignments.documentationSpecifications[0].markdownIncludeComplianceGroupNames = $true
    }
    else {
        $documentationHashtable.documentAssignments.documentationSpecifications[0].markdownIncludeComplianceGroupNames = $false
    }
    if ($NoEmbeddedHtml) {
        $documentationHashtable.documentAssignments.documentationSpecifications[0].markdownNoEmbeddedHtml = $true
    }
    else {
        $documentationHashtable.documentAssignments.documentationSpecifications[0].markdownNoEmbeddedHtml = $false
    }
    if ($AddToc) {
        $documentationHashtable.documentAssignments.documentationSpecifications[0].markdownAddToc = $true
    }
    else {
        $documentationHashtable.documentAssignments.documentationSpecifications[0].markdownAddToc = $false
    }

    $documentationHashtable.documentAssignments.documentationSpecifications[0].markdownMaxParameterLength = $MaxParameterLength
    if ($AdoProject -and $AdoOrganization -and $AdoWiki) {
        $documentationHashtable.documentAssignments.documentationSpecifications[0].markdownAdoWiki = $true
        $documentationHashtable.documentAssignments.documentationSpecifications[0].add("markdownAdoWikiConfig", `
            @{
                adoOrganization = $AdoOrganization
                adoProject      = $AdoProject
                adoWiki         = $AdoWiki
            }
        )
    }
    else {
        Write-Debug " No ADO information provided. Skipping ADO integration."
    }

    Write-Information "Updating documentPolicySets section in the template with assignment information..."
    $documentationHashtable.documentPolicySets[0].pacEnvironment = $PacEnvironmentSelector
    if ($AdoProject -and $AdoOrganization -and $AdoWiki) {
        $documentationHashtable.documentPolicySets[0].markdownAdoWiki = $true
    }
    else {
        Write-Debug " No ADO information provided. Skipping ADO integration."
    }

    $documentationHashtable.documentPolicySets[0].fileNameStem = $FileNameStem
    $documentationHashtable.documentPolicySets[0].environmentCategories = @() 
    $documentationHashtable.documentPolicySets[0].environmentColumnsInCsv = @() 

    $documentationHashtable.documentPolicySets[0].title = $ReportTitle
    $documentationHashtable.documentPolicySets[0].markdownIncludeComplianceGroupNames = $IncludeComplianceGroupNames
    if ($SuppressParameterSection) {
        $documentationHashtable.documentPolicySets[0].markdownSuppressParameterSection = $true
    }
    else {
        $documentationHashtable.documentPolicySets[0].markdownSuppressParameterSection = $false
    }
    if ($IncludeComplianceGroupNames) {
        $documentationHashtable.documentPolicySets[0].markdownIncludeComplianceGroupNames = $true
    }
    else {
        $documentationHashtable.documentPolicySets[0].markdownIncludeComplianceGroupNames = $false
    }
    if ($NoEmbeddedHtml) {
        $documentationHashtable.documentPolicySets[0].markdownNoEmbeddedHtml = $true
    }
    else {
        $documentationHashtable.documentPolicySets[0].markdownNoEmbeddedHtml = $false
    }
    if ($AddToc) {
        $documentationHashtable.documentPolicySets[0].markdownAddToc = $true
    }
    else {
        $documentationHashtable.documentPolicySets[0].markdownAddToc = $false
    }

    $documentationHashtable.documentPolicySets[0].markdownMaxParameterLength = $MaxParameterLength

    $planFile = Join-Path $OutputPath $( -join ('plans-', $PacEnvironmentSelector)) 'policy-plan.json'

    Build-HydrationDeploymentPlans -PacEnvironmentSelector $PacEnvironmentSelector `
        -DefinitionsRootFolder:$DefinitionsPath `
        -OutputFolder:$OutputPath `
        -FullExportForDocumentationFile
    $planContent = Get-DeploymentPlan -PlanFile $planFile
    $assignments = $planContent.assignments.new | ConvertTo-Json -Depth 100 | ConvertFrom-Json -AsHashtable
    $policySetDefinitionList = @()
    foreach ($aplan in $assignments.keys) {
        $scopeName = $assignments.$aplan.scope.split("/")[-1]
        if ($assignments.$aplan.policyDefinitionId -like "*policySetDefinitions*" -and $assignments.$aplan.policyDefinitionId -like "*$scopeName*") {
            $policySetDefinitionList += @{
                name      = $assignments.$aplan.policyDefinitionId.split("/")[-1]
                shortName = $assignments.$aplan.id.split("/")[-1] 
            }
        }
        elseif ($assignments.$aplan.policyDefinitionId -like "*policySetDefinitions*" -and !($assignments.$aplan.policyDefinitionId -like "*$scopeName*")) {
            $policySetDefinitionList += @{
                id        = $assignments.$aplan.policyDefinitionId
                shortName = $assignments.$aplan.id.split("/")[-1] 
            }
        }
    }
    Write-Information "PolicySet List Count: $($policySetDefinitionList.count)"

    if ($policySetDefinitionList.count -gt 0) {
        $documentationHashtable.documentPolicySets[0].policySets = $policySetDefinitionList | Sort-Object -Property @{Expression = { $_.name + $_.shortName + $_.id } } -Unique
    }
    else {
        Write-Information "No PolicySets assigned. Assignments will be documented without PolicySets."
        continue
    }
    $fullOutputPath = Join-Path $paths.outputPath "$FileNameStem.jsonc"
    Write-Information "Outputting new documentation file to $fullOutputPath"
    $documentationHashtable | ConvertTo-Json -Depth 100 | Out-File -FilePath $fullOutputPath -Force
}