private/Get-WtwPropertyNames.ps1

function Get-WtwPropertyNames {
    <#
    .SYNOPSIS
        Return an object's property names as an array, including for empty objects.
    .DESCRIPTION
        PowerShell's implicit `$Object.PSObject.Properties.Name` enumeration throws
        under strict mode when the property collection is empty. This helper always
        returns an array so callers can safely enumerate, count, and use `-contains`.

        Hashtables are handled separately: PowerShell only special-cases key access
        in the parser, so `PSObject.Properties` on a hashtable exposes the .NET
        surface (Count, Keys, Values, ...) and never the keys themselves.
    #>

    [CmdletBinding()]
    param(
        [AllowNull()]
        [object] $Object
    )

    if ($null -eq $Object) {
        return ,@()
    }

    if ($Object -is [System.Collections.IDictionary]) {
        return ,@($Object.Keys | ForEach-Object { [string] $_ })
    }

    $names = @($Object.PSObject.Properties | ForEach-Object { $_.Name })
    return ,$names
}

function Get-WtwPropertyValue {
    <#
    .SYNOPSIS
        Read a possibly absent object property without violating strict mode.
    .DESCRIPTION
        Works for both PSCustomObject (registry/config JSON) and hashtable inputs.
        Hashtables need the explicit branch: `PSObject.Properties['key']` on a
        hashtable resolves against the .NET surface, not the keys, so every lookup
        would silently return $DefaultValue.
    #>

    [CmdletBinding()]
    param(
        [AllowNull()]
        [object] $Object,

        [Parameter(Mandatory)]
        [string] $Name,

        [AllowNull()]
        [object] $DefaultValue = $null
    )

    if ($null -eq $Object) {
        return $DefaultValue
    }

    if ($Object -is [System.Collections.IDictionary]) {
        if (-not $Object.Contains($Name)) {
            return $DefaultValue
        }
        return $Object[$Name]
    }

    $property = $Object.PSObject.Properties[$Name]
    if ($null -eq $property) {
        return $DefaultValue
    }

    return $property.Value
}