Private/New-Win32ToolkitSparseConfig.ps1

function New-Win32ToolkitSparseConfig {
    <#
    .SYNOPSIS
        Builds a SPARSE PSADT config.psd1 (text) from an org template — only the keys the template
        overrides, nothing else.
 
    .DESCRIPTION
        PSADT loads its own signed module-default config.psd1 first, then SUPERIMPOSES the project's
        Config\config.psd1 over it key-by-key (Import-ADTModuleDataFile → Update-ADTImportedDataValues
        in PSAppDeployToolkit.psm1). So the project file need only carry the deltas; every key it omits
        keeps the module default. This replaces the previous approach of regex-patching a full copy of
        the default file, which drifted silently whenever PSADT reordered or renamed config text (see
        knowledge-base TODO "org template → data", F1).
 
        Two hard rules of the superimpose that shape this emitter:
          * An empty / whitespace value is IGNORED by PSADT (it cannot blank a default). So we OMIT any
            key the template doesn't set, rather than emit an empty string.
          * The signature/tamper check applies only to the MODULE's default file, never the caller's —
            an unsigned sparse project config is legal.
 
        The returned text must REPLACE the project's scaffolded full config.psd1 (do not write it
        alongside — a leftover full file would shadow module defaults with stale values).
 
    .PARAMETER Template
        The org-template object (New-OrgTemplate schema). Read defensively — older templates may lack
        newer fields.
 
    .OUTPUTS
        [string] the config.psd1 text (LF/CRLF as written by the caller; caller writes UTF-8 BOM).
    #>

    [CmdletBinding()]
    [OutputType([string])]
    param(
        [Parameter(Mandatory)]
        [PSCustomObject]$Template
    )

    # psd1 single-quoted literal, single quotes doubled. $env* tokens inside (e.g. LogPath) are
    # preserved verbatim — PSADT expands them itself at runtime, so a literal is exactly right.
    function Format-Psd1String { param([string]$s) "'" + ($s -replace "'", "''") + "'" }

    # Defensive field read — returns $null for a missing property on an older-schema template.
    function Get-Field { param([PSCustomObject]$o, [string]$name)
        if ($o -and $o.PSObject.Properties[$name]) { $o.$name } else { $null }
    }

    $toolkit = [ordered]@{}
    $ui      = [ordered]@{}

    #── Toolkit ─────────────────────────────────────────────────────────
    $company = Get-Field $Template 'CompanyName'
    if (-not [string]::IsNullOrWhiteSpace($company)) {
        $toolkit['CompanyName'] = Format-Psd1String $company
    }

    $logPath = Get-Field $Template 'LogPath'
    if (-not [string]::IsNullOrWhiteSpace($logPath)) {
        $toolkit['LogPath'] = Format-Psd1String $logPath
    }

    #── UI ──────────────────────────────────────────────────────────────
    # DialogStyle always emitted (Fluent default) so a project's dialog style is explicit and testable.
    $style = Get-Field $Template 'DialogStyle'
    if ([string]::IsNullOrWhiteSpace($style)) { $style = 'Fluent' }
    if ($style -notin @('Fluent', 'Classic')) { $style = 'Fluent' }
    $ui['DialogStyle'] = Format-Psd1String $style

    # FluentAccentColor is a BARE literal in config.psd1 (e.g. 0xFF0078D7) — never quoted, per the
    # PSADT config comment. Normalize any hex form to a canonical 0x literal; a value that isn't a
    # usable hex colour is OMITTED (degrades to the PSADT default) rather than emitted bare, which would
    # make the whole config.psd1 fail to parse on-device and abort the SYSTEM deploy.
    $accent = Get-Field $Template 'FluentAccentColor'
    if (-not [string]::IsNullOrWhiteSpace($accent)) {
        $accentLiteral = ConvertTo-Win32ToolkitAccentLiteral -Value $accent
        if ($accentLiteral) {
            $ui['FluentAccentColor'] = $accentLiteral
        } else {
            Write-Warning "Org template FluentAccentColor '$accent' is not a valid hex colour (use 0xAARRGGBB, #RRGGBB, or RRGGBB) — using the PSADT default accent."
        }
    }

    # LanguageOverride (C1): pin every dialog to one PSADT UI language; omit → per-user auto-detect.
    $lang = Get-Field $Template 'LanguageOverride'
    if (-not [string]::IsNullOrWhiteSpace($lang)) {
        $ui['LanguageOverride'] = Format-Psd1String $lang
    }

    #── assemble ────────────────────────────────────────────────────────
    $nl = "`r`n"
    $sb = [System.Text.StringBuilder]::new()
    [void]$sb.Append('# Sparse PSADT config generated by win32-toolkit org template.').Append($nl)
    [void]$sb.Append('# Only the keys below override the PSADT module default (superimposed key-by-key).').Append($nl)
    [void]$sb.Append('# Do not hand-edit — regenerated whenever the org template is applied.').Append($nl)
    [void]$sb.Append('@{').Append($nl)

    if ($toolkit.Count -gt 0) {
        [void]$sb.Append(' Toolkit = @{').Append($nl)
        foreach ($k in $toolkit.Keys) { [void]$sb.Append(" $k = $($toolkit[$k])").Append($nl) }
        [void]$sb.Append(' }').Append($nl)
    }
    if ($ui.Count -gt 0) {
        [void]$sb.Append(' UI = @{').Append($nl)
        foreach ($k in $ui.Keys) { [void]$sb.Append(" $k = $($ui[$k])").Append($nl) }
        [void]$sb.Append(' }').Append($nl)
    }

    [void]$sb.Append('}').Append($nl)
    return $sb.ToString()
}