Private/Repair-AzLocalExcludedSubscriptionCsv.ps1

########################################
<#
.SYNOPSIS
    Normalizes a legacy (v0.9.1) commented Excluded-Subscription-Ids.csv to the
    clean, comment-free v0.9.10 format while preserving any real subscription-id
    rows the operator added.
.DESCRIPTION
    v0.9.1 shipped a starter Excluded-Subscription-Ids.csv that embedded '#'
    guidance comment lines above the CSV header. Those comments parse fine as
    long as the file is untouched, but once an operator opens the file in Excel
    (or another spreadsheet editor) and saves it, the editor re-quotes the
    comment lines and pads them with trailing commas, so they no longer start
    with '#'. The exclusion parser then mistakes the first quoted comment for the
    header and throws, silently disabling every real exclusion row.
 
    v0.9.10 splits the guidance into a sidecar Excluded-Subscription-Ids_README.txt
    and ships a clean header-only CSV. Because Copy-/Update-AzLocalPipelineExample
    never overwrite an existing file, an early adopter who already landed on
    v0.9.1 would keep the fragile commented CSV. This helper proactively strips
    the legacy comment lines during the next Copy/Update so the latent Excel
    footgun is removed BEFORE it can be triggered.
 
    The detection is the presence of any '#' comment line - a clean v0.9.10 CSV
    has none, so this is a no-op (returns $false) for already-clean files and is
    safe to call unconditionally and repeatedly (idempotent). Real subscription-id
    rows are extracted with a GUID regex over the RAW lines (NOT ConvertFrom-Csv),
    so the helper still recovers the operator's GUIDs even if the file has already
    been mangled by Excel. The original file is recoverable from git history (the
    turnkey updater commits the config folder), so no separate backup is written.
.PARAMETER Path
    Full path to the Excluded-Subscription-Ids.csv to inspect and, if it is in
    the legacy commented format, normalize in place.
.OUTPUTS
    [bool] $true when the file was rewritten (legacy format detected and
    normalized); $false when no action was taken (file missing, or already in the
    clean comment-free format).
.NOTES
    Author : AzLocal.UpdateManagement
    Added : v0.9.10
 
    ONE-TIME MIGRATION. This helper exists solely to normalize files dropped by
    the short-lived v0.9.1 commented-CSV format. It is a permanent no-op for any
    clean v0.9.10+ CSV (no '#' lines), so it is safe to leave in place, but it
    can be removed in a future release once we are confident no v0.9.1-format
    files remain in the wild (the format was published only briefly). When
    removing, also delete its NestedModules entry in the manifest, the two call
    sites in Copy-/Update-AzLocalPipelineExample, and the v0.9.10 migration
    tests in Tests/ExcludedSubscriptions.Tests.ps1.
#>

########################################
function Repair-AzLocalExcludedSubscriptionCsv {
    [CmdletBinding(SupportsShouldProcess = $true)]
    [OutputType([bool])]
    param(
        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [string]$Path
    )

    if (-not (Test-Path -LiteralPath $Path -PathType Leaf)) {
        return $false
    }

    $rawLines = @(Get-Content -LiteralPath $Path -ErrorAction Stop)

    # Legacy-format signature: any '#' comment line. A clean v0.9.10 CSV carries
    # none, so already-clean files return $false (idempotent, safe to re-run).
    $hasComments = @($rawLines | Where-Object { $_.TrimStart().StartsWith('#') }).Count -gt 0
    if (-not $hasComments) {
        return $false
    }

    # Recover real subscription-id rows from the RAW lines so this survives a
    # file that Excel has already re-quoted (ConvertFrom-Csv would throw on it).
    $guidRegex = '^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$'
    $preserved = New-Object 'System.Collections.Generic.List[string]'
    foreach ($line in $rawLines) {
        $trimmed = $line.TrimStart()
        if ([string]::IsNullOrWhiteSpace($trimmed)) { continue }
        if ($trimmed.StartsWith('#'))                { continue }

        # First field is the subscription-id column. Tolerate Excel-added
        # surrounding double quotes; keep the remaining columns verbatim.
        $parts      = $line -split ',', 2
        $firstField = $parts[0].Trim().Trim('"').Trim()
        if ($firstField -match $guidRegex) {
            $rest = if ($parts.Count -gt 1) { $parts[1] } else { '' }
            $preserved.Add(($firstField + ',' + $rest))
        }
    }

    $clean = New-Object 'System.Collections.Generic.List[string]'
    $clean.Add('Subscription IDs,Subscription Name,Comment / Notes')
    foreach ($row in $preserved) {
        $clean.Add($row)
    }

    if ($PSCmdlet.ShouldProcess($Path, 'Normalize legacy commented Excluded-Subscription-Ids.csv to clean comment-free CSV')) {
        Set-Content -LiteralPath $Path -Value $clean.ToArray() -Encoding ASCII -ErrorAction Stop
        return $true
    }

    return $false
}