Private/Test-HDTRuleMatch.ps1
|
function Test-HDTRuleMatch { <# .SYNOPSIS Tests whether a rule's when conditions all match the current scope. .DESCRIPTION Rules are walked top to bottom; a rule applies if every when key matches". This is that test, and the four decisions it encodes are deliberate: NO CONDITION MATCHES. A rule with no when, or an empty when, applies to every machine. That is the Fallback rule. AN ABSENT OR NULL VALUE NEVER MATCHES. A rule keyed on a fact this machine does not have must not fire, including against an empty pattern. A machine with no TPM has HDTTPMVersion = $null, and a rule keyed on it is simply not for that machine. A LIST MATCHES ON ANY ELEMENT. HDTDefaultGateway is a list on a multi-NIC machine and MDT's DefaultGateway behaves the same way, so `when: { HDTDefaultGateway: "10.20.30.1" }` fires on a machine whose second adapter carries that gateway. THE OPERATOR IS CHOSEN PER PATTERN. -like when the expanded pattern contains * or ?, -eq otherwise. Both are case-insensitive. Always using -like would read the '[' in a model name as a character class, so a machine called 'Model[1]' would never match itself. Comparison is on ConvertTo-HDTComparableString output, which is why `HDTIsLaptop: true` in YAML matches the [bool] fact from CIM. The pattern is %Var%-expanded first, so a condition may reference another variable; a pattern whose token could not be resolved does not match, because comparing against a literal '%HDTFoo%' would be an accident rather than an answer. .PARAMETER When The rule's when mapping, as Import-HDTRuleDocument normalised it. $null or empty means "always". .PARAMETER Scope Name -> RAW value, the same dictionary %Var% expansion reads. .PARAMETER Unresolved An optional ArrayList that collects the names of tokens a pattern referenced but the scope could not supply. .OUTPUTS System.Boolean .EXAMPLE Test-HDTRuleMatch -When $rule.When -Scope $scope Returns $true when every condition of the rule matches. #> [CmdletBinding()] [OutputType([bool])] param( [Parameter(Mandatory = $true, Position = 0)] [AllowNull()] [System.Collections.IDictionary] $When, [Parameter(Mandatory = $true, Position = 1)] [ValidateNotNull()] [System.Collections.IDictionary] $Scope, [Parameter()] [AllowNull()] [System.Collections.ArrayList] $Unresolved ) Set-StrictMode -Version Latest $ErrorActionPreference = 'Stop' if ($null -eq $When) { return $true } # An unresolved token in a pattern has to be observable even when the caller # did not ask for the list, because it is what turns the condition off. $tracker = $Unresolved if ($null -eq $tracker) { $tracker = New-Object -TypeName System.Collections.ArrayList } foreach ($key in @($When.Keys)) { $name = [string] $key if (-not $Scope.Contains($name)) { return $false } $actual = $Scope[$name] if ($null -eq $actual) { return $false } $before = $tracker.Count $pattern = Expand-HDTVariableToken -Value (ConvertTo-HDTComparableString -Value $When[$key]) ` -Scope $Scope -Unresolved $tracker -Chain @($name) if ($tracker.Count -gt $before) { return $false } $isWildcard = ($pattern -match '[*?]') # @() over a scalar yields one candidate and over a list yields its # elements, which is exactly the "any element matches" rule. An empty # list therefore yields no candidate and does not match. $candidate = @($actual) $matched = $false foreach ($item in $candidate) { $text = ConvertTo-HDTComparableString -Value $item if ($null -eq $text) { continue } if ($isWildcard) { if ($text -like $pattern) { $matched = $true } } else { if ($text -eq $pattern) { $matched = $true } } } if (-not $matched) { return $false } } return $true } |