From 3a69d1435401432918b69ae73edc673b2a30bc6b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Przemys=C5=82aw=20K=C5=82ys?= Date: Wed, 18 Feb 2026 11:39:31 +0100 Subject: [PATCH 1/3] Fix Defender policy detection for new ADMX paths and add registry fallback (#81) --- CHANGELOG.MD | 7 ++ .../ConvertTo-XMLRegistryDefenderOnReport.ps1 | 39 +++++++++++ Private/Script.GPODictionary.ps1 | 28 ++++++-- Tests/DefenderContent.Tests.ps1 | 64 +++++++++++++++++++ 4 files changed, 131 insertions(+), 7 deletions(-) create mode 100644 Private/ConvertTo-XMLRegistryDefenderOnReport.ps1 create mode 100644 Tests/DefenderContent.Tests.ps1 diff --git a/CHANGELOG.MD b/CHANGELOG.MD index d56c414..f21d091 100644 --- a/CHANGELOG.MD +++ b/CHANGELOG.MD @@ -1,5 +1,12 @@ # GPOZaurr Release History +## Unreleased +- Fix `WindowsDefender` content detection for newer ADMX categories by supporting both: + - `Windows Components/Windows Defender*` + - `Windows Components/Microsoft Defender Antivirus*` +- Fix `WindowsDefenderExploitGuard` detection for legacy/new category naming variants. +- Add Defender registry fallback parsing (`SOFTWARE\Microsoft\Windows Defender*`) so Defender settings that land in `RegistrySettings` are still surfaced in `WindowsDefender` report output. [#81](https://github.com/EvotecIT/GPOZaurr/issues/81) + ## 1.1.9 - 2024.12.02 - Fixes `Invoke-GPOZaurr` when using SplitReports without path [#58](https://github.com/EvotecIT/GPOZaurr/issues/58) diff --git a/Private/ConvertTo-XMLRegistryDefenderOnReport.ps1 b/Private/ConvertTo-XMLRegistryDefenderOnReport.ps1 new file mode 100644 index 0000000..a698efd --- /dev/null +++ b/Private/ConvertTo-XMLRegistryDefenderOnReport.ps1 @@ -0,0 +1,39 @@ +function ConvertTo-XMLRegistryDefenderOnReport { + <# + .SYNOPSIS + Converts Defender-related raw registry settings from the RegistrySettings report. + + .DESCRIPTION + This function is used as a fallback for Defender settings that are not exposed as + policy categories but are still present in RegistrySettings output. + + .PARAMETER GPO + RegistrySettings report object generated by ConvertTo-XMLRegistrySettings -SingleObject. + #> + [cmdletBinding()] + param( + [PSCustomObject] $GPO + ) + + foreach ($Registry in $GPO.Settings) { + if ($Registry.Key -like 'SOFTWARE\Microsoft\Windows Defender*') { + [PSCustomObject] [ordered] @{ + DisplayName = $GPO.DisplayName + DomainName = $GPO.DomainName + GUID = $GPO.GUID + GpoType = $GPO.GpoType + FallbackSource = 'RegistrySettings' + Hive = $Registry.Hive + Key = $Registry.Key + Name = $Registry.Name + Type = $Registry.Type + Value = $Registry.Value + Changed = $Registry.Changed + Filters = $Registry.Filters + Linked = $GPO.Linked + LinksCount = $GPO.LinksCount + Links = $GPO.Links + } + } + } +} diff --git a/Private/Script.GPODictionary.ps1 b/Private/Script.GPODictionary.ps1 index 5c1ace7..7b14924 100644 --- a/Private/Script.GPODictionary.ps1 +++ b/Private/Script.GPODictionary.ps1 @@ -1170,12 +1170,23 @@ Settings = 'Policy' } ) - GPOPath = 'Policies -> Administrative Templates -> Windows Components/Windows Defender' + ByReports = @( + @{ + Report = 'RegistrySettings' + } + ) + GPOPath = @( + 'Policies -> Administrative Templates -> Windows Components/Windows Defender' + 'Policies -> Administrative Templates -> Windows Components/Microsoft Defender Antivirus' + ) Code = { - ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'Windows Components/Windows Defender*' + ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'Windows Components/Windows Defender*', 'Windows Components/Microsoft Defender Antivirus*' } CodeSingle = { - ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'Windows Components/Windows Defender*' -SingleObject + ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'Windows Components/Windows Defender*', 'Windows Components/Microsoft Defender Antivirus*' -SingleObject + } + CodeReport = { + ConvertTo-XMLRegistryDefenderOnReport -GPO $GPO } } WindowsDefenderExploitGuard = @{ @@ -1186,12 +1197,15 @@ Settings = 'Policy' } ) - GPOPath = 'Policies -> Administrative Templates -> Windows Components/Microsoft Defender Antivirus/Microsoft Defender Exploit Guard' + GPOPath = @( + 'Policies -> Administrative Templates -> Windows Components/Windows Defender/Windows Defender Exploit Guard' + 'Policies -> Administrative Templates -> Windows Components/Microsoft Defender Antivirus/Microsoft Defender Exploit Guard' + ) Code = { - ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'Windows Components/Microsoft Defender Antivirus/Microsoft Defender Exploit Guard*' + ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'Windows Components/Windows Defender*/Windows Defender Exploit Guard*', 'Windows Components/Microsoft Defender Antivirus/Microsoft Defender Exploit Guard*', 'Windows Components/Microsoft Defender Antivirus/Windows Defender Exploit Guard*' } CodeSingle = { - ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'Windows Components/Microsoft Defender Antivirus/Microsoft Defender Exploit Guard*' -SingleObject + ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'Windows Components/Windows Defender*/Windows Defender Exploit Guard*', 'Windows Components/Microsoft Defender Antivirus/Microsoft Defender Exploit Guard*', 'Windows Components/Microsoft Defender Antivirus/Windows Defender Exploit Guard*' -SingleObject } } # WindowsFirewall = @{ @@ -1428,4 +1442,4 @@ ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'Windows Components/Windows Update*', 'Windows Components/Delivery Optimization*' -SingleObject } } -} \ No newline at end of file +} diff --git a/Tests/DefenderContent.Tests.ps1 b/Tests/DefenderContent.Tests.ps1 new file mode 100644 index 0000000..7913045 --- /dev/null +++ b/Tests/DefenderContent.Tests.ps1 @@ -0,0 +1,64 @@ +Describe 'Defender content detection' { + BeforeAll { + Import-Module $PSScriptRoot\..\*.psd1 -Force + } + + It 'WindowsDefender dictionary supports old and new categories' { + InModuleScope GPOZaurr { + $Entry = $Script:GPODitionary['WindowsDefender'] + $Entry.GPOPath | Should -Contain 'Policies -> Administrative Templates -> Windows Components/Windows Defender' + $Entry.GPOPath | Should -Contain 'Policies -> Administrative Templates -> Windows Components/Microsoft Defender Antivirus' + $Entry.ByReports.Report | Should -Contain 'RegistrySettings' + $Entry.CodeReport.ToString() | Should -Match 'ConvertTo-XMLRegistryDefenderOnReport' + } + } + + It 'WindowsDefenderExploitGuard supports category variants' { + InModuleScope GPOZaurr { + $Entry = $Script:GPODitionary['WindowsDefenderExploitGuard'] + $Entry.GPOPath | Should -Contain 'Policies -> Administrative Templates -> Windows Components/Windows Defender/Windows Defender Exploit Guard' + $Entry.GPOPath | Should -Contain 'Policies -> Administrative Templates -> Windows Components/Microsoft Defender Antivirus/Microsoft Defender Exploit Guard' + $Entry.Code.ToString() | Should -Match 'Windows Components/Microsoft Defender Antivirus/Windows Defender Exploit Guard\*' + } + } + + It 'ConvertTo-XMLRegistryDefenderOnReport returns only Defender registry settings' { + InModuleScope GPOZaurr { + $GPO = [PSCustomObject] @{ + DisplayName = 'Test Defender GPO' + DomainName = 'contoso.com' + GUID = '11111111-1111-1111-1111-111111111111' + GpoType = 'Computer' + Linked = $true + LinksCount = 1 + Links = @('OU=Workstations,DC=contoso,DC=com') + Settings = @( + [PSCustomObject] @{ + Hive = 'HKEY_LOCAL_MACHINE' + Key = 'SOFTWARE\Microsoft\Windows Defender\MpEngine' + Name = 'MpFolderScanThreadCount' + Type = 'REG_DWORD' + Value = '4' + Changed = [datetime] '2026-02-18T11:15:00' + Filters = $null + } + [PSCustomObject] @{ + Hive = 'HKEY_LOCAL_MACHINE' + Key = 'SOFTWARE\Contoso\Other' + Name = 'Setting' + Type = 'REG_SZ' + Value = 'Value' + Changed = [datetime] '2026-02-18T11:15:00' + Filters = $null + } + ) + } + + [Array] $Result = ConvertTo-XMLRegistryDefenderOnReport -GPO $GPO + $Result.Count | Should -Be 1 + $Result[0].FallbackSource | Should -Be 'RegistrySettings' + $Result[0].Key | Should -Be 'SOFTWARE\Microsoft\Windows Defender\MpEngine' + $Result[0].Name | Should -Be 'MpFolderScanThreadCount' + } + } +} From 707c0ed4b2910d1876929a9d419f74874cb9af30 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Przemys=C5=82aw=20K=C5=82ys?= Date: Wed, 18 Feb 2026 11:48:10 +0100 Subject: [PATCH 2/3] Address PR feedback: make Defender fallback work without RegistrySettings type --- .../ConvertTo-XMLRegistryDefenderOnReport.ps1 | 18 ++++++++++- Private/Script.GPODictionary.ps1 | 18 ++++++----- Tests/DefenderContent.Tests.ps1 | 30 +++++++++++++++++-- 3 files changed, 55 insertions(+), 11 deletions(-) diff --git a/Private/ConvertTo-XMLRegistryDefenderOnReport.ps1 b/Private/ConvertTo-XMLRegistryDefenderOnReport.ps1 index a698efd..a118d8c 100644 --- a/Private/ConvertTo-XMLRegistryDefenderOnReport.ps1 +++ b/Private/ConvertTo-XMLRegistryDefenderOnReport.ps1 @@ -15,7 +15,23 @@ function ConvertTo-XMLRegistryDefenderOnReport { [PSCustomObject] $GPO ) - foreach ($Registry in $GPO.Settings) { + [Array] $RegistrySettings = @() + if ($GPO.Settings) { + $RegistrySettings = $GPO.Settings + } elseif ($GPO.DataSet) { + # This path supports direct use from dictionary code where RegistrySettings is not requested explicitly. + [Array] $DataSet = $GPO.DataSet + if ($DataSet.Count -gt 0 -and ( + $DataSet[0].PSObject.Properties.Name -contains 'Properties' -or + $DataSet[0].PSObject.Properties.Name -contains 'Registry' -or + $DataSet[0].PSObject.Properties.Name -contains 'Collection' + ) + ) { + $RegistrySettings = Get-XMLNestedRegistry -GPO $GPO -DataSet $GPO.DataSet + } + } + + foreach ($Registry in $RegistrySettings) { if ($Registry.Key -like 'SOFTWARE\Microsoft\Windows Defender*') { [PSCustomObject] [ordered] @{ DisplayName = $GPO.DisplayName diff --git a/Private/Script.GPODictionary.ps1 b/Private/Script.GPODictionary.ps1 index 7b14924..a774ccf 100644 --- a/Private/Script.GPODictionary.ps1 +++ b/Private/Script.GPODictionary.ps1 @@ -1169,10 +1169,9 @@ Category = 'RegistrySettings' Settings = 'Policy' } - ) - ByReports = @( @{ - Report = 'RegistrySettings' + Category = 'RegistrySettings' + Settings = 'RegistrySettings' } ) GPOPath = @( @@ -1180,13 +1179,16 @@ 'Policies -> Administrative Templates -> Windows Components/Microsoft Defender Antivirus' ) Code = { - ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'Windows Components/Windows Defender*', 'Windows Components/Microsoft Defender Antivirus*' + @( + ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'Windows Components/Windows Defender*', 'Windows Components/Microsoft Defender Antivirus*' + ConvertTo-XMLRegistryDefenderOnReport -GPO $GPO + ) | Where-Object { $_ } } CodeSingle = { - ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'Windows Components/Windows Defender*', 'Windows Components/Microsoft Defender Antivirus*' -SingleObject - } - CodeReport = { - ConvertTo-XMLRegistryDefenderOnReport -GPO $GPO + @( + ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'Windows Components/Windows Defender*', 'Windows Components/Microsoft Defender Antivirus*' -SingleObject + ConvertTo-XMLRegistryDefenderOnReport -GPO $GPO + ) | Where-Object { $_ } } } WindowsDefenderExploitGuard = @{ diff --git a/Tests/DefenderContent.Tests.ps1 b/Tests/DefenderContent.Tests.ps1 index 7913045..38c320a 100644 --- a/Tests/DefenderContent.Tests.ps1 +++ b/Tests/DefenderContent.Tests.ps1 @@ -8,8 +8,8 @@ Describe 'Defender content detection' { $Entry = $Script:GPODitionary['WindowsDefender'] $Entry.GPOPath | Should -Contain 'Policies -> Administrative Templates -> Windows Components/Windows Defender' $Entry.GPOPath | Should -Contain 'Policies -> Administrative Templates -> Windows Components/Microsoft Defender Antivirus' - $Entry.ByReports.Report | Should -Contain 'RegistrySettings' - $Entry.CodeReport.ToString() | Should -Match 'ConvertTo-XMLRegistryDefenderOnReport' + ($Entry.Types | Where-Object { $_.Category -eq 'RegistrySettings' -and $_.Settings -eq 'RegistrySettings' }).Count | Should -BeGreaterOrEqual 1 + $Entry.Code.ToString() | Should -Match 'ConvertTo-XMLRegistryDefenderOnReport' } } @@ -61,4 +61,30 @@ Describe 'Defender content detection' { $Result[0].Name | Should -Be 'MpFolderScanThreadCount' } } + + It 'ConvertTo-XMLRegistryDefenderOnReport supports raw DataSet input' { + InModuleScope GPOZaurr { + $GPO = [PSCustomObject] @{ + DisplayName = 'Test Defender GPO' + DomainName = 'contoso.com' + GUID = '11111111-1111-1111-1111-111111111111' + GpoType = 'Computer' + Linked = $true + LinksCount = 1 + Links = @('OU=Workstations,DC=contoso,DC=com') + DataSet = ([xml] @" + + + + + +"@).Root.Registry + } + + [Array] $Result = ConvertTo-XMLRegistryDefenderOnReport -GPO $GPO + $Result.Count | Should -Be 1 + $Result[0].Key | Should -Be 'SOFTWARE\Microsoft\Windows Defender\MpEngine' + $Result[0].Name | Should -Be 'MpFolderScanThreadCount' + } + } } From 98e93aa07d8b188db31ddd904d1a1a16f0db75c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Przemys=C5=82aw=20K=C5=82ys?= Date: Tue, 24 Feb 2026 14:28:47 +0100 Subject: [PATCH 3/3] Refine Defender output merge and list parsing for issue #81 feedback --- Private/ConvertTo-XMLGenericPolicy.ps1 | 44 ++++++++++++++- .../ConvertTo-XMLRegistryDefenderOnReport.ps1 | 56 +++++++++++++------ Public/Invoke-GPOZaurrContent.ps1 | 35 +++++++++++- Tests/DefenderContent.Tests.ps1 | 54 ++++++++++++++++-- 4 files changed, 164 insertions(+), 25 deletions(-) diff --git a/Private/ConvertTo-XMLGenericPolicy.ps1 b/Private/ConvertTo-XMLGenericPolicy.ps1 index e247531..3dac113 100644 --- a/Private/ConvertTo-XMLGenericPolicy.ps1 +++ b/Private/ConvertTo-XMLGenericPolicy.ps1 @@ -92,7 +92,26 @@ #> if ($Value.Value.Element) { - $Settings["$SubName"] = $Value.Value.Element.Data -join '; ' + [Array] $ElementValues = foreach ($Element in @($Value.Value.Element)) { + if (-not $Element) { + continue + } + $ElementName = [string] $Element.Name + $ElementData = [string] $Element.Data + + if (-not [string]::IsNullOrWhiteSpace($ElementName) -and ($ElementData -eq '' -or $ElementData -eq '0')) { + $ElementName + } elseif (-not [string]::IsNullOrWhiteSpace($ElementName) -and -not [string]::IsNullOrWhiteSpace($ElementData) -and $ElementName -ne $ElementData) { + "$ElementName ($ElementData)" + } elseif (-not [string]::IsNullOrWhiteSpace($ElementData)) { + $ElementData + } elseif (-not [string]::IsNullOrWhiteSpace($ElementName)) { + $ElementName + } + } + if ($ElementValues.Count -gt 0) { + $Settings["$SubName"] = $ElementValues -join '; ' + } } elseif ($null -eq $Value.Value.Name) { # Shouldn't happen but lets see Write-Verbose "Tracking $Value" @@ -174,7 +193,26 @@ #> if ($Value.Value.Element) { - $CreateGPO["$SubName"] = $Value.Value.Element.Data -join '; ' + [Array] $ElementValues = foreach ($Element in @($Value.Value.Element)) { + if (-not $Element) { + continue + } + $ElementName = [string] $Element.Name + $ElementData = [string] $Element.Data + + if (-not [string]::IsNullOrWhiteSpace($ElementName) -and ($ElementData -eq '' -or $ElementData -eq '0')) { + $ElementName + } elseif (-not [string]::IsNullOrWhiteSpace($ElementName) -and -not [string]::IsNullOrWhiteSpace($ElementData) -and $ElementName -ne $ElementData) { + "$ElementName ($ElementData)" + } elseif (-not [string]::IsNullOrWhiteSpace($ElementData)) { + $ElementData + } elseif (-not [string]::IsNullOrWhiteSpace($ElementName)) { + $ElementName + } + } + if ($ElementValues.Count -gt 0) { + $CreateGPO["$SubName"] = $ElementValues -join '; ' + } } elseif ($null -eq $Value.Value.Name) { # Shouldn't happen but lets see Write-Verbose "Tracking $Value" @@ -204,4 +242,4 @@ #} } } -} \ No newline at end of file +} diff --git a/Private/ConvertTo-XMLRegistryDefenderOnReport.ps1 b/Private/ConvertTo-XMLRegistryDefenderOnReport.ps1 index a118d8c..139f49d 100644 --- a/Private/ConvertTo-XMLRegistryDefenderOnReport.ps1 +++ b/Private/ConvertTo-XMLRegistryDefenderOnReport.ps1 @@ -31,25 +31,49 @@ function ConvertTo-XMLRegistryDefenderOnReport { } } + $UsedNames = [System.Collections.Generic.List[string]]::new() + $CreateGPO = [ordered] @{ + DisplayName = $GPO.DisplayName + DomainName = $GPO.DomainName + GUID = $GPO.GUID + GpoType = $GPO.GpoType + } + $DefenderSettings = 0 + foreach ($Registry in $RegistrySettings) { if ($Registry.Key -like 'SOFTWARE\Microsoft\Windows Defender*') { - [PSCustomObject] [ordered] @{ - DisplayName = $GPO.DisplayName - DomainName = $GPO.DomainName - GUID = $GPO.GUID - GpoType = $GPO.GpoType - FallbackSource = 'RegistrySettings' - Hive = $Registry.Hive - Key = $Registry.Key - Name = $Registry.Name - Type = $Registry.Type - Value = $Registry.Value - Changed = $Registry.Changed - Filters = $Registry.Filters - Linked = $GPO.Linked - LinksCount = $GPO.LinksCount - Links = $GPO.Links + $DefenderSettings++ + $SettingName = if ($Registry.Name) { $Registry.Name } else { $Registry.Key } + $PropertyName = Format-ToTitleCase -Text $SettingName -RemoveWhiteSpace -RemoveChar ',', '-', "'", '\(', '\)', ':' + + if ($PropertyName -in $UsedNames) { + $UsedNames.Add($PropertyName) + $TimesUsed = ($UsedNames | Group-Object | Where-Object { $_.Name -eq $PropertyName }).Count + $PropertyName = -join ($PropertyName, "$TimesUsed") + } else { + $UsedNames.Add($PropertyName) + } + + $SettingValue = $Registry.Value + if ($null -eq $SettingValue -or "$SettingValue" -eq '') { + $SettingValue = $Registry.Name + } + $CreateGPO[$PropertyName] = $SettingValue + + # Some Defender data can be encoded in either value or value-name depending on setting type. + if ($Registry.Name -and "$SettingValue" -ne "$($Registry.Name)") { + $CreateGPO["$($PropertyName)ValueName"] = $Registry.Name + } + if ($Registry.Key) { + $CreateGPO["$($PropertyName)RegistryKey"] = $Registry.Key } } } + + if ($DefenderSettings -gt 0) { + $CreateGPO['Linked'] = $GPO.Linked + $CreateGPO['LinksCount'] = $GPO.LinksCount + $CreateGPO['Links'] = $GPO.Links + [PSCustomObject] $CreateGPO + } } diff --git a/Public/Invoke-GPOZaurrContent.ps1 b/Public/Invoke-GPOZaurrContent.ps1 index f17b80d..1a1197d 100644 --- a/Public/Invoke-GPOZaurrContent.ps1 +++ b/Public/Invoke-GPOZaurrContent.ps1 @@ -287,6 +287,39 @@ } } } + if ($Output['Reports']['WindowsDefender']) { + # Defender can be represented by policy categories and raw registry settings. + # Merge entries per GPO so fallback fields do not create duplicated-looking rows. + $MergedWindowsDefender = [System.Collections.Generic.List[PSCustomObject]]::new() + $GroupedWindowsDefender = $Output['Reports']['WindowsDefender'] | Group-Object -Property DisplayName, DomainName, GUID, GpoType + foreach ($Group in $GroupedWindowsDefender) { + $MergedObject = [ordered] @{} + foreach ($Entry in $Group.Group) { + foreach ($Property in $Entry.PSObject.Properties) { + if (-not $MergedObject.Contains($Property.Name)) { + $MergedObject[$Property.Name] = $Property.Value + } else { + $CurrentValue = $MergedObject[$Property.Name] + $CurrentEmpty = $null -eq $CurrentValue -or ($CurrentValue -is [string] -and $CurrentValue -eq '') + $NewEmpty = $null -eq $Property.Value -or ($Property.Value -is [string] -and $Property.Value -eq '') + if ($CurrentEmpty -and -not $NewEmpty) { + $MergedObject[$Property.Name] = $Property.Value + } elseif (-not $CurrentEmpty -and -not $NewEmpty -and "$CurrentValue" -ne "$($Property.Value)") { + $PropertyCounter = 2 + $AlternativeName = -join ($Property.Name, "$PropertyCounter") + while ($MergedObject.Contains($AlternativeName)) { + $PropertyCounter++ + $AlternativeName = -join ($Property.Name, "$PropertyCounter") + } + $MergedObject[$AlternativeName] = $Property.Value + } + } + } + } + $MergedWindowsDefender.Add([PSCustomObject] $MergedObject) + } + $Output['Reports']['WindowsDefender'] = $MergedWindowsDefender + } # Normalize - meaning that before we return each GPO report we make sure that each entry has the same column names regardless which one is first. # Normally if you would have a GPO with just 2 entries for given subject (say LAPS), and then another GPO having 5 settings for the same type # and you would display them one after another - all entries would be shown using first object which has less properties then 2nd or 3rd object @@ -352,4 +385,4 @@ $Script:GPODitionary.Keys | Sort-Object | Where-Object { $_ -like "*$wordToComplete*" } } -Register-ArgumentCompleter -CommandName Invoke-GPOZaurrContent -ParameterName Type -ScriptBlock $SourcesAutoCompleter \ No newline at end of file +Register-ArgumentCompleter -CommandName Invoke-GPOZaurrContent -ParameterName Type -ScriptBlock $SourcesAutoCompleter diff --git a/Tests/DefenderContent.Tests.ps1 b/Tests/DefenderContent.Tests.ps1 index 38c320a..05398db 100644 --- a/Tests/DefenderContent.Tests.ps1 +++ b/Tests/DefenderContent.Tests.ps1 @@ -56,9 +56,8 @@ Describe 'Defender content detection' { [Array] $Result = ConvertTo-XMLRegistryDefenderOnReport -GPO $GPO $Result.Count | Should -Be 1 - $Result[0].FallbackSource | Should -Be 'RegistrySettings' - $Result[0].Key | Should -Be 'SOFTWARE\Microsoft\Windows Defender\MpEngine' - $Result[0].Name | Should -Be 'MpFolderScanThreadCount' + $Result[0].MpFolderScanThreadCount | Should -Be '4' + $Result[0].MpFolderScanThreadCountRegistryKey | Should -Be 'SOFTWARE\Microsoft\Windows Defender\MpEngine' } } @@ -83,8 +82,53 @@ Describe 'Defender content detection' { [Array] $Result = ConvertTo-XMLRegistryDefenderOnReport -GPO $GPO $Result.Count | Should -Be 1 - $Result[0].Key | Should -Be 'SOFTWARE\Microsoft\Windows Defender\MpEngine' - $Result[0].Name | Should -Be 'MpFolderScanThreadCount' + $Result[0].MpFolderScanThreadCount | Should -Be '4' + $Result[0].MpFolderScanThreadCountRegistryKey | Should -Be 'SOFTWARE\Microsoft\Windows Defender\MpEngine' + } + } + + It 'ConvertTo-XMLGenericPolicy prefers list item names over zero-only data' { + InModuleScope GPOZaurr { + [xml] $ListValue = @" + + + C:\PathOne + 0 + + + D:\PathTwo + 0 + + +"@ + + $GPO = [PSCustomObject] @{ + DisplayName = 'Test Defender GPO' + DomainName = 'contoso.com' + GUID = '11111111-1111-1111-1111-111111111111' + GpoType = 'Computer' + Linked = $true + LinksCount = 1 + Links = @('OU=Workstations,DC=contoso,DC=com') + DataSet = @( + [PSCustomObject] @{ + Category = 'Windows Components/Microsoft Defender Antivirus/Exclusions' + Name = 'Path Exclusions' + State = 'Enabled' + ListBox = @( + [PSCustomObject] @{ + Name = 'Path Exclusions' + Value = $ListValue.Value + } + ) + } + ) + } + + $Result = ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'Windows Components/Microsoft Defender Antivirus*' + $Result.PathExclusionsPathExclusions | Should -Match 'C:\\PathOne' + $Result.PathExclusionsPathExclusions | Should -Match 'D:\\PathTwo' + $Result.PathExclusionsPathExclusions | Should -Not -Be '0; 0' } } }