Fix for mismatch between Intune and Azure device compliance status
It happened to us several times that Azure showed company devices as non-compliant, but Intune showed them as compliant. And this wasn’t fixed over time.
This is quite a problem in case, you require device compliance in your Azure Conditional policies a.k.a. users will be denied access to protected resources.
Internet says that device compliance status is synchronized to Azure only when a change occurs. So in case, something unexpected happens on the cloud side, it can happen that Azure doesn’t retrieve new compliance data.
In such case, you have two options:
force another compliance status change on the device (by deploying a compliance policy that cannot be fulfilled and then removing such assignment to get a compliant device again)
use API to set device compliance state as required
I will focus on the latter method because it doesn’t take hours to fix the issue, but seconds.
Solution
You can fix compliance mismatch issues on-demand, but I prefer an automated solution to make sure any future problems will be fixed automatically.
Therefore my solution uses
Azure Automation to run the PowerShell code
Managed identity with appropriate Graph API permissions
Device.ReadWrite.All,DeviceManagementManagedDevices.Read.All
Below is the PowerShell code that will create & set Automation Account, but you can do all the stuff manually. It uses a combination of the official Az.Automation and my custom AzureResourceStuff modules.
Install-Module Az.Accounts, Az.Automation, AzureResourceStuff, AzureApplicationStuff
Connect-AzAccount
$automationAccountName = "DeviceComplianceMismatchFixer"
$azureLocation = "westeurope"
$automationResourceGroupName = "<chooseOne>"
$customRuntimeName = "PSH72"
$runbookName = "fixer"
$scheduleName = "every_hour"
# create Azure Automation Account
"Creating Automation Account"
$automationAccount = New-AzAutomationAccount -Name $automationAccountName -Location $azureLocation -ResourceGroupName $automationResourceGroupName -AssignSystemIdentity
# create Runtime Environment
$param = @{
runtimeName = $customRuntimeName
runtimeLanguage = "PowerShell"
runtimeVersion = "7.2"
resourceGroupName = $automationResourceGroupName
automationAccountName = $automationAccountName
}
"Creating custom environment runtime"
$customRuntime = New-AzureAutomationRuntime @param
# add required modules to the runtime
'Microsoft.Graph.Authentication', 'Microsoft.Graph.Identity.DirectoryManagement', 'Microsoft.Graph.DeviceManagement' | % {
$param = @{
resourceGroupName = $automationResourceGroupName
automationAccountName = $automationAccountName
runtimeName = $customRuntimeName
moduleName = $_
dontWait = $true
}
if ($moduleVersion) {
$param.moduleVersion = $moduleVersion
}
"Adding PSGallery module '$_'"
New-AzureAutomationRuntimeModule @param
}
# create runbook
$param = @{
Name = $runbookName
Type = "PowerShell"
ResourceGroupName = $automationResourceGroupName
AutomationAccountName = $automationAccountName
}
"Creating runbook"
$runbook = New-AzAutomationRunbook @param
# set runbook runtime
"Setting runbook runtime to '$customRuntimeName'"
$null = Set-AzureAutomationRunbookRuntime -resourceGroupName $automationResourceGroupName -automationAccountName $automationAccountName -runtimeName $customRuntimeName -runbookName $runbookName
# set runbook code
"Setting runbook content"
$runbookContent = @'
# fix mismatch in compliance status between Intune and Azure (Intune wins btw)
# requires scope: Device.ReadWrite.All, DeviceManagementManagedDevices.Read.All
$WHATIF = $false # set to $true if you don;t want to do any changes to your environment!
Connect-MgGraph -Identity
$azureDeviceList = Get-MgDevice -All -Property DeviceId, Id, IsCompliant
foreach ($device in (Get-MgDeviceManagementManagedDevice -All -Property DeviceName, AzureAdDeviceId, ComplianceState -Filter "ManagedDeviceOwnerType eq 'company'" | sort DeviceName)) {
$deviceName = $device.DeviceName
$intuneCompliance = $device.ComplianceState
$id = $device.AzureAdDeviceId
"Processing $deviceName ($id)"
if ($intuneCompliance -in "compliant", "inGracePeriod") {
$intuneIsCompliant = $true
} else {
$intuneIsCompliant = $false
}
$aadDevice = $azureDeviceList | ? DeviceId -EQ $id
if (!$aadDevice) {
" - Corresponding Azure record is missing!"
continue
}
$aadDeviceId = $aadDevice.Id
$azureIsCompliant = $aadDevice.IsCompliant
if ($intuneIsCompliant -ne $azureIsCompliant) {
# convert boolean to match intune status message
switch ($azureIsCompliant) {
$true { $azureIsCompliant = "compliant" }
$false { $azureIsCompliant = "noncompliant" }
}
" - $deviceName has mismatch in compliance status! Intune: $intuneCompliance, Azure: $azureIsCompliant. Fixing"
if ($azureIsCompliant -eq $null) {
" - Unable to fix. Azure object doesn't have any compliance value a.k.a. is invalid."
continue
}
if (!$WHATIF) {
$body = @{
isCompliant = $intuneIsCompliant
}
$url = "https://graph.microsoft.com/v1.0/devices/$aadDeviceId"
# set compliance status
Invoke-MgGraphRequest -Method PATCH -Uri $url -Body ($body | ConvertTo-Json)
# check compliance status
$device = Invoke-MgGraphRequest -Method GET -Uri $url
if ($device.isCompliant -ne $intuneIsCompliant) {
throw "Compliance wasn't changed!"
}
}
}
}
'@
$null = Set-AzureAutomationRunbookContent -runbookName $runbookName -resourceGroupName $automationResourceGroupName -automationAccountName $automationAccountName -content $runbookContent -publish
# set runbook schedule
$param = @{
Name = $scheduleName
StartTime = (Get-Date).AddMinutes(10)
ResourceGroupName = $automationResourceGroupName
AutomationAccountName = $automationAccountName
HourInterval = 1
}
"Creating schedule '$scheduleName'"
$schedule = New-AzAutomationSchedule @param
"Linking schedule '$scheduleName' to the runbook"
$scheduleLink = Register-AzAutomationScheduledRunbook -ScheduleName $scheduleName -RunbookName $runbookName -ResourceGroupName $automationResourceGroupName -AutomationAccountName $automationAccountName
# grant Automation Account System Managed Identity required permissions
Grant-AzureServicePrincipalPermission -servicePrincipalId '<idOfTheManagedIdentity>' -permissionType application -permissionList Device.ReadWrite.All, DeviceManagementManagedDevices.Read.All
And there you have it! Your automation is now set up to address any future compliance mismatches 😎
You can check the runbook job output in the Azure portal to see what issues were fixed etc.
Tips
How to find devices with compliance status mismatch
# Find devices with compliance status mismatch
'Microsoft.Graph.Authentication', 'Microsoft.Graph.Identity.DirectoryManagement', 'Microsoft.Graph.DeviceManagement' | % {
Import-Module $_
}
Connect-MgGraph
$azureDeviceList = Get-MgDevice -All -Property DeviceId, Id, IsCompliant
foreach ($device in (Get-MgDeviceManagementManagedDevice -All -Property DeviceName, AzureAdDeviceId, ComplianceState -Filter "ManagedDeviceOwnerType eq 'company'" | sort DeviceName)) {
$deviceName = $device.DeviceName
$intuneCompliance = $device.ComplianceState
$id = $device.AzureAdDeviceId
"Processing $deviceName ($id)"
if ($intuneCompliance -in "compliant", "inGracePeriod") {
$intuneIsCompliant = $true
} else {
$intuneIsCompliant = $false
}
$aadDevice = $azureDeviceList | ? DeviceId -EQ $id
if (!$aadDevice) {
" - Corresponding Azure record is missing!"
continue
}
$aadDeviceId = $aadDevice.Id
$azureIsCompliant = $aadDevice.IsCompliant
if ($intuneIsCompliant -ne $azureIsCompliant) {
# convert boolean to match intune status message
switch ($azureIsCompliant) {
$true { $azureIsCompliant = "compliant" }
$false { $azureIsCompliant = "noncompliant" }
}
" - $deviceName has mismatch in compliance status! Intune: $intuneCompliance, Azure: $azureIsCompliant. Fixing"
}
}
How to set device compliance status
# Set device compliance status in Azure
# objectId of the Azure device
$aadDeviceId = '<deviceObjectId>'
# what should be the compliance status
$isCompliant = $true # $false
Connect-MgGraph -Scope 'Device.ReadWrite.All', 'DeviceManagementManagedDevices.Read.All'
$body = @{
isCompliant = $isCompliant
}
$url = "https://graph.microsoft.com/v1.0/devices/$aadDeviceId"
# set compliance status
Invoke-MgGraphRequest -Method PATCH -Uri $url -Body ($body | ConvertTo-Json)
# check compliance status
$device = Invoke-MgGraphRequest -Method GET -Uri $url
if ($device.isCompliant -ne $isCompliant) {
throw "Compliance wasn't changed!"
}





