Skip to main content

Command Palette

Search for a command to run...

Fix for mismatch between Intune and Azure device compliance status

Updated
5 min read

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.

💡
You can check if you are affected using using PowerShell code mentioned in the Tips section

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

💡
Before we start I want to say that compliance can be set via the AADInternals module too. But my solution supports FIDO auth and uses native commands so is more readable.

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!"
}

More from this blog

D

Do it PowerShell way :)

78 posts

With over 15 years of experience as a system administrator, I have a passion for automating workflows using PowerShell. I believe in sharing my creations with the community. Why not, right? :)