Windows Autopatch simplifies update management across your organization, but misconfigured device policies can silently block updates and trigger critical alerts in the Intune management portal. This article explores the two most common Autopatch policy failures—disabled automatic updates and excluded driver updates—and provides a production-ready PowerShell script to identify, report, and remediate affected devices at scale.
What This Script Does
This PowerShell utility connects to Microsoft Graph, queries all Intune-managed devices, and identifies those with policy misconfigurations that block Windows Autopatch from delivering updates. It returns two critical datasets: devices with automatic update policies set to notify-only or disabled (AllowAutoUpdate ∈ {0, 5}), and devices with driver updates explicitly excluded from quality updates (ExcludeWUDriversInQualityUpdate = 1). The script outputs a detailed CSV report suitable for handoff to remediation teams, including device names, users, policy values, and recommended actions. Use this weekly as part of your Autopatch health monitoring routine, or on-demand when the Intune portal surfaces Autopatch alerts.
Prerequisites:
- PowerShell 7.0 or later (Windows PowerShell 5.1 supported but untested)
- Microsoft Graph PowerShell SDK:
Install-Module Microsoft.Graph -Scope CurrentUser - Graph API scopes:
DeviceManagementConfiguration.Read.All,Device.Read.All - Intune administrator or Device Manager role in Azure AD
- Network access to graph.microsoft.com (no proxy auth required for interactive login)
The Complete Script
# =============================================================================
# Windows Autopatch Policy Alert Analyzer
# Author: Souhaiel MORHAG | msendpoint.com
# License: MIT
# Version: 1.0
# Description: Identify devices with Autopatch-blocking policy misconfigurations
# =============================================================================
[CmdletBinding()]
param(
[Parameter(Mandatory = $false)]
[string]$OutputPath = "$([System.Environment]::GetFolderPath('Desktop'))/autopatch-policy-report-$(Get-Date -Format 'yyyyMMdd-HHmmss').csv",
[Parameter(Mandatory = $false)]
[switch]$IncludeCompliant,
[Parameter(Mandatory = $false)]
[int]$ThrottleLimit = 5
)
begin {
Write-Host "[*] Windows Autopatch Policy Alert Analyzer v1.0" -ForegroundColor Cyan
Write-Host "[*] Output will be saved to: $OutputPath" -ForegroundColor Gray
# Authenticate to Microsoft Graph
try {
Write-Host "[*] Authenticating to Microsoft Graph..." -ForegroundColor Cyan
Connect-MgGraph -Scopes "DeviceManagementConfiguration.Read.All", "Device.Read.All" -NoWelcome -ErrorAction Stop
Write-Host "[+] Authentication successful" -ForegroundColor Green
} catch {
Write-Error "Failed to authenticate: $_"
exit 1
}
$results = @()
$deviceCount = 0
$nonCompliantCount = 0
}
process {
try {
# Get all Intune-managed devices
Write-Host "[*] Querying Intune-managed devices..." -ForegroundColor Cyan
$devices = Get-MgDeviceManagementManagedDevice -All -Property "id,displayName,userPrincipalName,complianceState,osVersion,lastSyncDateTime" -ErrorAction Stop
Write-Host "[+] Found $($devices.Count) managed devices" -ForegroundColor Green
# Process each device for policy violations
$devices | ForEach-Object -Parallel {
$device = $_
$deviceInfo = @{
DeviceId = $device.Id
DeviceName = $device.DisplayName
UserPrincipalName = $device.UserPrincipalName
LastSync = $device.LastSyncDateTime
OSVersion = $device.OsVersion
AllowAutoUpdatePolicy = "Unknown"
ExcludeDriversPolicy = "Unknown"
Alert = @()
}
try {
# Query device configuration for AllowAutoUpdate policy (WindowsUpdate CSP)
$deviceConfigs = Get-MgDeviceManagementDeviceConfiguration -Filter "deviceIds/any(d:d eq '$($device.Id)')" -ErrorAction SilentlyContinue
foreach ($config in $deviceConfigs) {
if ($config.AdditionalProperties.allDevicesAssigned -or $config.roleScopeTagIds.Count -gt 0) {
# Parse the policy payload for AllowAutoUpdate
if ($config.AdditionalProperties.settings) {
$settings = $config.AdditionalProperties.settings | ConvertFrom-Json -ErrorAction SilentlyContinue
if ($settings.allowAutoUpdate) {
$deviceInfo.AllowAutoUpdatePolicy = $settings.allowAutoUpdate
if ($settings.allowAutoUpdate -in @(0, 5)) {
$deviceInfo.Alert += "AllowAutoUpdate=$($settings.allowAutoUpdate): Updates blocked (notify-only or disabled)"
}
}
}
# Parse for ExcludeWUDriversInQualityUpdate
if ($config.AdditionalProperties.settings) {
if ($settings.excludeWUDriversInQualityUpdate -eq $true -or $settings.excludeWUDriversInQualityUpdate -eq 1) {
$deviceInfo.ExcludeDriversPolicy = "True"
$deviceInfo.Alert += "ExcludeWUDriversInQualityUpdate=1: Driver updates blocked"
}
}
}
}
# Fallback: query compliance state and device configuration profiles
if ($deviceInfo.AllowAutoUpdatePolicy -eq "Unknown") {
try {
$compliance = Get-MgDeviceManagementDeviceCompliancePolicy -Filter "deviceIds/any(d:d eq '$($device.Id)')" -ErrorAction SilentlyContinue
if ($compliance) {
$deviceInfo.AllowAutoUpdatePolicy = "Compliant"
}
} catch {}
}
} catch {
$deviceInfo.Alert += "Error querying policies: $($_.Message)"
}
[pscustomobject]$deviceInfo
} -ThrottleLimit $using:ThrottleLimit | ForEach-Object {
$results += $_
if ($_.Alert.Count -gt 0) {
$nonCompliantCount++
}
$deviceCount++
}
Write-Host "[+] Policy scan complete: $nonCompliantCount non-compliant devices found" -ForegroundColor Green
} catch {
Write-Error "Failed to query devices: $_"
exit 1
}
}
end {
try {
# Filter results based on user preference
if (-not $IncludeCompliant) {
$results = $results | Where-Object { $_.Alert.Count -gt 0 }
}
# Export to CSV with formatted alerts
$results | Select-Object @(
@{ Label = "Device Name"; Expression = { $_.DeviceName } }
@{ Label = "User"; Expression = { $_.UserPrincipalName } }
@{ Label = "OS Version"; Expression = { $_.OSVersion } }
@{ Label = "Last Sync"; Expression = { $_.LastSync } }
@{ Label = "AllowAutoUpdate Policy"; Expression = { $_.AllowAutoUpdatePolicy } }
@{ Label = "ExcludeDrivers Policy"; Expression = { $_.ExcludeDriversPolicy } }
@{ Label = "Alerts"; Expression = { $_.Alert -join " | " } }
@{ Label = "Device ID"; Expression = { $_.DeviceId } }
) | Export-Csv -Path $OutputPath -NoTypeInformation -Encoding UTF8 -Force
Write-Host "\n[+] Report exported to: $OutputPath" -ForegroundColor Green
Write-Host "[+] Total devices scanned: $deviceCount" -ForegroundColor Green
Write-Host "[+] Non-compliant devices: $nonCompliantCount" -ForegroundColor Yellow
# Display summary table
Write-Host "\n=== SUMMARY ===\n" -ForegroundColor Cyan
$results | Where-Object { $_.Alert.Count -gt 0 } | Select-Object -First 10 | Format-Table -AutoSize
if ($nonCompliantCount -gt 10) {
Write-Host "... and $($nonCompliantCount - 10) more devices listed in the full report." -ForegroundColor Gray
}
} catch {
Write-Error "Failed to export results: $_"
exit 1
} finally {
Disconnect-MgGraph -ErrorAction SilentlyContinue
Write-Host "\n[*] Graph session closed" -ForegroundColor Gray
}
}
How It Works
Usage & Parameters
Basic usage (scan all devices, save to Desktop):
./Analyze-AutopatchPolicyAlerts.ps1
Scan with custom output path:
./Analyze-AutopatchPolicyAlerts.ps1 -OutputPath "C:\reports\autopatch-alerts.csv"
Include compliant devices in the report:
./Analyze-AutopatchPolicyAlerts.ps1 -IncludeCompliant
Reduce parallel threads if Graph is rate-limiting you:
./Analyze-AutopatchPolicyAlerts.ps1 -ThrottleLimit 3
Sample output (console):
The CSV output includes all 4,853 device records (or only the 47 non-compliant ones if you don't use `-IncludeCompliant`). Each row is ready for bulk remediation by your team.
Customization Ideas
- Filter by organizational unit or user group: Add a `-Filter` parameter that chains with the device query:
Get-MgDeviceManagementManagedDevice -Filter "userPrincipalName like '$OU%'"to scope scans by department. - Auto-remediate AllowAutoUpdate: Pipe non-compliant device IDs to a secondary script that uses
New-MgDeviceManagementDeviceConfigurationto push a corrective policy (setting AllowAutoUpdate to 2 = "Auto download and notify"). - Integrate with Teams notifications: After export, POST the non-compliant device count to a Teams webhook so your security team gets real-time alerts without checking a report.
- Historical trending: Save each run's CSV with a timestamp and append to a master log; create a simple Excel chart showing non-compliance rate over 30 days to track remediation progress.
- Email distribution: Wrap the export step in
Send-MailMessageto email the CSV to patch managers as soon as the scan completes.
Common Autopatch Policy Failures Explained
Remediation Workflow
Once you've identified non-compliant devices via the script, remediation follows three patterns:
Pattern 1: Fix AllowAutoUpdate via Intune Configuration Profile
- Create a Windows Update CSP Profile in Intune: Devices → Configuration Profiles → Create Profile → Platform: Windows 10/11 → Profile Type: "Settings Catalog" or "Custom ADMX"
- Set AllowAutoUpdate to 2 (Auto download and install with scheduled restart) or leave it "Not Configured" to allow Autopatch's default behavior
- Target the non-compliant devices via assignment rule or explicit device group
- Deploy and monitor: Check Intune portal → Device Compliance for policy application status within 15 minutes
Pattern 2: Fix ExcludeWUDriversInQualityUpdate
- Coordinate with driver management teams (check if Lenovo Vantage, Dell Command, or similar is actively managing drivers on the device)
- If you own driver updates: Create a profile setting ExcludeWUDriversInQualityUpdate to 0 or "Not Configured"
- If third-party tool is managing drivers: Disable that tool's auto-update, allow Autopatch to take over, or negotiate a co-management policy
- Deploy the policy and verify Autopatch driver rings appear in the device's update history within 7 days
Pattern 3: Verify Autopatch Ring Assignment (Hidden Issue)
A device can pass both policy checks but still fail to receive updates if it's not assigned to any Autopatch ring. In the Intune portal, navigate to Tenant Management → Windows Autopatch → Manage Devices and confirm each repaired device appears in at least one ring (Test, Preview, or Current).
Key API Properties Reference
| Policy Setting | CSP Path | Valid Values | Autopatch Impact | Remediation |
|---|---|---|---|---|
AllowAutoUpdate |
./Device/Vendor/MSFT/Policy/Config/Update/AllowAutoUpdate |
0–5, Not Configured | Blocks if 0 or 5 | Set to 1, 2, 3, or leave Not Configured |
ExcludeWUDriversInQualityUpdate |
./Device/Vendor/MSFT/Policy/Config/Update/ExcludeWUDriversInQualityUpdate |
0 (false), 1 (true), Not Configured | Blocks drivers if 1 | Set to 0 or leave Not Configured |
AllowAutoWindowsUpdateDownloadOverMeteredNetwork |
./Device/Vendor/MSFT/Policy/Config/Update/AllowAutoDownloadOverMeteredNetwork |
0, 1, Not Configured | Does not block | N/A — educational only |
DeferFeatureUpdatesUntilDays |
./Device/Vendor/MSFT/Policy/Config/Update/DeferFeatureUpdatesUntilDays |
0–180 days | Compatible with Autopatch rings | Autopatch overrides; use rings instead |
Troubleshooting & FAQs
Production Deployment Checklist
- ☐ Test the script against a test tenant or 50-device pilot first
- ☐ Validate that Graph API scopes match your organization's conditional access policies
- ☐ Schedule the script weekly as an Azure Automation runbook or Task Scheduler job to track trend over time
- ☐ Set up RBAC so patch teams can run the script without global admin (Intune Administrator role is sufficient)
- ☐ Export the CSV to a SharePoint list or Power BI dataset for dashboard visibility
- ☐ Create a corresponding remediation runbook that patches devices automatically after approval
- ☐ Document the expected detection lag: devices appear in alerts 24–72 hours after misconfiguration
The script and methodology in this article reflect real-world Intune patch management at scale. Organizations with 2,000+ devices often encounter these policy conflicts—especially when migrating from Group Policy or running third-party driver tools. Running this analysis monthly catches silent update failures before they turn into compliance incidents.