Private/Utils/Get-SeedValue.ps1

function Get-SeedValue {
    <#
    .SYNOPSIS
        Gets a value from seed data using a dot-notation field path.
 
    .DESCRIPTION
        This function traverses seed data (hashtable, PSCustomObject, or array) using a
        dot-notation path to retrieve values for field population. Handles arrays and
        IEnumerable collections properly to prevent unwrapping.
 
    .PARAMETER SeedObject
        The seed data to search in (hashtable, PSCustomObject, array, etc.).
 
    .PARAMETER FieldPath
        The dot-notation path to the value (e.g., "items.metadata.name").
 
    .EXAMPLE
        Get-SeedValue -SeedObject $seedData -FieldPath "items[0].name"
 
        Returns the name value from the first item in the seed data.
 
    .OUTPUTS
        The value at the specified path, or $null if not found.
        Arrays and IEnumerable collections are returned with comma operator to prevent unwrapping.
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $false)]
        [object]$SeedObject,

        [Parameter(Mandatory = $true)]
        [string]$FieldPath
    )

    Write-Verbose "Get-SeedValue: Called with SeedObject type='$(if ($null -ne $SeedObject) { $SeedObject.GetType().Name } else { 'null' })', FieldPath='$FieldPath'"

    if ($null -eq $SeedObject) {
        return $null
    }

    # Split path and traverse
    $segments = $FieldPath -split '\.'
    $current = $SeedObject

    Write-Verbose "Get-SeedValue: Segments count=$($segments.Count)"

    foreach ($segment in $segments) {
        Write-Verbose "Get-SeedValue: Loop iteration - segment='$segment'"

        if ($null -eq $current) {
            Write-Verbose "Get-SeedValue: current is null, returning null"
            return $null
        }

        # Skip empty segments
        if ([string]::IsNullOrEmpty($segment)) {
            Write-Verbose "Get-SeedValue: Skipping empty segment"
            continue
        }

        # Skip wildcard segments
        if ($segment -eq '*') {
            Write-Verbose "Get-SeedValue: Skipping wildcard segment"
            continue
        }

        Write-Verbose "Get-SeedValue: Processing segment='$segment', current type='$($current.GetType().Name)'"

        # Access property
        if ($current -is [hashtable]) {
            $current = $current[$segment]
            Write-Verbose "Get-SeedValue: Accessed hashtable[$segment], current is now type='$(if ($null -ne $current) { $current.GetType().Name } else { 'null' })'"
        }
        elseif ($current -is [PSCustomObject]) {
            $current = $current.$segment
            Write-Verbose "Get-SeedValue: Accessed PSCustomObject.$segment, current is now type='$(if ($null -ne $current) { $current.GetType().Name } else { 'null' })'"
        }
        else {
            Write-Verbose "Get-SeedValue: Current is neither hashtable nor PSCustomObject, returning null"
            return $null
        }
    }

    Write-Verbose "Get-SeedValue: Returning value type='$(if ($current) { $current.GetType().Name } else { "null" })'"
    # Use comma operator to prevent array unwrapping for single-element arrays and IEnumerable collections
    # This ensures that @( @{x=1} ) stays as an array, not unwrapped to just the hashtable
    # Also handles List<T> and other IEnumerable types from ConvertFrom-Yaml
    if ($current -is [array] -or ($current -is [System.Collections.IEnumerable] -and $current -isnot [string])) {
        return ,$current
    }
    return $current
}