From 885c5f40097f42fce2cadb59a314db59ccfc0c8d Mon Sep 17 00:00:00 2001 From: fadwen <110697945+fadwen@users.noreply.github.com> Date: Thu, 8 Oct 2026 21:20:23 -0700 Subject: [PATCH] fix(filters): name the escaped quote, the whitespace -contains and the exclude-mode effect The filter parser refused a double quote inside a value with "expected and, or or ) ... found hi\" which reads as a missing connector; the service has no escape (FLT-V44, FLT-V45), and the message now says so and points at -contains or -startsWith on the parts around the quote. A -contains value that is only whitespace matched every device in the probe (FLT-W27, FLT-Y03) and the evaluator agreed, with no warning; it now carries an AlwaysMatches warning like -ne with a value no device reports. In exclude mode "never matches" meant the filter excludes nobody and the assignment reaches every device in the group (W32-FILTER-EXCLUDE), and the message did not say so. Get-IslFilterWarningMessage appends the exclude-mode consequence for Test-IntuneAssignmentFilter (-Mode) and Test-IntuneDeployedScript (the assignment filter type); include mode and the other warning kinds are unchanged. The tenant-wide help example keeps to windows10AndLater, since the parser carries the Windows property set and value tables. --- CHANGELOG.md | 18 ++++++- Private/ConvertFrom-IslFilterRule.ps1 | 44 ++++++++++++++-- Private/Get-IslFilterWarningMessage.ps1 | 50 +++++++++++++++++++ Public/Test-IntuneAssignmentFilter.ps1 | 9 +++- Public/Test-IntuneDeployedScript.ps1 | 4 +- .../ConvertFrom-IslFilterRule.Tests.ps1 | 14 +++++- .../Get-IslFilterWarningMessage.Tests.ps1 | 45 +++++++++++++++++ .../Test-IntuneAssignmentFilter.Tests.ps1 | 18 +++++++ .../Test-IntuneDeployedScript.Tests.ps1 | 21 ++++++++ .../Test-IntuneAssignmentFilter.md | 16 ++++-- .../Test-IntuneDeployedScript.md | 10 ++-- en-US/IntuneScriptLab-Help.xml | 26 +++++++--- 12 files changed, 249 insertions(+), 26 deletions(-) create mode 100644 Private/Get-IslFilterWarningMessage.ps1 create mode 100644 Tests/Unit/Private/Get-IslFilterWarningMessage.Tests.ps1 diff --git a/CHANGELOG.md b/CHANGELOG.md index 646aec2..246e1f9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,7 +11,23 @@ release notes. ## [Unreleased] -Nothing yet. +### Added + +- The filter parser warns about a `-contains` value that is only whitespace: the service accepts + `" "`, trims it to nothing, and every value contains that, so the clause matches every device + (`FLT-W27`, `FLT-Y03`). `Test-IntuneDeployedScript` reports it as `IslFilterIssue` Information. +- In exclude mode, `Test-IntuneAssignmentFilter` and `Test-IntuneDeployedScript` say what a + warning means for the assignment: a clause that never matches excludes nobody, so the assignment + reaches every device in the group; one that matches every device excludes everybody. Include + mode keeps the parser's text. + +### Changed + +- A double quote inside a filter value (`"say \"hi\""` or `"say ""hi"""`) is now refused with a + message saying that no escape exists and that `-contains` or `-startsWith` on the parts around + the quote is the way through (`FLT-V44`, `FLT-V45`), instead of "expected 'and', 'or' or ')'". +- The `Test-IntuneAssignmentFilter` help example that checks every filter in a tenant keeps to the + Windows platform first, since the parser's property set and value tables are the Windows ones. ## [0.30.0] - 2026-10-08 diff --git a/Private/ConvertFrom-IslFilterRule.ps1 b/Private/ConvertFrom-IslFilterRule.ps1 index 4acfd36..c1e4ace 100644 --- a/Private/ConvertFrom-IslFilterRule.ps1 +++ b/Private/ConvertFrom-IslFilterRule.ps1 @@ -24,8 +24,9 @@ function ConvertFrom-IslFilterRule { -ErrorVariable, and the public command wants to write exactly one. The result carries the clauses in order, the tree the evaluator walks, and warnings for rules the service accepts but that never match a Windows device: an enumerated value outside the documented - set (cpuArchitecture "x64", deviceTrustType "Microsoft Entra joined"), the deprecated - osVersion property and the undocumented isTpmAttested. + set (cpuArchitecture "x64", deviceTrustType "Microsoft Entra joined"), a -contains value + that is only whitespace (the evaluator trims it to nothing, and every name contains that), + the deprecated osVersion property and the undocumented isTpmAttested. .PARAMETER Rule The rule text as the portal's rule syntax editor or the Graph assignmentFilter.rule holds it. @@ -93,6 +94,23 @@ function ConvertFrom-IslFilterRule { }) } + # A double quote inside a value has no escape (FLT-V44, FLT-V45): "say \"hi\"" tokenizes as a + # string ending in a backslash with a word glued to it, "say ""hi""" as two strings glued + # together. Either shape where a connector was expected is that mistake, not a missing 'and' + function Test-EscapedQuote { + param($Previous, $Next) + if (-not $Previous -or -not $Next -or $Previous.Type -ne 'string') { return $false } + $glued = $Next.Position -eq $Previous.End + 1 + [bool]($glued -and ($Next.Type -eq 'string' -or $Previous.Text.EndsWith('\'))) + } + + function Write-EscapeFailure { + param($Previous) + Write-Failure ("a double quote inside a value cannot be escaped: the service refuses both \`" and `"`" " + + "(the value at position $($Previous.Position)); match the parts around the quote with " + + '-contains or -startsWith instead') + } + # The value after an operator: a string, a list, $null or a bare version, checked against # what the property and the operator accept function Read-Value { @@ -130,7 +148,11 @@ function ConvertFrom-IslFilterRule { $next = Get-CurrentToken if ($next -and $next.Type -eq 'comma') { $state.Pos++ } elseif ($next -and $next.Type -ne 'rbracket') { - Write-Failure "expected ',' or ']' at position $($next.Position), found '$($next.Text)'" + if (Test-EscapedQuote -Previous $item -Next $next) { Write-EscapeFailure -Previous $item } + else { + Write-Failure ("expected ',' or ']' at position $($next.Position), " + + "found '$($next.Text)'") + } return } } @@ -190,6 +212,13 @@ function ConvertFrom-IslFilterRule { } } } + if ($Operator -eq 'contains' -and $kind -eq 'String' -and "$value".Trim().Length -eq 0) { + # The evaluator trims the value to nothing, and every name contains an empty string: + # -contains " " matched every device (FLT-W27, FLT-Y03) + Add-Warning -Kind 'AlwaysMatches' -Message ("'$value' is only whitespace, which the evaluator trims " + + "to nothing, and every value contains that; the clause at character $($token.Position) " + + 'matches every device') + } if ($isList -and $kind -eq 'String') { $value = @($value) } $value } @@ -292,7 +321,12 @@ function ConvertFrom-IslFilterRule { $close = Get-CurrentToken if (-not $close) { Write-Failure "the '(' at position $($token.Position) is not closed"; return } if ($close.Type -ne 'rparen') { - Write-Failure "expected 'and', 'or' or ')' at position $($close.Position), found '$($close.Text)'" + $previous = $tokens[$state.Pos - 1] + if (Test-EscapedQuote -Previous $previous -Next $close) { Write-EscapeFailure -Previous $previous } + else { + Write-Failure ("expected 'and', 'or' or ')' at position $($close.Position), " + + "found '$($close.Text)'") + } return } $state.Pos++ @@ -354,7 +388,9 @@ function ConvertFrom-IslFilterRule { $tree = Read-Expression $rest = Get-CurrentToken if (-not $state.Error -and $rest) { + $previous = $tokens[$state.Pos - 1] if ($rest.Type -eq 'rparen') { Write-Failure "unexpected ')' at position $($rest.Position)" } + elseif (Test-EscapedQuote -Previous $previous -Next $rest) { Write-EscapeFailure -Previous $previous } else { Write-Failure "expected 'and' or 'or' before '$($rest.Text)' at position $($rest.Position)" } } } diff --git a/Private/Get-IslFilterWarningMessage.ps1 b/Private/Get-IslFilterWarningMessage.ps1 new file mode 100644 index 0000000..9a176fb --- /dev/null +++ b/Private/Get-IslFilterWarningMessage.ps1 @@ -0,0 +1,50 @@ +function Get-IslFilterWarningMessage { + <# + .SYNOPSIS + The text of a filter parser warning, with what it means for the mode the filter is attached in. + + .DESCRIPTION + ConvertFrom-IslFilterRule reports a clause as "never matches" or "matches every device" + without knowing whether the filter is attached to an assignment in include or exclude mode, + and the two modes turn the same clause into opposite outcomes: an include filter that never + matches reaches nobody (W32-FILTER-INCLUDE), while an exclude filter that never matches + excludes nobody, so the assignment reaches every device in the group (W32-FILTER-EXCLUDE). + Test-IntuneAssignmentFilter and Test-IntuneDeployedScript know the mode, and append the + exclude-mode consequence here so one message is read the same way in both places. Include + mode and the other warning kinds come back unchanged. + + .PARAMETER Warning + One IntuneScriptLab.FilterWarning from ConvertFrom-IslFilterRule (Kind and Message). + + .PARAMETER Mode + Include (the default) or Exclude: how the filter is attached to the assignment. + + .EXAMPLE + Get-IslFilterWarningMessage -Warning $parsed.Warnings[0] -Mode Exclude + + The "never matches" message followed by "; as an exclude filter it excludes nobody, so the + assignment reaches every device in the group". + + .OUTPUTS + System.String + #> + [CmdletBinding()] + [OutputType([string])] + param( + [Parameter(Mandatory)] + $Warning, + + [ValidateSet('Include', 'Exclude')] + [string]$Mode = 'Include' + ) + + $note = if ($Mode -ne 'Exclude') { '' } + elseif ($Warning.Kind -eq 'NeverMatches') { + '; as an exclude filter it excludes nobody, so the assignment reaches every device in the group' + } + elseif ($Warning.Kind -eq 'AlwaysMatches') { + '; as an exclude filter it excludes every device, so the assignment reaches nobody' + } + else { '' } + "$($Warning.Message)$note" +} diff --git a/Public/Test-IntuneAssignmentFilter.ps1 b/Public/Test-IntuneAssignmentFilter.ps1 index 1360a31..fd5e252 100644 --- a/Public/Test-IntuneAssignmentFilter.ps1 +++ b/Public/Test-IntuneAssignmentFilter.ps1 @@ -118,7 +118,12 @@ function Test-IntuneAssignmentFilter { Write-Error @errorSplat return } - foreach ($warning in $parsed.Warnings) { Write-Warning $warning.Message } + # The parser does not know the mode; in exclude mode a clause that never matches excludes nobody + $messages = [System.Collections.Generic.List[string]]::new() + foreach ($warning in $parsed.Warnings) { + $messages.Add((Get-IslFilterWarningMessage -Warning $warning -Mode $Mode)) + } + foreach ($message in $messages) { Write-Warning $message } $matched = $null $applicable = $null @@ -152,7 +157,7 @@ function Test-IntuneAssignmentFilter { Applicable = $applicable Reason = $reason Clauses = $parsed.Clauses - Warnings = @($parsed.Warnings | ForEach-Object { $_.Message }) + Warnings = $messages.ToArray() Device = $deviceView } } diff --git a/Public/Test-IntuneDeployedScript.ps1 b/Public/Test-IntuneDeployedScript.ps1 index a4351cf..9c7a2f7 100644 --- a/Public/Test-IntuneDeployedScript.ps1 +++ b/Public/Test-IntuneDeployedScript.ps1 @@ -190,11 +190,13 @@ function Test-IntuneDeployedScript { ConvertTo-PolicyFinding @unreadSplat continue } + $mode = if ($filterType -eq 'exclude') { 'Exclude' } else { 'Include' } foreach ($warning in $parsed.Warnings) { $severity = if ($warning.Kind -eq 'NeverMatches') { 'Warning' } else { 'Information' } + $message = Get-IslFilterWarningMessage -Warning $warning -Mode $mode $issueSplat = @{ PolicyKind = $PolicyKind; Policy = $Policy; Rule = 'IslFilterIssue'; Severity = $severity - Message = "${label}: $($warning.Message). Rule: $($filter.rule)" + Message = "${label}: $message. Rule: $($filter.rule)" Evidence = $filterEvidence } ConvertTo-PolicyFinding @issueSplat diff --git a/Tests/Unit/Private/ConvertFrom-IslFilterRule.Tests.ps1 b/Tests/Unit/Private/ConvertFrom-IslFilterRule.Tests.ps1 index a5add8d..dbe9ac4 100644 --- a/Tests/Unit/Private/ConvertFrom-IslFilterRule.Tests.ps1 +++ b/Tests/Unit/Private/ConvertFrom-IslFilterRule.Tests.ps1 @@ -131,8 +131,11 @@ Describe 'ConvertFrom-IslFilterRule' -Tag 'Unit', 'Private' { @{ Rule = '(device.deviceName -eq "X") // comment'; Message = "*expected 'and' or 'or' before '//'*" } @{ Rule = '(device.deviceName -eq "X") xor (device.model -eq "Y")' Message = "*expected 'and' or 'or' before 'xor'*" } - @{ Rule = '(device.deviceName -eq "say \"hi\"")' - Message = "*expected 'and', 'or' or ')'*found 'hi\'*" } + # No escape exists (FLT-V44, FLT-V45): a backslash-quote or a doubled quote is named as such + @{ Rule = '(device.deviceName -eq "say \"hi\"")'; Message = '*cannot be escaped*position 24*' } + @{ Rule = '(device.deviceName -eq "say ""hi""")'; Message = '*cannot be escaped*position 24*' } + @{ Rule = 'device.deviceName -eq "say \"hi\""'; Message = '*cannot be escaped*position 23*' } + @{ Rule = '(device.deviceName -in ["say \"hi\""])'; Message = '*cannot be escaped*position 25*' } @{ Rule = '(device.deviceName -eq "X") or'; Message = '*expected a clause or "(" at the end*' } @{ Rule = '(device.deviceName -eq "X") and'; Message = '*expected a clause or "(" at the end*' } @{ Rule = '(device.deviceName -eq "X") or ()'; Message = "*unexpected ')'*" } @@ -256,6 +259,11 @@ Describe 'ConvertFrom-IslFilterRule' -Tag 'Unit', 'Private' { Text = "*'company'*Personal, Corporate, Unknown*" } @{ Rule = '(device.operatingSystemSKU -eq "Windows Enterprise")'; Kind = 'NeverMatches' Text = "*'Windows Enterprise'*" } + # " " is accepted and trimmed to nothing, which every value contains (FLT-W27, FLT-Y03) + @{ Rule = '(device.deviceName -contains " ")'; Kind = 'AlwaysMatches' + Text = "*' ' is only whitespace*matches every device*" } + @{ Rule = '(device.model -contains " ")'; Kind = 'AlwaysMatches' + Text = "*only whitespace*character 25 matches every device*" } ) { $parsed = ConvertFrom-Rule -Rule $Rule @($parsed.Warnings).Count | Should-Be 1 @@ -269,6 +277,8 @@ Describe 'ConvertFrom-IslFilterRule' -Tag 'Unit', 'Private' { @{ Rule = '(device.operatingSystemSKU -startsWith "Ent")' } @{ Rule = '(device.operatingSystemSKU -eq "EnterpriseSEval")' } @{ Rule = '(device.deviceTrustType -eq $null)' } + @{ Rule = '(device.deviceName -contains " x ")' } + @{ Rule = '(device.deviceName -eq " ")' } ) { @((ConvertFrom-Rule -Rule $Rule).Warnings).Count | Should-Be 0 } diff --git a/Tests/Unit/Private/Get-IslFilterWarningMessage.Tests.ps1 b/Tests/Unit/Private/Get-IslFilterWarningMessage.Tests.ps1 new file mode 100644 index 0000000..36ee41b --- /dev/null +++ b/Tests/Unit/Private/Get-IslFilterWarningMessage.Tests.ps1 @@ -0,0 +1,45 @@ +#Requires -Modules @{ ModuleName = 'Pester'; ModuleVersion = '6.2.0' } + +<# + The exclude-mode note on a filter parser warning: a clause that never matches excludes nobody + and one that matches every device excludes everybody (W32-FILTER-INCLUDE, W32-FILTER-EXCLUDE); + include mode and the other warning kinds are passed through unchanged. +#> + +BeforeAll { + $script:ModuleRoot = Split-Path -Parent (Split-Path -Parent (Split-Path -Parent $PSScriptRoot)) + Import-Module (Join-Path $script:ModuleRoot 'IntuneScriptLab.psd1') -Force + + function script:Get-Message { + param([string]$Kind, [string]$Mode) + $warning = [pscustomobject]@{ Kind = $Kind; Message = 'the clause at character 29 never matches' } + InModuleScope IntuneScriptLab -Parameters @{ Warning = $warning; Mode = $Mode } { + Get-IslFilterWarningMessage -Warning $Warning -Mode $Mode + } + } +} + +AfterAll { + Remove-Module IntuneScriptLab -Force -ErrorAction SilentlyContinue +} + +Describe 'Get-IslFilterWarningMessage' -Tag 'Unit', 'Private' { + + It 'appends what a clause does to an exclude filter' -ForEach @( + @{ Kind = 'NeverMatches' + Expected = '*never matches; as an exclude filter it excludes nobody, so the assignment reaches*' } + @{ Kind = 'AlwaysMatches' + Expected = '*as an exclude filter it excludes every device, so the assignment reaches nobody' } + ) { + Get-Message -Kind $Kind -Mode Exclude | Should-BeLikeString $Expected + } + + It 'passes a warning through unchanged in mode' -ForEach @( + @{ Kind = 'NeverMatches'; Mode = 'Include' } + @{ Kind = 'AlwaysMatches'; Mode = 'Include' } + @{ Kind = 'Deprecated'; Mode = 'Exclude' } + @{ Kind = 'Undocumented'; Mode = 'Exclude' } + ) { + Get-Message -Kind $Kind -Mode $Mode | Should-Be 'the clause at character 29 never matches' + } +} diff --git a/Tests/Unit/Public/Test-IntuneAssignmentFilter.Tests.ps1 b/Tests/Unit/Public/Test-IntuneAssignmentFilter.Tests.ps1 index 65d5d93..1be9c29 100644 --- a/Tests/Unit/Public/Test-IntuneAssignmentFilter.Tests.ps1 +++ b/Tests/Unit/Public/Test-IntuneAssignmentFilter.Tests.ps1 @@ -212,6 +212,24 @@ Describe 'Test-IntuneAssignmentFilter' -Tag 'Unit', 'Public' { $result.Matched | Should-BeFalse } + It 'says what a clause that means for an exclude filter' -ForEach @( + @{ Rule = '(device.cpuArchitecture -eq "x64")'; Effect = 'never matches' + Note = ('*never matches; as an exclude filter it excludes nobody, so the assignment reaches ' + + 'every device in the group') } + @{ Rule = '(device.deviceTrustType -ne "Hybrid Entra joined")'; Effect = 'matches every device' + Note = ('*matches every device; as an exclude filter it excludes every device, so the ' + + 'assignment reaches nobody') } + ) { + $warnings = @() + $excludeSplat = @{ Rule = $Rule; Device = $script:Joined; Mode = 'Exclude' } + $result = Test-IntuneAssignmentFilter @excludeSplat -WarningVariable warnings 3>$null + "$($warnings[0])" | Should-BeLikeString $Note + $result.Warnings[0] | Should-BeLikeString $Note + # Include mode keeps the parser's text, which already says what happens to the assignment + $included = Test-IntuneAssignmentFilter -Rule $Rule -Device $script:Joined 3>$null + $included.Warnings[0] | Should-NotBeLikeString '*as an exclude filter*' + } + It 'reads the local device when none is given' { Mock Get-IslFilterDeviceFact -ModuleName IntuneScriptLab { @{ deviceName = 'LOCAL-1'; cpuArchitecture = 'arm64' } diff --git a/Tests/Unit/Public/Test-IntuneDeployedScript.Tests.ps1 b/Tests/Unit/Public/Test-IntuneDeployedScript.Tests.ps1 index 841516a..e890b00 100644 --- a/Tests/Unit/Public/Test-IntuneDeployedScript.Tests.ps1 +++ b/Tests/Unit/Public/Test-IntuneDeployedScript.Tests.ps1 @@ -252,6 +252,27 @@ Describe 'Test-IntuneDeployedScript' -Tag 'Unit', 'Public' { } -Times 1 -Exactly } + It 'says that a never-matching clause on an exclude filter reaches every device in the group' { + # Widget 1.0 carries the lab-devices filter in exclude mode; point it at the x64 one + $target = $script:Tenant.apps[1].assignments[0].target + $target.deviceAndAppManagementAssignmentFilterId = 'flt-x64' + try { + $findings = @(Test-IntuneDeployedScript -IncludeRule IslFilterIssue) + } + finally { + $target.deviceAndAppManagementAssignmentFilterId = 'flt-lab' + } + $excluded = @($findings | Where-Object PolicyName -eq 'Widget 1.0') + $excluded.Count | Should-Be 1 + $excluded[0].Severity | Should-Be 'Warning' + $excluded[0].Message | Should-BeLikeString ("Filter 'x64 only' (exclude): 'x64'*never matches; " + + 'as an exclude filter it excludes nobody, so the assignment reaches every device in the group. ' + + 'Rule: *') + # The include assignments keep the parser's text + @($findings | Where-Object PolicyName -eq 'Fix-Widget')[0].Message | + Should-NotBeLikeString '*as an exclude filter*' + } + It 'skips the filter check after the tenant refuses to show a filter' { $filterRoute = { $Uri -like '*/assignmentFilters/*' } Mock Invoke-IslGraphRequest -ModuleName IntuneScriptLab -ParameterFilter $filterRoute { diff --git a/docs/IntuneScriptLab/Test-IntuneAssignmentFilter.md b/docs/IntuneScriptLab/Test-IntuneAssignmentFilter.md index 8f4108f..a18208c 100644 --- a/docs/IntuneScriptLab/Test-IntuneAssignmentFilter.md +++ b/docs/IntuneScriptLab/Test-IntuneAssignmentFilter.md @@ -97,11 +97,16 @@ match excludes the device. ### EXAMPLE 3 -Get-MgBetaDeviceManagementAssignmentFilter | Test-IntuneAssignmentFilter -SyntaxOnly | +Get-MgBetaDeviceManagementAssignmentFilter -All | + Where-Object { "$($_.Platform)" -eq 'windows10AndLater' } | + Test-IntuneAssignmentFilter -SyntaxOnly | Where-Object Warnings | Select-Object Rule, Warnings -Every filter in the tenant whose rule can never match a Windows device (a "x64" architecture, -a "Microsoft Entra joined" trust type), without evaluating anything. +Every Windows filter in the tenant whose rule can never match a Windows device (a "x64" +architecture, a "Microsoft Entra joined" trust type), without evaluating anything. The platform +filter matters: the parser knows the Windows property set and value tables, so a macOS filter +with the correct "x64" would be reported as never matching, and an iOS-only property such as +isRooted is refused. ### EXAMPLE 4 @@ -144,7 +149,10 @@ HelpMessage: '' Include (the default) or Exclude: how the filter is attached to the assignment. The verdict -is Applicable, which is Matched for an include filter and not Matched for an exclude one. +is Applicable, which is Matched for an include filter and not Matched for an exclude one. In +Exclude mode a warning about a clause that never matches adds that the filter excludes nobody, +so the assignment reaches every device in the group; one that matches every device adds that +the assignment reaches nobody. ```yaml Type: System.String diff --git a/docs/IntuneScriptLab/Test-IntuneDeployedScript.md b/docs/IntuneScriptLab/Test-IntuneDeployedScript.md index 6c05a18..9d023c3 100644 --- a/docs/IntuneScriptLab/Test-IntuneDeployedScript.md +++ b/docs/IntuneScriptLab/Test-IntuneDeployedScript.md @@ -56,10 +56,12 @@ justified (Validation\Findings.md): deviceTrustType): the service accepts the rule and the filter evaluator matches nothing on that clause, so a rule made of it reaches nobody as an include and excludes nobody - as an exclude (FLT-V25, FLT-E07, FLT-V27, FLT-F01). The same - value under -ne or -notIn matches every device; that, the - deprecated osVersion, the undocumented isTpmAttested and a - rule this evaluator cannot read are Information. + as an exclude (FLT-V25, FLT-E07, FLT-V27, FLT-F01); the + message says which for the assignment's own mode. The same + value under -ne or -notIn, and a -contains value that is only + whitespace, match every device; those, the deprecated + osVersion, the undocumented isTpmAttested and a rule this + evaluator cannot read are Information. IslAssignmentIssue Warning a policy with no assignment, or only exclusions: no device resolves it, so it never runs (ASSIGN-NONE, ASSIGN-EXCLONLY) IslAssignmentIssue Info a user-context remediation or platform script assigned to diff --git a/en-US/IntuneScriptLab-Help.xml b/en-US/IntuneScriptLab-Help.xml index adad4b5..a514297 100644 --- a/en-US/IntuneScriptLab-Help.xml +++ b/en-US/IntuneScriptLab-Help.xml @@ -4582,7 +4582,10 @@ Without it the local device is read. Include (the default) or Exclude: how the filter is attached to the assignment. The verdict -is Applicable, which is Matched for an include filter and not Matched for an exclude one. +is Applicable, which is Matched for an include filter and not Matched for an exclude one. In +Exclude mode a warning about a clause that never matches adds that the filter excludes nobody, +so the assignment reaches every device in the group; one that matches every device adds that +the assignment reaches nobody. System.String @@ -4701,11 +4704,16 @@ match excludes the device. --------- EXAMPLE 3 --------- - Get-MgBetaDeviceManagementAssignmentFilter | Test-IntuneAssignmentFilter -SyntaxOnly | + Get-MgBetaDeviceManagementAssignmentFilter -All | + Where-Object { "$($_.Platform)" -eq 'windows10AndLater' } | + Test-IntuneAssignmentFilter -SyntaxOnly | Where-Object Warnings | Select-Object Rule, Warnings € - Every filter in the tenant whose rule can never match a Windows device (a "x64" architecture, -a "Microsoft Entra joined" trust type), without evaluating anything. + Every Windows filter in the tenant whose rule can never match a Windows device (a "x64" +architecture, a "Microsoft Entra joined" trust type), without evaluating anything. The platform +filter matters: the parser knows the Windows property set and value tables, so a macOS filter +with the correct "x64" would be reported as never matching, and an iOS-only property such as +isRooted is refused. @@ -4779,10 +4787,12 @@ justified (Validation\Findings.md): deviceTrustType): the service accepts the rule and the filter evaluator matches nothing on that clause, so a rule made of it reaches nobody as an include and excludes nobody - as an exclude (FLT-V25, FLT-E07, FLT-V27, FLT-F01). The same - value under -ne or -notIn matches every device; that, the - deprecated osVersion, the undocumented isTpmAttested and a - rule this evaluator cannot read are Information. + as an exclude (FLT-V25, FLT-E07, FLT-V27, FLT-F01); the + message says which for the assignment's own mode. The same + value under -ne or -notIn, and a -contains value that is only + whitespace, match every device; those, the deprecated + osVersion, the undocumented isTpmAttested and a rule this + evaluator cannot read are Information. IslAssignmentIssue Warning a policy with no assignment, or only exclusions: no device resolves it, so it never runs (ASSIGN-NONE, ASSIGN-EXCLONLY) IslAssignmentIssue Info a user-context remediation or platform script assigned to