private/Select-BuildOSDeployBootProfile.ps1
|
#Requires -PSEdition Core function Select-BuildOSDeployBootProfile { <# .SYNOPSIS Selects an OSDeploy Boot build profile .DESCRIPTION Scans profile directories beneath the OSDeploy Boot-Assets osdeployboot-profiles directory for osdeployboot.json files. Profile folder names use the <Name>-<Architecture> format. The function displays profile settings in a single-selection Out-GridView picker when Name is omitted. An exact canonical Name returns the matching profile without displaying the picker. After selection, the function expands portable path tokens and verifies every configured WinPE driver, script, media script, and startup profile path. Profiles with missing paths are rejected and the picker is displayed again. Valid profiles are automatically rewritten when absolute OSDeployCore or module paths can be converted to portable tokens. Canceling the picker returns no object. .PARAMETER Name Specifies the exact canonical profile folder name to return without displaying the profile picker. Named profiles are still validated before they are returned. .PARAMETER Architecture Limits discovery to profiles whose JSON Architecture property is amd64 or arm64. When omitted, profiles for both architectures are displayed. .PARAMETER SkipPathValidation Returns an exact named profile without validating or migrating its configured content paths. This allows Build-OSDeployBoot to repair stale paths during a profile update. .PARAMETER SkipTokenMigration Validates configured content paths without rewriting absolute paths as portable tokens. This supports read-only profile builds. .EXAMPLE PS> Select-BuildOSDeployBootProfile -Architecture amd64 Displays valid amd64 build profiles for single selection. .EXAMPLE PS> Select-BuildOSDeployBootProfile -Name 'Contoso-amd64' Returns the validated Contoso-amd64 profile without displaying the picker. .INPUTS None. This function does not accept pipeline input. .OUTPUTS System.Management.Automation.PSCustomObject. Returns the selected profile summary, including its FullName. Returns no object when no profile exists or selection is canceled. .NOTES Author: David Segura Company: Recast Software Version: 1.0.0 Date: 2026-08-28 Change Summary: - Initial version. - Updated output path from builds to boot; moved profiles under Boot-Assets. - Added profile property display, architecture validation, and token migration. - Uses flat canonical profile folders and filters by the JSON Architecture property. Selecting a valid profile may overwrite its JSON file with tokenized module paths. .LINK Build-OSDeployBoot #> [CmdletBinding()] param ( [System.String] $Name, [ValidateSet('amd64', 'arm64')] [System.String] $Architecture, [System.Management.Automation.SwitchParameter] $SkipPathValidation, [System.Management.Automation.SwitchParameter] $SkipTokenMigration ) if ($SkipPathValidation -and -not $Name) { throw "[$(Get-Date -Format s)] [$($MyInvocation.MyCommand.Name)] SkipPathValidation requires an exact profile name." } $profilePath = Join-Path $script:OSDeployBootAssetsPath 'osdeployboot-profiles' if (-not (Test-Path -LiteralPath $profilePath -PathType Container)) { return $null } if ($Name) { Initialize-OSDeployBootAssetPath -ProfilePath (Join-Path $profilePath $Name) } else { Initialize-OSDeployBootAssetPath } $results = @(Get-ChildItem -LiteralPath $profilePath -Directory -ErrorAction SilentlyContinue | ForEach-Object { if ($Name -and $_.Name -ine $Name) { return } $profileFile = Join-Path $_.FullName 'osdeployboot.json' if (-not (Test-Path -LiteralPath $profileFile -PathType Leaf)) { return } try { $profileJson = Get-Content -LiteralPath $profileFile -Raw -ErrorAction Stop | ConvertFrom-Json -ErrorAction Stop if (-not $Architecture -or $profileJson.Architecture -eq $Architecture) { Get-Item -LiteralPath $profileFile } } catch { Write-Warning "[$(Get-Date -Format s)] Could not read build profile: $profileFile" } }) if ($results.Count -gt 0) { if (-not $Name) { Write-Host -ForegroundColor DarkGreen "[$(Get-Date -format s)] Build Profiles are saved in $profilePath" Write-HostDateTimeDarkGray '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.Directory.Name LastModified = $file.LastWriteTime Architecture = $json.Architecture Options = ($json.Options -join ', ') Languages = ($json.Languages -join ', ') SetInputLocale = $json.SetInputLocale SetAllIntl = $json.SetAllIntl SetTimeZone = $json.SetTimeZone WinPEStartupProfile = $json.WinPEStartupProfile WinPEScript = ($json.WinPEScript -join ', ') MediaScript = ($json.MediaScript -join ', ') WinPEDrivers = $driverCount FullName = $file.FullName } } while ($true) { $selected = if ($Name) { $profileObjects | Where-Object Name -IEQ $Name | Select-Object -First 1 } else { $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 "[$(Get-Date -Format s)] Could not read build profile: $($selected.FullName)" Write-Warning "[$(Get-Date -Format s)] $($_.Exception.Message)" if ($Name) { throw "[$(Get-Date -Format s)] [$($MyInvocation.MyCommand.Name)] Named build profile is invalid: $Name" } continue } if ($SkipPathValidation) { return $selected } $pathProperties = [ordered]@{ WinPEDriver = $profileJson.WinPEDriver WinPEScript = $profileJson.WinPEScript MediaScript = $profileJson.MediaScript WinPEStartupProfile = $profileJson.WinPEStartupProfile } # 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 "[$(Get-Date -Format s)] Build profile has path errors: $($selected.FullName)" foreach ($bad in $invalidPaths) { Write-Warning "[$(Get-Date -Format s)] [$($bad.Property)] Path not found: $($bad.Path)" } if ($Name) { throw "[$(Get-Date -Format s)] [$($MyInvocation.MyCommand.Name)] Named build profile has path errors: $Name" } Write-Warning "[$(Get-Date -Format s)] 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 -and -not $SkipTokenMigration) { Write-Verbose "[$($MyInvocation.MyCommand.Name)] Migrating build profile to use portable path tokens: $($selected.FullName)" $updatedProfile = [ordered]@{ Name = $selected.Name Architecture = $profileJson.Architecture Options = $profileJson.Options WinPEDriver = $tokenizedProperties['WinPEDriver'] WinPEScript = $tokenizedProperties['WinPEScript'] MediaScript = $tokenizedProperties['MediaScript'] WinPEStartupProfile = if ($tokenizedProperties['WinPEStartupProfile']) { $tokenizedProperties['WinPEStartupProfile'][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 "[$(Get-Date -Format s)] Could not migrate build profile tokens: $($_.Exception.Message)" } } return $selected } } return $null } |