Automatically maintain Microsoft Intune Compliance and App Protection policies with the latest supported minimum OS versions, ensuring devices access is restricted based on up-to-date device security.
IntuneComplianceMaintainer is a PowerShell automation script that keeps your Intune compliance and app-protection policies up-to-date with the latest OS version requirements across all major platforms. By leveraging the endoflife.date API and Microsoft Graph Windows Update Catalog data sources, it ensures your organisation maintains security posture while respecting configurable cadence periods for gradual rollout.
- Multi-Platform Support: iOS, iPadOS, macOS, Android, and Windows
- Dual Policy Types: Updates both compliance and app-protection policies
- Flexible Authentication: Supports Managed Identity (Azure Automation), App Registration with Certificate, or App Registration with Secret (including Azure Key Vault integration)
- Cadence Control: Configurable delay between update release and policy enforcement to account for update rollout schedule, with optional force-apply override
- Android Patch Level: Enforces minimum Android security patch level alongside OS version; targets the oldest maintained Android version for
osMinimumVersion(so any supported release passes) and derives the monthly patch date from Android's patch schedule (1st of each month) - Windows Advanced Options: Support for specific build numbers, update classifications, version ranges, and selectable app-protection target build (lowest by default)
- Safety Features: Optional downgrade protection and dry-run mode (downgrade check covers both OS version and patch level independently)
- Retry Logic: Built-in retry mechanism for API resilience
- Comprehensive Logging: Verbose logging with detailed result output
- PowerShell 5.1 or later
- Microsoft Graph API permissions:
DeviceManagementConfiguration.ReadWrite.All(for Compliance policies)DeviceManagementApps.ReadWrite.All(for App Protection policies)WindowsUpdates.ReadWrite.All(for Windows Update Catalog queries)
- For Managed Identity authentication:
Az.Accountsmodule (pre-installed in Azure Automation) - For Key Vault integration:
Az.KeyVaultmodule and appropriate Key Vault access - For certificate authentication: Certificate installed in CurrentUser\My store
Configure one of the following authentication modes:
# Managed Identity (recommended for Azure Automation)
$AuthMode = "ManagedIdentity"
$TenantId = "your-tenant-id"
# Optional: specify user-assigned managed identity client ID (leave blank for system-assigned)
$UserAssignedClientId = ""For Azure Automation:
- Enable system-assigned or user-assigned managed identity on the Automation Account
- Grant the identity necessary Graph API application permissions, or utilise the Grant-ManagedIdentityGraphAppRoles.ps1 script in this repo to automate this.
- Ensure Az.Accounts module is available (usually pre-installed in Azure Automation)
# App Registration with Certificate
$AuthMode = "AppRegCert"
$TenantId = "your-tenant-id"
$ClientId = "your-client-id"
$CertThumbprint = "certificate-thumbprint"# App Registration with Secret
$AuthMode = "AppRegSecret"
$TenantId = "your-tenant-id"
$ClientId = "your-client-id"
$ClientSecret = "your-client-secret"
# Optional: Use Key Vault
$KeyVaultName = "your-keyvault-name"
$KeyVaultSecretName = "your-secret-name"# Cadence: days to wait after release before enforcing
$CadenceDays = 14
# Define policy IDs to update (leave empty to skip platform)
$CompliancePolicies = @{
iOS = @("policy-guid-1", "policy-guid-2")
iPadOS = @("policy-guid-3")
macOS = @()
Android = @("policy-guid-4")
Windows = @("policy-guid-5")
}
$AppProtectionPolicies = @{
iOS = @("policy-guid-6")
iPadOS = @()
Android = @("policy-guid-7")
Windows = @("policy-guid-8")
}# Specify Windows build numbers (e.g., 26100 for 24H2, 26200 for 25H2)
$WindowsBuildNumbers = @("26100", "26200")
# Update classification: security or nonSecurity
$WindowsUpdateClassification = "nonSecurity"
# Number of recent cumulative updates to include
$WindowsNumberOfUpdates = 1
# Allow devices on newer builds (e.g Preview Updates)
$WindowsAllowNewerBuilds = $true
# Compliance mode: Ranges or MinimumVersion
$WindowsComplianceMode = "Ranges"
# App Protection target when using Ranges: Lowest (default) or Highest build in the range
$WindowsAppProtectionTarget = "Lowest"# Optional: enforce a specific minimum Android version instead of the oldest maintained
# e.g. "16" requires Android 16+ on all targeted policies
# Leave blank to use the oldest currently maintained version automatically
# Must be a currently maintained release — the script throws if the value has gone EOL
$AndroidMinimumVersion = ""# Prevent lowering existing minimum OS version
$AllowDowngrade = $false
# Dry run mode (report changes without applying)
$DryRun = $true
# Force apply even if cadence/effective date hasn't elapsed
$ForceApply = $false- Configure authentication and policy IDs in the script
- Run the script:
.\IntuneComplianceMaintainer.ps1Start with dry-run mode to preview changes:
# In script configuration:
$DryRun = $trueReview the output to ensure expected behavior, then disable dry-run:
$DryRun = $falseDeploy to Azure Automation for scheduled runs:
- Create an Automation Account with Managed Identity
- Assign required Graph API permissions to the Managed Identity
- Import the script as a runbook
- Configure schedule (e.g., daily or weekly)
- Queries endoflife.date API for the latest OS version
- Calculates effective date based on release date + cadence days
- If effective date has passed:
- Updates compliance policies with
osMinimumVersion - Updates app-protection policies with
minimumRequiredOsVersion(iOS/iPadOS only; macOS has no app-protection support)
- Updates compliance policies with
- Queries endoflife.date API to determine all currently maintained Android versions (e.g. 14, 15, 16, 17)
- Sets
osMinimumVersion/minimumRequiredOsVersionto the oldest maintained version by default — devices running any supported Android release satisfy the version check. Set$AndroidMinimumVersionto enforce a higher floor (e.g."16"for Android 16+) - Derives the monthly security patch date from Android's fixed release schedule (1st of each month) and applies cadence from that date
- If effective date has passed:
- Updates compliance policies with
osMinimumVersionandminAndroidSecurityPatchLevel - Updates app-protection policies with
minimumRequiredOsVersionandminimumRequiredPatchVersion - Both patch level fields use
YYYY-MM-DDformat
- Updates compliance policies with
- Downgrade protection evaluates OS version and patch level independently — a policy with a current OS version but stale patch level will still be updated
- Queries Microsoft Graph Windows Update Catalog for recent cumulative updates
- Calculates effective date based on release date + cadence days
- Supports two compliance modes:
- Ranges mode (recommended for multiple builds): Updates
validOperatingSystemBuildRanges. When using Ranges, app-protection targets the lowest build in the range by default; set$WindowsAppProtectionTarget = "Highest"to target the highest build instead. - MinimumVersion mode: Updates
osMinimumVersionwith highest build version
- You can force updates to apply even if the cadence/effective date hasn't elapsed by setting
$ForceApply = $true(applies to both compliance and app-protection). Use cautiously in production.
The script provides detailed logging and a summary table:
[2025-12-16 10:30:15][INFO] Starting run: DryRun=True; AllowDowngrade=False
[RESULT][iOS/Compliance] Compliance-iOS-Production: action=WouldUpdate; current=17.6.1; target=18.2.1; effective=2025-12-18
[RESULT][Android/Compliance] Compliance-Android-Corp: action=WouldUpdate; current=14; target=14; patch=2026-07-01; release=7/1/2026; effective=7/15/2026
[RESULT][Android/AppProtection] MAM-Android-Corp: action=WouldUpdate; current=14; target=14; patch=2026-07-01; release=7/1/2026; effective=7/15/2026
[RESULT][Windows/Compliance] Compliance-Windows-Corp: action=WouldUpdate; current=10.0.26100.2314-10.0.26100.2454; target=10.0.26100.2605-10.0.26100.9999; setting=Range
Platform Type Setting Name Current Target Action EffectiveDate
-------- ---- ------- ---- ------- ------ ------ -------------
iOS Compliance MinimumVersion Compliance-iOS-Production 17.6.1 18.2.1 WouldUpdate 12/18/2025
Android Compliance MinimumVersion Compliance-Android-Corp 14 14 WouldUpdate 7/15/2026
Android AppProtection MinimumVersion MAM-Android-Corp 14 14 WouldUpdate 7/15/2026
Windows Compliance Range Compliance-Windows-Corp 10.0.26100... 10.0.26100... WouldUpdate 12/17/2025
Note: For Android, the
CurrentandTargetcolumns reflectosMinimumVersion. Thepatch=field in the verbose log shows theminAndroidSecurityPatchLevel/minimumRequiredPatchVersionvalue being enforced. Where the OS version is already at the target, the update still proceeds if the patch level is stale.
- Updated: Policy was successfully updated
- WouldUpdate: Policy would be updated (dry-run mode)
- Skipped: Current version meets or exceeds target (downgrade protection)
- NotEffectiveYet: Cadence period hasn't elapsed
- NoData: No version data available from API
- Error: Update failed (see error details in output)
- Start with Dry-Run: Always test with
$DryRun = $truefirst - Gradual Rollout: Use appropriate
$CadenceDaysvalue for your environment (2-7 days recommended) - Downgrade Protection: Keep
$AllowDowngrade = $falseunless intentionally reverting - Selective Updates: Only specify policy IDs for platforms you want to automate
- Monitor Logs: Enable
$VerboseLogging = $truefor troubleshooting - Schedule Appropriately: Run daily or weekly depending on your compliance requirements
- Test Authentication: Verify Graph API permissions before production deployment
- Azure Automation: If using Managed Identity, enable it on the Automation Account (system-assigned or set
$UserAssignedClientId) and grant Graph app roles (DeviceManagementConfiguration.ReadWrite.All,DeviceManagementApps.ReadWrite.All,WindowsUpdates.ReadWrite.All).
- Build numbers correspond to Windows versions (e.g., 26100 = 24H2)
- Both compliance and app-protection policies supported
- Cadence calculated from latest patch release date
- Supports compliance policies only
- App-protection not available for macOS
- Supports both compliance and app-protection policies
osMinimumVersionis set to the oldest currently maintained Android version by default (e.g. 14), so devices on any supported release satisfy the compliance check; the maintained set is determined dynamically from the endoflife.date API- Set
$AndroidMinimumVersionto a specific version (e.g."16") to enforce a higher minimum. The value is validated against the live maintained list at runtime — if the version has gone EOL the script throws immediately rather than applying a stale policy - Security patch level is updated monthly, aligned with Android's fixed patch release schedule (1st of each month); cadence is applied from that date
- Compliance policies: patch level written to
minAndroidSecurityPatchLevel - App Protection policies: patch level written to
minimumRequiredPatchVersion - Both patch level fields use
YYYY-MM-DDformat (e.g.2026-07-01)
- Verify Graph API permissions are granted and admin-consented
- For Managed Identity: Ensure identity is enabled and permissions assigned
- For certificate auth: Confirm certificate is in CurrentUser\My store and thumbprint is correct
- For Key Vault: Verify access policies and secret exists
- Check retry count and delay settings
- Verify policy IDs are correct and accessible
- Ensure token has sufficient permissions for both read and write operations
- Verify internet connectivity to endoflife.date API
- For Windows: Confirm build numbers exist in Windows Update Catalog
- Check verbose logs for API response details
- Store secrets in Azure Key Vault when using AppRegSecret mode
- Use Managed Identity for Azure Automation scenarios
- Apply least-privilege Graph API permissions
- Review audit logs for policy changes
- Test in non-production environment first
- v1.2 (2026-07-01): Android multi-version support (targets oldest maintained release for
osMinimumVersion); optional$AndroidMinimumVersionoverride with live EOL validation; monthly Android security patch level enforcement (minAndroidSecurityPatchLevel/minimumRequiredPatchVersion); downgrade protection now evaluates OS version and patch level independently;Get-AzAccessTokenSecureString compatibility for Az module 12.0+; Windows App Protection cadence-check restructure to prevent false Error results - v1.1 (2025-12-19): Added Azure Automation managed identity support, force-apply option, Windows app protection target selection, release date tracking
- v1.0 (2025-12-15): Initial release
Thanks to Max Weber for inspiration on Windows Update Catalog Graph API usage in this blog
Use at your own discretion. Review and test thoroughly before production deployment.
This script modifies production Intune policies. Always test in a non-production environment and use dry-run mode before live deployment. The author assumes no liability for unintended changes or impacts to your environment.