private/BootMedia/Steps/Select-OSDeployCoreBuildProfile.ps1

#Requires -PSEdition Core

function Select-OSDeployCoreBuildProfile {
    <#
    .SYNOPSIS
        Displays build profiles in an Out-GridView picker.
 
    .DESCRIPTION
        Scans the Repository\build-profiles directory for saved JSON profiles and
        presents them in an Out-GridView for interactive selection. Each entry
        shows key profile properties (Architecture, Languages, TimeZone, etc.)
        read from the JSON file. The returned object has a FullName property
        with the full path to the selected JSON file.
 
    .OUTPUTS
        PSCustomObject with FullName property, or $null if no selection is made.
 
    .NOTES
        Author: David Segura
        Company: Recast Software
        Change Summary:
         - Initial version.
         - Updated output path from builds to boot-media; moved build-profiles under Repository.
         - Added JSON content display in Out-GridView showing Architecture, Languages, and other profile properties.
    #>

    [CmdletBinding()]
    param ()

    $profilePath = Join-Path $script:OSDeployCoreRepositoryPath 'build-profiles'

    if (-not (Test-Path -Path $profilePath)) {
        return $null
    }

    $results = @(Get-ChildItem -Path $profilePath -Filter '*.json' -File -ErrorAction SilentlyContinue)

    if ($results.Count -gt 0) {
        Write-OSDeployCoreProgress 'Select a Build Profile (Cancel to create a new one)'

        $profileObjects = foreach ($file in $results) {
            try {
                $json = Get-Content -LiteralPath $file.FullName -Raw -ErrorAction Stop | ConvertFrom-Json -ErrorAction Stop
            }
            catch {
                $json = $null
            }
            $driverCount = if ($json.WinPEDriver) { @($json.WinPEDriver).Count } else { 0 }
            [PSCustomObject]@{
                Name                = $file.BaseName
                LastModified        = $file.LastWriteTime
                Architecture        = $json.Architecture
                Languages           = ($json.Languages -join ', ')
                SetInputLocale      = $json.SetInputLocale
                SetAllIntl          = $json.SetAllIntl
                SetTimeZone         = $json.SetTimeZone
                WinPEStartupProfile  = $json.WinPEStartupProfile
                WinPECustomWallpaper = $json.WinPECustomWallpaper
                WinPEAppScript       = ($json.WinPEAppScript -join ', ')
                WinPEScript          = ($json.WinPEScript -join ', ')
                WinPEMediaScript     = ($json.WinPEMediaScript -join ', ')
                WinPEDrivers         = $driverCount
                FullName             = $file.FullName
            }
        }

        while ($true) {
            $selected = $profileObjects |
                Out-GridView -OutputMode Single -Title 'Select a Build Profile (Cancel to create a new one)'

            if (-not $selected) {
                return $null
            }

            # Validate all path-bearing properties in the selected profile
            try {
                $profileJson = Get-Content -LiteralPath $selected.FullName -Raw -ErrorAction Stop | ConvertFrom-Json -ErrorAction Stop
            }
            catch {
                Write-Warning "Could not read build profile: $($selected.FullName)"
                Write-Warning $_.Exception.Message
                continue
            }

            $pathProperties = [ordered]@{
                WinPEDriver          = $profileJson.WinPEDriver
                WinPEAppScript       = $profileJson.WinPEAppScript
                WinPEScript          = $profileJson.WinPEScript
                WinPEMediaScript     = $profileJson.WinPEMediaScript
                WinPEStartupProfile  = $profileJson.WinPEStartupProfile
                WinPECustomWallpaper = $profileJson.WinPECustomWallpaper
            }

            # Expand tokens to absolute paths before Test-Path validation
            $expandedProperties = [ordered]@{}
            foreach ($prop in $pathProperties.Keys) {
                $expandedProperties[$prop] = Expand-OSDeployBuildProfileToken $pathProperties[$prop]
            }

            $invalidPaths = foreach ($prop in $expandedProperties.Keys) {
                foreach ($entry in @($expandedProperties[$prop])) {
                    if ($entry -and -not (Test-Path -LiteralPath $entry)) {
                        [PSCustomObject]@{ Property = $prop; Path = $entry }
                    }
                }
            }

            if ($invalidPaths) {
                Write-Warning "Build profile has path errors: $($selected.FullName)"
                foreach ($bad in $invalidPaths) {
                    Write-Warning "[$($bad.Property)] Path not found: $($bad.Path)"
                }
                Write-Warning 'Fix the build profile and try again, or cancel to create a new one.'
                continue
            }

            # Auto-migrate: re-tokenize paths and rewrite the file if anything changed
            $tokenizedProperties = [ordered]@{}
            foreach ($prop in $pathProperties.Keys) {
                $tokenizedProperties[$prop] = ConvertTo-OSDeployBuildProfileToken $pathProperties[$prop]
            }

            $needsMigration = $false
            foreach ($prop in $pathProperties.Keys) {
                $original = @($pathProperties[$prop]) | Where-Object { $_ }
                $tokenized = @($tokenizedProperties[$prop]) | Where-Object { $_ }
                if (($original -join '|') -ine ($tokenized -join '|')) {
                    $needsMigration = $true
                    break
                }
            }

            if ($needsMigration) {
                Write-Verbose "Migrating build profile to use module path tokens: $($selected.FullName)"
                $updatedProfile = [ordered]@{
                    Architecture        = $profileJson.Architecture
                    WinPEDriver         = $tokenizedProperties['WinPEDriver']
                    WinPEAppScript      = $tokenizedProperties['WinPEAppScript']
                    WinPEScript         = $tokenizedProperties['WinPEScript']
                    WinPEMediaScript    = $tokenizedProperties['WinPEMediaScript']
                    WinPEStartupProfile  = if ($tokenizedProperties['WinPEStartupProfile']) { $tokenizedProperties['WinPEStartupProfile'][0] } else { $null }
                    WinPECustomWallpaper = if ($tokenizedProperties['WinPECustomWallpaper']) { $tokenizedProperties['WinPECustomWallpaper'][0] } else { $null }
                    Languages            = $profileJson.Languages
                    SetAllIntl          = $profileJson.SetAllIntl
                    SetInputLocale      = $profileJson.SetInputLocale
                    SetTimeZone         = $profileJson.SetTimeZone
                }
                try {
                    $updatedProfile | ConvertTo-Json -Depth 5 -WarningAction SilentlyContinue | Out-File -LiteralPath $selected.FullName -Encoding utf8 -Force
                }
                catch {
                    Write-Warning "Could not migrate build profile tokens: $($_.Exception.Message)"
                }
            }

            return $selected
        }
    }

    return $null
}