Group Policy was a declarative system that wrote registry keys over a domain channel. Intune configuration is also declarative, but it writes through the Configuration Service Provider (CSP) surface over MDM. The mental model translates, but the tooling does not — and the single biggest mistake teams make is treating the Settings Catalog like a GPMC clone instead of a CSP front-end. This guide builds a hardened baseline the way a platform team should: pick the right profile type, ingest the ADMX you actually need, scope with filters, resolve conflicts deterministically, and version-control the whole thing as JSON through Microsoft Graph.
1. Settings Catalog vs templates vs administrative templates
Intune gives you three overlapping ways to author device configuration, and they are not interchangeable. Choosing wrong means you author a setting that silently never applies, or you split one logical baseline across three profile types nobody can audit.
| Profile type | What it is | When to use |
|---|---|---|
| Settings Catalog | A flat, searchable list of every CSP-backed setting Intune exposes. No opinionated grouping. | Default choice for anything new. Mix Windows, Edge, Office, and OneDrive settings in one profile. |
| Templates (e.g. Device restrictions, Endpoint protection) | Curated, fixed groups of settings with a guided UI. | Legacy / familiarity. Microsoft is steering everything toward the catalog; avoid for greenfield. |
| Administrative Templates (ADMX) | A GPO-like tree of ADMX-backed policies, including ingested third-party ADMX. | When you need a specific ADMX setting (Chrome, custom LOB app) that is not surfaced in the catalog. |
The Settings Catalog and Administrative Templates are both ADMX-aware under the hood for many Windows settings. The practical difference: the catalog is a flat search experience that also covers non-ADMX CSPs, while the Administrative Templates profile mirrors the familiar
Computer Configuration / User Configurationtree. For a brand-new baseline, default to the Settings Catalog and only reach for Administrative Templates when a setting genuinely is not in the catalog.
The decision rule I give teams: start in the Settings Catalog. If the setting is not there, check whether the owning vendor ships an ADMX. If it does, ingest it (Section 3). If it does not, you are looking at a custom OMA-URI profile — which is a different article and a maintenance liability you want to minimize.
2. How Settings Catalog maps to Windows CSPs under the hood
Every setting you toggle in the catalog resolves to a CSP node and a value. When the profile is assigned, Intune sends the OMA-DM payload to the device, the MDM stack hands it to the relevant CSP, and the CSP writes the effective configuration (often, but not always, the registry). Understanding this matters because the CSP is the source of truth for behavior, not the friendly name in the portal.
Take a concrete example. In the catalog you might enable, under Administrative Templates > Windows Components > BitLocker Drive Encryption, the OS-drive encryption settings. Those resolve to the BitLocker CSP (./Device/Vendor/MSFT/BitLocker). A setting like Minimum PIN length maps to ./Device/Vendor/MSFT/BitLocker/SystemDrivesMinimumPINLength. If two profiles set that node to different values, the CSP — not the portal — decides what happens, and you get a conflict (Section 5).
A few CSP realities that bite GPO veterans:
- Not every GPO has a CSP equivalent. Roughly the GPO settings that map cleanly are exposed; the rest simply do not exist in MDM. Group Policy analytics (Section 6) tells you which.
- User-scope vs device-scope is explicit. A catalog setting is tagged for device or user; an Entra-joined device with no enrolled user context will silently skip user-scoped settings.
- The Policy CSP areas (
./Device/Vendor/MSFT/Policy/Config/<Area>/<Setting>) back most of the “classic” Windows hardening you care about —DeviceLock,Browser,Defender,Update.
When you export a profile to JSON (Section 8), you see the catalog’s internal setting IDs, which look like device_vendor_msft_bitlocker_systemdrivesminimumpinlength. That string is your durable identifier — friendly names get re-labelled, IDs do not.
3. Importing third-party and custom ADMX/ADML for app policy
Chrome, custom LOB apps, and some Edge/Office settings ship as ADMX you must ingest before Intune can author them. Ingestion uploads the ADMX (definitions) plus at least one ADML (language strings) into the tenant, after which the settings appear in the Administrative Templates profile authoring surface. Edge and Office for the most part are already built into the catalog, so you usually only ingest Chrome and in-house ADMX.
In the admin center: Devices > Configuration > Import ADMX. Upload the .admx, then the matching .adml for your locale (e.g. en-US). Status moves from uploadInProgress to available. Only then can you create an Administrative Templates profile that references it.
The Graph entity is groupPolicyUploadedDefinitionFile. Its policyType distinguishes the two ADMX worlds:
admxBacked— built-in ADMX Microsoft ships (you do not upload these).admxIngested— third-party / custom ADMX you imported.
Upload the ADMX first, then attach the ADML with the addLanguageFiles action. ADML content is base64-encoded:
Connect-MgGraph -Scopes "DeviceManagementConfiguration.ReadWrite.All"
$admx = [Convert]::ToBase64String([IO.File]::ReadAllBytes("C:\admx\chrome.admx"))
$adml = [Convert]::ToBase64String([IO.File]::ReadAllBytes("C:\admx\en-US\chrome.adml"))
$body = @{
"@odata.type" = "#microsoft.graph.groupPolicyUploadedDefinitionFile"
fileName = "chrome.admx"
displayName = "Google Chrome"
defaultLanguageCode = "en-US"
content = $admx
groupPolicyUploadedLanguageFiles = @(
@{
"@odata.type" = "#microsoft.graph.groupPolicyUploadedLanguageFile"
fileName = "chrome.adml"
languageCode = "en-US"
content = $adml
}
)
} | ConvertTo-Json -Depth 5
Invoke-MgGraphRequest -Method POST `
-Uri "https://graph.microsoft.com/beta/deviceManagement/groupPolicyUploadedDefinitionFiles" `
-Body $body -ContentType "application/json"
Ingested ADMX has hard limits. The ADMX file must be under the documented size cap (around 1 MB), it cannot reference settings already delivered by a built-in ADMX, and a small number of complex policy element types are unsupported. If ingestion sticks in
uploadFailed, it is almost always an unsupported element or a missing referenced namespace, not a transient error. Validate the ADMX renders cleanly in on-prem GPMC first.
After it reaches available, the Chrome settings appear under Administrative Templates and you author them like any other policy — for example Set the default search provider, or Disable saving browser history.
4. Authoring a hardened baseline and scoping it with filters
Now the actual baseline. Create it under Devices > Configuration > Create > New policy, platform Windows 10 and later, profile type Settings Catalog. Add settings by searching the catalog. A credible Windows hardening starter set, all CSP-backed:
| Setting (catalog path) | Backing CSP node | Value |
|---|---|---|
| BitLocker > Require device encryption | BitLocker/RequireDeviceEncryption |
Enabled |
| BitLocker > System drives minimum PIN length | BitLocker/SystemDrivesMinimumPINLength |
6 |
| DeviceLock > Min device password length | DeviceLock/MinDevicePasswordLength |
12 |
| Microsoft Defender > Real-time monitoring | Defender/AllowRealtimeMonitoring |
Allowed |
| LocalPoliciesSecurityOptions > Smart Screen | Browser/AllowSmartScreen |
Enabled |
| Administrative Templates > Disable LM hash | LanmanWorkstation / security options |
Enabled |
Keep one baseline per platform and per purpose. Do not bury Edge settings, BitLocker, and password policy in three profiles when one catalog profile holds them all — fewer profiles means fewer conflict surfaces.
Scope with filters, not group sprawl. Assign the profile to a broad group (e.g. All devices or All corporate Windows), then narrow with an assignment filter evaluated at assignment time. Filters keep your group model flat and your targeting precise.
Create a filter under Tenant administration > Filters. The rule uses Intune device properties with -eq, -ne, -startsWith, -contains, and version operators. Example: only corporate-owned Windows 11 workstations.
(device.deviceOwnership -eq "Corporate") and (device.osVersion -startsWith "10.0.2")
Provision it via Graph as a deviceAndAppManagementAssignmentFilter:
$filter = @{
displayName = "Corp Win11 Workstations"
platform = "windows10AndLater"
rule = '(device.deviceOwnership -eq "Corporate") and (device.osVersion -startsWith "10.0.2")'
assignmentFilterManagementType = "devices"
} | ConvertTo-Json
Invoke-MgGraphRequest -Method POST `
-Uri "https://graph.microsoft.com/beta/deviceManagement/assignmentFilters" `
-Body $filter -ContentType "application/json"
Then on the profile’s Assignments tab, add the group and set the filter to Include (apply only to matches) or Exclude. Filters are evaluated per-assignment, so the same broad group can carry different profiles narrowed to different device shapes. A tenant supports up to 200 filters.
5. Policy conflict resolution: precedence, ‘not configured’, and last-writer behavior
This is where multi-profile estates fall apart. Intune’s conflict model is not “highest priority wins” for most configuration profiles — it is closer to “a real conflict on the same CSP node results in Conflict, and the device gets an undefined winner.” The rules you must internalize:
Not configurednever conflicts. If a profile leaves a setting unconfigured, it does not participate. This is your primary de-conflict tool: split overlapping settings so only one profile ever sets a given node.- Same value from two profiles is fine. Two profiles setting
MinDevicePasswordLength = 12both report Success. Conflicts arise only on differing values for the same node. - Differing values =
Conflict. Both profiles reportConflictfor that setting, and the applied value is not deterministic. Do not rely on it. - Settings Catalog has no manual priority slider the way some legacy profile families do. You resolve conflicts by not creating them — one node, one owning profile.
- Security baselines (the Intune-managed baselines) do have an explicit precedence and will flag conflicts against your custom profiles in their own reporting view.
The operational discipline that prevents 90% of conflicts: maintain a single source-of-truth spreadsheet (or, better, the JSON in git per Section 8) mapping every CSP node you configure to exactly one profile. If two profiles touch the same node, that is a code-review failure, not a runtime surprise. Treat overlapping nodes the way you treat two Terraform resources managing the same cloud object.
When you genuinely need a per-group override (e.g. executives get a looser PIN), do it by scoping, not overlap: profile A excludes the exec filter, profile B includes it. Same node, but never on the same device at the same time.
6. Migrating GPOs with Group Policy analytics and the migration report
Before you reauthor a single GPO by hand, run it through Group Policy analytics. It parses an exported GPO backup, cross-references every setting against the CSP database, and tells you what is MDM-supported, deprecated, or unavailable — then lets you migrate the supported settings straight into a Settings Catalog profile.
Workflow:
- On a domain controller or management box, export the GPO:
Backup-GPO -Name "Corp Baseline" -Path C:\gpobackup, or GPMC > right-click GPO > Back Up. Analytics consumes the*.xmlreport inside the backup. - In Intune: Devices > Group Policy analytics > Import, upload the GPO report XML.
- Intune produces a migration report with a per-setting MDM support percentage and a breakdown of each setting’s status.
The Graph entities are groupPolicyObjectFiles (the uploaded GPO) and groupPolicyMigrationReports (the analysis result):
# List migration reports and their MDM coverage
$reports = Invoke-MgGraphRequest -Method GET `
-Uri "https://graph.microsoft.com/beta/deviceManagement/groupPolicyMigrationReports"
$reports.value | ForEach-Object {
[pscustomobject]@{
Ou = $_.ouDistinguishedName
SupportedSettings = $_.migrationReadiness
TotalSettings = $_.totalSettingsCount
SupportedCount = $_.supportedSettingsCount
}
} | Format-Table -AutoSize
The report classifies each setting into one of three buckets that map directly to your migration plan:
| Report status | Meaning | Action |
|---|---|---|
| Supported (MDM) | Setting has a CSP and Intune surfaces it | Migrate to Settings Catalog (analytics can do this for you) |
| Deprecated | Setting exists but Microsoft no longer recommends it | Drop it; find the modern equivalent |
| Not available | No CSP / no MDM mapping | Re-architect: filter, script, or accept the gap |
From the migration report you can click Migrate to generate a Settings Catalog profile pre-populated with the supported settings. Treat that as a draft — review every value, then export to JSON and put it under version control rather than assigning it blind.
7. Per-setting reporting and diagnosing ‘Error’/‘Conflict’ states
A profile that says “85% success” is hiding the 15% that matters. Drill into Devices > Configuration > [profile] > View report, then Per setting status, which gives you a row per setting with Success / Error / Conflict / Not applicable counts.
The three failure modes and how to read them:
Conflict— another profile sets the same CSP node to a different value (Section 5). Fix it by de-overlapping, not by re-pushing.Error— the CSP rejected the value or the device could not apply it. Common causes: the setting targets hardware/edition the device lacks (e.g. BitLocker on a TPM-less VM), a value out of the CSP’s allowed range, or a user-scoped setting on a device with no user context.Not applicable— the device is out of scope for that setting (wrong edition, filter excluded it). This is usually correct, not a bug.
For a stuck device, pull the per-device per-setting state from Graph and the on-device MDM diagnostics. The catalog’s deviceStatuses surface the applied state:
$policyId = "<configurationPolicyId>"
Invoke-MgGraphRequest -Method GET `
-Uri "https://graph.microsoft.com/beta/deviceManagement/configurationPolicies/$policyId/deviceStatuses" |
Select-Object -ExpandProperty value |
Where-Object status -ne "compliant" |
Format-Table id, status, lastReportedDateTime -AutoSize
On the device itself, the authoritative log is the MDM diagnostics. Generate the report and read the CSP results:
# On the affected Windows endpoint
mdmdiagnosticstool.exe -area DeviceProvisioning -cab C:\temp\mdm.cab
# Or pull the live policy area into a readable report:
mdmdiagnosticstool.exe -out C:\temp\MDMReport
The generated MDMDiagReport.html lists every applied policy area with its result code. A 0x80070032 (not supported) or 0x86000020-class result pinpoints exactly which CSP node refused the value — far faster than guessing from the portal’s aggregate counts.
8. Exporting, version-controlling, and reimporting profiles as JSON via Graph
The endgame: your baseline lives in git, not in someone’s portal session. The Settings Catalog profile is just a JSON document at /beta/deviceManagement/configurationPolicies/{id} with an expandable settings collection. Export it, commit it, diff it, and reimport it into another tenant.
Export a profile with its settings expanded:
$id = "<configurationPolicyId>"
$policy = Invoke-MgGraphRequest -Method GET `
-Uri "https://graph.microsoft.com/beta/deviceManagement/configurationPolicies/$id`?`$expand=settings"
# Strip server-managed fields so it re-imports cleanly
$export = $policy | Select-Object name, description, platforms, technologies, roleScopeTagIds, settings
$export | ConvertTo-Json -Depth 20 | Out-File ".\baselines\win-hardening-v3.json" -Encoding utf8
Commit win-hardening-v3.json. Now you get real diffs in pull requests — a reviewer sees that systemdrivesminimumpinlength went from 6 to 8 before it ever reaches a device. The internal setting IDs (e.g. device_vendor_msft_bitlocker_systemdrivesminimumpinlength) are stable, so diffs are meaningful across exports.
Reimport into a new tenant or restore a known-good version:
$json = Get-Content ".\baselines\win-hardening-v3.json" -Raw | ConvertFrom-Json
$body = @{
name = $json.name
description = $json.description
platforms = $json.platforms # e.g. "windows10"
technologies = $json.technologies # e.g. "mdm"
roleScopeTagIds = $json.roleScopeTagIds
settings = $json.settings
} | ConvertTo-Json -Depth 20
Invoke-MgGraphRequest -Method POST `
-Uri "https://graph.microsoft.com/beta/deviceManagement/configurationPolicies" `
-Body $body -ContentType "application/json"
platformsandtechnologiesare required and must match the original (windows10/mdmfor a Windows catalog profile). The most common reimport failure is dropping thesettingscollection’s nested@odata.typediscriminators during a hand-edit — keep-Depth 20on everyConvertTo-Jsonso nothing gets truncated. Assignments do not travel with the policy export; reapply groups and filters separately (deliberately, so you never copy a production assignment into a test tenant by accident).
This is the bridge from clicking-in-a-portal to GitOps for endpoint config: PRs, review, environments, and the ability to roll back a baseline by re-POSTing last week’s JSON.
Enterprise scenario
A platform team running a ~22,000-seat tenant was mid-migration off on-prem GPO. They had a single legacy “Workstation Baseline” GPO with ~340 settings and tried to lift-and-shift it by hand into one Settings Catalog profile, then assigned it to All devices. Within a day, the per-setting report lit up with Conflict on the password and BitLocker settings, and a cluster of Error states on a fleet of TPM-less hypervisor-hosted VDI images.
Two root causes. First, the conflict: a separate, older “VDI Security” profile already set DeviceLock/MinDevicePasswordLength to a different value on the same machines — two profiles, one CSP node, differing values, undefined winner. Second, the errors: the BitLocker settings were genuinely Not applicable on the TPM-less VDI but the team had forced RequireDeviceEncryption, so the CSP returned a hard error rather than skipping.
The fix was discipline, not more profiles. They ran the legacy GPO through Group Policy analytics first, which flagged 38 of the 340 settings as Not available (no CSP) — settings they had been about to reauthor pointlessly. They split the baseline by purpose (password, BitLocker, Defender, browser), kept each CSP node owned by exactly one profile, and used assignment filters to carve the VDI estate out of BitLocker entirely instead of letting it error:
(device.deviceModel -ne "Virtual Machine") and (device.osVersion -startsWith "10.0.2")
That filter, set to Include on the BitLocker profile, meant the VDI images were Not applicable by design — clean reporting, no errors. Every profile was exported to JSON and committed, so the next change went through a pull request where the password-length bump was visible as a one-line diff. Conflicts dropped to zero, and the “85% success” number became a real 99.x% because the remaining gaps were intentional scoping, not silent failures.
The lesson the team wrote into their runbook: a
Conflictis a design defect introduced at authoring time, and anErroris usually a scoping defect — neither is a device problem. Fix them in the profile model and the filters, never by re-pushing policy at the endpoint.
Verify
Confirm the baseline is actually applying — not just assigned — before you call it done.