Private/ConvertTo-FlatObject.ps1


<#
.SYNOPSIS
    Flattens a complex PowerShell object into a single-level PSCustomObject with scalar property values.
 
.DESCRIPTION
    ConvertTo-FlatObject accepts any PowerShell object via the pipeline or direct parameter and
    converts all of its properties into flat, scalar-compatible values. This makes the output
    safe for tabular consumers such as Export-Csv, Format-Table, or ConvertTo-Json where nested
    objects and collections would otherwise produce unhelpful representations like
    "System.Object[]" or "@{Key=Value}".
 
    The function applies the following serialization rules to each property value:
 
      - Collections (arrays, lists, any IEnumerable except strings):
            Joined into a single semicolon-delimited string using '; ' as the separator.
 
      - Nested objects (hashtables or PSCustomObjects):
            Serialized to a compact single-line JSON string with a maximum depth of 3.
 
      - Scalar values (strings, integers, booleans, DateTimes, nulls, etc.):
            Passed through as-is without modification.
 
    Strings are explicitly excluded from collection handling because the .NET string type
    implements IEnumerable<char>. Without this guard, a string value would be character-joined
    (e.g., "hello" -> "h; e; l; l; o").
 
    Null input objects are silently skipped with no output and no error.
 
.PARAMETER InputObject
    The object to flatten. Accepts any PowerShell object type including PSCustomObject,
    deserialized objects (from Import-Csv, ConvertFrom-Json, Get-ADUser, etc.), and
    strongly-typed .NET objects. Accepts pipeline input.
 
.INPUTS
    System.Object
        Any object passed via the pipeline or the InputObject parameter.
 
.OUTPUTS
    System.Management.Automation.PSCustomObject
        A flat PSCustomObject where every property value is a scalar (string, number,
        boolean, or null). Property order is preserved from the source object.
 
.EXAMPLE
    # Flatten a single object with mixed property types
    $obj = [PSCustomObject]@{
        Name = "Server01"
        Tags = @("web", "prod", "east")
        Network = @{ IP = "10.0.0.1"; VLAN = 100 }
        Uptime = 99.9
    }
 
    $obj | ConvertTo-FlatObject
 
    # Output:
    # Name : Server01
    # Tags : web; prod; east
    # Network : {"IP":"10.0.0.1","VLAN":100}
    # Uptime : 99.9
 
.EXAMPLE
    # Flatten a collection of objects and export to CSV
    Get-ADComputer -Filter * -Properties * |
        ConvertTo-FlatObject |
        Export-Csv -Path "C:\Reports\computers.csv" -NoTypeInformation
 
    # Without flattening, multi-value AD attributes would appear as "System.String[]" in the CSV.
 
.EXAMPLE
    # Flatten objects from a JSON import
    $data = Get-Content ".\devices.json" | ConvertFrom-Json
    $data | ConvertTo-FlatObject | Format-Table -AutoSize
 
.NOTES
    Name : ConvertTo-FlatObject
    Author : Vibhu Bhatnagar
    Version : 1.0.0
 
    - Nested object serialization uses ConvertTo-Json with -Depth 3. Objects deeper than
      3 levels will be truncated by ConvertTo-Json with no error from this function.
    - This function does not recurse into nested objects; it serializes them as JSON strings.
      If you need full recursive flattening with dotted-path property names
      (e.g., "Network.IP"), that requires a separate recursive implementation.
    - PSObject.Properties reflection works on any object type, including deserialized
      CIM/WMI instances, Active Directory objects, and XML elements.
    - Compatible with PowerShell 5.1 and PowerShell 7+.
 
.LINK
    ConvertTo-Json
    Export-Csv
    Select-Object
#>

function ConvertTo-FlatObject {
    [CmdletBinding()]
    [OutputType([PSCustomObject])]
    param([Parameter(ValueFromPipeline)][object]$InputObject)
    process {
        if ($null -eq $InputObject) { return }
        $props = [ordered]@{}
        foreach ($prop in $InputObject.PSObject.Properties) {
            $val = $prop.Value
            $props[$prop.Name] = switch ($true) {
                ($val -is [System.Collections.IEnumerable] -and $val -isnot [string]) {
                    ($val | ForEach-Object { $_ }) -join '; '; break
                }
                ($val -is [hashtable] -or $val -is [PSCustomObject]) {
                    $val | ConvertTo-Json -Compress -Depth 3; break
                }
                default { $val }
            }
        }
        [PSCustomObject]$props
    }
}
#endregion