Report on Non-Compliant Intune devices

I received a question on the Microsoft Tech Community forum regarding creating a report about non-compliant Intune devices. The report should give insight into what device has what issue with which setting; the person asking the question was already working on a Microsoft Graph script but couldn’t find a suitable way. In this blog post, I will show how to create this report. There are other scripts out there, but I always like a challenge (And Microsoft Graph can be quite a challenge sometimes 😉 )

What are compliant policies in Intune?

“Compliance policies in Intune:

  • Define the rules and settings that users and devices must meet to be compliant.
  • Include actions that apply to devices that are noncompliant. Actions for noncompliance can alert users to the conditions of noncompliance and safeguard data on noncompliant devices.
  • Can be combined with Conditional Access, which can then block users and devices that don’t meet the rules.
  • Can override the configuration of settings that you also manage through device configuration policies. To learn more about conflict resolution for policies, see Compliance and device configuration policies that conflict.”

Source: https://learn.microsoft.com/en-us/mem/intune/protect/device-compliance-get-started

How does the script work?

The script uses Microsft Graph modules and cmdlets to connect to Intune, retrieve all non-compliant devices, and check which setting is marked as non-compliant for that device. You can also go here to create a report and download it. But… That’s not PowerShell 😉

If you run the script for the first time, it will ask you to allow specific Microsoft Graph permissions if needed. It will always check if the necessary Modules are installed and prompt for an Admin account is you’re not already connected to your tenant.

After retrieving the devices and the reasons for their non-compliance, it will report this in your PowerShell console sessions as a table. If needed, you can also choose to save the report as a .csv or .xlsx file using the -outputfile parameter.

Running the script

In the example below, I ran the scripts against my tenant.

Get-IntuneNonCompliantDevices

As you can see, two devices have issues. DESKTOP-CLQ818J has no compliance policy assigned and doesn’t have BitLocker enabled. It reports that for both user and device settings. PSIF-0 only has no Compliance policy assigned.

You can run the script using the -outputfile parameter to create a report in Excel or CSV format. For example:

Get-IntuneNonCompliantDevices -outputfile c:\temp\IntuneNonCompliantDevices.xlsx

The report will look like this:

The GracePeriodUntil column shows #’s because it doesn’t understand 31-12-9999 23:59:59 😉

Wrapping up

In the chapter above, I showed you how the Function works and how you can use it; the report is simple and was sufficient for my needs. The Intune Admin center has a report pane for it here, too, but the Function is easier for my workflow.

The script

Below are the contents of the script. Save it to c:\scripts\Get-IntuneNonCompliantDevices.ps1, and run it in your PowerShell session using “. c:\scripts\Get-IntuneNonCompliantDevices.ps1.” This will enable the Function Get-IntuneNonCompliantDevices in your session, which you can then start using like in the examples above.

function Get-IntuneNonCompliantDevices {
    param(
        [parameter(Mandatory = $false)][string]$outputfile
    )

    #Check if filename is correct
    if ($outputfile) {
        if (-not ($outputfile.EndsWith('.csv') -or $outputfile.EndsWith('.xlsx'))) {
            Write-Warning ("The specified {0} output file should use the .csv or .xlsx extension, exiting..." -f $outputfile)
            return
        }
    
        #Check access to the path, and if the file already exists, append if it does or test the creation of a new one
        if (-not (Test-Path -Path $outputfile)) {
            try {
                New-Item -Path $outputfile -ItemType File -Force:$true -Confirm:$false -ErrorAction Stop | Out-Null
                Remove-Item -Path $outputfile -Force:$true -Confirm:$false | Out-Null
                Write-Host ("Specified {0} filename is correct, and the path is accessible, continuing..." -f $outputfile) -ForegroundColor Green
            }
            catch {
                Write-Warning ("Path to specified {0} filename is not accessible, correct or file is in use, exiting..." -f $outputfile)
                return
            }
        }
        else {
            Write-Warning ("Specified file {0} already exists, overwriting it..." -f $outputfile)
        }
    }

    #Check if necessary modules are installed, install missing modules if not
    if (-not ((Get-Module Microsoft.Graph.Authentication, Microsoft.Graph.Beta.DeviceManagement -ListAvailable).count -eq 2)) {
        Write-Warning ("One or more required modules were not found, installing now...")
        try {
            Install-Module Microsoft.Graph.Authentication, Microsoft.Graph.Beta.DeviceManagement -Confirm:$false -SkipPublisherCheck -Scope CurrentUser -ErrorAction Stop
        }
        catch {
            Write-Warning ("Error installing required modules, exiting...")
            return
        }
    }
    else {
        try {
            Import-Module Microsoft.Graph.Authentication, Microsoft.Graph.Beta.DeviceManagement -ErrorAction Stop
        }
        catch {
            Write-Warning { "Error importing required modules, exiting..." }
            return
        }
    }

    #Connect MgGraph
    try {
        Connect-MgGraph -Scopes 'DeviceManagementManagedDevices.Read.All' -NoWelcome
        Write-Host ("Connected to Microsoft Graph, continuing...") -ForegroundColor Green
    } 
    catch {
        Write-Warning ("Error connecting Microsoft Graph, check Permissions/Account. Exiting...")
        return
    }

    #Gon-compliant devices and determine why they are not compliant
    $total = foreach ($policywithnoncompliance in Get-MgBetaDeviceManagementDeviceCompliancePolicySettingStateSummary | Where-Object { $_.NonCompliantDeviceCount -gt 0 -or $_.ConflictDeviceCount -gt 0 -or $_.NotApplicableDeviceCount -gt 0 }) {
        Foreach ($setting in $policywithnoncompliance) {
            $devices = Get-MgBetaDeviceManagementDeviceCompliancePolicySettingStateSummaryDeviceComplianceSettingState -DeviceCompliancePolicySettingStateSummaryId $setting.id | Where-Object State -EQ NonCompliant
            foreach ($device in $devices) {
                [PSCustomObject]@{
                    State            = $device.State
                    GracePeriodUntil = $device.ComplianceGracePeriodExpirationDateTime
                    DeviceName       = $device.DeviceName
                    DeviceModel      = $device.DeviceModel
                    Setting          = $device.Setting
                    UserOrDevice     = If ($device.UserPrincipalName) { $device.UserPrincipalName } else { $device.DeviceName }
                }
            }
        }
    }

    #Return in table if $outputfile was not specified
    if (-not $outputfile) {
        $total | Sort-Object State, DeviceName, Setting, UserOrDevice | Format-Table -AutoSize
    }

    #Export results to either CSV of XLSX, install ImportExcel module if needed
    if ($outputfile.EndsWith('.csv')) {
        try {
            New-Item -Path $outputfile -ItemType File -Force:$true -Confirm:$false -ErrorAction Stop | Out-Null
            $total | Sort-Object State, DeviceName, Setting, UserOrDevice | Export-Csv -Path $outputfile -Encoding UTF8 -Delimiter ';' -NoTypeInformation
            Write-Host ("`nExported results to {0}" -f $outputfile) -ForegroundColor Green
        }
        catch {
            Write-Warning ("`nCould not export results to {0}, check path and permissions" -f $outputfile)
            return
        }
    }
    
    if ($outputfile.EndsWith('.xlsx')) {
        try {
            #Test path and remove empty file afterwards because xlsx is corrupted if not
            New-Item -Path $outputfile -ItemType File -Force:$true -Confirm:$false -ErrorAction Stop | Out-Null
            Remove-Item -Path $outputfile -Force:$true -Confirm:$false | Out-Null
            
            #Install ImportExcel module if needed
            write-host ("`nChecking if ImportExcel PowerShell module is installed...") -ForegroundColor Green
            if (-not (Get-Module -ListAvailable | Where-Object Name -Match ImportExcel)) {
                Write-Warning ("`nImportExcel PowerShell Module was not found, installing...")
                Install-Module ImportExcel -Scope CurrentUser -Force:$true
                Import-Module ImportExcel
            }
            #Export results to path
            $total | Sort-Object State, DeviceName, Setting, UserOrDevice | Export-Excel -AutoSize -BoldTopRow -FreezeTopRow -AutoFilter -Path $outputfile
            Write-Host ("`nExported results to {0}" -f $outputfile) -ForegroundColor Green
        }
        catch {
            Write-Warning ("`nCould not export results to {0}, check path and permissions" -f $outputfile)
            return
        }
    }
}

Download the script(s) from GitHub here.

3 thoughts on “Report on Non-Compliant Intune devices

  1. Pingback: Intune Newsletter - 3rd May 2024 - Andrew Taylor

  2. The most important part, is how do we authenticate via client secret to be able to execute this script?
    We are only being given access to Graph via Azure Application, thus if we want to automate the reports, using someone’s admin account is a no good decision.

    I am looking to find a script that will use Azure application to authenticate and retrieve the non-compliant devices and their non-compliant setting, but I am struggling to find something and this looks like a copy of a script fluctuating in the internet from 2015.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.