private/ConvertFrom-ArraysToOrderedHashTables.ps1

function ConvertFrom-ArraysToOrderedHashTables {
    [CmdletBinding()]
    param (
        [parameter(Mandatory = $true, ValueFromPipeline = $true)] $Item,
        # This determines the arrays that are converted to OrderedHashTables. Only arrays where the objects have a property
        # with one of the values in this array can be converted, using that property as the key
        [parameter(Mandatory = $false)] [String[]] $ArrayIdProps = @("name", "Name")
    )

    Write-Debug "BEGIN: ConvertFrom-ArraysToOrderedHashTables";

    if ( $Item.GetType().Name -eq "PSCustomObject" ) {

        Write-Verbose "Processing PSCustomObject...";
        $cnt = @($Item.PSobject.Properties).Count
        if ($cnt) {
            Write-Verbose "Properties: $($Item.PSObject.Properties.Name -join ', ')";
        } else {
            Write-Verbose "The object is empty - no further processing";
        }

        # Loop through the properties, changing arrays and processing PSCustomObject's
        foreach ($p in $Item.PSObject.Properties) {

            $prop = $p.Name
            if ($null -eq $Item.$prop) {
                Write-Verbose "Skipping property '$prop' as type cannot be determined for null";
                continue;
            }
            Write-Verbose "Processing property '$prop' of type '$($Item.$prop.GetType().Name)'";

            # If the current property is a non-empty array
            if ( $Item.$prop.GetType().Name -eq "Object[]" -and $Item.$prop.Count -gt 0) {
            
                $itemCount = $Item.$prop.Count;
                foreach ($idProp in $arrayIdProps) {
                    # If the item in the array is a primitive type, calling PSobject.Properties.Name will throw...
                    $matchedItemCount = $Item.$prop.Where({ (-not $_.GetType().IsPrimitive) -and $_.PSobject.Properties.Name -contains $idProp }, 'Default').Count;

                    if ( $matchedItemCount -eq $itemCount ) {
                        Write-Verbose "Extracting ordered hash table from array using property $idProp as the key";
                        
                        # First convert each item in the array to have ordered hashtables instead of arrays if required
                        for ($i=0; $i -lt $itemCount; $i++ ){
                            $Item.$prop[$i] = ConvertFrom-ArraysToOrderedHashTables -Item $Item.$prop[$i];
                        }
                        # Then convert the actual array to a ordered hashtables
                        $Item.$prop = ArrayToOrderedHash -Array $Item.$prop -KeyProperty $idProp -Verbose:$VerbosePreference;
                        
                        break;
                    }
                }
            }

            elseif ( $Item.$prop.GetType().Name -eq "PSCustomObject" ) {
                Write-Verbose "Converting PSCustomObject property using a recursive function call...";

                $Item.$prop = ConvertFrom-ArraysToOrderedHashTables -Item $Item.$prop;
            }
        }

        return $Item;
    }
    elseif ( $Item.GetType().BaseType -eq "Object[]" ) {
        Write-Verbose "Processing Array...";

        $wrapper = [PSCustomObject]@{
            WrappedObject = $Item
        }

        $result = ConvertFrom-ArraysToOrderedHashTables -obj $wrapper;
        return $result.WrappedObject;
    }
    else {
        Write-Verbose "Unknown input object type, not supportted";
        return $Item;
    }
}

function ArrayToOrderedHash {
    [CmdletBinding()]
    param (
        [parameter(Mandatory = $true)] [System.Array] $Array,
        [parameter(Mandatory = $true)] [String] $KeyProperty
    )

    Write-Debug "BEGIN: ArrayToOrderedHash(KeyProperty=$KeyProperty)"

    $hash = [ordered]@{};
    $array | ForEach-Object { $hash[$_.$keyProperty] = $_ };
    return $hash;
}