Private/Get-HDTConsoleDetectRuleText.ps1

function Get-HDTConsoleDetectRuleText {
    <#
        .SYNOPSIS
            A detection rule as the lines an administrator types into the
            console's Detection box.
 
        .DESCRIPTION
            THE BOX HOLDS THE DOCUMENT'S OWN BLOCK, minus the key it hangs off.
            app.yaml writes
 
                detect:
                  type: msiProduct
                  productCode: '{23170F69}'
 
            and the box shows the two lines under it, unindented - because what
            is typed back goes through the same YAML parser, and a block indented
            under nothing is not a block.
 
            THE ORDER IS Get-HDTApplicationDetectText'S, not a hashtable's. A
            hashtable enumerates in a different order on 5.1 and 7, so rendering
            the rule here would make the box read differently on the two engines
            for the same document. This calls the one function that already
            settles the order, and takes the indentation off.
 
            AN APPLICATION WITH NO RULE GETS AN EMPTY STRING, not the sentence
            Get-HDTConsoleDetectionText writes. That sentence is for reading; in
            a box that writes app.yaml it would become the rule.
 
            THE KEYS GO BACK TO camelCase. ConvertTo-HDTApplicationCatalog hands
            out a projection with PascalCase properties - Type, ProductCode -
            because that is how the engine reads a rule off an object, while the
            document is authored in camelCase like every other HDT file. YAML
            keys are case-sensitive, so a box showing 'Type:' would write a
            document the validator refuses.
 
            AND A KEY THE RULE NEVER DECLARED IS NOT SHOWN. That same projection
            fills every optional key in, empty where the file left it out, so a
            step can read a stable shape under StrictMode. A box is not a step:
            "version: ''" in it becomes an empty version in app.yaml the moment
            somebody tabs away.
 
        .PARAMETER Detect
            The rule, or $null for an application that declares none.
 
        .INPUTS
            None. This command does not accept pipeline input.
 
        .OUTPUTS
            System.String
 
        .EXAMPLE
            Get-HDTConsoleDetectRuleText -Detect $application.Detect
    #>

    [CmdletBinding()]
    [OutputType([string])]
    param(
        [Parameter(Mandatory = $true, Position = 0)]
        [AllowNull()]
        [object] $Detect
    )

    Set-StrictMode -Version Latest
    $ErrorActionPreference = 'Stop'

    if ($null -eq $Detect) { return '' }

    $rule = $Detect

    if (-not ($rule -is [System.Collections.IDictionary])) {
        $copy = [ordered] @{}
        foreach ($property in @($rule.PSObject.Properties)) { $copy[$property.Name] = $property.Value }
        $rule = $copy
    }

    $declared = [ordered] @{}

    foreach ($key in @($rule.Keys)) {
        $value = [string] $rule[$key]
        if ([string]::IsNullOrEmpty($value)) { continue }

        $name = [string] $key
        $declared[($name.Substring(0, 1).ToLowerInvariant() + $name.Substring(1))] = $value
    }

    if ($declared.Count -eq 0) { return '' }

    $rule = $declared

    # The first line is 'detect:' and the rest are indented under it; the box
    # wants the rest, at the margin.
    $line = @(Get-HDTApplicationDetectText -Detect $rule -Key 'detect')

    $body = New-Object -TypeName System.Collections.ArrayList

    foreach ($current in $line[1..($line.Count - 1)]) {
        [void] $body.Add(([string] $current).Trim())
    }

    return ($body -join [System.Environment]::NewLine)
}