Public/Assert-RequiredProperties.ps1

function Assert-RequiredProperties {
    <#
    .SYNOPSIS
        Validates that an object has all required properties and none are empty.
        Throws a descriptive error if any property is missing or blank.
 
    .DESCRIPTION
        Used by consumer repos to validate JSON config entries without
        duplicating the Get-Member + IsNullOrWhiteSpace loop.
 
    .PARAMETER Object
        The PSCustomObject to validate (e.g. a single config entry).
 
    .PARAMETER Properties
        Array of property names that must be present and non-empty.
 
    .PARAMETER Context
        String used in error messages to identify the object
        (e.g. "VM 'ubuntu-01-ci'").
 
    .EXAMPLE
        Assert-RequiredProperties -Object $vm `
            -Properties @('vmName', 'ipAddress') `
            -Context "VM '$($vm.vmName)'"
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory)]
        [object] $Object,

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

        [Parameter(Mandatory)]
        [string] $Context
    )

    $members = $Object.PSObject.Properties.Name

    # Collect all errors before throwing so the consumer sees the full picture
    # in one run rather than fixing one property at a time.
    $errors = [System.Collections.Generic.List[string]]::new()

    foreach ($property in $Properties) {
        if ($members -notcontains $property) {
            $errors.Add("$Context is missing required property '$property'.")
            continue
        }

        # Scalars (strings, numbers): IsNullOrWhiteSpace after a [string] cast.
        # Arrays: count, because asking "does this collection have elements?"
        # is more direct than relying on [string](@()) = "" as a side-effect.
        # String is excluded from the array branch despite implementing
        # IEnumerable<char> so that "" still goes through IsNullOrWhiteSpace.
        $value   = $Object.$property
        $isEmpty = if ($value -is [System.Collections.IEnumerable] -and
                       $value -isnot [string]) {
            @($value).Count -eq 0
        } else {
            # Cast to [string] so IsNullOrWhiteSpace receives the right type
            # regardless of whether the value is a string, int, etc.
            [string]::IsNullOrWhiteSpace([string]$value)
        }
        if ($isEmpty) {
            $errors.Add("$Context has empty required property '$property'.")
        }
    }

    if ($errors.Count -gt 0) {
        throw ($errors -join [Environment]::NewLine)
    }
}