Public/Test-HDTStepCondition.ps1

function Test-HDTStepCondition {
    <#
        .SYNOPSIS
            Evaluates a step or group condition against the resolved variables.
 
        .DESCRIPTION
            The runtime half of the closed grammar ConvertFrom-HDTStepCondition
            defines. It expands %Var% on BOTH operands
            against the live variable dictionary, renders both through
            ConvertTo-HDTComparableString so a boolean is 'True' and an integer is
            its invariant text, and compares CASE-INSENSITIVELY.
 
              == / -eq equal
              != / -ne not equal
              -like wildcard match
              -notlike wildcard non-match
 
            AN ABSENT CONDITION IS TRUE. $null, an empty string and whitespace all
            return $true, so a step with no condition: always runs and a group
            with no condition: imposes nothing on its children.
 
            AN UNRESOLVED TOKEN IS LEFT LITERAL, which is 02-03's rule and is the
            behaviour that matters most here. A condition naming a variable that
            does not exist evaluates to $false - it does not throw, and the token
            does not silently become the empty string. A condition that collapsed
            to "" == "" is how MDT-era task sequences ran the wrong branch on a
            typo. The token name is appended to -Unresolved so the loop can log
            it, because invisible is the only thing worse than wrong.
 
            A MALFORMED CONDITION THROWS, but it should never reach here:
            Assert-HDTSequenceDocument parses every condition at import, so
            authoring catches it first.
 
        .PARAMETER Condition
            The condition text. $null, empty and whitespace are all true.
 
        .PARAMETER Variable
            The live variable dictionary, name -> value. Looked up
            case-insensitively when it was built that way, which every HDT
            dictionary is.
 
        .PARAMETER Unresolved
            An ArrayList to append the name of every %Var% that resolved to
            nothing. Optional.
 
        .PARAMETER Path
            The file the condition came from, for the error message. Optional.
 
        .OUTPUTS
            System.Boolean
 
        .EXAMPLE
            Test-HDTStepCondition -Condition '"%_HDTPhase%" == "FullOS"' -Variable $context.Variable
 
            The phase condition. _HDTPhase is WinPE or FullOS, never "OS".
 
        .EXAMPLE
            $unresolved = New-Object System.Collections.ArrayList
            Test-HDTStepCondition -Condition '"%HDTSite%" == "HQ"' -Variable $v -Unresolved $unresolved
 
            Returns $false and leaves 'HDTSite' in $unresolved when nothing set it.
    #>

    [CmdletBinding()]
    [OutputType([bool])]
    param(
        [Parameter(Mandatory = $true, Position = 0)]
        [AllowEmptyString()]
        [AllowNull()]
        [string] $Condition,

        [Parameter(Mandatory = $true)]
        [ValidateNotNull()]
        [System.Collections.IDictionary] $Variable,

        [Parameter()]
        [AllowNull()]
        [System.Collections.ArrayList] $Unresolved,

        [Parameter()]
        [string] $Path
    )

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

    if ([string]::IsNullOrWhiteSpace($Condition)) {
        return $true
    }

    $parsed = ConvertFrom-HDTStepCondition -Condition $Condition -Path $Path

    $left = ConvertTo-HDTComparableString -Value (Expand-HDTVariableToken -Value $parsed.Left -Scope $Variable -Unresolved $Unresolved -Path $Path)
    $right = ConvertTo-HDTComparableString -Value (Expand-HDTVariableToken -Value $parsed.Right -Scope $Variable -Unresolved $Unresolved -Path $Path)

    switch ($parsed.Operator) {
        '==' { return ($left -eq $right) }
        '-eq' { return ($left -eq $right) }
        '!=' { return ($left -ne $right) }
        '-ne' { return ($left -ne $right) }
        '-like' { return ($left -like $right) }
        '-notlike' { return ($left -notlike $right) }
    }

    # Unreachable: the operator came from the grammar's own closed set.
    return $false
}