Assess endpoints against CIS and Microsoft security baselines, identify configuration drift, create remediation plans, build custom profiles, and generate audit-ready compliance reports.
Security baselines define the minimum security configuration standards for your endpoints. Defender Vulnerability Management evaluates devices against CIS benchmarks, Microsoft security baselines, and custom baselines to identify configuration drift, misconfigurations, and non-compliant devices. This lab covers deploying baselines, assessing compliance, tracking remediation, and building configuration management dashboards.
An enterprise must demonstrate CIS Level 1 compliance across 3,000 Windows endpoints for PCI-DSS audit. The security team discovers 40% of endpoints have non-compliant configurations: disabled firewalls, missing BitLocker encryption, and outdated security settings. They need baseline assessments, remediation tracking, and audit-ready reports.
Misconfigurations cause 80% of security breaches. A single endpoint with a disabled firewall or missing encryption can become the entry point for ransomware. Security baselines transform configuration management from ad-hoc to systematic, providing continuous compliance monitoring.
Machine.Read.All, SecurityBaselinesAssessment.Read.All for programmatic accessAz and Microsoft.Graph modules installedThe security baselines dashboard in Defender Vulnerability Management provides a centralised view of how your endpoints measure up against industry-standard benchmarks. Before diving into specific compliance checks, you need to orient yourself with the available baselines, understand the assessment scoring model, and use the API to pull baseline profiles programmatically for automation workflows.
# ============================================================
# Retrieve MDE Security Baselines via Microsoft Graph API
# ============================================================
# WHAT: Pulls the list of available security baseline profiles
# and their compliance summaries from the Defender API.
# WHY: Enables automation of compliance reporting and lets you
# compare baselines programmatically before choosing which
# to deploy across your fleet.
# PREREQ: Install-Module Microsoft.Graph -Scope CurrentUser
# Connect-MgGraph -Scopes "DeviceManagementConfiguration.Read.All"
# ============================================================
# Connect to Microsoft Graph with required scopes
Connect-MgGraph -Scopes "DeviceManagementConfiguration.Read.All",
"SecurityEvents.Read.All"
# Get all available security baseline profiles
$baselines = Invoke-MgGraphRequest -Method GET `
-Uri "https://graph.microsoft.com/beta/security/baselineProfiles" `
-OutputType PSObject
# Display each baseline with its compliance summary
foreach ($baseline in $baselines.value) {
Write-Host "โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ" -ForegroundColor Cyan
Write-Host "Baseline: $($baseline.displayName)" -ForegroundColor White
Write-Host " ID: $($baseline.id)"
Write-Host " Benchmark: $($baseline.benchmark)"
Write-Host " Version: $($baseline.version)"
Write-Host " OS: $($baseline.operatingSystem)"
Write-Host " Created: $($baseline.createdDateTime)"
}
# Get detailed compliance for a specific baseline profile
$profileId = $baselines.value[0].id # First baseline
$compliance = Invoke-MgGraphRequest -Method GET `
-Uri "https://graph.microsoft.com/beta/security/baselineProfiles/$profileId/deviceStates" `
-OutputType PSObject
# Summarise compliance states
$compliance.value | Group-Object -Property state |
Select-Object Name, Count |
Sort-Object Count -Descending |
Format-Table -AutoSizeCIS benchmarks are the gold standard for endpoint hardening. DVM maps each CIS Level 1 and Level 2 check to the actual device configuration state, letting you see exactly which settings are compliant and which are not. In this step you will walk through the CIS assessment categories and use Intune to create configuration profiles that enforce the most critical settings automatically.
# ============================================================
# Create Intune Configuration Profile for CIS Baseline Settings
# ============================================================
# WHAT: Creates a Device Configuration profile in Intune that
# enforces key CIS Level 1 settings: password policy,
# firewall, BitLocker, and screen lock.
# WHY: Manual GPO enforcement doesn't scale. Intune profiles
# ensure every enrolled device receives and maintains
# the correct configuration regardless of network location.
# PREREQ: Connect-MgGraph -Scopes "DeviceManagementConfiguration.ReadWrite.All"
# ============================================================
# Connect with Intune write permissions
Connect-MgGraph -Scopes "DeviceManagementConfiguration.ReadWrite.All"
# Define the CIS-aligned configuration profile payload
$profileBody = @{
"@odata.type" = "#microsoft.graph.windows10CustomConfiguration"
displayName = "CIS Level 1 - Windows 11 Baseline"
description = "Enforces CIS Level 1 benchmark settings for password, firewall, BitLocker, and screen lock"
omaSettings = @(
# โโ Password minimum length (CIS 1.1.4) โโ
@{
"@odata.type" = "#microsoft.graph.omaSettingInteger"
displayName = "Minimum Password Length"
omaUri = "./Device/Vendor/MSFT/Policy/Config/DeviceLock/MinDevicePasswordLength"
value = 14 # CIS recommends 14+ characters
},
# โโ Account lockout threshold (CIS 1.2.1) โโ
@{
"@odata.type" = "#microsoft.graph.omaSettingInteger"
displayName = "Account Lockout Threshold"
omaUri = "./Device/Vendor/MSFT/Policy/Config/DeviceLock/MaxDevicePasswordFailedAttempts"
value = 5 # Lock after 5 failed attempts
},
# โโ Screen lock timeout (CIS 2.3.7.5) โโ
@{
"@odata.type" = "#microsoft.graph.omaSettingInteger"
displayName = "Inactivity Lock (seconds)"
omaUri = "./Device/Vendor/MSFT/Policy/Config/DeviceLock/MaxInactivityTimeDeviceLock"
value = 300 # 5 minutes
},
# โโ Enable Windows Firewall - Domain Profile (CIS 9.1.1) โโ
@{
"@odata.type" = "#microsoft.graph.omaSettingInteger"
displayName = "Enable Domain Firewall"
omaUri = "./Vendor/MSFT/Firewall/MdmStore/DomainProfile/EnableFirewall"
value = 1 # 1 = Enabled
}
)
} | ConvertTo-Json -Depth 10
# Create the profile in Intune
Invoke-MgGraphRequest -Method POST `
-Uri "https://graph.microsoft.com/beta/deviceManagement/deviceConfigurations" `
-Body $profileBody `
-ContentType "application/json"
Write-Host "โ
CIS Level 1 configuration profile created in Intune" -ForegroundColor GreenMicrosoft publishes its own security baselines for Windows, Edge, and Microsoft 365 Apps. These baselines reflect Microsoft's internal hardening standards and sometimes differ from CIS recommendations. Using the DeviceTvmSecureConfigurationAssessment table in Advanced Hunting, you can query compliance across both frameworks simultaneously and identify where they overlap or diverge.
// ============================================================
// Analyse Secure Configuration Compliance Across All Baselines
// ============================================================
// WHAT: Queries DeviceTvmSecureConfigurationAssessment to show
// compliance status per configuration check, grouped by
// sub-category (e.g., Firewall, BitLocker, Antivirus).
// WHY: Gives you a single-pane view of which configuration
// areas are strongest and which need urgent attention.
// TABLE: DeviceTvmSecureConfigurationAssessment
// KEY FIELDS:
// ConfigurationId - unique ID for each security check
// ConfigurationSubcategory - grouping (Firewall, Credential Guard, etc.)
// IsCompliant - true/false per device per check
// IsApplicable - whether the check applies to this device
// OUTPUT: Subcategory, total applicable devices, compliant count, percentage
DeviceTvmSecureConfigurationAssessment
| where IsApplicable == true
| summarize
TotalDevices = dcount(DeviceId),
CompliantCount = dcountif(DeviceId, IsCompliant == true),
NonCompliant = dcountif(DeviceId, IsCompliant == false)
by ConfigurationSubcategory
| extend CompliancePct = round(100.0 * CompliantCount / TotalDevices, 1)
| order by CompliancePct asc // Worst compliance first
| project ConfigurationSubcategory, TotalDevices, CompliantCount,
NonCompliant, CompliancePct// ============================================================
// Top 20 Most-Failing Configuration Checks
// ============================================================
// WHAT: Identifies the individual configuration checks that
// fail on the most devices - your biggest compliance gaps.
// WHY: Fixing one broadly-failing check can improve compliance
// across hundreds of devices in a single remediation effort.
DeviceTvmSecureConfigurationAssessment
| where IsApplicable == true
| where IsCompliant == false
| summarize FailedDevices = dcount(DeviceId)
by ConfigurationId, ConfigurationSubcategory, ConfigurationDescription
| top 20 by FailedDevices
| project ConfigurationId, ConfigurationSubcategory,
ConfigurationDescription, FailedDevicesConfiguration drift occurs when devices gradually move out of compliance - often silently. A GPO update may override an Intune policy, a user may disable a firewall for troubleshooting and forget to re-enable it, or an OS update may reset security settings. This step uses KQL to detect drift patterns and pinpoint which devices changed state recently.
// ============================================================
// Detect Configuration Drift - Devices That Became Non-Compliant
// ============================================================
// WHAT: Compares the latest secure-config assessment against
// the state from 7 days ago to find devices that were
// compliant but have since drifted out of compliance.
// WHY: Drift is often invisible until an audit. This query
// surfaces regressions so you can fix them before they
// compound into systemic non-compliance.
// HOW: Two time-windowed summaries are joined by DeviceId +
// ConfigurationId. Any check that was true (compliant)
// 7 days ago but is now false (non-compliant) is drift.
let currentState = DeviceTvmSecureConfigurationAssessment
| where Timestamp > ago(1d)
| where IsApplicable == true
| project DeviceId, DeviceName, ConfigurationId,
ConfigurationSubcategory, CurrentCompliant = IsCompliant;
let previousState = DeviceTvmSecureConfigurationAssessment
| where Timestamp between (ago(8d) .. ago(7d))
| where IsApplicable == true
| project DeviceId, ConfigurationId,
PreviousCompliant = IsCompliant;
currentState
| join kind=inner previousState on DeviceId, ConfigurationId
| where PreviousCompliant == true and CurrentCompliant == false
| summarize DriftedChecks = count() by DeviceName, DeviceId,
ConfigurationSubcategory
| order by DriftedChecks desc
| project DeviceName, ConfigurationSubcategory, DriftedChecksRemediation plans translate compliance gaps into actionable work items. For each critical baseline failure, you need to assign ownership, define rollback procedures, and track progress against audit deadlines. This step also shows how to use KQL to generate a prioritised remediation queue based on the number of affected devices and the severity of each configuration gap.
// ============================================================
// Generate Prioritised Remediation Queue
// ============================================================
// WHAT: Builds a ranked remediation list showing which config
// checks to fix first based on affected device count
// and the configuration subcategory.
// WHY: Fixing the top 10 checks can often remediate 80% of
// your non-compliant devices - maximum impact with
// minimum effort.
DeviceTvmSecureConfigurationAssessment
| where IsApplicable == true
| where IsCompliant == false
| summarize
AffectedDevices = dcount(DeviceId),
SampleDevices = make_set(DeviceName, 3) // Show 3 example devices
by ConfigurationId, ConfigurationSubcategory,
ConfigurationDescription
| order by AffectedDevices desc
| extend Priority = case(
AffectedDevices > 500, "๐ด Critical",
AffectedDevices > 100, "๐ High",
AffectedDevices > 25, "๐ก Medium",
"๐ข Low")
| project Priority, ConfigurationId, ConfigurationSubcategory,
ConfigurationDescription, AffectedDevices, SampleDevicesCompliance is not a one-time achievement - it is a continuous process. Tracking compliance trends over time reveals whether your remediation efforts are working, whether new deployments introduce regressions, and whether your organisation is moving towards or away from its compliance targets. This step sets up the monitoring cadence.
// ============================================================
// Compliance Trend Over the Last 30 Days
// ============================================================
// WHAT: Shows daily compliance percentage across all devices
// and configuration checks for the past 30 days.
// WHY: Trend lines reveal whether remediation is outpacing
// drift. A flat or declining line means you need to
// escalate remediation efforts or investigate root causes.
DeviceTvmSecureConfigurationAssessment
| where Timestamp > ago(30d)
| where IsApplicable == true
| summarize
TotalChecks = count(),
CompliantChecks = countif(IsCompliant == true)
by Day = bin(Timestamp, 1d)
| extend CompliancePct = round(100.0 * CompliantChecks / TotalChecks, 1)
| order by Day asc
| project Day, CompliancePct, TotalChecks, CompliantChecksNo off-the-shelf baseline perfectly fits every organisation. Custom profiles let you combine the most relevant checks from CIS and Microsoft baselines, exclude checks that don't apply to your environment, and add organisation-specific requirements. This step also builds KQL dashboard tiles that give leadership a real-time view of baseline compliance status.
// ============================================================
// Dashboard Tile 1: Overall Compliance Scorecard
// ============================================================
// WHAT: Single-number compliance percentage for executive
// dashboards and status boards.
DeviceTvmSecureConfigurationAssessment
| where Timestamp > ago(1d)
| where IsApplicable == true
| summarize
TotalChecks = count(),
PassedChecks = countif(IsCompliant == true)
| extend OverallCompliance = strcat(tostring(round(100.0 * PassedChecks / TotalChecks, 1)), "%")
| extend Status = iff(100.0 * PassedChecks / TotalChecks >= 95, "โ
On Target", "โ ๏ธ Below Target")
| project OverallCompliance, Status, PassedChecks, TotalChecks// ============================================================
// Dashboard Tile 2: Compliance by Device Group
// ============================================================
// WHAT: Breaks down compliance percentage by MachineGroup so
// you can see which teams or departments need attention.
// WHY: Enables targeted remediation campaigns per business
// unit rather than a one-size-fits-all approach.
DeviceTvmSecureConfigurationAssessment
| where Timestamp > ago(1d)
| where IsApplicable == true
| join kind=inner (
DeviceInfo
| where Timestamp > ago(1d)
| summarize arg_max(Timestamp, MachineGroup) by DeviceId
) on DeviceId
| summarize
TotalChecks = count(),
PassedChecks = countif(IsCompliant == true)
by MachineGroup
| extend CompliancePct = round(100.0 * PassedChecks / TotalChecks, 1)
| order by CompliancePct asc
| project MachineGroup, CompliancePct, PassedChecks, TotalChecks// ============================================================
// Dashboard Tile 3: Critical Security Controls Status
// ============================================================
// WHAT: Focused view on the most critical security controls:
// firewall, antivirus, BitLocker, credential guard.
// WHY: These are the controls auditors check first and the
// ones most likely to be exploited if misconfigured.
DeviceTvmSecureConfigurationAssessment
| where Timestamp > ago(1d)
| where IsApplicable == true
| where ConfigurationSubcategory in (
"Firewall", "Antivirus", "Data encryption",
"Credential Guard", "Attack surface reduction")
| summarize
TotalDevices = dcount(DeviceId),
CompliantCount = dcountif(DeviceId, IsCompliant == true)
by ConfigurationSubcategory
| extend CompliancePct = round(100.0 * CompliantCount / TotalDevices, 1)
| extend StatusIcon = case(
CompliancePct >= 95, "๐ข",
CompliancePct >= 80, "๐ก",
"๐ด")
| project StatusIcon, ConfigurationSubcategory, CompliancePct,
CompliantCount, TotalDevices
| order by CompliancePct ascProactive alerting ensures that compliance regressions are caught immediately, not during the next quarterly audit. This step configures custom detection rules that fire when critical security controls are disabled, and sets up email notifications so the right teams can respond before an attacker exploits the gap.
// ============================================================
// Custom Detection Rule: Critical Security Control Disabled
// ============================================================
// WHAT: Fires an alert when a device has a critical security
// control (firewall, AV, BitLocker) marked non-compliant.
// WHY: Disabled firewalls and AV are the #1 exploited
// misconfiguration in ransomware attacks. Immediate
// alerting gives you minutes, not days, to respond.
// USAGE: Save this as a Custom Detection Rule with frequency
// set to "Every hour" and action "Generate alert".
DeviceTvmSecureConfigurationAssessment
| where Timestamp > ago(1h)
| where IsApplicable == true
| where IsCompliant == false
| where ConfigurationSubcategory in (
"Firewall", "Antivirus", "Data encryption")
| project Timestamp, DeviceId, DeviceName,
ConfigurationId, ConfigurationSubcategory,
ConfigurationDescription
| join kind=inner (
DeviceInfo
| where Timestamp > ago(1d)
| summarize arg_max(Timestamp, MachineGroup, OSPlatform) by DeviceId
) on DeviceId
| project Timestamp, DeviceName, MachineGroup, OSPlatform,
ConfigurationSubcategory, ConfigurationDescriptionIntune is the enforcement arm of your baseline compliance programme. While DVM identifies gaps, Intune closes them by pushing configuration profiles and remediation scripts to non-compliant devices. This step creates a PowerShell remediation script that runs on Intune-managed devices to fix common baseline failures automatically.
# ============================================================
# Intune Proactive Remediation Script: Fix Firewall & Audit Policy
# ============================================================
# WHAT: Detects and remediates disabled Windows Firewall profiles
# and missing audit policy settings on Intune-managed devices.
# WHY: Some baseline failures cannot be fixed with config profiles
# alone (e.g., advanced audit policies). Proactive remediation
# scripts run on a schedule and self-heal non-compliant devices.
# DEPLOY: Intune > Devices > Remediations > Create script package
# Detection script: Returns exit code 1 if non-compliant
# Remediation script: Fixes the issue and returns exit code 0
# ============================================================
# --- DETECTION SCRIPT (save as Detect-BaselineCompliance.ps1) ---
$issues = @()
# Check Windows Firewall - all profiles must be enabled
$fwProfiles = Get-NetFirewallProfile
foreach ($profile in $fwProfiles) {
if ($profile.Enabled -eq $false) {
$issues += "Firewall profile '$($profile.Name)' is DISABLED"
}
}
# Check Windows Defender real-time protection
$mpPref = Get-MpPreference
if ($mpPref.DisableRealtimeMonitoring -eq $true) {
$issues += "Real-time protection is DISABLED"
}
# Check audit policy for logon events
$auditLogon = auditpol /get /subcategory:"Logon" /r | ConvertFrom-Csv
if ($auditLogon.'Inclusion Setting' -notmatch 'Success') {
$issues += "Logon audit policy missing Success auditing"
}
if ($issues.Count -gt 0) {
Write-Host "NON-COMPLIANT: $($issues -join '; ')"
exit 1 # Non-compliant triggers remediation
} else {
Write-Host "COMPLIANT: All baseline checks passed"
exit 0 # Compliant, no action needed
}
# --- REMEDIATION SCRIPT (save as Remediate-BaselineCompliance.ps1) ---
try {
# Enable all firewall profiles
Set-NetFirewallProfile -Profile Domain,Private,Public -Enabled True
Write-Host "Firewall profiles enabled"
# Enable real-time protection
Set-MpPreference -DisableRealtimeMonitoring $false
Write-Host "Real-time protection enabled"
# Enable logon audit policy
auditpol /set /subcategory:"Logon" /success:enable /failure:enable
Write-Host "Logon audit policy configured"
exit 0
} catch {
Write-Host "Remediation failed: $_"
exit 1
}Audit-ready reports must tell a complete story: what your compliance targets are, where you stand today, what you remediated during the audit period, and what exceptions exist with documented justifications. This step uses PowerShell to pull compliance data from the API and generate a structured report suitable for PCI-DSS, ISO 27001, or NIST auditors.
# ============================================================
# Generate Audit-Ready Baseline Compliance Report
# ============================================================
# WHAT: Exports DVM baseline compliance data via the Defender
# API and builds a structured CSV report with compliance
# status, remediation evidence, and exception documentation.
# WHY: Auditors need evidence in a standardised format. This
# script produces a report that maps directly to PCI-DSS
# Requirement 2.2 (configuration standards) and ISO 27001
# Annex A.12.6 (technical vulnerability management).
# ============================================================
# Connect to Microsoft Graph
Connect-MgGraph -Scopes "SecurityEvents.Read.All"
# Pull secure configuration assessment data
$assessments = Invoke-MgGraphRequest -Method GET `
-Uri "https://graph.microsoft.com/beta/security/secureConfigurationAssessments?`$top=1000" `
-OutputType PSObject
# Build the report
$report = foreach ($item in $assessments.value) {
[PSCustomObject]@{
DeviceName = $item.deviceName
ConfigurationId = $item.configurationId
Category = $item.configurationCategory
Subcategory = $item.configurationSubcategory
Description = $item.configurationDescription
IsCompliant = $item.isCompliant
IsApplicable = $item.isApplicable
AssessmentDate = $item.timestamp
RecommendedValue = $item.recommendedValue
CurrentValue = $item.detectedValue
}
}
# Export to CSV for auditors
$reportPath = "C:\Reports\DVM-Baseline-Compliance-$(Get-Date -Format 'yyyy-MM-dd').csv"
$report | Export-Csv -Path $reportPath -NoTypeInformation -Encoding UTF8
Write-Host "โ
Report exported to: $reportPath" -ForegroundColor Green
# Generate summary statistics
$totalChecks = ($report | Where-Object { $_.IsApplicable -eq $true }).Count
$compliantCount = ($report | Where-Object { $_.IsApplicable -eq $true -and $_.IsCompliant -eq $true }).Count
$compliancePct = [math]::Round(($compliantCount / $totalChecks) * 100, 1)
Write-Host "โโโโโโโโโโโโโโโโโโโโโโโโโโ" -ForegroundColor Cyan
Write-Host "AUDIT REPORT SUMMARY" -ForegroundColor White
Write-Host " Total Checks: $totalChecks"
Write-Host " Compliant: $compliantCount"
Write-Host " Non-Compliant: $($totalChecks - $compliantCount)"
Write-Host " Compliance Rate: $compliancePct%"
Write-Host "โโโโโโโโโโโโโโโโโโโโโโโโโโ" -ForegroundColor Cyan| Resource | Description |
|---|---|
| Security baselines assessment | Official docs for DVM baselines assessment dashboard and configuration |
| Intune security baselines | Deploy and manage security baselines via Microsoft Intune |
| Security baselines assessment API | REST API reference for programmatic baseline compliance retrieval |
| CIS Benchmarks | Official CIS benchmark documentation and configuration guides |
| DeviceTvmSecureConfigurationAssessment | Advanced Hunting schema reference for secure configuration data |
| Intune compliance policies | Create and deploy device compliance policies in Intune |