IntuneHow-to

Custom compliance in Intune: writing the discovery script and JSON rules

Extend Intune compliance with your own checks: write a discovery script that returns compressed JSON, define rules in the JSON schema, upload both, and troubleshoot error codes 65007 to 65010.

Built-in compliance covers encryption, OS version and Defender state, but sooner or later you need a rule that Intune doesn't offer: a security agent above a minimum version, a firewall profile that must stay on, a registry value your auditors care about. Custom compliance settings let you pair a discovery script with a JSON rules file and feed the result into the same compliance state Conditional Access evaluates. In this post I'll walk through the script, the JSON schema, the upload and policy steps, and the error codes you'll meet when the two don't agree.

How this guide is organised: How it works → Step-by-step → Verify → Tips & gotchasFlow diagram of the article's sections in reading order: 1. How it works. 2. Step-by-step (4 steps: Write the discovery script; Write the JSON rules file; Upload the script; Create the compliance policy). 3. Verify. 4. Tips & gotchas. Toolbox: Scripts › Add, Manage devices › Compliance, SettingName, IsEquals, NotEquals.1How it works2Step-by-step3Verify4Tips & gotchas1Write the discovery script2Write the JSON rules file3Upload the script4Create the compliancepolicyTOOLBOXScripts › AddManage devices › ComplianceSettingNameIsEqualsNotEqualsHow this guide is organised: How it works → Step-by-step → Verify → Tips & gotchasFlow diagram of the article's sections in reading order: 1. How it works. 2. Step-by-step (4 steps: Write the discovery script; Write the JSON rules file; Upload the script; Create the compliance policy). 3. Verify. 4. Tips & gotchas. Toolbox: Scripts › Add, Manage devices › Compliance, SettingName, IsEquals, NotEquals.1How it works2Step-by-step1Write the discovery script2Write the JSON rules file3Upload the script4Create the compliance policy3Verify4Tips & gotchasTOOLBOXScripts › AddManage devices › ComplianceSettingNameIsEqualsNotEquals
At a glance: how this guide is organised · 4 steps · 5 key settings and tools

How it works#

A custom compliance policy has two parts. The discovery script runs on the device and returns the current value of one or more settings. The JSON file declares, for each of those settings, what value counts as compliant and what to tell the user when it doesn't. Each compliance policy supports exactly one script, each script can be attached to one policy, and one script can discover many settings. The custom results merge with the built-in settings into a single compound rule set, so a failed custom rule makes the device noncompliant just like a failed BitLocker check.

Supported platforms per Microsoft Learn are Windows (excluding Home editions) using PowerShell, Linux (Ubuntu Desktop 24.04 LTS and 26.04 LTS, Red Hat Enterprise Linux 9 and 10) using any installed interpreter, and macOS using Bash. On Windows the Intune Management Extension does the work: it's installed automatically if missing, checks for new or updated scripts every eight hours, runs the discovery script every eight hours, and also runs it when a user selects Check Compliance in the Company Portal. A push notification can't trigger it on demand.

Step-by-step#

Step 1: Write the discovery script#

The Windows script must end by emitting a hashtable as a single line of compressed JSON. Every key you reference in the JSON must be present in the output on every device, so always assign a value even when the thing you're looking for is missing:

PowerShell
$fw    = Get-NetFirewallProfile -Profile Domain
$agent = Get-Item 'C:\Program Files\Contoso Agent\agent.exe' -ErrorAction SilentlyContinue

$hash = @{
    FirewallDomainEnabled = ($fw.Enabled -eq 'True')
    ContosoAgentVersion   = if ($agent) { $agent.VersionInfo.ProductVersion } else { '0.0.0.0' }
}
return $hash | ConvertTo-Json -Compress

Expected output: {"ContosoAgentVersion":"2.1.4.0","FirewallDomainEnabled":true}. Keep it lean: Microsoft limits scripts and their output to 1 MB each and run time to 10 minutes on Windows and macOS (5 minutes on Linux), but more importantly the output that reaches the compliance engine is limited to 2,048 characters. Anything longer is truncated into invalid JSON. If you have many checks, split them across policies.

Step 2: Write the JSON rules file#

JSON
{
  "Rules": [
    {
      "SettingName": "FirewallDomainEnabled",
      "Operator": "IsEquals",
      "DataType": "Boolean",
      "Operand": true,
      "MoreInfoUrl": "https://intranet.contoso.com/it/firewall",
      "RemediationStrings": [
        {
          "Language": "en_US",
          "Title": "Windows Firewall must be on for the domain profile.",
          "Description": "Turn the firewall back on or contact the service desk."
        }
      ]
    },
    {
      "SettingName": "ContosoAgentVersion",
      "Operator": "GreaterEquals",
      "DataType": "Version",
      "Operand": "2.1.0.0",
      "MoreInfoUrl": "https://intranet.contoso.com/it/agent",
      "RemediationStrings": [
        {
          "Language": "en_US",
          "Title": "Contoso Agent is out of date. Version found: {ActualValue}.",
          "Description": "Install the latest version from the Company Portal."
        }
      ]
    }
  ]
}

The fields, as documented:

FieldNotes
SettingNameMust match the key in the script output exactly; it's case-sensitive
OperatorIsEquals, NotEquals, GreaterThan, GreaterEquals, LessThan, LessEquals
DataTypeBoolean, Int64, Double, String, DateTime, Version
OperandThe value to compare against; a JSON boolean for Boolean, a string for Version
MoreInfoUrlLink shown to the user for guidance
RemediationStringsArray of Language, Title and Description; at least one entry for en_US; {ActualValue} inserts the discovered value

A rules file can be up to 100 KB and contain up to 100 rules.

Step 3: Upload the script#

Go to Endpoint security › Device compliance › Scripts › Add and choose the platform (the same Compliance node is also reachable from Devices › Manage devices › Compliance). Give it a name, paste the script, and for Windows review the three settings: Run this script using the logged on credentials (default No, so it runs as System and falls back to System when nobody is signed in), Enforce script signature check, and Run script in 64 bit PowerShell Host. Set the last one to Yes unless you have a reason not to; the default is the 32-bit host, where registry and file system redirection means HKLM\SOFTWARE and C:\Program Files paths can resolve to their 32-bit equivalents and give you a different answer than you tested with. Intune doesn't validate the script for syntax errors, so test it locally first, and note that the script upload workflow doesn't support scope tags.

Step 4: Create the compliance policy#

In Devices › Manage devices › Compliance › Create policy, pick Windows 10 and later. On Compliance settings, expand Custom Compliance, set Custom compliance to Require, select your discovery script, then upload the JSON under Upload and validate the JSON file with your custom compliance settings. Intune validates the file and renders the rules as a table; fix any problems it reports before continuing. For Linux and macOS, use Add settings, pick Custom Compliance, switch Require Custom Compliance to True, and select the script and rules file. Configure Actions for noncompliance (a grace period of a day or two is kind to users), assign to a pilot group and create.

Verify#

  • On a pilot device, run the script in an elevated 64-bit PowerShell session and confirm it returns a single line of valid JSON with every expected key.
  • Sync the device, then open Reports › Device compliance, select the Reports tab and the Noncompliant devices and settings tile, choose the OS, and generate the report. Each failing custom setting appears as its own line with the discovered value.
  • On the device, the Company Portal shows your remediation title and description for a failing rule, with {ActualValue} replaced.
  • Break something deliberately (set the agent version operand higher than what's installed) and confirm the device flips to noncompliant, then restore it and confirm it recovers. Allow up to eight hours for the automatic cycle, or sync from the Company Portal website on Windows to speed it up.

Tips & gotchas#

Four error codes cover almost every custom compliance failure in the device compliance reports:

CodeMeaningUsual fix
65007Script returned failureUnhandled exception or non-zero exit; wrap risky cmdlets with -ErrorAction SilentlyContinue and test as System
65008Setting missing in the script resultA JSON SettingName has no matching key, or the case differs
65009Invalid JSON for the discovered settingOutput wasn't compressed to one line, extra text was written to the pipeline, or the 2,048-character limit was exceeded
65010Invalid datatype for the discovered settingThe script returned a string where the JSON declares Boolean or Int64, or a version string that isn't parseable
  • Make return $hash | ConvertTo-Json -Compress the last line and don't Write-Output anything else; stray output breaks the JSON.
  • Avoid nested objects or arrays in the hashtable; keep values scalar so the data types line up with the JSON.
  • For Version comparisons, normalise to four parts (2.1.0.0) on both sides; file metadata sometimes carries build suffixes.
  • Scripts that don't show up for selection, or linger after deletion, usually just need a refresh of the blade; cancel and restart the wizard if that doesn't help. You can't delete a script while a policy references it.
  • Linux scripts run in the user's context and can't read settings that need elevation, so plan rules accordingly.
  • Pair custom compliance with Conditional Access only after the pilot proves the rule is stable; a flapping script locks users out of everything.

References#

Written and checked against current Microsoft Learn documentation. Test changes with a pilot group before rolling them out to everyone, and if an admin center path has moved since, search for the setting name instead.

Spotted a mistake, or did this fix work differently for you? Email me or message me on LinkedIn — corrections are credited in the article.

OE
Written by

Omer Eltayeb

Independent Microsoft Intune consultant in Cairo, Egypt, former Microsoft Cloud Solutions Architect, Microsoft Certified Trainer and Microsoft Innovative Educator Expert (2024–26) and Microsoft Elevate Educator Expert (2026–27). I share practical, step-by-step guides, study plans, scripts and toolkits for Microsoft Intune, Microsoft Entra ID, Microsoft Defender and Exchange Online with the community.

Microsoft Certified Trainer (MCT) 2026Microsoft Innovative Educator Expert 2025–2026Microsoft Elevate Educator Expert 2026–2027ISC2 Certified Information Systems Security Professional (CISSP)Microsoft 365 Certified: Enterprise Administrator Expert (MS-102)