functions/Update-PolicyAssignmentCsvParameterFile.ps1

function Update-PolicyAssignmentCsvParameterFile {
<#
    Updates an existing EPAC policy assignment parameter CSV from a generated CSV.
 
    Reconciles a CSV generated by Build-PolicyDocumentation with an existing
    assignment parameter CSV. Rows are matched by name and referencePath.
 
    Generated metadata is refreshed. Existing columns ending in Effect or
    Parameters and existing custom columns are preserved. Policies no longer
    present in the generated CSV are removed, and newly generated policies are
    added with their generated defaults.
 
    Path to a current CSV generated by Build-PolicyDocumentation.
 
    Path to the existing assignment parameter CSV to update in place.
 
    ./Build-PolicyDocumentation.ps1 -Interactive:$false
 
    ./Update-PolicyAssignmentCsvParameterFile.ps1 `
        -GeneratedCsvPath ./Outputs/policy-documentation/security-baseline.csv `
        -ParameterCsvPath ./Definitions/policyAssignments/security-baseline-parameters.csv
 
    ./Update-PolicyAssignmentCsvParameterFile.ps1 `
        -GeneratedCsvPath ./Outputs/policy-documentation/security-baseline.csv `
        -ParameterCsvPath ./Definitions/policyAssignments/security-baseline-parameters.csv `
        -WhatIf
#>



[CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = "Medium")]
param (
    [Parameter(Mandatory = $true)]
    [string] $GeneratedCsvPath,

    [Parameter(Mandatory = $true)]
    [string] $ParameterCsvPath
)


$generatedPath = (Resolve-Path -LiteralPath $GeneratedCsvPath -ErrorAction Stop).Path
$parameterPath = (Resolve-Path -LiteralPath $ParameterCsvPath -ErrorAction Stop).Path

if (-not (Test-Path -LiteralPath $generatedPath -PathType Leaf)) {
    throw "Generated CSV path '$GeneratedCsvPath' is not a file."
}
if (-not (Test-Path -LiteralPath $parameterPath -PathType Leaf)) {
    throw "Parameter CSV path '$ParameterCsvPath' is not a file."
}
if ($generatedPath -eq $parameterPath) {
    throw "GeneratedCsvPath and ParameterCsvPath must reference different files."
}

[object[]] $generatedRows = @(Import-Csv -LiteralPath $generatedPath -ErrorAction Stop)
[object[]] $existingRows = @(Import-Csv -LiteralPath $parameterPath -ErrorAction Stop)
$mergeResult = Merge-PolicyAssignmentCsvParameterRow `
    -GeneratedRows $generatedRows `
    -ExistingRows $existingRows

if ($PSCmdlet.ShouldProcess($parameterPath, "Reconcile policy assignment parameter CSV")) {
    $targetBytes = [System.IO.File]::ReadAllBytes($parameterPath)
    $hasUtf8Bom = $targetBytes.Length -ge 3 -and
        $targetBytes[0] -eq 0xEF -and
        $targetBytes[1] -eq 0xBB -and
        $targetBytes[2] -eq 0xBF
    $encoding = [System.Text.UTF8Encoding]::new($hasUtf8Bom)
    $targetText = $encoding.GetString($targetBytes)
    $lineEnding = if ($targetText.Contains("`r`n")) { "`r`n" } else { "`n" }
    $temporaryPath = "$parameterPath.$PID.$([guid]::NewGuid().ToString("N")).tmp"

    try {
        [string[]] $csvLines = @($mergeResult.Rows | ConvertTo-Csv)
        $csvText = ($csvLines -join $lineEnding) + $lineEnding
        [System.IO.File]::WriteAllText($temporaryPath, $csvText, $encoding)
        [System.IO.File]::Move($temporaryPath, $parameterPath, $true)
    }
    finally {
        if (Test-Path -LiteralPath $temporaryPath) {
            Remove-Item -LiteralPath $temporaryPath -Force
        }
    }

    Write-Information (
        "Updated '$parameterPath': added {0}, refreshed {1}, removed {2} row(s)." -f
        $mergeResult.AddedCount,
        $mergeResult.UpdatedCount,
        $mergeResult.RemovedCount
    ) -InformationAction Continue
}
}