From 28b940a1763e196f66e479ea5e85b444492e34b4 Mon Sep 17 00:00:00 2001 From: Boris Drogja Date: Wed, 9 Jul 2025 13:22:57 +0200 Subject: [PATCH 1/5] Comprehensive MDE policy validation with 46 automated organized based on custom made benchmarks, covering antivirus configurations, global settings (manual review), and policy design quality (manual review). See MDE-FEATURE-DOCUMENTATION.md for complete feature documentation. --- MDE-FEATURE-DOCUMENTATION.md | 197 ++++ powershell/Maester.psd1 | 12 +- powershell/internal/Get-MtMdeConfig.ps1 | 113 ++ .../public/defender/MtMdeConfigurations.ps1 | 458 +++++++++ powershell/public/defender/MtMdeHelpers.ps1 | 962 ++++++++++++++++++ .../Test-MtMdeAntivirusPolicy.Tests.ps1 | 306 ++++++ .../Test-MtMdeGlobalConfiguration.Tests.ps1 | 355 +++++++ .../Defender/Test-MtMdePolicyDesign.Tests.ps1 | 91 ++ tests/Maester/Defender/defender-config.json | 14 + 9 files changed, 2504 insertions(+), 4 deletions(-) create mode 100644 MDE-FEATURE-DOCUMENTATION.md create mode 100644 powershell/internal/Get-MtMdeConfig.ps1 create mode 100644 powershell/public/defender/MtMdeConfigurations.ps1 create mode 100644 powershell/public/defender/MtMdeHelpers.ps1 create mode 100644 tests/Maester/Defender/Test-MtMdeAntivirusPolicy.Tests.ps1 create mode 100644 tests/Maester/Defender/Test-MtMdeGlobalConfiguration.Tests.ps1 create mode 100644 tests/Maester/Defender/Test-MtMdePolicyDesign.Tests.ps1 create mode 100644 tests/Maester/Defender/defender-config.json diff --git a/MDE-FEATURE-DOCUMENTATION.md b/MDE-FEATURE-DOCUMENTATION.md new file mode 100644 index 000000000..9f056e65e --- /dev/null +++ b/MDE-FEATURE-DOCUMENTATION.md @@ -0,0 +1,197 @@ +# Microsoft Defender for Endpoint (MDE) Extension for Maester + +## Feature Summary + +This feature branch extends the Maester security testing framework with comprehensive Microsoft Defender for Endpoint (MDE) policy validation capabilities. The extension includes 46 automated tests covering antivirus configurations, global settings, and policy design quality - all based on real-world MDE deployment experience. + +### Key Components +- **26 Antivirus Policy Tests** (MDE.AV01-26): Validate core security settings +- **16 Global Configuration Tests** (MDE.GC01-16): Check tenant-wide advanced features +- **4 Policy Design Tests** (MDE.PD01-04): Ensure consistent naming and structure +- **ASR Rules Category** (WIP): Attack Surface Reduction rules validation (planned) +- **Unified Configuration System**: Single JSON file controls all test behavior +- **Automatic Test Skipping**: Smart logic handles missing dependencies gracefully + +## Configuration System + +The extension uses a centralized configuration file at `/tests/Maester/Defender/defender-config.json` that controls all MDE test behavior. + +### Configuration Structure + +```json +{ + "ComplianceLogic": "AllPolicies", + "PolicyFiltering": "OnlyAssigned", + "DeviceFiltering": { + "OperatingSystems": ["Windows"], + "ManagementAgents": ["msSense", "mdm"], + "ComplianceStates": ["Compliant", "NonCompliant", "Unknown"] + }, + "TestSpecific": { + "MDE.AV01": { + "ComplianceLogic": "AtLeastOne" + } + } +} +``` + +### Configuration Options + +#### ComplianceLogic +- **`AllPolicies`** (default): All applicable policies must be compliant +- **`AtLeastOne`**: At least one policy must be compliant (useful for optional features) + +#### PolicyFiltering +- **`OnlyAssigned`** (default): Only test policies with device assignments +- **`All`**: Test all policies regardless of assignments +- **`None`**: Skip policy filtering entirely + +#### DeviceFiltering +Controls which devices are considered when evaluating policies: + +- **`OperatingSystems`**: `["Windows", "macOS", "iOS", "Android"]` +- **`ManagementAgents`**: `["msSense", "mdm", "configurationManager", "sccm"]` +- **`ComplianceStates`**: `["Compliant", "NonCompliant", "Unknown", "NotApplicable"]` + +#### TestSpecific Overrides +Individual tests can override global settings: +```json +"TestSpecific": { + "MDE.AV01": { + "ComplianceLogic": "AtLeastOne", + "PolicyFiltering": "All" + } +} +``` + +## Architecture Complexity - Why Not Individual Files? + +Initially, we considered creating separate test files for each antivirus setting (26 individual files). However, this approach became impractical due to Microsoft Graph API limitations: + +### API Response Challenges +- **Inconsistent Naming**: Settings don't follow predictable patterns (`_1`, `_2`, etc.) +- **Nested Structures**: Configuration policies use complex JSON with varying paths +- **Bulk Responses**: APIs return all settings together, not individually +- **Performance**: 26 separate API calls would be inefficient and hit rate limits + +### Unified Engine Benefits +Instead, we implemented a unified test engine that: +- Makes a single API call to retrieve all antivirus policies +- Processes 26 different settings from the same response +- Applies consistent compliance logic across all tests +- Reduces code duplication by 90% +- Handles skip conditions uniformly + +```powershell +# Single function handles all 26 antivirus tests +Invoke-MtMdeUnifiedTest -TestId "MDE.AV01" -ConfigKey "allowArchiveScanning" +Invoke-MtMdeUnifiedTest -TestId "MDE.AV02" -ConfigKey "allowBehaviorMonitoring" +# ... etc +``` + +## Test Categories and Baseline + +All tests are based on practical experience from real-world MDE deployments and industry best practices. The test categories reflect common security challenges: + +### 1. Antivirus Baseline (MDE.AV01-26) +Core protection settings that form the foundation of endpoint security: + +| Test ID | Setting | Expected Value | Severity | Rationale | +|---------|---------|---------------|----------|-----------| +| MDE.AV01 | Archive Scanning | Enabled | 🟑 Medium | Malware often hides in compressed files | +| MDE.AV02 | Behavior Monitoring | Enabled | 🟠 High | Required for EDR capabilities | +| MDE.AV03 | Cloud Protection | Enabled | 🟠 High | Zero-day protection via cloud intelligence | +| MDE.AV06 | Realtime Monitoring | Enabled | πŸ”΄ Critical | Core protection mechanism | +| MDE.AV17 | PUA Protection | Block Mode | 🟠 High | Prevents unwanted software installations | +| MDE.AV19 | Disable Local Admin Merge | Enabled | πŸ”΄ Critical | Prevents local policy bypasses | +| MDE.AV20 | Tamper Protection | Enabled | πŸ”΄ Critical | Protects against malicious disabling | + +*Complete list includes 26 settings covering scan engines, cloud protection, scheduling, and remediation* + +### 2. Global Configuration (MDE.GC01-16) +Tenant-wide advanced features that enhance security posture: + +| Test ID | Feature | Expected | Severity | Note | +|---------|---------|----------|----------|------| +| MDE.GC02 | Tamper Protection (Global) | On | 🟠 High | Tenant-wide enforcement | +| MDE.GC03 | EDR in Block Mode | On | 🟠 High | Only for Defender AV devices | +| MDE.GC07 | Custom Network Indicators | On | 🟠 High | IOC-based blocking | +| MDE.GC08 | Web Content Filtering | On | 🟠 High | Requires P2 licensing | + +**Important**: Global configuration settings cannot currently be validated via Microsoft Graph API. Microsoft has not exposed these tenant-level settings through programmatic access. These tests are included but will be automatically skipped, serving as documentation of recommended settings that must be verified manually through the Microsoft 365 Defender portal. + +### 3. Policy Design Quality (MDE.PD01-04) +Governance and organizational best practices: + +| Test ID | Check | Purpose | Severity | +|---------|-------|---------|----------| +| MDE.PD01 | Consistent Naming Convention | Policy organization | 🟒 Low | +| MDE.PD02 | Dedicated Exclusion Profiles | Separation of concerns | 🟑 Medium | +| MDE.PD03 | Granular Device Targeting | Least privilege principle | 🟑 Medium | +| MDE.PD04 | Staging Groups (Pilotβ†’Prod) | Change management | 🟑 Medium | + +## Test Execution and Results + +### Severity Levels +- **πŸ”΄ Critical**: Core security functions that must never be disabled +- **🟠 High**: Important security features with significant impact +- **🟑 Medium**: Standard features that improve security posture +- **🟒 Low**: Performance optimizations and nice-to-have features + +### Skip Logic +Tests are automatically skipped when: +- No Graph connection available +- No MDE-enrolled devices found +- No relevant policies configured +- API permissions insufficient + +### Sample Usage +```powershell +# Connect to required services +Connect-Maester -Service Graph + +# Run all MDE tests with Maester +Invoke-Maester -Path "./tests/Maester/Defender/" + +# Generate HTML report using Maester +Invoke-Maester -Path "./tests/Maester/Defender/" -OutputHtml "mde-report.html" +``` + +## Test Tags and Organization + +All MDE tests use consistent Pester tags for easy filtering and organization: + +### Tag Structure +- **`MDE`**: Applied to all MDE-related tests (universal tag) +- **`MDE-Antivirus`**: Antivirus policy content tests (MDE.AV01-26) +- **`MDE-GlobalConfig`**: Global configuration tests (MDE.GC01-16) +- **`MDE-PolicyDesign`**: Policy design quality tests (MDE.PD01-04) +- **`MDE-ASR`**: Attack Surface Reduction rules (planned/WIP) + +### Usage Examples +```powershell +# Run all MDE tests +Invoke-Maester -Tag "MDE" + +# Run only antivirus policy tests +Invoke-Maester -Tag "MDE-Antivirus" + +# Run specific test by ID +Invoke-Maester -Tag "MDE.AV01" + +# Exclude global config tests (since they skip anyway) +Invoke-Maester -Tag "MDE" -ExcludeTag "MDE-GlobalConfig" +``` + +## Roadmap and Future Development + +### Work in Progress +- **ASR Rules Category**: Implementation of Attack Surface Reduction rules validation is planned and will follow the same unified engine pattern established for antivirus tests. + +## Integration with Maester Framework + +This extension follows Maester's architectural principles: +- Uses existing connection management (`Connect-Maester`) +- Leverages framework helpers (`Add-MtTestResultDetail`, `Invoke-MtGraphRequest`) +- Maintains consistent test patterns and result formatting +- Preserves compatibility with framework updates \ No newline at end of file diff --git a/powershell/Maester.psd1 b/powershell/Maester.psd1 index 7a545dbd7..f3c69dd20 100644 --- a/powershell/Maester.psd1 +++ b/powershell/Maester.psd1 @@ -12,7 +12,7 @@ RootModule = 'Maester.psm1' # Version number of this module. -ModuleVersion = '0.1.0' +ModuleVersion = '0.2.0' # Supported PSEditions CompatiblePSEditions = 'Core', 'Desktop' @@ -85,7 +85,7 @@ FunctionsToExport = 'Add-MtTestResultDetail', 'ConvertFrom-MailAuthenticationRecordSpf', 'Disconnect-Maester', 'Get-MailAuthenticationRecord', 'Get-MtAdminPortalUrl', 'Get-MtAuthenticationMethodPolicyConfig', - 'Get-MtConditionalAccessPolicy', 'Get-MtExo', 'Get-MtGraphScope', 'Get-MtGroupMember', + 'Get-MtConditionalAccessPolicy', 'Get-MtExo', 'Get-MtGraphScope', 'Get-MtGroupMember', 'Get-MtMdeConfig', 'Get-MtMdeConfiguration', 'Get-MtMdeDeviceCount', 'Get-MtHtmlReport', 'Get-MtLicenseInformation', 'Get-MtRole', 'Get-MtRoleMember', 'Get-MtSession', 'Get-MtUser', 'Get-MtUserAuthenticationMethod', 'Get-MtUserAuthenticationMethodInfoByType', 'Install-MaesterTests', @@ -134,7 +134,10 @@ FunctionsToExport = 'Add-MtTestResultDetail', 'Test-MtCisaSpoSharingAllowedDomain', 'Test-MtCisaUnmanagedRoleAssignment', 'Test-MtCisaWeakFactor', 'Test-MtConditionalAccessWhatIf', 'Test-MtConnection', 'Test-MtDeviceComplianceSettings', 'Test-MtEidscaControl', 'Test-MtGroupCreationRestricted', 'Test-MtHighRiskAppPermissions', - 'Test-MtManagedDeviceCleanupSettings', 'Test-MtPimAlertsExists', 'Test-MtPrivPermanentDirectoryRole', + 'Test-MtManagedDeviceCleanupSettings', + 'Invoke-MtMdeUnifiedTest', 'Get-MtMdeUnifiedConfiguration', + 'Get-MtMdeConfig', 'Get-MtMdeConfiguration', 'Get-MtMdeDeviceCount', 'Test-MtMdePolicyHasAssignments', + 'Test-MtPimAlertsExists', 'Test-MtPrivPermanentDirectoryRole', 'Test-MtTeamsRestrictParticipantGiveRequestControl', 'Test-MtUserAccessAdmin', 'Test-ORCA100', 'Test-ORCA101', 'Test-ORCA102', 'Test-ORCA103', 'Test-ORCA104', 'Test-ORCA105', 'Test-ORCA106', 'Test-ORCA107', 'Test-ORCA108', 'Test-ORCA108_1', 'Test-ORCA109', 'Test-ORCA110', @@ -147,7 +150,8 @@ FunctionsToExport = 'Add-MtTestResultDetail', 'Test-ORCA227', 'Test-ORCA228', 'Test-ORCA229', 'Test-ORCA230', 'Test-ORCA231', 'Test-ORCA232', 'Test-ORCA233', 'Test-ORCA233_1', 'Test-ORCA234', 'Test-ORCA235', 'Test-ORCA236', 'Test-ORCA237', 'Test-ORCA238', 'Test-ORCA239', 'Test-ORCA240', 'Test-ORCA241', 'Test-ORCA242', 'Test-ORCA243', - 'Test-ORCA244', 'Update-MaesterTests' + 'Test-ORCA244', 'Update-MaesterTests', + 'Write-MtProgress', 'ConvertTo-MtMaesterResult', 'Get-MtUserInteractive', 'Reset-MtProgressView', 'Get-IsNewMaesterVersionAvailable' # Cmdlets to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no cmdlets to export. CmdletsToExport = @() diff --git a/powershell/internal/Get-MtMdeConfig.ps1 b/powershell/internal/Get-MtMdeConfig.ps1 new file mode 100644 index 000000000..dce5060d7 --- /dev/null +++ b/powershell/internal/Get-MtMdeConfig.ps1 @@ -0,0 +1,113 @@ +<# +.SYNOPSIS + Gets MDE configuration from defender-config.json + +.DESCRIPTION + Loads test settings like compliance logic and device filtering from the config file. + Falls back to defaults if file isn't found. + +.PARAMETER Path + Path to config file or directory. Defaults to tests/Maester/Defender. + +.EXAMPLE + $config = Get-MtMdeConfig + Loads from default location + +.EXAMPLE + $config = Get-MtMdeConfig -Path 'C:\custom' + Loads from custom path +#> + +function Get-MtMdeConfig { + [CmdletBinding()] + [OutputType([object])] + param( + # Config file path or directory + [Parameter(Mandatory = $false)] + $Path + ) + + # Use default location if none specified + if (-not $Path) { + $Path = Join-Path (Get-Location) "tests/Maester/Defender" + } + + Write-Verbose "Getting MDE config from $Path" + + try { + # Look for config file in directory or parent directories + if (Test-Path $Path -PathType Container) { + $ConfigFilePath = Join-Path -Path $Path -ChildPath 'defender-config.json' + if (-not (Test-Path -Path $ConfigFilePath)) { + Write-Verbose "Config file not found in $Path. Checking parent directories." + $defenderDir = Join-Path -Path $Path -ChildPath 'tests/Maester/Defender/defender-config.json' + if (Test-Path -Path $defenderDir) { + $ConfigFilePath = $defenderDir + } else { + # Search up to 5 parent directories + for ($i = 1; $i -le 5; $i++) { + if (Test-Path -Path $ConfigFilePath) { + break + } + $parentDir = Split-Path -Path $Path -Parent + if ($parentDir -eq $Path -or [string]::IsNullOrEmpty($parentDir)) { + break + } + $Path = $parentDir + $ConfigFilePath = Join-Path -Path $Path -ChildPath 'tests/Maester/Defender/defender-config.json' + } + } + } + } else { + # Use file path directly + $ConfigFilePath = $Path + } + + if (-not (Test-Path -Path $ConfigFilePath)) { + Write-Verbose "MDE config file not found at $ConfigFilePath. Using default configuration." + # Use defaults when config file missing + return @{ + ComplianceLogic = "AllPolicies" + PolicyFiltering = "OnlyAssigned" + DeviceFiltering = @{ + OperatingSystems = @("Windows") + ManagementAgents = @("msSense", "mdm") + ComplianceStates = @("Compliant", "NonCompliant", "Unknown") + } + TestSpecific = @{} + } + } + + Write-Verbose "Loading MDE config from $ConfigFilePath" + $configContent = Get-Content -Path $ConfigFilePath -Raw | ConvertFrom-Json + + # Merge config with defaults + $config = @{ + ComplianceLogic = if ($configContent.ComplianceLogic) { $configContent.ComplianceLogic } else { "AllPolicies" } + PolicyFiltering = if ($configContent.PolicyFiltering) { $configContent.PolicyFiltering } else { "OnlyAssigned" } + DeviceFiltering = @{ + OperatingSystems = if ($configContent.DeviceFiltering.OperatingSystems) { $configContent.DeviceFiltering.OperatingSystems } else { @("Windows") } + ManagementAgents = if ($configContent.DeviceFiltering.ManagementAgents) { $configContent.DeviceFiltering.ManagementAgents } else { @("msSense", "mdm") } + ComplianceStates = if ($configContent.DeviceFiltering.ComplianceStates) { $configContent.DeviceFiltering.ComplianceStates } else { @("Compliant", "NonCompliant", "Unknown") } + } + TestSpecific = if ($configContent.TestSpecific) { $configContent.TestSpecific } else { @{} } + } + + Write-Verbose "MDE config loaded successfully" + return $config + + } catch { + Write-Warning "Failed to load MDE config from $ConfigFilePath`: $($_.Exception.Message). Using default configuration." + # Fall back to defaults on error + return @{ + ComplianceLogic = "AllPolicies" + PolicyFiltering = "OnlyAssigned" + DeviceFiltering = @{ + OperatingSystems = @("Windows") + ManagementAgents = @("msSense", "mdm") + ComplianceStates = @("Compliant", "NonCompliant", "Unknown") + } + TestSpecific = @{} + } + } +} \ No newline at end of file diff --git a/powershell/public/defender/MtMdeConfigurations.ps1 b/powershell/public/defender/MtMdeConfigurations.ps1 new file mode 100644 index 000000000..67296a351 --- /dev/null +++ b/powershell/public/defender/MtMdeConfigurations.ps1 @@ -0,0 +1,458 @@ +<# +.SYNOPSIS + Gets configuration settings for Microsoft Defender for Endpoint tests + +.DESCRIPTION + Returns test configuration for MDE tests including settings, expected values, + and test metadata. Supports Antivirus, Global Config, and Policy Design tests. + +.PARAMETER TestId + The MDE test identifier (e.g., "MDE.AV01", "MDE.GC01", "MDE.PD01") + +.EXAMPLE + Get-MtMdeUnifiedConfiguration -TestId "MDE.AV01" + + Gets configuration for the Archive Scanning test + +.EXAMPLE + Get-MtMdeUnifiedConfiguration + + Returns all available MDE test configurations +#> + +#region Unified MDE Configuration +function Get-MtMdeUnifiedConfiguration { + [CmdletBinding()] + [OutputType([hashtable])] + param( + [Parameter(Mandatory = $false)] + [string]$TestId + ) + + $mdeConfig = Get-MtMdeConfig + $configurations = @{} + + # Helper function to create standardized test configuration + function New-MdeTestConfig { + param( + [string]$TestId, + [string]$SettingName, + [string]$Category, + [string]$Severity, + [string]$TestType, + [string]$Description, + [string]$SecurityImpact, + [array]$ActionSteps, + [hashtable]$ComplianceParameters = @{}, + [hashtable]$TestSpecificData = @{} + ) + + $config = @{ + # Standard properties for all tests + TestId = $TestId + SettingName = $SettingName + Category = $Category + Severity = $Severity + TestType = $TestType + Description = $Description + SecurityImpact = $SecurityImpact + ActionSteps = $ActionSteps + + # Compliance parameters (unified structure) + ComplianceParameters = $ComplianceParameters + + # Test-specific data (flexible for different test types) + TestSpecificData = $TestSpecificData + + # Applied from global config + ComplianceLogic = "AllPolicies" # Will be overridden below + } + + return $config + } + + # Antivirus Policy Tests (MDE.AV01-MDE.AV26) + $configurations["MDE.AV01"] = New-MdeTestConfig -TestId "MDE.AV01" -SettingName "Archive Scanning" -Category "Scan Engines" -Severity "Medium" -TestType "Automated" -Description "Verify that archive scanning is enabled to detect malware in compressed files." -SecurityImpact "Disabled archive scanning allows malware to hide in compressed files (ZIP, RAR, etc.)" -ActionSteps @("Enable **Allow Archive Scanning**") -ComplianceParameters @{ + ComplianceCheck = "Boolean" + ExpectedValue = "_1" + } -TestSpecificData @{ + SettingId = "device_vendor_msft_policy_config_defender_allowarchivescanning" + } + + $configurations["MDE.AV02"] = New-MdeTestConfig -TestId "MDE.AV02" -SettingName "Behavior Monitoring" -Category "Scan Engines" -Severity "High" -TestType "Automated" -Description "Verify that behavior monitoring is enabled - prerequisite for EDR capabilities." -SecurityImpact "Disabled behavior monitoring reduces ability to detect zero-day threats and advanced persistent threats (APTs)" -ActionSteps @("Enable **Allow Behavior Monitoring**") -ComplianceParameters @{ + ComplianceCheck = "Boolean" + ExpectedValue = "_1" + } -TestSpecificData @{ + SettingId = "device_vendor_msft_policy_config_defender_allowbehaviormonitoring" + } + + $configurations["MDE.AV03"] = New-MdeTestConfig -TestId "MDE.AV03" -SettingName "Cloud Protection" -Category "Scan Engines" -Severity "High" -TestType "Automated" -Description "Verify that cloud protection is enabled with CloudLevel β‰₯ High." -SecurityImpact "Disabled cloud protection reduces real-time threat detection and response capabilities" -ActionSteps @("Enable **Allow Cloud Protection**") -ComplianceParameters @{ + ComplianceCheck = "Boolean" + ExpectedValue = "_1" + } -TestSpecificData @{ + SettingId = "device_vendor_msft_policy_config_defender_allowcloudprotection" + } + + $configurations["MDE.AV04"] = New-MdeTestConfig -TestId "MDE.AV04" -SettingName "Email Scanning" -Category "Scan Engines" -Severity "Medium" -TestType "Automated" -Description "Verify that email scanning is enabled for Exchange queues protection." -SecurityImpact "Disabled email scanning allows malware to enter through Exchange message queues" -ActionSteps @("Enable **Allow Email Scanning**") -ComplianceParameters @{ + ComplianceCheck = "Boolean" + ExpectedValue = "_1" + } -TestSpecificData @{ + SettingId = "device_vendor_msft_policy_config_defender_allowemailscanning" + } + + $configurations["MDE.AV05"] = New-MdeTestConfig -TestId "MDE.AV05" -SettingName "Script Scanning" -Category "Scan Engines" -Severity "High" -TestType "Automated" -Description "Verify that script scanning is enabled to block malicious JavaScript." -SecurityImpact "Disabled script scanning allows malicious PowerShell, JavaScript, and VBScript execution" -ActionSteps @("Enable **Allow Script Scanning**") -ComplianceParameters @{ + ComplianceCheck = "Boolean" + ExpectedValue = "_1" + } -TestSpecificData @{ + SettingId = "device_vendor_msft_policy_config_defender_allowscriptscanning" + } + + $configurations["MDE.AV06"] = New-MdeTestConfig -TestId "MDE.AV06" -SettingName "Real-time Monitoring" -Category "Scan Engines" -Severity "High" -TestType "Automated" -Description "Verify that realtime monitoring is enabled - core protection function." -SecurityImpact "Disabled real-time monitoring allows malware to execute without immediate detection" -ActionSteps @("Enable **Allow Real-time Monitoring**") -ComplianceParameters @{ + ComplianceCheck = "Boolean" + ExpectedValue = "_1" + } -TestSpecificData @{ + SettingId = "device_vendor_msft_policy_config_defender_allowrealtimemonitoring" + } + + $configurations["MDE.AV07"] = New-MdeTestConfig -TestId "MDE.AV07" -SettingName "Allow Full Scan Removable Drives" -Category "Scan Configuration" -Severity "Medium" -TestType "Automated" -Description "Verify that full scan of removable drives is enabled to mitigate USB risks." -SecurityImpact "Disabled removable drive scanning allows USB-based malware infections" -ActionSteps @("Enable **Allow Full Scan on Removable Drives**") -ComplianceParameters @{ + ComplianceCheck = "Boolean" + ExpectedValue = "_1" + } -TestSpecificData @{ + SettingId = "device_vendor_msft_policy_config_defender_allowfullscanremovabledrivescanning" + } + + $configurations["MDE.AV08"] = New-MdeTestConfig -TestId "MDE.AV08" -SettingName "Allow Full Scan Mapped Drives" -Category "Scan Configuration" -Severity "Low" -TestType "Automated" -Description "Verify that full scan of mapped network drives is disabled for performance reasons." -SecurityImpact "Full scan on mapped drives can cause performance issues" -ActionSteps @("Disable **Allow Full Scan on Mapped Network Drives** for performance") -ComplianceParameters @{ + ComplianceCheck = "Boolean" + ExpectedValue = "_0" + } -TestSpecificData @{ + SettingId = "device_vendor_msft_policy_config_defender_allowfullscanonmappednetworkdrives" + } + + $configurations["MDE.AV09"] = New-MdeTestConfig -TestId "MDE.AV09" -SettingName "Network File Scanning" -Category "Scan Configuration" -Severity "Medium" -TestType "Automated" -Description "Verify that scanning network files is enabled despite SMB load considerations." -SecurityImpact "Disabled network file scanning creates attack vectors through shared files" -ActionSteps @("Enable **Allow Scanning Network Files**") -ComplianceParameters @{ + ComplianceCheck = "Boolean" + ExpectedValue = "_1" + } -TestSpecificData @{ + SettingId = "device_vendor_msft_policy_config_defender_allowscanningnetworkfiles" + } + + $configurations["MDE.AV10"] = New-MdeTestConfig -TestId "MDE.AV10" -SettingName "Avg CPU Load Factor" -Category "Performance" -Severity "Low" -TestType "Automated" -Description "Verify that average CPU load factor is configured between 20-30%." -SecurityImpact "Inappropriate CPU load settings may impact system performance or scan effectiveness" -ActionSteps @("Set **Average CPU Load Factor** to 20-30%") -ComplianceParameters @{ + ComplianceCheck = "Range" + ExpectedValue = 25 + RangeMin = 20 + RangeMax = 30 + } -TestSpecificData @{ + SettingId = "device_vendor_msft_policy_config_defender_avgcpuloadfactor" + } + + $configurations["MDE.AV11"] = New-MdeTestConfig -TestId "MDE.AV11" -SettingName "Schedule Scan Day" -Category "Scheduled Scans" -Severity "Medium" -TestType "Automated" -Description "Verify that scans are scheduled for every day during off-hours." -SecurityImpact "Irregular scan schedule may miss persistent threats" -ActionSteps @("Configure **Schedule Scan Day** for daily scanning") -ComplianceParameters @{ + ComplianceCheck = "Enum" + ExpectedValue = "_0" + ValidValues = @("_0", "_1", "_2", "_3", "_4", "_5", "_6", "_7") + } -TestSpecificData @{ + SettingId = "device_vendor_msft_policy_config_defender_schedulescanday" + } + + $configurations["MDE.AV12"] = New-MdeTestConfig -TestId "MDE.AV12" -SettingName "Schedule Quick Scan Time" -Category "Scheduled Scans" -Severity "Low" -TestType "Automated" -Description "Verify that quick scan time is not configured (not required)." -SecurityImpact "Not required - Quick scans are replaced by real-time protection" -ActionSteps @("No action needed - Quick scan timing not required") -ComplianceParameters @{ + ComplianceCheck = "NotRequired" + ExpectedValue = $null + } -TestSpecificData @{ + SettingId = "device_vendor_msft_policy_config_defender_schedulequickscantime" + } + + $configurations["MDE.AV13"] = New-MdeTestConfig -TestId "MDE.AV13" -SettingName "Check Signatures Before Scan" -Category "Signatur & Cloud" -Severity "High" -TestType "Automated" -Description "Verify that signature checking before scan is enabled for zero-day protection." -SecurityImpact "Scans with outdated signatures may miss recent threats and zero-day attacks" -ActionSteps @("Enable **Check for Signatures Before Running Scan**") -ComplianceParameters @{ + ComplianceCheck = "Boolean" + ExpectedValue = "_1" + } -TestSpecificData @{ + SettingId = "device_vendor_msft_policy_config_defender_checkforsignaturesbeforerunningscan" + } + + $configurations["MDE.AV14"] = New-MdeTestConfig -TestId "MDE.AV14" -SettingName "Cloud Block Level" -Category "Signatur & Cloud" -Severity "High" -TestType "Automated" -Description "Verify that cloud block level is set to High, High+, or Zero Tolerance." -SecurityImpact "Low cloud block level reduces proactive threat blocking capabilities" -ActionSteps @("Set **Cloud Block Level** to High, High Plus, or Zero Tolerance") -ComplianceParameters @{ + ComplianceCheck = "MinimumLevel" + ExpectedValue = "_2" + MinimumValue = 2 + ValidLevels = @{ + "_0" = 0 # Default + "_2" = 2 # High + "_4" = 4 # High Plus + "_6" = 6 # Zero Tolerance + } + } -TestSpecificData @{ + SettingId = "device_vendor_msft_policy_config_defender_cloudblocklevel" + } + + $configurations["MDE.AV15"] = New-MdeTestConfig -TestId "MDE.AV15" -SettingName "Cloud Extended Timeout" -Category "Signatur & Cloud" -Severity "Medium" -TestType "Automated" -Description "Verify that cloud extended timeout is configured between 30-50 seconds (UX vs Detection balance)." -SecurityImpact "Insufficient cloud timeout may prevent thorough analysis vs UX impact" -ActionSteps @("Set **Cloud Extended Timeout** to 30-50 seconds") -ComplianceParameters @{ + ComplianceCheck = "Range" + ExpectedValue = 40 + RangeMin = 30 + RangeMax = 50 + } -TestSpecificData @{ + SettingId = "device_vendor_msft_policy_config_defender_cloudextendedtimeout" + } + + $configurations["MDE.AV16"] = New-MdeTestConfig -TestId "MDE.AV16" -SettingName "Signature Update Interval" -Category "Signatur & Cloud" -Severity "High" -TestType "Automated" -Description "Verify that signature update interval is configured between 1-4 hours considering bandwidth." -SecurityImpact "Infrequent signature updates reduce detection of latest threats" -ActionSteps @("Set **Signature Update Interval** to 1-4 hours") -ComplianceParameters @{ + ComplianceCheck = "Range" + ExpectedValue = 2 + RangeMin = 1 + RangeMax = 4 + } -TestSpecificData @{ + SettingId = "device_vendor_msft_policy_config_defender_signatureupdateinterval" + } + + $configurations["MDE.AV17"] = New-MdeTestConfig -TestId "MDE.AV17" -SettingName "PUA Protection" -Category "Protection" -Severity "High" -TestType "Automated" -Description "Verify that PUA (Potentially Unwanted Applications) protection is enabled to block Shadow IT." -SecurityImpact "Disabled PUA protection allows Shadow IT and potentially unwanted applications" -ActionSteps @("Set **PUA Protection** to On - Block mode") -ComplianceParameters @{ + ComplianceCheck = "Enum" + ExpectedValue = "_1" + ValidValues = @("_1", "_2") + } -TestSpecificData @{ + SettingId = "device_vendor_msft_policy_config_defender_puaprotection" + } + + $configurations["MDE.AV18"] = New-MdeTestConfig -TestId "MDE.AV18" -SettingName "Network Protection" -Category "Advanced Protection" -Severity "High" -TestType "Automated" -Description "Verify that Network Protection is enabled in block mode." -SecurityImpact "Disabled network protection allows web-based threats and malicious IP connections" -ActionSteps @("Set **Network Protection** to Enabled or Audit mode") -ComplianceParameters @{ + ComplianceCheck = "Enum" + ExpectedValue = "_1" + ValidValues = @("_1", "_2") + } -TestSpecificData @{ + SettingId = "device_vendor_msft_policy_config_defender_enablenetworkprotection" + } + + $configurations["MDE.AV19"] = New-MdeTestConfig -TestId "MDE.AV19" -SettingName "Disable Local Admin Merge" -Category "Protection" -Severity "Critical" -TestType "Automated" -Description "Verify that local admin merge is disabled to block local exclusions." -SecurityImpact "Local admin policy override allows privilege escalation to bypass security controls" -ActionSteps @("Enable **Disable Local Admin Merge** to prevent local overrides") -ComplianceParameters @{ + ComplianceCheck = "Boolean" + ExpectedValue = "_1" + } -TestSpecificData @{ + SettingId = "device_vendor_msft_defender_configuration_disablelocaladminmerge" + } + + $configurations["MDE.AV20"] = New-MdeTestConfig -TestId "MDE.AV20" -SettingName "Tamper Protection" -Category "Protection" -Severity "Critical" -TestType "Manual" -Description "Verify that Tamper Protection is enabled tenant-wide." -SecurityImpact "Disabled tamper protection allows local administrators to disable security features" -ActionSteps @("Enable **Tamper Protection** tenant-wide in Microsoft 365 Defender portal") -ComplianceParameters @{ + ComplianceCheck = "Manual" + ExpectedValue = "Enabled" + } -TestSpecificData @{ + NavigationPath = "Settings β†’ Endpoints β†’ Advanced Features β†’ Tamper Protection" + PortalUrl = "https://security.microsoft.com" + } + + $configurations["MDE.AV21"] = New-MdeTestConfig -TestId "MDE.AV21" -SettingName "Real-Time Scan Direction" -Category "Protection" -Severity "Medium" -TestType "Automated" -Description "Verify that real-time scan direction is set to both incoming and outgoing." -SecurityImpact "Limited scan direction may miss malware in certain file operations" -ActionSteps @("Set **Real-time Scan Direction** to Both (incoming and outgoing)") -ComplianceParameters @{ + ComplianceCheck = "Enum" + ExpectedValue = "_0" + ValidValues = @("_0", "_1") + } -TestSpecificData @{ + SettingId = "device_vendor_msft_policy_config_defender_realtimescandirection" + } + + $configurations["MDE.AV22"] = New-MdeTestConfig -TestId "MDE.AV22" -SettingName "Retain Cleaned Malware" -Category "Cleanup & Quarantine" -Severity "Medium" -TestType "Automated" -Description "Verify that cleaned malware is retained for 90 days for audit purposes." -SecurityImpact "Short retention may impact forensic analysis and threat investigation" -ActionSteps @("Set **Retain Cleaned Malware** to at least 30 days (recommended: 90 days) for forensic evidence") -ComplianceParameters @{ + ComplianceCheck = "MinimumValue" + ExpectedValue = 30 + MinimumValue = 30 + } -TestSpecificData @{ + SettingId = "device_vendor_msft_policy_config_defender_daystoretaincleanedmalware" + } + + $configurations["MDE.AV23"] = New-MdeTestConfig -TestId "MDE.AV23" -SettingName "Disable Catch-up Full Scan" -Category "Cleanup & Quarantine" -Severity "Low" -TestType "Automated" -Description "Verify that catch-up full scan is disabled to avoid additional load." -SecurityImpact "Enabled catchup scans may cause performance issues on mobile devices" -ActionSteps @("Enable **Disable Catchup Full Scan** to avoid additional system load") -ComplianceParameters @{ + ComplianceCheck = "Boolean" + ExpectedValue = "_1" + } -TestSpecificData @{ + SettingId = "device_vendor_msft_policy_config_defender_disablecatchupfullscan" + } + + $configurations["MDE.AV24"] = New-MdeTestConfig -TestId "MDE.AV24" -SettingName "Disable Catch-up Quick Scan" -Category "Cleanup & Quarantine" -Severity "Low" -TestType "Automated" -Description "Verify that catch-up quick scan is disabled." -SecurityImpact "Enabled catchup scans may cause performance issues on mobile devices" -ActionSteps @("Enable **Disable Catchup Quick Scan** to avoid additional system load") -ComplianceParameters @{ + ComplianceCheck = "Boolean" + ExpectedValue = "_1" + } -TestSpecificData @{ + SettingId = "device_vendor_msft_policy_config_defender_disablecatchupquickscan" + } + + $configurations["MDE.AV25"] = New-MdeTestConfig -TestId "MDE.AV25" -SettingName "Remediation Action" -Category "Cleanup & Quarantine" -Severity "High" -TestType "Manual" -Description "Verify that remediation action for all threat levels is set to Quarantine." -SecurityImpact "Inappropriate remediation actions may allow threats to persist or cause data loss" -ActionSteps @("Set **Default Action** to Quarantine for consistent threat handling") -ComplianceParameters @{ + ComplianceCheck = "Manual" + ExpectedValue = "Quarantine" + } -TestSpecificData @{ + NavigationPath = "Endpoint Security β†’ Antivirus β†’ Threat Severity Default Action" + PortalUrl = "https://endpoint.microsoft.com" + SettingId = "device_vendor_msft_policy_config_defender_threatseveritydefaultaction" + } + + $configurations["MDE.AV26"] = New-MdeTestConfig -TestId "MDE.AV26" -SettingName "Submit Samples Consent" -Category "Cleanup & Quarantine" -Severity "Medium" -TestType "Automated" -Description "Verify that sample submission consent is configured to send safe samples automatically." -SecurityImpact "Restricted sample submission reduces threat intelligence and protection quality" -ActionSteps @("Set **Submit Samples Consent** to send safe samples automatically (check GDPR compliance)") -ComplianceParameters @{ + ComplianceCheck = "Enum" + ExpectedValue = "_1" + ValidValues = @("_1", "_2") + } -TestSpecificData @{ + SettingId = "device_vendor_msft_policy_config_defender_submitsamplesconsent" + } + + # Global Configuration Tests (MDE.GC01-MDE.GC16) + $configurations["MDE.GC01"] = New-MdeTestConfig -TestId "MDE.GC01" -SettingName "Preview Features" -Category "Global Config" -Severity "Low" -TestType "GlobalConfig" -Description "Verify that Preview Features are enabled organization-wide in Microsoft Defender XDR." -SecurityImpact "Disabled preview features may result in missing new security capabilities and protections" -ActionSteps @("Enable **Preview Features** organization-wide") -ComplianceParameters @{ + ComplianceCheck = "Manual" + ExpectedValue = "On" + } -TestSpecificData @{ + NavigationPath = "Settings β†’ Microsoft Defender XDR β†’ Preview Features" + PortalUrl = "https://security.microsoft.com" + } + + $configurations["MDE.GC02"] = New-MdeTestConfig -TestId "MDE.GC02" -SettingName "Tamper Protection" -Category "Global Config" -Severity "High" -TestType "GlobalConfig" -Description "Verify that Tamper Protection is enabled tenant-wide in Advanced Features." -SecurityImpact "Disabled tamper protection allows local administrators to disable security features" -ActionSteps @("Enable **Tamper Protection** tenant-wide") -ComplianceParameters @{ + ComplianceCheck = "Manual" + ExpectedValue = "Enabled" + } -TestSpecificData @{ + NavigationPath = "Settings β†’ Endpoints β†’ Advanced Features β†’ Tamper Protection" + PortalUrl = "https://security.microsoft.com" + } + + $configurations["MDE.GC03"] = New-MdeTestConfig -TestId "MDE.GC03" -SettingName "EDR in Block Mode" -Category "Global Config" -Severity "High" -TestType "GlobalConfig" -Description "Verify that EDR in Block Mode is enabled for Microsoft Defender Antivirus devices." -SecurityImpact "Disabled EDR block mode reduces ability to block threats in real-time" -ActionSteps @("Enable **EDR in Block Mode** for Defender AV devices") -ComplianceParameters @{ + ComplianceCheck = "Manual" + ExpectedValue = "Enabled" + } -TestSpecificData @{ + NavigationPath = "Settings β†’ Endpoints β†’ Advanced Features β†’ EDR in Block Mode" + PortalUrl = "https://security.microsoft.com" + } + + $configurations["MDE.GC04"] = New-MdeTestConfig -TestId "MDE.GC04" -SettingName "Automatically Resolve Alerts" -Category "Global Config" -Severity "Medium" -TestType "GlobalConfig" -Description "Verify that Automatically Resolve Alerts is properly configured." -SecurityImpact "Manual alert resolution increases workload and may delay response" -ActionSteps @("Configure **Auto-resolution** for appropriate alert types") -ComplianceParameters @{ + ComplianceCheck = "Manual" + ExpectedValue = "Configured" + } -TestSpecificData @{ + NavigationPath = "Settings β†’ Endpoints β†’ Advanced Features β†’ Auto-resolution" + PortalUrl = "https://security.microsoft.com" + } + + $configurations["MDE.GC05"] = New-MdeTestConfig -TestId "MDE.GC05" -SettingName "Allow or Block File" -Category "Global Config" -Severity "Medium" -TestType "GlobalConfig" -Description "Verify that Allow or Block File capability is enabled for IOC handling." -SecurityImpact "Disabled file blocking reduces ability to quickly respond to threats" -ActionSteps @("Enable **Allow or Block File** capability") -ComplianceParameters @{ + ComplianceCheck = "Manual" + ExpectedValue = "Enabled" + } -TestSpecificData @{ + NavigationPath = "Settings β†’ Endpoints β†’ Advanced Features β†’ Allow or Block File" + PortalUrl = "https://security.microsoft.com" + } + + $configurations["MDE.GC06"] = New-MdeTestConfig -TestId "MDE.GC06" -SettingName "Hide Duplicate Device Records" -Category "Global Config" -Severity "Low" -TestType "GlobalConfig" -Description "Verify that Hide Duplicate Device Records is enabled to reduce clutter." -SecurityImpact "Duplicate records create confusion in device management" -ActionSteps @("Enable **Hide Duplicate Device Records**") -ComplianceParameters @{ + ComplianceCheck = "Manual" + ExpectedValue = "Enabled" + } -TestSpecificData @{ + NavigationPath = "Settings β†’ Endpoints β†’ Advanced Features β†’ Device Management" + PortalUrl = "https://security.microsoft.com" + } + + $configurations["MDE.GC07"] = New-MdeTestConfig -TestId "MDE.GC07" -SettingName "Custom Network Indicators" -Category "Global Config" -Severity "High" -TestType "GlobalConfig" -Description "Verify that Custom Network Indicators are enabled for IOC management." -SecurityImpact "Disabled network indicators reduce ability to block malicious IPs and URLs" -ActionSteps @("Enable **Custom Network Indicators**") -ComplianceParameters @{ + ComplianceCheck = "Manual" + ExpectedValue = "Enabled" + } -TestSpecificData @{ + NavigationPath = "Settings β†’ Endpoints β†’ Advanced Features β†’ Indicators" + PortalUrl = "https://security.microsoft.com" + } + + $configurations["MDE.GC08"] = New-MdeTestConfig -TestId "MDE.GC08" -SettingName "Web Content Filtering" -Category "Global Config" -Severity "High" -TestType "GlobalConfig" -Description "Verify that Web Content Filtering is enabled (requires Defender for Endpoint P2 license)." -SecurityImpact "Disabled web filtering allows access to malicious websites" -ActionSteps @("Enable **Web Content Filtering** (requires P2 license)") -ComplianceParameters @{ + ComplianceCheck = "Manual" + ExpectedValue = "Enabled" + } -TestSpecificData @{ + NavigationPath = "Settings β†’ Endpoints β†’ Advanced Features β†’ Web Content Filtering" + PortalUrl = "https://security.microsoft.com" + } + + $configurations["MDE.GC09"] = New-MdeTestConfig -TestId "MDE.GC09" -SettingName "Device Discovery" -Category "Global Config" -Severity "Medium" -TestType "GlobalConfig" -Description "Verify that Device Discovery is enabled for Shadow IT visibility." -SecurityImpact "Disabled device discovery reduces visibility into unmanaged devices" -ActionSteps @("Enable **Device Discovery** for Shadow IT visibility") -ComplianceParameters @{ + ComplianceCheck = "Manual" + ExpectedValue = "Enabled" + } -TestSpecificData @{ + NavigationPath = "Settings β†’ Endpoints β†’ Advanced Features β†’ Device Discovery" + PortalUrl = "https://security.microsoft.com" + } + + $configurations["MDE.GC10"] = New-MdeTestConfig -TestId "MDE.GC10" -SettingName "Download Quarantined Files" -Category "Global Config" -Severity "Medium" -TestType "GlobalConfig" -Description "Verify that Download Quarantined Files capability is enabled for forensic analysis." -SecurityImpact "Disabled download capability reduces forensic investigation capabilities" -ActionSteps @("Enable **Download Quarantined Files** for forensics") -ComplianceParameters @{ + ComplianceCheck = "Manual" + ExpectedValue = "Enabled" + } -TestSpecificData @{ + NavigationPath = "Settings β†’ Endpoints β†’ Advanced Features β†’ General" + PortalUrl = "https://security.microsoft.com" + } + + $configurations["MDE.GC11"] = New-MdeTestConfig -TestId "MDE.GC11" -SettingName "Streamlined Connectivity" -Category "Global Config" -Severity "Medium" -TestType "GlobalConfig" -Description "Verify that Streamlined Connectivity is enabled as default configuration." -SecurityImpact "Disabled streamlined connectivity may affect sensor communication" -ActionSteps @("Enable **Streamlined Connectivity** as default") -ComplianceParameters @{ + ComplianceCheck = "Manual" + ExpectedValue = "Enabled" + } -TestSpecificData @{ + NavigationPath = "Settings β†’ Endpoints β†’ Advanced Features β†’ General" + PortalUrl = "https://security.microsoft.com" + } + + $configurations["MDE.GC12"] = New-MdeTestConfig -TestId "MDE.GC12" -SettingName "Apply Streamlined Connectivity to Intune/DFC" -Category "Global Config" -Severity "Medium" -TestType "GlobalConfig" -Description "Verify that Streamlined Connectivity is applied to Intune/DFC for synchronization." -SecurityImpact "Poor Intune integration affects device management sync" -ActionSteps @("Enable **Streamlined Connectivity to Intune/DFC**") -ComplianceParameters @{ + ComplianceCheck = "Manual" + ExpectedValue = "Enabled" + } -TestSpecificData @{ + NavigationPath = "Settings β†’ Endpoints β†’ Advanced Features β†’ General" + PortalUrl = "https://security.microsoft.com" + } + + $configurations["MDE.GC13"] = New-MdeTestConfig -TestId "MDE.GC13" -SettingName "Isolation Exclusion Rules" -Category "Global Config" -Severity "High" -TestType "GlobalConfig" -Description "Verify that Isolation Exclusion Rules are disabled unless specifically required." -SecurityImpact "Broad isolation exclusions reduce incident response capabilities" -ActionSteps @("Disable **Isolation Exclusion Rules** unless business-justified") -ComplianceParameters @{ + ComplianceCheck = "Manual" + ExpectedValue = "Disabled" + } -TestSpecificData @{ + NavigationPath = "Settings β†’ Endpoints β†’ Advanced Features β†’ General" + PortalUrl = "https://security.microsoft.com" + } + + $configurations["MDE.GC14"] = New-MdeTestConfig -TestId "MDE.GC14" -SettingName "Deception Capabilities" -Category "Global Config" -Severity "Low" -TestType "GlobalConfig" -Description "Verify that Deception capabilities are properly evaluated (optional honeypot deployment)." -SecurityImpact "Unused deception capabilities miss advanced threat detection opportunities" -ActionSteps @("Evaluate **Deception** capabilities for honeypot deployment") -ComplianceParameters @{ + ComplianceCheck = "Manual" + ExpectedValue = "Evaluated" + } -TestSpecificData @{ + NavigationPath = "Settings β†’ Endpoints β†’ Advanced Features β†’ General" + PortalUrl = "https://security.microsoft.com" + } + + $configurations["MDE.GC15"] = New-MdeTestConfig -TestId "MDE.GC15" -SettingName "Microsoft Intune Connection" -Category "Global Config" -Severity "Medium" -TestType "GlobalConfig" -Description "Verify that Microsoft Intune Connection is enabled as prerequisite for MDM integration." -SecurityImpact "Disabled Intune connection affects device management integration" -ActionSteps @("Enable **Microsoft Intune Connection** for MDM integration") -ComplianceParameters @{ + ComplianceCheck = "Manual" + ExpectedValue = "Enabled" + } -TestSpecificData @{ + NavigationPath = "Settings β†’ Endpoints β†’ Advanced Features β†’ General" + PortalUrl = "https://security.microsoft.com" + } + + $configurations["MDE.GC16"] = New-MdeTestConfig -TestId "MDE.GC16" -SettingName "Authenticated Telemetry" -Category "Global Config" -Severity "Low" -TestType "GlobalConfig" -Description "Verify that Authenticated Telemetry settings comply with privacy requirements." -SecurityImpact "Inappropriate telemetry settings may violate privacy regulations" -ActionSteps @("Review **Authenticated Telemetry** for privacy compliance") -ComplianceParameters @{ + ComplianceCheck = "Manual" + ExpectedValue = "Compliant" + } -TestSpecificData @{ + NavigationPath = "Settings β†’ Endpoints β†’ Advanced Features β†’ General" + PortalUrl = "https://security.microsoft.com" + } + + # Policy Design Tests (MDE.PD01-MDE.PD04) + $configurations["MDE.PD01"] = New-MdeTestConfig -TestId "MDE.PD01" -SettingName "Policy Naming Convention" -Category "Policy Design" -Severity "Low" -TestType "PolicyDesign" -Description "Verify consistent policy naming convention across all MDE policies." -SecurityImpact "Inconsistent naming creates confusion and management overhead in large environments" -ActionSteps @("Implement consistent naming convention (ROLE-v#) across all MDE policies") -ComplianceParameters @{ + ComplianceCheck = "Manual" + ExpectedValue = "Consistent naming (ROLE-v#)" + } -TestSpecificData @{ + NavigationPath = "Microsoft Endpoint Manager β†’ Endpoint Security β†’ Antivirus" + PortalUrl = "https://endpoint.microsoft.com" + } + + $configurations["MDE.PD02"] = New-MdeTestConfig -TestId "MDE.PD02" -SettingName "Exclusions in Dedicated Profiles" -Category "Policy Design" -Severity "Medium" -TestType "PolicyDesign" -Description "Verify that exclusions are configured in dedicated profiles to reduce baseline complexity." -SecurityImpact "Mixed baseline and exclusion policies create complexity and potential conflicts" -ActionSteps @("Separate exclusions into dedicated profiles away from baseline policies") -ComplianceParameters @{ + ComplianceCheck = "Manual" + ExpectedValue = "Separated profiles" + } -TestSpecificData @{ + NavigationPath = "Microsoft Endpoint Manager β†’ Endpoint Security β†’ Antivirus" + PortalUrl = "https://endpoint.microsoft.com" + } + + $configurations["MDE.PD03"] = New-MdeTestConfig -TestId "MDE.PD03" -SettingName "Granular Device Profiles" -Category "Policy Design" -Severity "Medium" -TestType "PolicyDesign" -Description "Verify that device profiles are granular and follow least privilege principle." -SecurityImpact "Broad device assignments reduce security and increase complexity" -ActionSteps @("Implement granular device targeting based on roles and least privilege") -ComplianceParameters @{ + ComplianceCheck = "Manual" + ExpectedValue = "Granular targeting" + } -TestSpecificData @{ + NavigationPath = "Microsoft Endpoint Manager β†’ Groups β†’ Device Groups" + PortalUrl = "https://endpoint.microsoft.com" + } + + $configurations["MDE.PD04"] = New-MdeTestConfig -TestId "MDE.PD04" -SettingName "Staging Deployment Buckets" -Category "Policy Design" -Severity "Medium" -TestType "PolicyDesign" -Description "Verify that staging deployment buckets are implemented (e.g., DG-CL-GEN-PILOT β†’ PROD)." -SecurityImpact "Direct production deployment increases risk of widespread issues" -ActionSteps @("Implement staging buckets (Pilot β†’ Production) for gradual rollout") -ComplianceParameters @{ + ComplianceCheck = "Manual" + ExpectedValue = "Pilot β†’ Prod buckets" + } -TestSpecificData @{ + NavigationPath = "Microsoft Endpoint Manager β†’ Groups β†’ Device Groups" + PortalUrl = "https://endpoint.microsoft.com" + } + + # Apply MDE config overrides for ComplianceLogic + foreach ($configKey in $configurations.Keys) { + $testSpecificConfig = $mdeConfig.TestSpecific.$configKey + if ($testSpecificConfig -and $testSpecificConfig.ComplianceLogic) { + $configurations[$configKey].ComplianceLogic = $testSpecificConfig.ComplianceLogic + } else { + $configurations[$configKey].ComplianceLogic = $mdeConfig.ComplianceLogic + } + } + + if ($TestId) { + return $configurations[$TestId] + } else { + return $configurations + } +} + +#endregion \ No newline at end of file diff --git a/powershell/public/defender/MtMdeHelpers.ps1 b/powershell/public/defender/MtMdeHelpers.ps1 new file mode 100644 index 000000000..3168a6494 --- /dev/null +++ b/powershell/public/defender/MtMdeHelpers.ps1 @@ -0,0 +1,962 @@ +<# +.SYNOPSIS + Helper functions for Microsoft Defender for Endpoint tests + +.DESCRIPTION + Contains functions for device management, policy retrieval, and the unified test engine + used by all MDE tests in the Maester framework. + +.NOTES + All MDE helper functions are consolidated in this file for easier maintenance. +#> + +#Requires -Version 5.1 + +#region Core Configuration Functions + +<# +.SYNOPSIS + Gets information about your organization's Defender-protected devices and their policies + +.DESCRIPTION + Retrieves device inventory, configuration policies, and compliance information + from Microsoft Graph API for use in MDE tests. + +.PARAMETER DisableCache + Bypasses the Graph API response cache and fetches fresh data + +.EXAMPLE + Get-MtMdeConfiguration + + Gets current MDE device and policy information +#> +function Get-MtMdeConfiguration { + [CmdletBinding()] + param( + [switch]$DisableCache + ) + + Write-Verbose "Getting managed devices from Microsoft Graph" + $deviceParams = @{ + RelativeUri = 'deviceManagement/managedDevices' + ApiVersion = 'v1.0' + Select = 'id,deviceName,operatingSystem,complianceState,managementAgent,azureADDeviceId,lastSyncDateTime' + DisableCache = $DisableCache + } + $managedDevices = Invoke-MtGraphRequest @deviceParams + + if ($managedDevices) { + foreach ($device in $managedDevices) { + if ($device.lastSyncDateTime) { + try { + $parsedDate = [DateTime]::Parse($device.lastSyncDateTime) + $device.lastSyncDateTime = $parsedDate.ToString("yyyy-MM-ddTHH:mm:ss.fffZ") + } catch { + Write-Verbose "Could not parse date for device $($device.deviceName): $($device.lastSyncDateTime)" + } + } + } + } + + Write-Verbose "Getting device configuration policies" + $configParams = @{ + RelativeUri = 'deviceManagement/configurationPolicies' + ApiVersion = 'beta' + DisableCache = $DisableCache + } + $configPolicies = Invoke-MtGraphRequest @configParams + + Write-Verbose "Getting device compliance policies" + $complianceParams = @{ + RelativeUri = 'deviceManagement/deviceCompliancePolicies' + ApiVersion = 'v1.0' + DisableCache = $DisableCache + } + $compliancePolicies = Invoke-MtGraphRequest @complianceParams + + Write-Verbose "Getting security baselines" + $baselinesParams = @{ + RelativeUri = 'deviceManagement/templates' + ApiVersion = 'beta' + Filter = "isof('microsoft.graph.securityBaselineTemplate')" + DisableCache = $DisableCache + } + $securityBaselines = Invoke-MtGraphRequest @baselinesParams + + $configuration = @{ + ManagedDevices = $managedDevices + ConfigurationPolicies = $configPolicies + CompliancePolicies = $compliancePolicies + SecurityBaselines = $securityBaselines + Timestamp = Get-Date + } + + return $configuration +} + +<# +.SYNOPSIS + Counts how many devices are protected by Microsoft Defender for Endpoint + +.DESCRIPTION + Returns the number of Windows devices that can receive MDE antivirus policies. + Only includes devices managed by Defender ('msSense') or Intune ('mdm'). + +.PARAMETER IncludeDetails + Returns detailed device information instead of just the count + +.EXAMPLE + Get-MtMdeDeviceCount + + Returns the number of MDE-protected devices +#> +function Get-MtMdeDeviceCount { + [CmdletBinding()] + [OutputType([int], ParameterSetName = 'Count')] + [OutputType([hashtable], ParameterSetName = 'Details')] + param( + [Parameter(ParameterSetName = 'Details')] + [switch]$IncludeDetails + ) + + try { + $mdeConfig = Get-MtMdeConfiguration -ErrorAction SilentlyContinue + + $mdeDevices = @() + $mdeDeviceCount = 0 + + if ($mdeConfig -and $mdeConfig.ManagedDevices) { + # Only include devices managed by Defender ('msSense') or Intune ('mdm') since these can receive antivirus policies + $mdeDevices = $mdeConfig.ManagedDevices | Where-Object { + $_.managementAgent -in @('msSense', 'mdm') -and + $_.operatingSystem -eq 'Windows' + } + $mdeDeviceCount = $mdeDevices.Count + } + + Write-Verbose "Found $mdeDeviceCount Windows devices eligible for MDE antivirus policy testing (msSense: $(@($mdeDevices | Where-Object {$_.managementAgent -eq 'msSense'}).Count), mdm: $(@($mdeDevices | Where-Object {$_.managementAgent -eq 'mdm'}).Count))" + + if ($IncludeDetails) { + return @{ + Count = $mdeDeviceCount + Devices = $mdeDevices + TotalManagedDevices = if ($mdeConfig.ManagedDevices) { $mdeConfig.ManagedDevices.Count } else { 0 } + } + } else { + return $mdeDeviceCount + } + + } catch [Microsoft.Graph.PowerShell.Runtime.GraphException] { + Write-Verbose "Microsoft Graph API error getting MDE device count: $($_.Exception.Message)" + + if ($IncludeDetails) { + return @{ + Count = 0 + Devices = @() + TotalManagedDevices = 0 + Error = "Graph API error: $($_.Exception.Message)" + } + } else { + return 0 + } + } catch [System.UnauthorizedAccessException] { + Write-Verbose "Insufficient permissions to get MDE device count: $($_.Exception.Message)" + + if ($IncludeDetails) { + return @{ + Count = 0 + Devices = @() + TotalManagedDevices = 0 + Error = "Access denied: $($_.Exception.Message)" + } + } else { + return 0 + } + } catch { + Write-Verbose "Unexpected error getting MDE device count: $($_.Exception.Message)" + + if ($IncludeDetails) { + return @{ + Count = 0 + Devices = @() + TotalManagedDevices = 0 + Error = "Unexpected error: $($_.Exception.Message)" + } + } else { + return 0 + } + } +} + +#endregion + +#region Policy Assignment Functions + +<# +.SYNOPSIS + Checks if a policy is assigned to any groups or devices + +.DESCRIPTION + Returns true if the policy has active assignments (not just exclusions). + This helps ensure we only test policies that are actually deployed. + +.PARAMETER PolicyId + The ID of the policy to check + +.PARAMETER PolicyType + Type of policy: "ConfigurationPolicy" or "DeviceConfiguration" + +.EXAMPLE + Test-MtMdePolicyHasAssignments -PolicyId "abc-123" -PolicyType "ConfigurationPolicy" + + Returns $true if policy is assigned to groups or devices +#> +function Test-MtMdePolicyHasAssignments { + [CmdletBinding()] + [OutputType([bool])] + param( + [Parameter(Mandatory = $true)] + [string]$PolicyId, + + [Parameter(Mandatory = $true)] + [ValidateSet("ConfigurationPolicy")] + [string]$PolicyType + ) + + try { + $endpoint = "deviceManagement/configurationPolicies/$PolicyId/assignments" + + $assignmentParams = @{ + RelativeUri = $endpoint + ApiVersion = 'beta' + ErrorAction = 'Stop' + } + $assignments = Invoke-MtGraphRequest @assignmentParams + + if (-not $assignments -or $assignments.Count -eq 0) { + Write-Verbose "Policy $PolicyId has no assignments" + return $false + } + + # Look for inclusion assignments (not just exclusions) + $validAssignments = @() + foreach ($assignment in $assignments) { + if ($assignment.target.'@odata.type' -in @( + '#microsoft.graph.groupAssignmentTarget', + '#microsoft.graph.allDevicesAssignmentTarget', + '#microsoft.graph.allLicensedUsersAssignmentTarget' + )) { + $validAssignments += $assignment + } + } + + if ($validAssignments.Count -gt 0) { + Write-Verbose "Policy $PolicyId has $($validAssignments.Count) valid assignments" + return $true + } else { + Write-Verbose "Policy $PolicyId has only exclusion assignments" + return $false + } + + } catch { + # If we can't check assignments, assume policy is assigned (fail-safe approach) + Write-Verbose "Error getting assignments for policy $PolicyId - $($_.Exception.Message). Assuming policy is assigned." + return $true + } +} + +#endregion + +#region Policy Retrieval Functions + +<# +.SYNOPSIS + Gets Microsoft Defender Antivirus policies that are assigned to devices + +.DESCRIPTION + Retrieves configuration policies from Microsoft Graph, filters for + Defender Antivirus policies, and checks which ones are actually assigned. + +.OUTPUTS + Hashtable with ConfigurationPolicies array and TotalCount +#> +function Get-MdePolicyConfiguration { + [CmdletBinding()] + [OutputType([hashtable])] + param() + + try { + $mdeGlobalConfig = Get-MtMdeConfig + $mdeConfig = Get-MtMdeConfiguration + + if (-not $mdeConfig) { + Write-Verbose "Unable to retrieve MDE configuration" + return @{ + ConfigurationPolicies = @() + TotalCount = 0 + Error = "Failed to retrieve MDE configuration" + } + } + + # Find Microsoft Defender Antivirus policies for Windows + $configPolicies = @() + if ($mdeConfig.ConfigurationPolicies) { + $configPolicies = $mdeConfig.ConfigurationPolicies | Where-Object { + $_.templateReference.templateDisplayName -eq "Microsoft Defender Antivirus" -and + $_.platforms -eq "windows10" + } + } + + # Apply policy filtering based on configuration + $finalConfigPolicies = @() + + switch ($mdeGlobalConfig.PolicyFiltering) { + "All" { + $finalConfigPolicies = $configPolicies + Write-Verbose "Policy filtering: All - Including all $($configPolicies.Count) policies" + } + "IncludeUnassigned" { + $finalConfigPolicies = $configPolicies + Write-Verbose "Policy filtering: IncludeUnassigned - Including all $($configPolicies.Count) policies" + } + "OnlyAssigned" { + if ($configPolicies.Count -gt 0) { + Write-Verbose "Checking assignments for $($configPolicies.Count) policies" + foreach ($policy in $configPolicies) { + if (Test-MtMdePolicyHasAssignments -PolicyId $policy.id -PolicyType "ConfigurationPolicy") { + $finalConfigPolicies += $policy + } + } + Write-Verbose "Found $($finalConfigPolicies.Count) assigned policies" + } + } + default { + Write-Verbose "Invalid PolicyFiltering value '$($mdeGlobalConfig.PolicyFiltering)', defaulting to OnlyAssigned" + if ($configPolicies.Count -gt 0) { + foreach ($policy in $configPolicies) { + if (Test-MtMdePolicyHasAssignments -PolicyId $policy.id -PolicyType "ConfigurationPolicy") { + $finalConfigPolicies += $policy + } + } + } + } + } + + return @{ + ConfigurationPolicies = $finalConfigPolicies + TotalCount = $finalConfigPolicies.Count + } + + } catch [Microsoft.Graph.PowerShell.Runtime.GraphException] { + Write-Verbose "Microsoft Graph API error retrieving MDE policies: $($_.Exception.Message)" + return @{ + ConfigurationPolicies = @() + TotalCount = 0 + Error = "Graph API error: $($_.Exception.Message)" + } + } catch [System.UnauthorizedAccessException] { + Write-Verbose "Insufficient permissions to retrieve MDE policies: $($_.Exception.Message)" + return @{ + ConfigurationPolicies = @() + TotalCount = 0 + Error = "Access denied: $($_.Exception.Message)" + } + } catch { + Write-Verbose "Unexpected error retrieving MDE policies: $($_.Exception.Message)" + return @{ + ConfigurationPolicies = @() + TotalCount = 0 + Error = "Unexpected error: $($_.Exception.Message)" + } + } +} + +<# +.SYNOPSIS + Tests policy compliance for MDE settings + +.DESCRIPTION + Analyzes configuration policies for compliance with the specified setting. + Returns detailed compliance results categorized by compliant, non-compliant, and not-configured. + +.PARAMETER PolicyConfiguration + Policy configuration object from Get-MdePolicyConfiguration + +.PARAMETER SettingConfig + Setting configuration object containing compliance criteria + +.OUTPUTS + Hashtable containing compliance analysis results +#> +function Test-MdePolicyCompliance { + [CmdletBinding()] + [OutputType([hashtable])] + param( + [Parameter(Mandatory = $true)] + [hashtable]$PolicyConfiguration, + + [Parameter(Mandatory = $true)] + [hashtable]$SettingConfig + ) + + $compliantPolicies = @() + $nonCompliantPolicies = @() + $notConfiguredPolicies = @() + + # Focus only on Settings Catalog policies + + # Check new configuration policies + if ($PolicyConfiguration.ConfigurationPolicies.Count -gt 0) { + foreach ($policy in $PolicyConfiguration.ConfigurationPolicies) { + try { + # Get all settings using Maester framework (includes automatic paging) + $settingsParams = @{ + RelativeUri = "deviceManagement/configurationPolicies/$($policy.id)/settings" + ApiVersion = 'beta' + ErrorAction = 'SilentlyContinue' + } + $policySettings = Invoke-MtGraphRequest @settingsParams + + $specificSetting = $policySettings | Where-Object { + $_.settingInstance.settingDefinitionId -eq $SettingConfig.TestSpecificData.SettingId + } + + if ($specificSetting) { + $settingValue = Get-MdeSettingValue -Setting $specificSetting -SettingConfig $SettingConfig + $complianceResult = Test-MdeSettingCompliance -Value $settingValue -SettingConfig $SettingConfig + + switch ($complianceResult) { + "Compliant" { $compliantPolicies += $policy.name } + "NonCompliant" { $nonCompliantPolicies += $policy.name } + "NotConfigured" { $notConfiguredPolicies += $policy.name } + } + } else { + # Policy exists but setting not configured + $notConfiguredPolicies += $policy.name + } + } catch [Microsoft.Graph.PowerShell.Runtime.GraphException] { + Write-Verbose "Graph API error analyzing configuration policy $($policy.name): $($_.Exception.Message)" + $notConfiguredPolicies += $policy.name + } catch { + Write-Verbose "Error analyzing configuration policy $($policy.name): $($_.Exception.Message)" + $notConfiguredPolicies += $policy.name + } + } + } + + return @{ + CompliantPolicies = $compliantPolicies + NonCompliantPolicies = $nonCompliantPolicies + NotConfiguredPolicies = $notConfiguredPolicies + HasCompliant = $compliantPolicies.Count -gt 0 + HasNonCompliant = $nonCompliantPolicies.Count -gt 0 + HasNotConfigured = $notConfiguredPolicies.Count -gt 0 + } +} + +<# +.SYNOPSIS + Formats MDE test results into markdown report + +.DESCRIPTION + Generates comprehensive markdown report for MDE test results including + policy analysis, compliance status, and remediation guidance. + +.PARAMETER SettingConfig + Setting configuration object + +.PARAMETER ComplianceResults + Compliance analysis results + +.PARAMETER DeviceCount + Number of MDE devices + +.PARAMETER PolicyConfiguration + Policy configuration details + +.PARAMETER TestResult + Overall test result (pass/fail) + +.OUTPUTS + String containing formatted markdown report +#> + +<# +.SYNOPSIS + Tests compliance for a specific setting value + +.DESCRIPTION + Evaluates a setting value against the configuration criteria to determine + if it meets compliance requirements. + +.PARAMETER Value + The setting value to test + +.PARAMETER SettingConfig + Configuration object containing compliance criteria + +.OUTPUTS + String - "Compliant", "NonCompliant", or "NotConfigured" +#> +function Test-MdeSettingCompliance { + [CmdletBinding()] + [OutputType([string])] + param( + $Value, + [Parameter(Mandatory = $true)] + [hashtable]$SettingConfig + ) + + if ($null -eq $Value -or $Value -eq "") { + return "NotConfigured" + } + + switch ($SettingConfig.ComplianceParameters.ComplianceCheck) { + "Boolean" { + # Compare string values directly (e.g., "_1" with "_1") + if ([string]$Value -eq [string]$SettingConfig.ComplianceParameters.ExpectedValue) { + return "Compliant" + } else { + return "NonCompliant" + } + } + "Range" { + # Convert to numeric for range comparison + try { + $numValue = [int]$Value + $min = $SettingConfig.ComplianceParameters.RangeMin + $max = $SettingConfig.ComplianceParameters.RangeMax + if ($numValue -ge $min -and $numValue -le $max) { + return "Compliant" + } else { + return "NonCompliant" + } + } catch { + return "NonCompliant" + } + } + "Enum" { + # Compare string values directly (e.g., "_1" with allowed values) + if ([string]$Value -in $SettingConfig.ComplianceParameters.ValidValues) { + return "Compliant" + } else { + return "NonCompliant" + } + } + "MinimumLevel" { + # Compare numeric level values (e.g., Cloud Block Level) + if ($SettingConfig.ComplianceParameters.ValidLevels -and $SettingConfig.ComplianceParameters.ValidLevels.ContainsKey([string]$Value)) { + $actualLevel = $SettingConfig.ComplianceParameters.ValidLevels[[string]$Value] + $minimumLevel = $SettingConfig.ComplianceParameters.MinimumValue + if ($actualLevel -ge $minimumLevel) { + return "Compliant" + } else { + return "NonCompliant" + } + } else { + return "NonCompliant" + } + } + "MinimumValue" { + # Compare numeric values with minimum threshold (e.g., Retention Days) + try { + $numValue = [int]$Value + $minimumValue = $SettingConfig.ComplianceParameters.MinimumValue + if ($numValue -ge $minimumValue) { + return "Compliant" + } else { + return "NonCompliant" + } + } catch { + return "NonCompliant" + } + } + "NotRequired" { + # This setting is not required for compliance + return "Compliant" + } + "Manual" { + # This setting requires manual verification + return "NotConfigured" + } + "Custom" { + if ($SettingConfig.CustomValidation) { + $result = & $SettingConfig.CustomValidation $Value + return if ($result) { "Compliant" } else { "NonCompliant" } + } + return "NotConfigured" + } + default { + return "NotConfigured" + } + } +} + +<# +.SYNOPSIS + Extracts setting values from Graph API responses + +.DESCRIPTION + Parses setting objects from Microsoft Graph API configuration policy responses + and extracts the appropriate value based on the setting type. + +.PARAMETER Setting + The setting object from Graph API + +.PARAMETER SettingConfig + Configuration object containing extraction logic + +.OUTPUTS + Object - The extracted setting value +#> +function Get-MdeSettingValue { + [CmdletBinding()] + [OutputType([object])] + param( + [Parameter(Mandatory = $true)] + $Setting, + + [Parameter(Mandatory = $true)] + [hashtable]$SettingConfig + ) + + switch ($SettingConfig.ComplianceParameters.ComplianceCheck) { + "Boolean" { + $choiceValue = $Setting.settingInstance.choiceSettingValue.value + # Extract the suffix (_0, _1, etc.) which indicates the Graph API enum value + if ($choiceValue -match "_(\d)$") { + return "_$($matches[1])" + } + return $null + } + "Range" { + return $Setting.settingInstance.simpleSettingValue.value + } + "Enum" { + $choiceValue = $Setting.settingInstance.choiceSettingValue.value + # Extract the suffix (_0, _1, _2, etc.) which indicates the Graph API enum value + if ($choiceValue -match "_(\d)$") { + return "_$($matches[1])" + } + return $choiceValue + } + "MinimumLevel" { + $choiceValue = $Setting.settingInstance.choiceSettingValue.value + # Extract the suffix for level-based values like Cloud Block Level + if ($choiceValue -match "_(\d+)$") { + return "_$($matches[1])" + } + return $choiceValue + } + "MinimumValue" { + # Simple numeric values like retention days + return $Setting.settingInstance.simpleSettingValue.value + } + "NotRequired" { + # Not required settings always return a compliant value + return "NotRequired" + } + "Manual" { + # Manual settings require manual verification + return "ManualVerificationRequired" + } + default { + $choiceValue = $Setting.settingInstance.choiceSettingValue.value + # Extract the suffix for any other setting types + if ($choiceValue -match "_(\d)$") { + return "_$($matches[1])" + } + return $choiceValue + } + } +} + +#endregion + + +#region Unified Test Engine Functions + +<# +.SYNOPSIS + Unified, intelligent test engine for all Microsoft Defender for Endpoint tests + +.DESCRIPTION + This function provides a single, unified testing engine for all MDE test types. + It automatically handles test skipping based on TestType and ComplianceCheck, + eliminating the need for redundant manual skipping code in Pester tests. + +.PARAMETER TestId + The MDE test identifier (e.g., "MDE.AV01", "MDE.GC01") + +.PARAMETER TestName + The Pester test name for result tracking (optional, auto-generated if not provided) + +.EXAMPLE + Invoke-MtMdeUnifiedTest -TestId "MDE.AV01" + + Runs the archive scanning test using unified configuration + +.EXAMPLE + Invoke-MtMdeUnifiedTest -TestId "MDE.AV20" + + Automatically skips the tamper protection test since it's marked as Manual +#> +function Invoke-MtMdeUnifiedTest { + [CmdletBinding()] + [OutputType([bool])] + param( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$TestId, + + [Parameter(Mandatory = $false)] + [string]$TestName + ) + + try { + # Get unified configuration + $config = Get-MtMdeUnifiedConfiguration -TestId $TestId + + if (-not $config) { + Write-Warning "Configuration not found for TestId: $TestId" + Add-MtTestResultDetail -Description "Configuration not found for TestId: $TestId" -GraphObjectType 'Devices' + return $null + } + + # Auto-generate test name if not provided + if (-not $TestName) { + $TestName = "$($config.TestId): $($config.SettingName)" + } + + # Simple check: If it's a manual test, skip it automatically + if ($config.ComplianceParameters.ComplianceCheck -eq "Manual" -or $config.TestType -in @("Manual", "GlobalConfig", "PolicyDesign")) { + + # Generate appropriate manual verification markdown + $manualMarkdown = New-MtMdeManualVerificationMarkdown -Config $config + + # Use Maester's standard skip mechanism for manual tests with details + $skipBecause = switch ($config.TestType) { + "GlobalConfig" { "This test requires manual verification in Microsoft 365 Defender portal" } + "PolicyDesign" { "This test requires manual verification in Microsoft Endpoint Manager portal" } + default { "This test requires manual verification" } + } + + # Return a special skip indicator that Pester tests can handle + return @{ + IsSkipped = $true + SkipReason = $skipBecause + TestType = "Manual" + TestDetails = $manualMarkdown + Severity = $config.Severity + } + } + + # For automated tests, check prerequisites + if (-not (Test-MtConnection Graph)) { + Add-MtTestResultDetail -SkippedBecause NotConnectedGraph -GraphObjectType 'Devices' + return $null + } + + $mdeDeviceCount = Get-MtMdeDeviceCount + if ($mdeDeviceCount -eq 0) { + Add-MtTestResultDetail -Description "No MDE devices found - $($config.SettingName) configuration not applicable" -GraphObjectType 'Devices' + return $true + } + + # For automated tests, execute the test logic directly + if ($config.TestType -eq "Automated") { + # Get policy configuration using dedicated helper + $policyConfiguration = Get-MdePolicyConfiguration + + if ($policyConfiguration.Error) { + Add-MtTestResultDetail -Description "Error retrieving MDE policies: $($policyConfiguration.Error)" -GraphObjectType 'Devices' + return $null + } + + if ($policyConfiguration.TotalCount -eq 0) { + Add-MtTestResultDetail -Description "No assigned Microsoft Defender Antivirus policies found. Unassigned policies are not tested." -GraphObjectType 'Devices' + return $null + } + + # Test policy compliance using dedicated helper + $complianceResults = Test-MdePolicyCompliance -PolicyConfiguration $policyConfiguration -SettingConfig $config + + # Determine test result based on compliance logic + $testResult = switch ($config.ComplianceLogic) { + "AtLeastOne" { $complianceResults.HasCompliant -and -not $complianceResults.HasNonCompliant } + "AllPolicies" { $complianceResults.CompliantPolicies.Count -eq $policyConfiguration.TotalCount } + default { $complianceResults.HasCompliant -and -not $complianceResults.HasNonCompliant } + } + + # Prepare objects for reporting + $failedObjects = @() + if ($complianceResults.NonCompliantPolicies.Count -gt 0) { + $failedObjects += $policyConfiguration.ConfigurationPolicies | Where-Object { $_.name -in $complianceResults.NonCompliantPolicies } + } + + # Generate detailed markdown directly (consolidated from template system) + $resultStatus = if ($testResult) { "βœ… PASSED" } else { "❌ FAILED" } + + # Convert technical expected value to user-friendly format + $friendlyExpectedValue = switch ($config.ComplianceParameters.ExpectedValue) { + "_0" { "Disabled" } + "_1" { "Enabled" } + "_2" { "Audit Mode" } + default { $config.ComplianceParameters.ExpectedValue } + } + + $detailedMarkdown = @" +**Microsoft Defender Antivirus Policy Compliance** + +Verifies that assigned Windows Antivirus policies are properly configured. Only policies with active group assignments are tested. + +### $($config.TestId): $($config.SettingName) - $resultStatus + +**Setting**: $($config.SettingName) | **Expected**: $friendlyExpectedValue | **Category**: $($config.Category) | **Severity**: $($config.Severity) + +**Devices**: $mdeDeviceCount MDE Windows devices | **Policies**: $($policyConfiguration.TotalCount) assigned Settings Catalog policies + +**Results**: βœ… $($complianceResults.CompliantPolicies.Count) Compliant | ❌ $($complianceResults.NonCompliantPolicies.Count) Non-Compliant | ⚠️ $($complianceResults.NotConfiguredPolicies.Count) Not Configured + +$( if ($complianceResults.CompliantPolicies.Count -gt 0) { +"**βœ… Compliant Policies**: +$($complianceResults.CompliantPolicies | ForEach-Object { "- $_" } | Out-String)" +} else { "" } ) + +$( if ($complianceResults.NonCompliantPolicies.Count -gt 0) { +"**❌ Non-Compliant Policies**: +$($complianceResults.NonCompliantPolicies | ForEach-Object { "- $_" } | Out-String) +⚠️ **Impact**: $($config.SecurityImpact)" +} else { "" } ) + +$( if ($complianceResults.NotConfiguredPolicies.Count -gt 0) { +"**⚠️ Not Configured Policies**: +$($complianceResults.NotConfiguredPolicies | ForEach-Object { "- $_" } | Out-String)" +} else { "" } ) + +$( if (-not $testResult) { +"**🎯 Action Required**: +1. Open [Endpoint Manager](https://endpoint.microsoft.com) β†’ **Endpoint Security** β†’ **Antivirus** +2. Edit policies: **$($complianceResults.NonCompliantPolicies + $complianceResults.NotConfiguredPolicies -join ', ')** +3. Set **$($config.SettingName)** to: **$friendlyExpectedValue** +4. Deploy changes to device groups" +} else { +"**βœ… Compliance Status**: All assigned policies are properly configured." +} ) + +**Resources**: [Endpoint Manager](https://endpoint.microsoft.com) | [MDE Documentation](https://docs.microsoft.com/en-us/microsoft-365/security/defender-endpoint/microsoft-defender-antivirus-windows) +"@ + + # Use -Description parameter with our generated markdown + Add-MtTestResultDetail -Description $detailedMarkdown -Result $testResult -GraphObjects $failedObjects -GraphObjectType 'Devices' -TestName $TestName -Severity $config.Severity + + return $testResult + } + + # This should not be reached for properly configured tests + Write-Warning "Unhandled test type: $($config.TestType) for TestId: $TestId" + return $null + + } catch { + # Check if this is a skip-related error (from Set-ItResult) + if ($_.Exception.Message -match "is skipped") { + # Don't treat skip operations as errors + return $null + } + + # Only treat actual errors as errors + Add-MtTestResultDetail -SkippedBecause Error -SkippedError $_ + return $null + } +} + +<# +.SYNOPSIS + Generates manual verification markdown for all test types + +.DESCRIPTION + Creates standardized manual verification instructions based on test configuration. + This replaces the separate detail formatter functions with a single, unified approach. + +.PARAMETER Config + The unified test configuration object + +.OUTPUTS + String containing formatted markdown for manual verification +#> +function New-MtMdeManualVerificationMarkdown { + [CmdletBinding()] + [OutputType([string])] + param( + [Parameter(Mandatory = $true)] + [hashtable]$Config + ) + + # Determine portal information based on test type + $portalInfo = switch ($Config.TestType) { + "PolicyDesign" { + @{ + Portal = "Microsoft Endpoint Manager Admin Center" + Url = "https://endpoint.microsoft.com" + NavPath = "Endpoint Security β†’ Antivirus or Device Configuration" + } + } + "GlobalConfig" { + @{ + Portal = "Microsoft 365 Defender Portal" + Url = $Config.TestSpecificData.PortalUrl + NavPath = $Config.TestSpecificData.NavigationPath + } + } + "Manual" { + @{ + Portal = if ($Config.TestSpecificData.PortalUrl -like "*endpoint*") { "Microsoft Endpoint Manager" } else { "Microsoft 365 Defender Portal" } + Url = $Config.TestSpecificData.PortalUrl + NavPath = $Config.TestSpecificData.NavigationPath + } + } + default { + @{ + Portal = "Microsoft 365 Defender Portal" + Url = "https://security.microsoft.com" + NavPath = "Manual review required" + } + } + } + + # Generate test-specific checklist content based on TestId + $checklistContent = "- Verify that **$($Config.SettingName)** is configured correctly`n- Ensure configuration aligns with security best practices`n- Document any deviations or exceptions" + + return @" +## $($Config.TestId): $($Config.SettingName) - ⏭️ MANUAL REVIEW REQUIRED + +**Test ID**: $($Config.TestId) +**Category**: $($Config.Category) +**Expected**: $($Config.ComplianceParameters.ExpectedValue) +**Severity**: $($Config.Severity) + +### πŸ“‹ Manual Review Checklist: + +**What to Review:** +$checklistContent + +### 🎯 Action Required: +1. Open **$($portalInfo.Portal)** $($portalInfo.Url) +2. Navigate to **$($portalInfo.NavPath)** +3. Review current configuration against the checklist above +4. Document findings and compliance status +5. $($Config.ActionSteps -join '; ') + +$(if ($Config.TestSpecificData.AuditNote) { +"### πŸ” Audit Note: +$($Config.TestSpecificData.AuditNote)" +}) + +### ⚠️ Security Impact: +$($Config.SecurityImpact) + +### πŸ“Š Compliance Status: +This test requires manual review and cannot be automated. Please perform the review steps above and document your findings in your compliance tracking system. +"@ +} + diff --git a/tests/Maester/Defender/Test-MtMdeAntivirusPolicy.Tests.ps1 b/tests/Maester/Defender/Test-MtMdeAntivirusPolicy.Tests.ps1 new file mode 100644 index 000000000..d708bab8d --- /dev/null +++ b/tests/Maester/Defender/Test-MtMdeAntivirusPolicy.Tests.ps1 @@ -0,0 +1,306 @@ +Describe "Microsoft Defender Antivirus - Policy Compliance" -Tag "Maester", "MDE", "Security", "All", "MDE-Antivirus", "Automated" { + + # Scan Engines Tests (MDE.AV01 to MDE.AV09) - Using Unified Test Engine + It "MDE.AV01: Archive Scanning should be allowed. See https://maester.dev/docs/tests/MDE.AV01" -Tag "MDE.AV01" { + <# + Verify that archive scanning is enabled to detect malware in compressed files. + Category: Scan Engines | Severity: Medium + #> + $result = Invoke-MtMdeUnifiedTest -TestId "MDE.AV01" -TestName $____Pester.CurrentTest.ExpandedName + if ($null -ne $result) { + $result | Should -Be $true -Because "archive scanning helps detect malware hidden in compressed files" + } + } + + It "MDE.AV02: Behavior Monitoring should be allowed. See https://maester.dev/docs/tests/MDE.AV02" -Tag "MDE.AV02" { + <# + Verify that behavior monitoring is enabled - prerequisite for EDR capabilities. + Category: Scan Engines | Severity: High + #> + $result = Invoke-MtMdeUnifiedTest -TestId "MDE.AV02" -TestName $____Pester.CurrentTest.ExpandedName + if ($null -ne $result) { + $result | Should -Be $true -Because "behavior monitoring is essential for detecting advanced threats" + } + } + + It "MDE.AV03: Cloud Protection should be allowed. See https://maester.dev/docs/tests/MDE.AV03" -Tag "MDE.AV03" { + <# + Verify that cloud protection is enabled with CloudLevel β‰₯ High. + Category: Scan Engines | Severity: High + #> + $result = Invoke-MtMdeUnifiedTest -TestId "MDE.AV03" -TestName $____Pester.CurrentTest.ExpandedName + if ($null -ne $result) { + $result | Should -Be $true -Because "cloud protection provides real-time threat intelligence" + } + } + + It "MDE.AV04: Email Scanning should be allowed. See https://maester.dev/docs/tests/MDE.AV04" -Tag "MDE.AV04" { + <# + Verify that email scanning is enabled for Exchange queues protection. + Category: Scan Engines | Severity: Medium + #> + $result = Invoke-MtMdeUnifiedTest -TestId "MDE.AV04" -TestName $____Pester.CurrentTest.ExpandedName + if ($null -ne $result) { + $result | Should -Be $true -Because "email scanning should be enabled to protect Exchange queues" + } + } + + It "MDE.AV05: Script Scanning should be allowed. See https://maester.dev/docs/tests/MDE.AV05" -Tag "MDE.AV05" { + <# + Verify that script scanning is enabled to block malicious JavaScript. + Category: Scan Engines | Severity: High + #> + $result = Invoke-MtMdeUnifiedTest -TestId "MDE.AV05" -TestName $____Pester.CurrentTest.ExpandedName + if ($null -ne $result) { + $result | Should -Be $true -Because "script scanning should be enabled to block malicious scripts" + } + } + + It "MDE.AV06: Real-time Monitoring should be allowed. See https://maester.dev/docs/tests/MDE.AV06" -Tag "MDE.AV06" { + <# + Verify that realtime monitoring is enabled - core protection function. + Category: Scan Engines | Severity: Critical + #> + $result = Invoke-MtMdeUnifiedTest -TestId "MDE.AV06" -TestName $____Pester.CurrentTest.ExpandedName + if ($null -ne $result) { + $result | Should -Be $true -Because "real-time monitoring provides essential protection against live threats" + } + } + + It "MDE.AV07: Full Scan Removable Drives should be allowed. See https://maester.dev/docs/tests/MDE.AV07" -Tag "MDE.AV07" { + <# + Verify that full scan of removable drives is enabled to mitigate USB risks. + Category: Scan Engines | Severity: Medium + #> + $result = Invoke-MtMdeUnifiedTest -TestId "MDE.AV07" -TestName $____Pester.CurrentTest.ExpandedName + if ($null -ne $result) { + $result | Should -Be $true -Because "full scan of removable drives should be enabled to mitigate USB risks" + } + } + + It "MDE.AV08: Full Scan Mapped Drives should be disabled for performance. See https://maester.dev/docs/tests/MDE.AV08" -Tag "MDE.AV08" { + <# + Verify that full scan of mapped network drives is disabled for performance reasons. + Category: Scan Engines | Severity: Low + #> + $result = Invoke-MtMdeUnifiedTest -TestId "MDE.AV08" -TestName $____Pester.CurrentTest.ExpandedName + if ($null -ne $result) { + $result | Should -Be $true -Because "full scan of mapped drives should be disabled for performance optimization" + } + } + + It "MDE.AV09: Scanning Network Files should be allowed. See https://maester.dev/docs/tests/MDE.AV09" -Tag "MDE.AV09" { + <# + Verify that scanning network files is enabled despite SMB load considerations. + Category: Scan Engines | Severity: Medium + #> + $result = Invoke-MtMdeUnifiedTest -TestId "MDE.AV09" -TestName $____Pester.CurrentTest.ExpandedName + if ($null -ne $result) { + $result | Should -Be $true -Because "scanning network files should be enabled for comprehensive protection" + } + } + + # Performance Tests (MDE.AV10) + It "MDE.AV10: CPU Load Factor should be optimized (20-30%). See https://maester.dev/docs/tests/MDE.AV10" -Tag "MDE.AV10" { + <# + Verify that average CPU load factor is configured between 20-30%. + Category: Performance | Severity: Low + #> + $result = Invoke-MtMdeUnifiedTest -TestId "MDE.AV10" -TestName $____Pester.CurrentTest.ExpandedName + if ($null -ne $result) { + $result | Should -Be $true -Because "CPU load should be balanced between performance and security" + } + } + + # Scheduling Tests (MDE.AV11 to MDE.AV12) + It "MDE.AV11: Scan should be scheduled every day. See https://maester.dev/docs/tests/MDE.AV11" -Tag "MDE.AV11" { + <# + Verify that scans are scheduled for every day during off-hours. + Category: Scheduling | Severity: Medium + #> + $result = Invoke-MtMdeUnifiedTest -TestId "MDE.AV11" -TestName $____Pester.CurrentTest.ExpandedName + if ($null -ne $result) { + $result | Should -Be $true -Because "scans should be scheduled every day for comprehensive coverage" + } + } + + It "MDE.AV12: Quick Scan Time configuration should not be required. See https://maester.dev/docs/tests/MDE.AV12" -Tag "MDE.AV12" { + <# + Verify that quick scan time is not configured (not required). + Category: Scheduling | Severity: Low + #> + $result = Invoke-MtMdeUnifiedTest -TestId "MDE.AV12" -TestName $____Pester.CurrentTest.ExpandedName + if ($null -ne $result) { + $result | Should -Be $true -Because "quick scan time configuration is not required" + } + } + + # Signature & Cloud Tests (MDE.AV13 to MDE.AV16) + It "MDE.AV13: Signatures should be checked before scan. See https://maester.dev/docs/tests/MDE.AV13" -Tag "MDE.AV13" { + <# + Verify that signature checking before scan is enabled for zero-day protection. + Category: Signature & Cloud | Severity: High + #> + $result = Invoke-MtMdeUnifiedTest -TestId "MDE.AV13" -TestName $____Pester.CurrentTest.ExpandedName + if ($null -ne $result) { + $result | Should -Be $true -Because "signatures should be checked before scan for zero-day protection" + } + } + + It "MDE.AV14: Cloud Block Level should be High or higher. See https://maester.dev/docs/tests/MDE.AV14" -Tag "MDE.AV14" { + <# + Verify that cloud block level is set to High, High+, or Zero Tolerance. + Category: Signature & Cloud | Severity: High + #> + $result = Invoke-MtMdeUnifiedTest -TestId "MDE.AV14" -TestName $____Pester.CurrentTest.ExpandedName + if ($null -ne $result) { + $result | Should -Be $true -Because "cloud block level should be High or higher for maximum protection" + } + } + + It "MDE.AV15: Cloud Extended Timeout should be 30-50 seconds. See https://maester.dev/docs/tests/MDE.AV15" -Tag "MDE.AV15" { + <# + Verify that cloud extended timeout is configured between 30-50 seconds (UX vs Detection balance). + Category: Signature & Cloud | Severity: Medium + #> + $result = Invoke-MtMdeUnifiedTest -TestId "MDE.AV15" -TestName $____Pester.CurrentTest.ExpandedName + if ($null -ne $result) { + $result | Should -Be $true -Because "cloud extended timeout should be 30-50 seconds for optimal UX and detection" + } + } + + It "MDE.AV16: Signature Update Interval should be 1-4 hours. See https://maester.dev/docs/tests/MDE.AV16" -Tag "MDE.AV16" { + <# + Verify that signature update interval is configured between 1-4 hours considering bandwidth. + Category: Signature & Cloud | Severity: High + #> + $result = Invoke-MtMdeUnifiedTest -TestId "MDE.AV16" -TestName $____Pester.CurrentTest.ExpandedName + if ($null -ne $result) { + $result | Should -Be $true -Because "signature update interval should be 1-4 hours for current protection" + } + } + + # Protection Tests (MDE.AV17 to MDE.AV21) + It "MDE.AV17: PUA Protection should be enabled to block potentially unwanted applications. See https://maester.dev/docs/tests/MDE.AV17" -Tag "MDE.AV17"{ + <# + Verify that PUA (Potentially Unwanted Applications) protection is enabled to block Shadow IT. + Category: Protection | Severity: High + #> + $result = Invoke-MtMdeUnifiedTest -TestId "MDE.AV17" -TestName $____Pester.CurrentTest.ExpandedName + if ($null -ne $result) { + $result | Should -Be $true -Because "PUA protection should be enabled to block potentially unwanted applications" + } + } + + It "MDE.AV18: Network Protection should be enabled in block mode. See https://maester.dev/docs/tests/MDE.AV18" -Tag "MDE.AV18" { + <# + Verify that Network Protection is enabled in block mode. + Category: Protection | Severity: High + #> + $result = Invoke-MtMdeUnifiedTest -TestId "MDE.AV18" -TestName $____Pester.CurrentTest.ExpandedName + if ($null -ne $result) { + $result | Should -Be $true -Because "network protection should be enabled in block mode" + } + } + + It "MDE.AV19: Local Admin Merge should be disabled. See https://maester.dev/docs/tests/MDE.AV19" -Tag "MDE.AV19" { + <# + Verify that local admin merge is disabled to block local exclusions. + Category: Protection | Severity: Critical + #> + $result = Invoke-MtMdeUnifiedTest -TestId "MDE.AV19" -TestName $____Pester.CurrentTest.ExpandedName + if ($null -ne $result) { + $result | Should -Be $true -Because "local admin merge should be disabled to prevent local exclusions" + } + } + + It "MDE.AV20: Tamper Protection should be enabled tenant-wide. See https://maester.dev/docs/tests/MDE.AV20" -Tag "MDE.AV20" { + <# + Verify that Tamper Protection is enabled tenant-wide. + Category: Protection | Severity: Critical + #> + $result = Invoke-MtMdeUnifiedTest -TestId "MDE.AV20" -TestName $____Pester.CurrentTest.ExpandedName + if ($null -ne $result) { + if ($result.IsSkipped) { + # Add test details for skipped tests to ensure they appear in HTML reports + Add-MtTestResultDetail -Description $result.TestDetails -GraphObjectType 'Devices' -Severity $result.Severity + Set-ItResult -Skipped -Because $result.SkipReason + } else { + $result | Should -Be $true -Because "tamper protection should be enabled tenant-wide" + } + } + } + + It "MDE.AV21: Real-Time Scan Direction should be configured for both directions. See https://maester.dev/docs/tests/MDE.AV21" -Tag "MDE.AV21" { + <# + Verify that real-time scan direction is set to both incoming and outgoing. + Category: Protection | Severity: Medium + #> + $result = Invoke-MtMdeUnifiedTest -TestId "MDE.AV21" -TestName $____Pester.CurrentTest.ExpandedName + if ($null -ne $result) { + $result | Should -Be $true -Because "real-time scan should monitor both incoming and outgoing traffic" + } + } + + # Cleanup & Quarantine Tests (MDE.AV22 to MDE.AV26) + It "MDE.AV22: Cleaned Malware should be retained for 90 days. See https://maester.dev/docs/tests/MDE.AV22" -Tag "MDE.AV22"{ + <# + Verify that cleaned malware is retained for 90 days for audit purposes. + Category: Cleanup & Quarantine | Severity: Medium + #> + $result = Invoke-MtMdeUnifiedTest -TestId "MDE.AV22" -TestName $____Pester.CurrentTest.ExpandedName + if ($null -ne $result) { + $result | Should -Be $true -Because "cleaned malware should be retained for 90 days for audit purposes" + } + } + + It "MDE.AV23: Catch-up Full Scan should be disabled. See https://maester.dev/docs/tests/MDE.AV23" -Tag "MDE.AV23" { + <# + Verify that catch-up full scan is disabled to avoid additional load. + Category: Cleanup & Quarantine | Severity: Low + #> + $result = Invoke-MtMdeUnifiedTest -TestId "MDE.AV23" -TestName $____Pester.CurrentTest.ExpandedName + if ($null -ne $result) { + $result | Should -Be $true -Because "catch-up full scan should be disabled to avoid additional system load" + } + } + + It "MDE.AV24: Catch-up Quick Scan should be disabled. See https://maester.dev/docs/tests/MDE.AV24" -Tag "MDE.AV24" { + <# + Verify that catch-up quick scan is disabled. + Category: Cleanup & Quarantine | Severity: Low + #> + $result = Invoke-MtMdeUnifiedTest -TestId "MDE.AV24" -TestName $____Pester.CurrentTest.ExpandedName + if ($null -ne $result) { + $result | Should -Be $true -Because "catch-up quick scan should be disabled" + } + } + + It "MDE.AV25: Remediation Action should be set to Quarantine. See https://maester.dev/docs/tests/MDE.AV25" -Tag "MDE.AV25" { + <# + Verify that remediation action for all threat levels is set to Quarantine. + Category: Cleanup & Quarantine | Severity: High + #> + $result = Invoke-MtMdeUnifiedTest -TestId "MDE.AV25" -TestName $____Pester.CurrentTest.ExpandedName + if ($null -ne $result) { + if ($result.IsSkipped) { + # Add test details for skipped tests to ensure they appear in HTML reports + Add-MtTestResultDetail -Description $result.TestDetails -GraphObjectType 'Devices' -Severity $result.Severity + Set-ItResult -Skipped -Because $result.SkipReason + } else { + $result | Should -Be $true -Because "remediation action should be consistently set to quarantine for all threat levels" + } + } + } + + It "MDE.AV26: Sample Submission should be configured to send safe samples automatically. See https://maester.dev/docs/tests/MDE.AV26" -Tag "MDE.AV26" { + <# + Verify that sample submission consent is configured to send safe samples automatically. + Category: Cleanup & Quarantine | Severity: Medium + #> + $result = Invoke-MtMdeUnifiedTest -TestId "MDE.AV26" -TestName $____Pester.CurrentTest.ExpandedName + if ($null -ne $result) { + $result | Should -Be $true -Because "sample submission should be configured to send safe samples automatically" + } + } +} \ No newline at end of file diff --git a/tests/Maester/Defender/Test-MtMdeGlobalConfiguration.Tests.ps1 b/tests/Maester/Defender/Test-MtMdeGlobalConfiguration.Tests.ps1 new file mode 100644 index 000000000..2ab3106c6 --- /dev/null +++ b/tests/Maester/Defender/Test-MtMdeGlobalConfiguration.Tests.ps1 @@ -0,0 +1,355 @@ +Describe "Microsoft Defender for Endpoint - Global Configuration" -Tag "Maester", "MDE", "Security", "All", "MDE-GlobalConfig", "ManualReview" { + + # Global Configuration Tests (MDE.GC01 to MDE.GC16) - Using Unified Test Engine + It "MDE.GC01: Preview Features should be enabled organization-wide. See https://maester.dev/docs/tests/MDE.GC01" -Tag "MDE.GC01", "ManualReview" { + <# + Verify that Preview Features are enabled organization-wide in Microsoft Defender XDR. + Category: Global Config | Severity: Low 🟒 + + Manual Review Required: + - Check Preview Features setting in Microsoft Defender XDR portal + - Navigate to Settings β†’ Microsoft Defender XDR β†’ Preview Features + - Verify organization-wide activation + #> + $result = Invoke-MtMdeUnifiedTest -TestId "MDE.GC01" -TestName $____Pester.CurrentTest.ExpandedName + if ($null -ne $result) { + if ($result.IsSkipped) { + # Add test details for skipped tests to ensure they appear in HTML reports + Add-MtTestResultDetail -Description $result.TestDetails -GraphObjectType 'Devices' -Severity $result.Severity + Set-ItResult -Skipped -Because $result.SkipReason + } else { + $result | Should -Be $true -Because "preview features should be enabled organization-wide" + } + } + } + + It "MDE.GC02: Tamper Protection should be enabled tenant-wide. See https://maester.dev/docs/tests/MDE.GC02" -Tag "MDE.GC02", "ManualReview" { + <# + Verify that Tamper Protection is enabled tenant-wide in Advanced Features. + Category: Global Config | Severity: High 🟠 + + Manual Review Required: + - Check Tamper Protection setting in Microsoft Defender XDR portal + - Navigate to Settings β†’ Endpoints β†’ Advanced Features + - Verify tenant-wide activation + #> + $result = Invoke-MtMdeUnifiedTest -TestId "MDE.GC02" -TestName $____Pester.CurrentTest.ExpandedName + if ($null -ne $result) { + if ($result.IsSkipped) { + # Add test details for skipped tests to ensure they appear in HTML reports + Add-MtTestResultDetail -Description $result.TestDetails -GraphObjectType 'Devices' -Severity $result.Severity + Set-ItResult -Skipped -Because $result.SkipReason + } else { + $result | Should -Be $true -Because "tamper protection should be enabled tenant-wide" + } + } + } + + It "MDE.GC03: EDR in Block Mode should be enabled for Defender AV devices. See https://maester.dev/docs/tests/MDE.GC03" -Tag "MDE.GC03", "ManualReview" { + <# + Verify that EDR in Block Mode is enabled for Microsoft Defender Antivirus devices. + Category: Global Config | Severity: High 🟠 + + Manual Review Required: + - Check EDR in Block Mode setting in Microsoft Defender XDR portal + - Navigate to Settings β†’ Endpoints β†’ Advanced Features + - Verify activation for Defender AV devices only + #> + $result = Invoke-MtMdeUnifiedTest -TestId "MDE.GC03" -TestName $____Pester.CurrentTest.ExpandedName + if ($null -ne $result) { + if ($result.IsSkipped) { + # Add test details for skipped tests to ensure they appear in HTML reports + Add-MtTestResultDetail -Description $result.TestDetails -GraphObjectType 'Devices' -Severity $result.Severity + Set-ItResult -Skipped -Because $result.SkipReason + } else { + $result | Should -Be $true -Because "EDR in block mode should be enabled for Defender AV devices" + } + } + } + + It "MDE.GC04: Automatically Resolve Alerts should be configured. See https://maester.dev/docs/tests/MDE.GC04" -Tag "MDE.GC04", "ManualReview" { + <# + Verify that Automatically Resolve Alerts is properly configured. + Category: Global Config | Severity: Medium 🟑 + + Manual Review Required: + - Check auto-resolution settings in Microsoft Defender XDR portal + - Navigate to Settings β†’ Endpoints β†’ Advanced Features + - Verify auto-resolution is active for appropriate alert types + #> + $result = Invoke-MtMdeUnifiedTest -TestId "MDE.GC04" -TestName $____Pester.CurrentTest.ExpandedName + if ($null -ne $result) { + if ($result.IsSkipped) { + # Add test details for skipped tests to ensure they appear in HTML reports + Add-MtTestResultDetail -Description $result.TestDetails -GraphObjectType 'Devices' -Severity $result.Severity + Set-ItResult -Skipped -Because $result.SkipReason + } else { + $result | Should -Be $true -Because "automatically resolve alerts should be configured" + } + } + } + + It "MDE.GC05: Allow or Block File capability should be enabled. See https://maester.dev/docs/tests/MDE.GC05" -Tag "MDE.GC05", "ManualReview" { + <# + Verify that Allow or Block File capability is enabled for IOC handling. + Category: Global Config | Severity: Medium 🟑 + + Manual Review Required: + - Check Allow or Block File setting in Microsoft Defender XDR portal + - Navigate to Settings β†’ Endpoints β†’ Advanced Features + - Verify IOC handling capabilities are enabled + #> + $result = Invoke-MtMdeUnifiedTest -TestId "MDE.GC05" -TestName $____Pester.CurrentTest.ExpandedName + if ($null -ne $result) { + if ($result.IsSkipped) { + # Add test details for skipped tests to ensure they appear in HTML reports + Add-MtTestResultDetail -Description $result.TestDetails -GraphObjectType 'Devices' -Severity $result.Severity + Set-ItResult -Skipped -Because $result.SkipReason + } else { + $result | Should -Be $true -Because "allow or block file capability should be enabled" + } + } + } + + It "MDE.GC06: Hide Duplicate Device Records should be enabled. See https://maester.dev/docs/tests/MDE.GC06" -Tag "MDE.GC06", "ManualReview" { + <# + Verify that Hide Duplicate Device Records is enabled to reduce clutter. + Category: Global Config | Severity: Low 🟒 + + Manual Review Required: + - Check duplicate device handling in Microsoft Defender XDR portal + - Navigate to Settings β†’ Endpoints β†’ Advanced Features + - Verify duplicate records are hidden to avoid clutter + #> + $result = Invoke-MtMdeUnifiedTest -TestId "MDE.GC06" -TestName $____Pester.CurrentTest.ExpandedName + if ($null -ne $result) { + if ($result.IsSkipped) { + # Add test details for skipped tests to ensure they appear in HTML reports + Add-MtTestResultDetail -Description $result.TestDetails -GraphObjectType 'Devices' -Severity $result.Severity + Set-ItResult -Skipped -Because $result.SkipReason + } else { + $result | Should -Be $true -Because "hide duplicate device records should be enabled" + } + } + } + + It "MDE.GC07: Custom Network Indicators should be enabled. See https://maester.dev/docs/tests/MDE.GC07" -Tag "MDE.GC07", "ManualReview" { + <# + Verify that Custom Network Indicators are enabled for IOC management. + Category: Global Config | Severity: High 🟠 + + Manual Review Required: + - Check Custom Network Indicators in Microsoft Defender XDR portal + - Navigate to Settings β†’ Endpoints β†’ Advanced Features + - Verify IOC list management capabilities are enabled + #> + $result = Invoke-MtMdeUnifiedTest -TestId "MDE.GC07" -TestName $____Pester.CurrentTest.ExpandedName + if ($null -ne $result) { + if ($result.IsSkipped) { + # Add test details for skipped tests to ensure they appear in HTML reports + Add-MtTestResultDetail -Description $result.TestDetails -GraphObjectType 'Devices' -Severity $result.Severity + Set-ItResult -Skipped -Because $result.SkipReason + } else { + $result | Should -Be $true -Because "custom network indicators should be enabled" + } + } + } + + It "MDE.GC08: Web Content Filtering should be enabled (requires P2 license). See https://maester.dev/docs/tests/MDE.GC08" -Tag "MDE.GC08", "ManualReview" { + <# + Verify that Web Content Filtering is enabled (requires Defender for Endpoint P2 license). + Category: Global Config | Severity: High 🟠 + + Manual Review Required: + - Check Web Content Filtering in Microsoft Defender XDR portal + - Navigate to Settings β†’ Endpoints β†’ Advanced Features + - Verify P2 license is available and feature is enabled + #> + $result = Invoke-MtMdeUnifiedTest -TestId "MDE.GC08" -TestName $____Pester.CurrentTest.ExpandedName + if ($null -ne $result) { + if ($result.IsSkipped) { + # Add test details for skipped tests to ensure they appear in HTML reports + Add-MtTestResultDetail -Description $result.TestDetails -GraphObjectType 'Devices' -Severity $result.Severity + Set-ItResult -Skipped -Because $result.SkipReason + } else { + $result | Should -Be $true -Because "web content filtering should be enabled (requires P2 license)" + } + } + } + + It "MDE.GC09: Device Discovery should be enabled for Shadow IT visibility. See https://maester.dev/docs/tests/MDE.GC09" -Tag "MDE.GC09", "ManualReview" { + <# + Verify that Device Discovery is enabled for Shadow IT visibility. + Category: Global Config | Severity: Medium 🟑 + + Manual Review Required: + - Check Device Discovery settings in Microsoft Defender XDR portal + - Navigate to Settings β†’ Endpoints β†’ Advanced Features + - Verify Shadow IT visibility capabilities are enabled + #> + $result = Invoke-MtMdeUnifiedTest -TestId "MDE.GC09" -TestName $____Pester.CurrentTest.ExpandedName + if ($null -ne $result) { + if ($result.IsSkipped) { + # Add test details for skipped tests to ensure they appear in HTML reports + Add-MtTestResultDetail -Description $result.TestDetails -GraphObjectType 'Devices' -Severity $result.Severity + Set-ItResult -Skipped -Because $result.SkipReason + } else { + $result | Should -Be $true -Because "device discovery should be enabled for Shadow IT visibility" + } + } + } + + It "MDE.GC10: Download Quarantined Files should be enabled for forensics. See https://maester.dev/docs/tests/MDE.GC10" -Tag "MDE.GC10", "ManualReview" { + <# + Verify that Download Quarantined Files capability is enabled for forensic analysis. + Category: Global Config | Severity: Medium 🟑 + + Manual Review Required: + - Check quarantined file download in Microsoft Defender XDR portal + - Navigate to Settings β†’ Endpoints β†’ Advanced Features + - Verify forensic capabilities are enabled + #> + $result = Invoke-MtMdeUnifiedTest -TestId "MDE.GC10" -TestName $____Pester.CurrentTest.ExpandedName + if ($null -ne $result) { + if ($result.IsSkipped) { + # Add test details for skipped tests to ensure they appear in HTML reports + Add-MtTestResultDetail -Description $result.TestDetails -GraphObjectType 'Devices' -Severity $result.Severity + Set-ItResult -Skipped -Because $result.SkipReason + } else { + $result | Should -Be $true -Because "download quarantined files should be enabled for forensics" + } + } + } + + It "MDE.GC11: Streamlined Connectivity should be enabled (default). See https://maester.dev/docs/tests/MDE.GC11" -Tag "MDE.GC11", "ManualReview" { + <# + Verify that Streamlined Connectivity is enabled as default configuration. + Category: Global Config | Severity: Medium 🟑 + + Manual Review Required: + - Check Streamlined Connectivity in Microsoft Defender XDR portal + - Navigate to Settings β†’ Endpoints β†’ Advanced Features + - Verify default connectivity settings are properly configured + #> + $result = Invoke-MtMdeUnifiedTest -TestId "MDE.GC11" -TestName $____Pester.CurrentTest.ExpandedName + if ($null -ne $result) { + if ($result.IsSkipped) { + # Add test details for skipped tests to ensure they appear in HTML reports + Add-MtTestResultDetail -Description $result.TestDetails -GraphObjectType 'Devices' -Severity $result.Severity + Set-ItResult -Skipped -Because $result.SkipReason + } else { + $result | Should -Be $true -Because "streamlined connectivity should be enabled (default)" + } + } + } + + It "MDE.GC12: Apply Streamlined Connectivity to Intune/DFC should be enabled. See https://maester.dev/docs/tests/MDE.GC12" -Tag "MDE.GC12", "ManualReview" { + <# + Verify that Streamlined Connectivity is applied to Intune/DFC for synchronization. + Category: Global Config | Severity: Medium 🟑 + + Manual Review Required: + - Check Intune integration settings in Microsoft Defender XDR portal + - Navigate to Settings β†’ Endpoints β†’ Advanced Features + - Verify Intune-sync connectivity is properly configured + #> + $result = Invoke-MtMdeUnifiedTest -TestId "MDE.GC12" -TestName $____Pester.CurrentTest.ExpandedName + if ($null -ne $result) { + if ($result.IsSkipped) { + # Add test details for skipped tests to ensure they appear in HTML reports + Add-MtTestResultDetail -Description $result.TestDetails -GraphObjectType 'Devices' -Severity $result.Severity + Set-ItResult -Skipped -Because $result.SkipReason + } else { + $result | Should -Be $true -Because "streamlined connectivity to Intune/DFC should be enabled" + } + } + } + + It "MDE.GC13: Isolation Exclusion Rules should be disabled unless required. See https://maester.dev/docs/tests/MDE.GC13" -Tag "MDE.GC13", "ManualReview" { + <# + Verify that Isolation Exclusion Rules are disabled unless specifically required. + Category: Global Config | Severity: High 🟠 + + Manual Review Required: + - Check Isolation Exclusion Rules in Microsoft Defender XDR portal + - Navigate to Settings β†’ Endpoints β†’ Advanced Features + - Verify rules are disabled unless business justification exists + #> + $result = Invoke-MtMdeUnifiedTest -TestId "MDE.GC13" -TestName $____Pester.CurrentTest.ExpandedName + if ($null -ne $result) { + if ($result.IsSkipped) { + # Add test details for skipped tests to ensure they appear in HTML reports + Add-MtTestResultDetail -Description $result.TestDetails -GraphObjectType 'Devices' -Severity $result.Severity + Set-ItResult -Skipped -Because $result.SkipReason + } else { + $result | Should -Be $true -Because "isolation exclusion rules should be disabled unless required" + } + } + } + + It "MDE.GC14: Deception capabilities should be evaluated (optional honeypots). See https://maester.dev/docs/tests/MDE.GC14" -Tag "MDE.GC14", "ManualReview" { + <# + Verify that Deception capabilities are properly evaluated (optional honeypot deployment). + Category: Global Config | Severity: Low 🟒 + + Manual Review Required: + - Check Deception settings in Microsoft Defender XDR portal + - Navigate to Settings β†’ Endpoints β†’ Advanced Features + - Evaluate if honeypot deployment aligns with security strategy + #> + $result = Invoke-MtMdeUnifiedTest -TestId "MDE.GC14" -TestName $____Pester.CurrentTest.ExpandedName + if ($null -ne $result) { + if ($result.IsSkipped) { + # Add test details for skipped tests to ensure they appear in HTML reports + Add-MtTestResultDetail -Description $result.TestDetails -GraphObjectType 'Devices' -Severity $result.Severity + Set-ItResult -Skipped -Because $result.SkipReason + } else { + $result | Should -Be $true -Because "deception capabilities should be evaluated (optional honeypots)" + } + } + } + + It "MDE.GC15: Microsoft Intune Connection should be enabled as MDM prerequisite. See https://maester.dev/docs/tests/MDE.GC15" -Tag "MDE.GC15", "ManualReview" { + <# + Verify that Microsoft Intune Connection is enabled as prerequisite for MDM integration. + Category: Global Config | Severity: Medium 🟑 + + Manual Review Required: + - Check Microsoft Intune Connection in Microsoft Defender XDR portal + - Navigate to Settings β†’ Endpoints β†’ Advanced Features + - Verify MDM integration prerequisites are met + #> + $result = Invoke-MtMdeUnifiedTest -TestId "MDE.GC15" -TestName $____Pester.CurrentTest.ExpandedName + if ($null -ne $result) { + if ($result.IsSkipped) { + # Add test details for skipped tests to ensure they appear in HTML reports + Add-MtTestResultDetail -Description $result.TestDetails -GraphObjectType 'Devices' -Severity $result.Severity + Set-ItResult -Skipped -Because $result.SkipReason + } else { + $result | Should -Be $true -Because "Microsoft Intune connection should be enabled as MDM prerequisite" + } + } + } + + It "MDE.GC16: Authenticated Telemetry should be reviewed for privacy compliance. See https://maester.dev/docs/tests/MDE.GC16" -Tag "MDE.GC16", "ManualReview" { + <# + Verify that Authenticated Telemetry settings comply with privacy requirements. + Category: Global Config | Severity: Low 🟒 + + Manual Review Required: + - Check Authenticated Telemetry in Microsoft Defender XDR portal + - Navigate to Settings β†’ Endpoints β†’ Advanced Features + - Review privacy and data protection compliance + #> + $result = Invoke-MtMdeUnifiedTest -TestId "MDE.GC16" -TestName $____Pester.CurrentTest.ExpandedName + if ($null -ne $result) { + if ($result.IsSkipped) { + # Add test details for skipped tests to ensure they appear in HTML reports + Add-MtTestResultDetail -Description $result.TestDetails -GraphObjectType 'Devices' -Severity $result.Severity + Set-ItResult -Skipped -Because $result.SkipReason + } else { + $result | Should -Be $true -Because "authenticated telemetry should be reviewed for privacy compliance" + } + } + } +} \ No newline at end of file diff --git a/tests/Maester/Defender/Test-MtMdePolicyDesign.Tests.ps1 b/tests/Maester/Defender/Test-MtMdePolicyDesign.Tests.ps1 new file mode 100644 index 000000000..5be56ff2a --- /dev/null +++ b/tests/Maester/Defender/Test-MtMdePolicyDesign.Tests.ps1 @@ -0,0 +1,91 @@ +Describe "Microsoft Defender for Endpoint - Policy Design Quality" -Tag "Maester", "MDE", "Security", "All", "MDE-PolicyDesign", "ManualReview" { + + # Policy Design Quality Tests (MDE.PD01 to MDE.PD04) - Using Unified Test Engine + It "MDE.PD01: Policy naming should follow consistent convention (ROLE-v#). See https://maester.dev/docs/tests/MDE.PD01" -Tag "MDE.PD01" { + <# + Verify that policy naming follows consistent convention (e.g., AV-PL-Client-Gen-v1). + Category: Policy Design | Severity: Low + + Manual Review Required: + - Check all MDE policy names in Microsoft Endpoint Manager + - Verify naming follows organizational standard (ROLE-v#) + - Example: AV-PL-Client-Gen-v1, AV-EX-Server-DB-v2 + #> + $result = Invoke-MtMdeUnifiedTest -TestId "MDE.PD01" -TestName $____Pester.CurrentTest.ExpandedName + if ($null -ne $result) { + if ($result.IsSkipped) { + # Add test details for skipped tests to ensure they appear in HTML reports + Add-MtTestResultDetail -Description $result.TestDetails -GraphObjectType 'Devices' -Severity $result.Severity + Set-ItResult -Skipped -Because $result.SkipReason + } else { + $result | Should -Be $true -Because "policy naming should follow consistent convention" + } + } + } + + It "MDE.PD02: Exclusions should be in dedicated profiles. See https://maester.dev/docs/tests/MDE.PD02" -Tag "MDE.PD02" { + <# + Verify that exclusions are configured in dedicated profiles to reduce baseline complexity. + Category: Policy Design | Severity: Medium + + Manual Review Required: + - Review MDE exclusion policies in Microsoft Endpoint Manager + - Verify exclusions are separated from baseline antivirus policies + - Check that baseline policies are kept clean and focused + #> + $result = Invoke-MtMdeUnifiedTest -TestId "MDE.PD02" -TestName $____Pester.CurrentTest.ExpandedName + if ($null -ne $result) { + if ($result.IsSkipped) { + # Add test details for skipped tests to ensure they appear in HTML reports + Add-MtTestResultDetail -Description $result.TestDetails -GraphObjectType 'Devices' -Severity $result.Severity + Set-ItResult -Skipped -Because $result.SkipReason + } else { + $result | Should -Be $true -Because "exclusions should be configured in dedicated profiles" + } + } + } + + It "MDE.PD03: Device profiles should be granular (Least Privilege). See https://maester.dev/docs/tests/MDE.PD03" -Tag "MDE.PD03" { + <# + Verify that device profiles are granular and follow least privilege principle. + Category: Policy Design | Severity: Medium + + Manual Review Required: + - Review device group assignments in Microsoft Endpoint Manager + - Verify policies are targeted to specific device types/roles + - Check that policies follow least privilege access patterns + #> + $result = Invoke-MtMdeUnifiedTest -TestId "MDE.PD03" -TestName $____Pester.CurrentTest.ExpandedName + if ($null -ne $result) { + if ($result.IsSkipped) { + # Add test details for skipped tests to ensure they appear in HTML reports + Add-MtTestResultDetail -Description $result.TestDetails -GraphObjectType 'Devices' -Severity $result.Severity + Set-ItResult -Skipped -Because $result.SkipReason + } else { + $result | Should -Be $true -Because "device profiles should be granular and follow least privilege principle" + } + } + } + + It "MDE.PD04: Staging buckets should be implemented (Pilot β†’ Prod). See https://maester.dev/docs/tests/MDE.PD04" -Tag "MDE.PD04" { + <# + Verify that staging deployment buckets are implemented (e.g., DG-CL-GEN-PILOT β†’ PROD). + Category: Policy Design | Severity: Medium + + Manual Review Required: + - Review device group structure in Microsoft Endpoint Manager + - Verify pilot groups exist (e.g., DG-CL-GEN-PILOT) + - Check staged rollout process is documented and followed + #> + $result = Invoke-MtMdeUnifiedTest -TestId "MDE.PD04" -TestName $____Pester.CurrentTest.ExpandedName + if ($null -ne $result) { + if ($result.IsSkipped) { + # Add test details for skipped tests to ensure they appear in HTML reports + Add-MtTestResultDetail -Description $result.TestDetails -GraphObjectType 'Devices' -Severity $result.Severity + Set-ItResult -Skipped -Because $result.SkipReason + } else { + $result | Should -Be $true -Because "staging deployment buckets should be implemented" + } + } + } +} \ No newline at end of file diff --git a/tests/Maester/Defender/defender-config.json b/tests/Maester/Defender/defender-config.json new file mode 100644 index 000000000..465f6b5bb --- /dev/null +++ b/tests/Maester/Defender/defender-config.json @@ -0,0 +1,14 @@ +{ + "ComplianceLogic": "AllPolicies", + "PolicyFiltering": "OnlyAssigned", + "DeviceFiltering": { + "OperatingSystems": ["Windows"], + "ManagementAgents": ["msSense", "mdm"], + "ComplianceStates": ["Compliant", "NonCompliant", "Unknown"] + }, + "TestSpecific": { + "MDE.AV01": { + "ComplianceLogic": "AllPolicies" + } + } +} \ No newline at end of file From f9eae310d4228d65607368634a624e726f7b718d Mon Sep 17 00:00:00 2001 From: Boris Drogja Date: Wed, 9 Jul 2025 16:27:02 +0200 Subject: [PATCH 2/5] Added "Defender" Tag to all Defender Tests --- tests/Maester/Defender/Test-MtMdeAntivirusPolicy.Tests.ps1 | 2 +- tests/Maester/Defender/Test-MtMdeGlobalConfiguration.Tests.ps1 | 2 +- tests/Maester/Defender/Test-MtMdePolicyDesign.Tests.ps1 | 2 +- tests/Maester/Defender/Test-MtMdiHealthIssues.Tests.ps1 | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/Maester/Defender/Test-MtMdeAntivirusPolicy.Tests.ps1 b/tests/Maester/Defender/Test-MtMdeAntivirusPolicy.Tests.ps1 index d708bab8d..375032c30 100644 --- a/tests/Maester/Defender/Test-MtMdeAntivirusPolicy.Tests.ps1 +++ b/tests/Maester/Defender/Test-MtMdeAntivirusPolicy.Tests.ps1 @@ -1,4 +1,4 @@ -Describe "Microsoft Defender Antivirus - Policy Compliance" -Tag "Maester", "MDE", "Security", "All", "MDE-Antivirus", "Automated" { +Describe "Microsoft Defender Antivirus - Policy Compliance" -Tag "Maester", "MDE", "Security", "All", "MDE-Antivirus", "Defender", "Automated" { # Scan Engines Tests (MDE.AV01 to MDE.AV09) - Using Unified Test Engine It "MDE.AV01: Archive Scanning should be allowed. See https://maester.dev/docs/tests/MDE.AV01" -Tag "MDE.AV01" { diff --git a/tests/Maester/Defender/Test-MtMdeGlobalConfiguration.Tests.ps1 b/tests/Maester/Defender/Test-MtMdeGlobalConfiguration.Tests.ps1 index 2ab3106c6..2c1df0fc0 100644 --- a/tests/Maester/Defender/Test-MtMdeGlobalConfiguration.Tests.ps1 +++ b/tests/Maester/Defender/Test-MtMdeGlobalConfiguration.Tests.ps1 @@ -1,4 +1,4 @@ -Describe "Microsoft Defender for Endpoint - Global Configuration" -Tag "Maester", "MDE", "Security", "All", "MDE-GlobalConfig", "ManualReview" { +Describe "Microsoft Defender for Endpoint - Global Configuration" -Tag "Maester", "MDE", "Security", "All", "MDE-GlobalConfig", "Defender", "ManualReview" { # Global Configuration Tests (MDE.GC01 to MDE.GC16) - Using Unified Test Engine It "MDE.GC01: Preview Features should be enabled organization-wide. See https://maester.dev/docs/tests/MDE.GC01" -Tag "MDE.GC01", "ManualReview" { diff --git a/tests/Maester/Defender/Test-MtMdePolicyDesign.Tests.ps1 b/tests/Maester/Defender/Test-MtMdePolicyDesign.Tests.ps1 index 5be56ff2a..1b231c39a 100644 --- a/tests/Maester/Defender/Test-MtMdePolicyDesign.Tests.ps1 +++ b/tests/Maester/Defender/Test-MtMdePolicyDesign.Tests.ps1 @@ -1,4 +1,4 @@ -Describe "Microsoft Defender for Endpoint - Policy Design Quality" -Tag "Maester", "MDE", "Security", "All", "MDE-PolicyDesign", "ManualReview" { +Describe "Microsoft Defender for Endpoint - Policy Design Quality" -Tag "Maester", "MDE", "Security", "All", "MDE-PolicyDesign", "Defender", "ManualReview" { # Policy Design Quality Tests (MDE.PD01 to MDE.PD04) - Using Unified Test Engine It "MDE.PD01: Policy naming should follow consistent convention (ROLE-v#). See https://maester.dev/docs/tests/MDE.PD01" -Tag "MDE.PD01" { diff --git a/tests/Maester/Defender/Test-MtMdiHealthIssues.Tests.ps1 b/tests/Maester/Defender/Test-MtMdiHealthIssues.Tests.ps1 index 36e2b2427..e448f621d 100644 --- a/tests/Maester/Defender/Test-MtMdiHealthIssues.Tests.ps1 +++ b/tests/Maester/Defender/Test-MtMdiHealthIssues.Tests.ps1 @@ -40,7 +40,7 @@ BeforeDiscovery { $MdiHealthActiveIssues = $MdiHealthIssues | Where-Object { $_.status -ne "closed" } } -Describe "Defender for Identity health issues" -Tag "Maester", "Defender", "Security", "All", "MDI" -ForEach $MdiHealthActiveIssues { +Describe "Defender for Identity health issues" -Tag "Maester", "Defender", "Security", "All", "MDI", "Defender" -ForEach $MdiHealthActiveIssues { It "MT.1059: MDI Health Issues - . See https://maester.dev/docs/tests/MT.1058" -Tag "MT.1058", "Severity:Medium", $displayName { $issueUrl = "https://security.microsoft.com/identities/health-issues" From ebbbbfff9972510216badcb85c3ae029484f728c Mon Sep 17 00:00:00 2001 From: Boris Drogja Date: Wed, 9 Jul 2025 19:21:04 +0200 Subject: [PATCH 3/5] Unintentionally increased ModuleVersion Number, reverted --- powershell/Maester.psd1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/powershell/Maester.psd1 b/powershell/Maester.psd1 index f3c69dd20..3790f2b1d 100644 --- a/powershell/Maester.psd1 +++ b/powershell/Maester.psd1 @@ -12,7 +12,7 @@ RootModule = 'Maester.psm1' # Version number of this module. -ModuleVersion = '0.2.0' +ModuleVersion = '0.1.0' # Supported PSEditions CompatiblePSEditions = 'Core', 'Desktop' From 859f972b4112f8a058f9b26840fa5031368cafb2 Mon Sep 17 00:00:00 2001 From: Boris Drogja Date: Wed, 9 Jul 2025 19:33:35 +0200 Subject: [PATCH 4/5] Fixed for consistency reasons --- powershell/public/defender/MtMdeHelpers.ps1 | 45 ++--- .../Test-MtMdeAntivirusPolicy.Tests.ps1 | 168 +++++++++++++++--- 2 files changed, 157 insertions(+), 56 deletions(-) diff --git a/powershell/public/defender/MtMdeHelpers.ps1 b/powershell/public/defender/MtMdeHelpers.ps1 index 3168a6494..ff8b41a30 100644 --- a/powershell/public/defender/MtMdeHelpers.ps1 +++ b/powershell/public/defender/MtMdeHelpers.ps1 @@ -3,7 +3,7 @@ Helper functions for Microsoft Defender for Endpoint tests .DESCRIPTION - Contains functions for device management, policy retrieval, and the unified test engine + Contains functions for device management, policy retrieval, and the unified test engine used by all MDE tests in the Maester framework. .NOTES @@ -19,7 +19,7 @@ Gets information about your organization's Defender-protected devices and their policies .DESCRIPTION - Retrieves device inventory, configuration policies, and compliance information + Retrieves device inventory, configuration policies, and compliance information from Microsoft Graph API for use in MDE tests. .PARAMETER DisableCache @@ -107,7 +107,7 @@ function Get-MtMdeConfiguration { .EXAMPLE Get-MtMdeDeviceCount - + Returns the number of MDE-protected devices #> function Get-MtMdeDeviceCount { @@ -274,7 +274,7 @@ function Test-MtMdePolicyHasAssignments { Gets Microsoft Defender Antivirus policies that are assigned to devices .DESCRIPTION - Retrieves configuration policies from Microsoft Graph, filters for + Retrieves configuration policies from Microsoft Graph, filters for Defender Antivirus policies, and checks which ones are actually assigned. .OUTPUTS @@ -370,7 +370,9 @@ function Get-MdePolicyConfiguration { } } } +#endregion +#region Policy Compliance Functions <# .SYNOPSIS Tests policy compliance for MDE settings @@ -454,32 +456,6 @@ function Test-MdePolicyCompliance { } } -<# -.SYNOPSIS - Formats MDE test results into markdown report - -.DESCRIPTION - Generates comprehensive markdown report for MDE test results including - policy analysis, compliance status, and remediation guidance. - -.PARAMETER SettingConfig - Setting configuration object - -.PARAMETER ComplianceResults - Compliance analysis results - -.PARAMETER DeviceCount - Number of MDE devices - -.PARAMETER PolicyConfiguration - Policy configuration details - -.PARAMETER TestResult - Overall test result (pass/fail) - -.OUTPUTS - String containing formatted markdown report -#> <# .SYNOPSIS @@ -591,7 +567,9 @@ function Test-MdeSettingCompliance { } } } +#endregion +#region Configuration Setting Value parser <# .SYNOPSIS Extracts setting values from Graph API responses @@ -670,7 +648,6 @@ function Get-MdeSettingValue { } } } - #endregion @@ -863,13 +840,16 @@ $( if (-not $testResult) { # Don't treat skip operations as errors return $null } - + # Only treat actual errors as errors Add-MtTestResultDetail -SkippedBecause Error -SkippedError $_ return $null } } +#endregion + +#region Manual Verification Markdown Generation <# .SYNOPSIS Generates manual verification markdown for all test types @@ -960,3 +940,4 @@ This test requires manual review and cannot be automated. Please perform the rev "@ } +#endregion \ No newline at end of file diff --git a/tests/Maester/Defender/Test-MtMdeAntivirusPolicy.Tests.ps1 b/tests/Maester/Defender/Test-MtMdeAntivirusPolicy.Tests.ps1 index 375032c30..5233a00b0 100644 --- a/tests/Maester/Defender/Test-MtMdeAntivirusPolicy.Tests.ps1 +++ b/tests/Maester/Defender/Test-MtMdeAntivirusPolicy.Tests.ps1 @@ -8,7 +8,12 @@ Describe "Microsoft Defender Antivirus - Policy Compliance" -Tag "Maester", "MDE #> $result = Invoke-MtMdeUnifiedTest -TestId "MDE.AV01" -TestName $____Pester.CurrentTest.ExpandedName if ($null -ne $result) { - $result | Should -Be $true -Because "archive scanning helps detect malware hidden in compressed files" + if ($result.IsSkipped) { + Add-MtTestResultDetail -Description $result.TestDetails -GraphObjectType 'Devices' -Severity $result.Severity + Set-ItResult -Skipped -Because $result.SkipReason + } else { + $result | Should -Be $true -Because "archive scanning helps detect malware hidden in compressed files" + } } } @@ -19,7 +24,12 @@ Describe "Microsoft Defender Antivirus - Policy Compliance" -Tag "Maester", "MDE #> $result = Invoke-MtMdeUnifiedTest -TestId "MDE.AV02" -TestName $____Pester.CurrentTest.ExpandedName if ($null -ne $result) { - $result | Should -Be $true -Because "behavior monitoring is essential for detecting advanced threats" + if ($result.IsSkipped) { + Add-MtTestResultDetail -Description $result.TestDetails -GraphObjectType 'Devices' -Severity $result.Severity + Set-ItResult -Skipped -Because $result.SkipReason + } else { + $result | Should -Be $true -Because "behavior monitoring is essential for detecting advanced threats" + } } } @@ -30,7 +40,12 @@ Describe "Microsoft Defender Antivirus - Policy Compliance" -Tag "Maester", "MDE #> $result = Invoke-MtMdeUnifiedTest -TestId "MDE.AV03" -TestName $____Pester.CurrentTest.ExpandedName if ($null -ne $result) { - $result | Should -Be $true -Because "cloud protection provides real-time threat intelligence" + if ($result.IsSkipped) { + Add-MtTestResultDetail -Description $result.TestDetails -GraphObjectType 'Devices' -Severity $result.Severity + Set-ItResult -Skipped -Because $result.SkipReason + } else { + $result | Should -Be $true -Because "cloud protection provides real-time threat intelligence" + } } } @@ -41,7 +56,12 @@ Describe "Microsoft Defender Antivirus - Policy Compliance" -Tag "Maester", "MDE #> $result = Invoke-MtMdeUnifiedTest -TestId "MDE.AV04" -TestName $____Pester.CurrentTest.ExpandedName if ($null -ne $result) { - $result | Should -Be $true -Because "email scanning should be enabled to protect Exchange queues" + if ($result.IsSkipped) { + Add-MtTestResultDetail -Description $result.TestDetails -GraphObjectType 'Devices' -Severity $result.Severity + Set-ItResult -Skipped -Because $result.SkipReason + } else { + $result | Should -Be $true -Because "email scanning should be enabled to protect Exchange queues" + } } } @@ -52,7 +72,12 @@ Describe "Microsoft Defender Antivirus - Policy Compliance" -Tag "Maester", "MDE #> $result = Invoke-MtMdeUnifiedTest -TestId "MDE.AV05" -TestName $____Pester.CurrentTest.ExpandedName if ($null -ne $result) { - $result | Should -Be $true -Because "script scanning should be enabled to block malicious scripts" + if ($result.IsSkipped) { + Add-MtTestResultDetail -Description $result.TestDetails -GraphObjectType 'Devices' -Severity $result.Severity + Set-ItResult -Skipped -Because $result.SkipReason + } else { + $result | Should -Be $true -Because "script scanning should be enabled to block malicious scripts" + } } } @@ -63,7 +88,12 @@ Describe "Microsoft Defender Antivirus - Policy Compliance" -Tag "Maester", "MDE #> $result = Invoke-MtMdeUnifiedTest -TestId "MDE.AV06" -TestName $____Pester.CurrentTest.ExpandedName if ($null -ne $result) { - $result | Should -Be $true -Because "real-time monitoring provides essential protection against live threats" + if ($result.IsSkipped) { + Add-MtTestResultDetail -Description $result.TestDetails -GraphObjectType 'Devices' -Severity $result.Severity + Set-ItResult -Skipped -Because $result.SkipReason + } else { + $result | Should -Be $true -Because "real-time monitoring provides essential protection against live threats" + } } } @@ -74,7 +104,12 @@ Describe "Microsoft Defender Antivirus - Policy Compliance" -Tag "Maester", "MDE #> $result = Invoke-MtMdeUnifiedTest -TestId "MDE.AV07" -TestName $____Pester.CurrentTest.ExpandedName if ($null -ne $result) { - $result | Should -Be $true -Because "full scan of removable drives should be enabled to mitigate USB risks" + if ($result.IsSkipped) { + Add-MtTestResultDetail -Description $result.TestDetails -GraphObjectType 'Devices' -Severity $result.Severity + Set-ItResult -Skipped -Because $result.SkipReason + } else { + $result | Should -Be $true -Because "full scan of removable drives should be enabled to mitigate USB risks" + } } } @@ -85,7 +120,12 @@ Describe "Microsoft Defender Antivirus - Policy Compliance" -Tag "Maester", "MDE #> $result = Invoke-MtMdeUnifiedTest -TestId "MDE.AV08" -TestName $____Pester.CurrentTest.ExpandedName if ($null -ne $result) { - $result | Should -Be $true -Because "full scan of mapped drives should be disabled for performance optimization" + if ($result.IsSkipped) { + Add-MtTestResultDetail -Description $result.TestDetails -GraphObjectType 'Devices' -Severity $result.Severity + Set-ItResult -Skipped -Because $result.SkipReason + } else { + $result | Should -Be $true -Because "full scan of mapped drives should be disabled for performance optimization" + } } } @@ -96,7 +136,12 @@ Describe "Microsoft Defender Antivirus - Policy Compliance" -Tag "Maester", "MDE #> $result = Invoke-MtMdeUnifiedTest -TestId "MDE.AV09" -TestName $____Pester.CurrentTest.ExpandedName if ($null -ne $result) { - $result | Should -Be $true -Because "scanning network files should be enabled for comprehensive protection" + if ($result.IsSkipped) { + Add-MtTestResultDetail -Description $result.TestDetails -GraphObjectType 'Devices' -Severity $result.Severity + Set-ItResult -Skipped -Because $result.SkipReason + } else { + $result | Should -Be $true -Because "scanning network files should be enabled for comprehensive protection" + } } } @@ -108,7 +153,12 @@ Describe "Microsoft Defender Antivirus - Policy Compliance" -Tag "Maester", "MDE #> $result = Invoke-MtMdeUnifiedTest -TestId "MDE.AV10" -TestName $____Pester.CurrentTest.ExpandedName if ($null -ne $result) { - $result | Should -Be $true -Because "CPU load should be balanced between performance and security" + if ($result.IsSkipped) { + Add-MtTestResultDetail -Description $result.TestDetails -GraphObjectType 'Devices' -Severity $result.Severity + Set-ItResult -Skipped -Because $result.SkipReason + } else { + $result | Should -Be $true -Because "CPU load should be balanced between performance and security" + } } } @@ -120,7 +170,12 @@ Describe "Microsoft Defender Antivirus - Policy Compliance" -Tag "Maester", "MDE #> $result = Invoke-MtMdeUnifiedTest -TestId "MDE.AV11" -TestName $____Pester.CurrentTest.ExpandedName if ($null -ne $result) { - $result | Should -Be $true -Because "scans should be scheduled every day for comprehensive coverage" + if ($result.IsSkipped) { + Add-MtTestResultDetail -Description $result.TestDetails -GraphObjectType 'Devices' -Severity $result.Severity + Set-ItResult -Skipped -Because $result.SkipReason + } else { + $result | Should -Be $true -Because "scans should be scheduled every day for comprehensive coverage" + } } } @@ -131,7 +186,12 @@ Describe "Microsoft Defender Antivirus - Policy Compliance" -Tag "Maester", "MDE #> $result = Invoke-MtMdeUnifiedTest -TestId "MDE.AV12" -TestName $____Pester.CurrentTest.ExpandedName if ($null -ne $result) { - $result | Should -Be $true -Because "quick scan time configuration is not required" + if ($result.IsSkipped) { + Add-MtTestResultDetail -Description $result.TestDetails -GraphObjectType 'Devices' -Severity $result.Severity + Set-ItResult -Skipped -Because $result.SkipReason + } else { + $result | Should -Be $true -Because "quick scan time configuration is not required" + } } } @@ -143,7 +203,12 @@ Describe "Microsoft Defender Antivirus - Policy Compliance" -Tag "Maester", "MDE #> $result = Invoke-MtMdeUnifiedTest -TestId "MDE.AV13" -TestName $____Pester.CurrentTest.ExpandedName if ($null -ne $result) { - $result | Should -Be $true -Because "signatures should be checked before scan for zero-day protection" + if ($result.IsSkipped) { + Add-MtTestResultDetail -Description $result.TestDetails -GraphObjectType 'Devices' -Severity $result.Severity + Set-ItResult -Skipped -Because $result.SkipReason + } else { + $result | Should -Be $true -Because "signatures should be checked before scan for zero-day protection" + } } } @@ -154,7 +219,12 @@ Describe "Microsoft Defender Antivirus - Policy Compliance" -Tag "Maester", "MDE #> $result = Invoke-MtMdeUnifiedTest -TestId "MDE.AV14" -TestName $____Pester.CurrentTest.ExpandedName if ($null -ne $result) { - $result | Should -Be $true -Because "cloud block level should be High or higher for maximum protection" + if ($result.IsSkipped) { + Add-MtTestResultDetail -Description $result.TestDetails -GraphObjectType 'Devices' -Severity $result.Severity + Set-ItResult -Skipped -Because $result.SkipReason + } else { + $result | Should -Be $true -Because "cloud block level should be High or higher for maximum protection" + } } } @@ -165,7 +235,12 @@ Describe "Microsoft Defender Antivirus - Policy Compliance" -Tag "Maester", "MDE #> $result = Invoke-MtMdeUnifiedTest -TestId "MDE.AV15" -TestName $____Pester.CurrentTest.ExpandedName if ($null -ne $result) { - $result | Should -Be $true -Because "cloud extended timeout should be 30-50 seconds for optimal UX and detection" + if ($result.IsSkipped) { + Add-MtTestResultDetail -Description $result.TestDetails -GraphObjectType 'Devices' -Severity $result.Severity + Set-ItResult -Skipped -Because $result.SkipReason + } else { + $result | Should -Be $true -Because "cloud extended timeout should be 30-50 seconds for optimal UX and detection" + } } } @@ -176,7 +251,12 @@ Describe "Microsoft Defender Antivirus - Policy Compliance" -Tag "Maester", "MDE #> $result = Invoke-MtMdeUnifiedTest -TestId "MDE.AV16" -TestName $____Pester.CurrentTest.ExpandedName if ($null -ne $result) { - $result | Should -Be $true -Because "signature update interval should be 1-4 hours for current protection" + if ($result.IsSkipped) { + Add-MtTestResultDetail -Description $result.TestDetails -GraphObjectType 'Devices' -Severity $result.Severity + Set-ItResult -Skipped -Because $result.SkipReason + } else { + $result | Should -Be $true -Because "signature update interval should be 1-4 hours for current protection" + } } } @@ -188,7 +268,12 @@ Describe "Microsoft Defender Antivirus - Policy Compliance" -Tag "Maester", "MDE #> $result = Invoke-MtMdeUnifiedTest -TestId "MDE.AV17" -TestName $____Pester.CurrentTest.ExpandedName if ($null -ne $result) { - $result | Should -Be $true -Because "PUA protection should be enabled to block potentially unwanted applications" + if ($result.IsSkipped) { + Add-MtTestResultDetail -Description $result.TestDetails -GraphObjectType 'Devices' -Severity $result.Severity + Set-ItResult -Skipped -Because $result.SkipReason + } else { + $result | Should -Be $true -Because "PUA protection should be enabled to block potentially unwanted applications" + } } } @@ -199,7 +284,12 @@ Describe "Microsoft Defender Antivirus - Policy Compliance" -Tag "Maester", "MDE #> $result = Invoke-MtMdeUnifiedTest -TestId "MDE.AV18" -TestName $____Pester.CurrentTest.ExpandedName if ($null -ne $result) { - $result | Should -Be $true -Because "network protection should be enabled in block mode" + if ($result.IsSkipped) { + Add-MtTestResultDetail -Description $result.TestDetails -GraphObjectType 'Devices' -Severity $result.Severity + Set-ItResult -Skipped -Because $result.SkipReason + } else { + $result | Should -Be $true -Because "network protection should be enabled in block mode" + } } } @@ -210,7 +300,12 @@ Describe "Microsoft Defender Antivirus - Policy Compliance" -Tag "Maester", "MDE #> $result = Invoke-MtMdeUnifiedTest -TestId "MDE.AV19" -TestName $____Pester.CurrentTest.ExpandedName if ($null -ne $result) { - $result | Should -Be $true -Because "local admin merge should be disabled to prevent local exclusions" + if ($result.IsSkipped) { + Add-MtTestResultDetail -Description $result.TestDetails -GraphObjectType 'Devices' -Severity $result.Severity + Set-ItResult -Skipped -Because $result.SkipReason + } else { + $result | Should -Be $true -Because "local admin merge should be disabled to prevent local exclusions" + } } } @@ -238,7 +333,12 @@ Describe "Microsoft Defender Antivirus - Policy Compliance" -Tag "Maester", "MDE #> $result = Invoke-MtMdeUnifiedTest -TestId "MDE.AV21" -TestName $____Pester.CurrentTest.ExpandedName if ($null -ne $result) { - $result | Should -Be $true -Because "real-time scan should monitor both incoming and outgoing traffic" + if ($result.IsSkipped) { + Add-MtTestResultDetail -Description $result.TestDetails -GraphObjectType 'Devices' -Severity $result.Severity + Set-ItResult -Skipped -Because $result.SkipReason + } else { + $result | Should -Be $true -Because "real-time scan should monitor both incoming and outgoing traffic" + } } } @@ -250,7 +350,12 @@ Describe "Microsoft Defender Antivirus - Policy Compliance" -Tag "Maester", "MDE #> $result = Invoke-MtMdeUnifiedTest -TestId "MDE.AV22" -TestName $____Pester.CurrentTest.ExpandedName if ($null -ne $result) { - $result | Should -Be $true -Because "cleaned malware should be retained for 90 days for audit purposes" + if ($result.IsSkipped) { + Add-MtTestResultDetail -Description $result.TestDetails -GraphObjectType 'Devices' -Severity $result.Severity + Set-ItResult -Skipped -Because $result.SkipReason + } else { + $result | Should -Be $true -Because "cleaned malware should be retained for 90 days for audit purposes" + } } } @@ -261,7 +366,12 @@ Describe "Microsoft Defender Antivirus - Policy Compliance" -Tag "Maester", "MDE #> $result = Invoke-MtMdeUnifiedTest -TestId "MDE.AV23" -TestName $____Pester.CurrentTest.ExpandedName if ($null -ne $result) { - $result | Should -Be $true -Because "catch-up full scan should be disabled to avoid additional system load" + if ($result.IsSkipped) { + Add-MtTestResultDetail -Description $result.TestDetails -GraphObjectType 'Devices' -Severity $result.Severity + Set-ItResult -Skipped -Because $result.SkipReason + } else { + $result | Should -Be $true -Because "catch-up full scan should be disabled to avoid additional system load" + } } } @@ -272,7 +382,12 @@ Describe "Microsoft Defender Antivirus - Policy Compliance" -Tag "Maester", "MDE #> $result = Invoke-MtMdeUnifiedTest -TestId "MDE.AV24" -TestName $____Pester.CurrentTest.ExpandedName if ($null -ne $result) { - $result | Should -Be $true -Because "catch-up quick scan should be disabled" + if ($result.IsSkipped) { + Add-MtTestResultDetail -Description $result.TestDetails -GraphObjectType 'Devices' -Severity $result.Severity + Set-ItResult -Skipped -Because $result.SkipReason + } else { + $result | Should -Be $true -Because "catch-up quick scan should be disabled" + } } } @@ -300,7 +415,12 @@ Describe "Microsoft Defender Antivirus - Policy Compliance" -Tag "Maester", "MDE #> $result = Invoke-MtMdeUnifiedTest -TestId "MDE.AV26" -TestName $____Pester.CurrentTest.ExpandedName if ($null -ne $result) { - $result | Should -Be $true -Because "sample submission should be configured to send safe samples automatically" + if ($result.IsSkipped) { + Add-MtTestResultDetail -Description $result.TestDetails -GraphObjectType 'Devices' -Severity $result.Severity + Set-ItResult -Skipped -Because $result.SkipReason + } else { + $result | Should -Be $true -Because "sample submission should be configured to send safe samples automatically" + } } } } \ No newline at end of file From 614d8abb2430706931e6626cc1e57934b86a22a7 Mon Sep 17 00:00:00 2001 From: Sam Erde <20478745+SamErde@users.noreply.github.com> Date: Thu, 16 Oct 2025 08:43:12 -0400 Subject: [PATCH 5/5] Remove duplicate 'Defender' tag from tests --- tests/Maester/Defender/Test-MtMdiHealthIssues.Tests.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Maester/Defender/Test-MtMdiHealthIssues.Tests.ps1 b/tests/Maester/Defender/Test-MtMdiHealthIssues.Tests.ps1 index e448f621d..68cb69fec 100644 --- a/tests/Maester/Defender/Test-MtMdiHealthIssues.Tests.ps1 +++ b/tests/Maester/Defender/Test-MtMdiHealthIssues.Tests.ps1 @@ -40,7 +40,7 @@ BeforeDiscovery { $MdiHealthActiveIssues = $MdiHealthIssues | Where-Object { $_.status -ne "closed" } } -Describe "Defender for Identity health issues" -Tag "Maester", "Defender", "Security", "All", "MDI", "Defender" -ForEach $MdiHealthActiveIssues { +Describe "Defender for Identity health issues" -Tag "Maester", "Defender", "Security", "MDI" -ForEach $MdiHealthActiveIssues { It "MT.1059: MDI Health Issues - . See https://maester.dev/docs/tests/MT.1058" -Tag "MT.1058", "Severity:Medium", $displayName { $issueUrl = "https://security.microsoft.com/identities/health-issues"