Intune macOS custom compliance arrived in service release 2607, which finally puts the Mac estate level with Windows and Linux. If you have been approximating Mac compliance with the built-in settings and quietly accepting the gaps, that workaround is over. The Windows equivalent, and the sync mechanics behind both, are in forcing an Intune sync.
What Intune macOS custom compliance actually is
A shell script that emits JSON, plus a JSON rules file that says what the values should be. Intune runs the script, reads the output, compares it against the rules, and surfaces the result alongside the built-in compliance settings in the normal reporting. The pattern is identical to Windows with PowerShell and to Linux, so if you have written one before there is nothing new to learn. Microsoft covers the shared pattern in Use custom compliance settings in Microsoft Intune.
The point is that it evaluates things the built-in settings never covered. Configuration profiles you care about, a specific agent being present and running, a setting your security team asked for that Microsoft has not shipped a toggle for. Compliance state only matters once it reaches Entra, which is where Conditional Access policy design picks it up, and device based Conditional Access needs at least Entra ID P1.
The script
One rule: it must write a single flat JSON object to standard output and nothing else. That constraint is stated in the discovery script reference and it is absolute. Any stray echo, warning or debug line and the whole evaluation fails rather than returning a wrong answer, which is at least an honest failure mode.
#!/bin/bash
# Intune custom compliance for macOS.
# Emits ONE flat JSON object on stdout. Nothing else, ever.
# FileVault
if fdesetup status | grep -q “FileVault is On”; then
filevault=”true”
else
filevault=”false”
fi
# Firewall: 0 = off, 1 = on for specific services, 2 = on for essential services
fw=$(/usr/libexec/PlistBuddy -c “Print :globalstate”
/Library/Preferences/com.apple.alf.plist 2>/dev/null)
[ -z “$fw” ] && fw=0
# System Integrity Protection
if csrutil status | grep -q “enabled”; then
sip=”true”
else
sip=”false”
fi
# Minor version of the running OS, useful for gating on patch level
osver=$(sw_vers -productVersion)
# Single line, no trailing output.
echo “{“FileVault”:”$filevault”,”Firewall”:$fw,”SIP”:”$sip”,”OSVersion”:”$osver”}”
The rules file
Each rule names a key from the script output, an operator, a data type and the expected value. The remediation strings are what the user sees in Company Portal, so write them as instructions rather than as a description of the failure. If a device never reports a result at all, work the Intune device not syncing diagnosis order first.
{
“Rules”: [
{
“SettingName”: “FileVault”,
“Operator”: “IsEquals”,
“DataType”: “String”,
“Operand”: “true”,
“MoreInfoUrl”: “https://support.apple.com/guide/mac-help/mh11785”,
“RemediationStrings”: [
{
“Language”: “en_US”,
“Title”: “Turn on FileVault disk encryption”,
“Description”: “Open System Settings, then Privacy and Security, then FileVault, and select Turn On.”
}
]
},
{
“SettingName”: “Firewall”,
“Operator”: “IsGreaterThan”,
“DataType”: “Int64”,
“Operand”: 0,
“MoreInfoUrl”: “https://support.apple.com/guide/mac-help/mh34041”,
“RemediationStrings”: [
{
“Language”: “en_US”,
“Title”: “Turn on the firewall”,
“Description”: “Open System Settings, then Network, then Firewall, and switch it on.”
}
]
},
{
“SettingName”: “SIP”,
“Operator”: “IsEquals”,
“DataType”: “String”,
“Operand”: “true”,
“MoreInfoUrl”: “”,
“RemediationStrings”: [
{
“Language”: “en_US”,
“Title”: “System Integrity Protection is disabled”,
“Description”: “Contact IT. Re-enabling SIP requires starting up in Recovery.”
}
]
}
]
}
Caution
Data types are strict and mismatches fail silently in the sense that the device simply reports non-compliant. If the script emits 2 as a number, the rule must use Int64. If it emits "2" as a string, it must use String. Half an hour disappears here if you are not deliberate about it.
Rolling it out without locking anyone out
- Set the non-compliance action to nothing at first. Compliance policies are reporting only until Conditional Access consumes them, and you want the reporting before the enforcement.
- Run against a pilot group and read the results, not the intent. Custom compliance surfaces real estate variety very quickly, which is the point.
- Only then wire it to Conditional Access. A custom rule with a bug plus a block policy is how you lock a fleet of Macs out of the tenant at 09:00.
What I would do
Start with three rules, not thirty. FileVault, firewall, and whichever single thing your security team asks about most often. Three rules that everyone trusts beats a comprehensive policy nobody believes.
And keep the script boring. Every conditional you add is another way for it to emit something that is not valid JSON, and the failure mode for that is a device that reports non-compliant for reasons nobody can see.



