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.
Pingback: Intune Newsletter - 3rd May 2024 - Andrew Taylor
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.
You could use a certificate to connect, see https://helloitsliam.com/2022/04/20/connect-to-microsoft-graph-powershell-using-an-app-registration/ and https://seanmcavinue.net/2021/07/21/using-azure-keyvault-to-secure-graph-api-automation-scripts/ .