private/BootMedia/Steps/Select-OSDeployCoreBuildProfile.ps1

#Requires -PSEdition Core

function Select-OSDeployCoreBuildProfile {
    <#
    .SYNOPSIS
        Displays build profiles in an Out-GridView picker.
 
    .DESCRIPTION
        Scans the OSDRepo\build-profiles\{Architecture} subdirectory for saved JSON profiles and
        presents them in an Out-GridView for interactive selection. When no Architecture is
        specified, profiles from both amd64 and arm64 subfolders are shown. 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; moved build-profiles under Repository.
         - Added JSON content display in Out-GridView showing Architecture, Languages, and other profile properties.
         - Profiles stored in architecture-specific subfolders (build-profiles\amd64, build-profiles\arm64).
         - Auto-migrates legacy flat build-profiles\*.json files to the correct arch subfolder on first run.
    #>

    [CmdletBinding()]
    param (
        [ValidateSet('amd64', 'arm64')]
        [System.String]
        $Architecture
    )

    # Migrate any legacy flat-folder profiles to architecture subfolders
    $legacyBase = Join-Path $script:OSDeployOSDRepoPath 'build-profiles'
    if (Test-Path -Path $legacyBase) {
        $legacyFiles = @(Get-ChildItem -Path $legacyBase -Filter '*.json' -File -ErrorAction SilentlyContinue)
        foreach ($file in $legacyFiles) {
            try {
                $j = Get-Content -LiteralPath $file.FullName -Raw -ErrorAction Stop | ConvertFrom-Json -ErrorAction Stop
                $targetArch = if ($j.Architecture -in @('amd64', 'arm64')) { $j.Architecture } else { 'amd64' }
                $targetDir = Join-Path $legacyBase $targetArch
                if (-not (Test-Path -Path $targetDir)) {
                    New-Item -ItemType Directory -Path $targetDir -Force | Out-Null
                }
                $targetFile = Join-Path $targetDir $file.Name
                if (-not (Test-Path -LiteralPath $targetFile)) {
                    Move-Item -LiteralPath $file.FullName -Destination $targetFile -Force
                    Write-Host -ForegroundColor DarkYellow "[$(Get-Date -format 'yyyy-MM-ddTHH:mm:ss')] Migrated build profile to $targetFile"
                }
            }
            catch {
                Write-Warning "Could not migrate build profile: $($file.FullName) - $($_.Exception.Message)"
            }
        }
    }

    if ($Architecture) {
        $profilePath = Join-Path $script:OSDeployOSDRepoPath 'build-profiles' $Architecture
        if (-not (Test-Path -Path $profilePath)) {
            return $null
        }
        $results = @(Get-ChildItem -Path $profilePath -Filter '*.json' -File -ErrorAction SilentlyContinue)
    }
    else {
        $profilePath = Join-Path $script:OSDeployOSDRepoPath 'build-profiles'
        $results = @()
        foreach ($arch in @('amd64', 'arm64')) {
            $archPath = Join-Path $script:OSDeployOSDRepoPath 'build-profiles' $arch
            if (Test-Path -Path $archPath) {
                $results += @(Get-ChildItem -Path $archPath -Filter '*.json' -File -ErrorAction SilentlyContinue)
            }
        }
    }

    if ($results.Count -gt 0) {
        Write-Host -ForegroundColor DarkGreen "[$(Get-Date -format 'yyyy-MM-ddTHH:mm:ss')] Build Profiles are saved in $profilePath"
        Write-OSDeployCoreProgress 'Select an OSDeploy 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 an OSDeploy 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
}