From 35484ac9c2151e60fcf3f1e5068dd16c9e5e6fdc Mon Sep 17 00:00:00 2001 From: Roy Klooster Date: Mon, 26 Oct 2026 10:02:18 +0100 Subject: [PATCH 1/2] fix(intune-anomalies): show deprecated setting values by their Intune name ConfiguredValue on a deprecated-setting row was the option id, such as com.apple.applicationaccess_allowgenmoji_false. It is now the option's label from the expanded setting definition (False), or the part after the definition id when the definition has no label for it. The Maester pack shows this column; the HTML report does not. Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 4 ++++ Tests/IntuneAnomalyCore.Tests.ps1 | 17 +++++++++++++++ module/Core/IntuneAnomalyResolvers.ps1 | 29 ++++++++++++++++++++++++-- module/Private/IntuneAnomalies.ps1 | 7 +++---- 4 files changed, 51 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 381c5bf..1b63836 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,10 @@ The format follows [Conventional Commits](https://www.conventionalcommits.org/) ## Unreleased +### Fixes + +- **Deprecated settings carry the configured value by its Intune name** (`False` instead of `com.apple.applicationaccess_allowgenmoji_false`). The Maester pack shows it; the HTML report does not show this column. + ## [1.3.0] - 2026-10-26 Release for the Intune Anomalies report. diff --git a/Tests/IntuneAnomalyCore.Tests.ps1 b/Tests/IntuneAnomalyCore.Tests.ps1 index 3c7df50..46138b9 100644 --- a/Tests/IntuneAnomalyCore.Tests.ps1 +++ b/Tests/IntuneAnomalyCore.Tests.ps1 @@ -501,6 +501,23 @@ Describe 'Resolvers' { @($r.Rows).SettingDisplayName | Should -Be @('Old thing (Deprecated)') @($r.Rows)[0].DetectionSource | Should -Be 'CatalogDisplayName' } + It 'Deprecated: shows the option label as the configured value, else the part after the definition id' { + $r = InModuleScope RKSolutions { + $choice = '#microsoft.graph.deviceManagementConfigurationChoiceSettingInstance' + $policy = [pscustomobject]@{ id = 'p1'; name = 'Pol'; platforms = 'macOS'; settings = @( + [pscustomobject]@{ + settingInstance = [pscustomobject]@{ '@odata.type' = $choice; settingDefinitionId = 'def_a'; choiceSettingValue = [pscustomobject]@{ value = 'def_a_false' } } + settingDefinitions = @([pscustomobject]@{ id = 'def_a'; displayName = 'A (Deprecated)'; options = @([pscustomobject]@{ itemId = 'def_a_false'; displayName = 'False' }) }) + } + [pscustomobject]@{ + settingInstance = [pscustomobject]@{ '@odata.type' = $choice; settingDefinitionId = 'def_b'; choiceSettingValue = [pscustomobject]@{ value = 'def_b_1' } } + settingDefinitions = @([pscustomobject]@{ id = 'def_b'; displayName = 'B (Deprecated)' }) + }) } + $d = [pscustomobject]@{ TenantName = 'T'; SecurityKeyContext = [pscustomobject]@{ ConfigurationPolicies = @($policy) }; Warnings = @() } + Resolve-IntuneDeprecatedAnomalies -Data $d + } + @($r.Rows).ConfiguredValue | Should -Be @('False', '1') + } } Describe 'Unreadable keys, settings fallback and null children' { diff --git a/module/Core/IntuneAnomalyResolvers.ps1 b/module/Core/IntuneAnomalyResolvers.ps1 index 6e73ec6..f721bed 100644 --- a/module/Core/IntuneAnomalyResolvers.ps1 +++ b/module/Core/IntuneAnomalyResolvers.ps1 @@ -454,6 +454,25 @@ function Get-IntuneSettingValueString { } } +function Get-IntuneSettingValueLabel { + <# + Turns a choice value such as com.apple.applicationaccess_allowgenmoji_false into its label from the + expanded setting definition ("False"), or into the part after the definition id ("false") when the + definition has no label for it. Other values come back as they are. Collection values are mapped one by one. + #> + [CmdletBinding()] + [OutputType([string])] + param([string] $DefinitionId, [string] $Value, [hashtable] $Catalog) + + $options = if ($Catalog -and $DefinitionId -and $Catalog.ContainsKey($DefinitionId)) { $Catalog[$DefinitionId].Options } + $labels = foreach ($v in ($Value -split '; ')) { + if ($options -and $options[$v]) { $options[$v] } + elseif ($DefinitionId -and $v.StartsWith("${DefinitionId}_")) { $v.Substring($DefinitionId.Length + 1) } + else { $v } + } + return (@($labels) -join '; ') +} + function Get-IntuneSettingDeprecations { <# Recursively walk a settingInstance (and any nested group children) and @@ -476,7 +495,7 @@ function Get-IntuneSettingDeprecations { $hits.Add([PSCustomObject]@{ DefinitionId = $defId DisplayName = if ([string]::IsNullOrWhiteSpace($check.DisplayName)) { $defId } else { $check.DisplayName } - Value = $value + Value = Get-IntuneSettingValueLabel -DefinitionId $defId -Value $value -Catalog $Catalog Source = $check.Source }) } @@ -513,7 +532,13 @@ function Resolve-IntuneDeprecatedAnomalies { $catalog = @{} foreach ($policy in $Context.ConfigurationPolicies) { foreach ($s in @($policy.settings)) { - foreach ($def in @($s.settingDefinitions)) { if ($def.id -and -not $catalog.ContainsKey([string]$def.id)) { $catalog[[string]$def.id] = [pscustomobject]@{ DisplayName = [string]$def.displayName } } } + foreach ($def in @($s.settingDefinitions)) { + if (-not $def.id -or $catalog.ContainsKey([string]$def.id)) { continue } + # Choice definitions carry their options: itemId (the configured value) -> displayName (its label). + $options = @{} + foreach ($o in @($def.options)) { if ($o.itemId) { $options[[string]$o.itemId] = [string]$o.displayName } } + $catalog[[string]$def.id] = [pscustomobject]@{ DisplayName = [string]$def.displayName; Options = $options } + } } } $out = [System.Collections.Generic.List[PSObject]]::new() diff --git a/module/Private/IntuneAnomalies.ps1 b/module/Private/IntuneAnomalies.ps1 index af49608..cfb8d95 100644 --- a/module/Private/IntuneAnomalies.ps1 +++ b/module/Private/IntuneAnomalies.ps1 @@ -188,10 +188,9 @@ function New-IntuneAnomaliesHTMLReport { } # Generate table rows for deprecated Settings Catalog settings. - # Customer / ConfiguredValue / DetectionSource intentionally omitted - they're - # either constant (Customer) or redundant with the Setting Definition ID for - # the common choice-setting case where Value == DefId + "_". The full - # record still carries those fields for downstream consumers. + # Customer / ConfiguredValue / DetectionSource intentionally omitted - Customer + # is constant here. The full record still carries those fields for downstream + # consumers such as the Maester pack, which shows ConfiguredValue. $deprecatedSettingsRows = "" foreach ($item in $Report_DeprecatedSettings) { $deprecatedSettingsRows += @" From ec3c36568c58b24fef3356d54c0185e9fc1ad5ab Mon Sep 17 00:00:00 2001 From: Roy Klooster Date: Mon, 26 Oct 2026 10:28:41 +0100 Subject: [PATCH 2/2] fix(intune-anomalies): keep simple setting values as they are Co-Authored-By: Claude Opus 5.5 --- Tests/IntuneAnomalyCore.Tests.ps1 | 8 ++++++-- module/Core/IntuneAnomalyResolvers.ps1 | 3 ++- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/Tests/IntuneAnomalyCore.Tests.ps1 b/Tests/IntuneAnomalyCore.Tests.ps1 index 46138b9..366f735 100644 --- a/Tests/IntuneAnomalyCore.Tests.ps1 +++ b/Tests/IntuneAnomalyCore.Tests.ps1 @@ -501,7 +501,7 @@ Describe 'Resolvers' { @($r.Rows).SettingDisplayName | Should -Be @('Old thing (Deprecated)') @($r.Rows)[0].DetectionSource | Should -Be 'CatalogDisplayName' } - It 'Deprecated: shows the option label as the configured value, else the part after the definition id' { + It 'Deprecated: shows the option label as the configured value, else the part after the definition id, and keeps simple values' { $r = InModuleScope RKSolutions { $choice = '#microsoft.graph.deviceManagementConfigurationChoiceSettingInstance' $policy = [pscustomobject]@{ id = 'p1'; name = 'Pol'; platforms = 'macOS'; settings = @( @@ -512,11 +512,15 @@ Describe 'Resolvers' { [pscustomobject]@{ settingInstance = [pscustomobject]@{ '@odata.type' = $choice; settingDefinitionId = 'def_b'; choiceSettingValue = [pscustomobject]@{ value = 'def_b_1' } } settingDefinitions = @([pscustomobject]@{ id = 'def_b'; displayName = 'B (Deprecated)' }) + } + [pscustomobject]@{ + settingInstance = [pscustomobject]@{ '@odata.type' = '#microsoft.graph.deviceManagementConfigurationSimpleSettingInstance'; settingDefinitionId = 'def_c'; simpleSettingValue = [pscustomobject]@{ value = 'def_c_custom' } } + settingDefinitions = @([pscustomobject]@{ id = 'def_c'; displayName = 'C (Deprecated)' }) }) } $d = [pscustomobject]@{ TenantName = 'T'; SecurityKeyContext = [pscustomobject]@{ ConfigurationPolicies = @($policy) }; Warnings = @() } Resolve-IntuneDeprecatedAnomalies -Data $d } - @($r.Rows).ConfiguredValue | Should -Be @('False', '1') + @($r.Rows).ConfiguredValue | Should -Be @('False', '1', 'def_c_custom') } } diff --git a/module/Core/IntuneAnomalyResolvers.ps1 b/module/Core/IntuneAnomalyResolvers.ps1 index f721bed..7541fd2 100644 --- a/module/Core/IntuneAnomalyResolvers.ps1 +++ b/module/Core/IntuneAnomalyResolvers.ps1 @@ -495,7 +495,8 @@ function Get-IntuneSettingDeprecations { $hits.Add([PSCustomObject]@{ DefinitionId = $defId DisplayName = if ([string]::IsNullOrWhiteSpace($check.DisplayName)) { $defId } else { $check.DisplayName } - Value = Get-IntuneSettingValueLabel -DefinitionId $defId -Value $value -Catalog $Catalog + # Only choice values are option ids; a simple value is what the admin typed and stays as it is. + Value = if ([string]$SettingInstance.'@odata.type' -like '*choiceSetting*') { Get-IntuneSettingValueLabel -DefinitionId $defId -Value $value -Catalog $Catalog } else { $value } Source = $check.Source }) }