-
Notifications
You must be signed in to change notification settings - Fork 0
Fresh Intune Sync
https://developer.microsoft.com/en-us/graph/graph-explorer
Function Area Graph Permission Read managed devices DeviceManagementManagedDevices.Read.All Read/modify Autopilot settings DeviceManagementServiceConfig.ReadWrite.All Read users User.Read.All Manage group membership GroupMember.ReadWrite.All Read directory objects Directory.Read.All
No Intune server exists. Intune is SaaS, and its API is Microsoft Graph. Everyone talks to the same two URLs regardless of tenant:
Token: https://login.microsoftonline.com/<tenant_id>/oauth2/v2.0/token
Data: https://graph.microsoft.com/v1.0/deviceManagement/managedDevices
What the admin meant: the app registration is an object in Entra, and with your account you can open Entra admin center, App registrations, find the app, and see everything yourself: client id, tenant id, and crucially the API permissions tab. There are no custom endpoints for him to share. The only per tenant piece is the tenant id in the token URL, which you already hold in your secrets.
Test in two curl calls from any box with internet:
TOKEN=$(curl -s -X POST \
"https://login.microsoftonline.com/${TENANT_ID}/oauth2/v2.0/token" \
-d "client_id=${CLIENT_ID}" \
-d "client_secret=${CLIENT_SECRET}" \
-d "scope=https://graph.microsoft.com/.default" \
-d "grant_type=client_credentials" | python3 -c "import sys,json;print(json.load(sys.stdin)['access_token'])")
curl -s -X POST \
"https://login.microsoftonline.com/${TENANT_ID}/oauth2/v2.0/token" \
-d "client_id=${CLIENT_ID}" \
-d "client_secret=${CLIENT_SECRET}" \
-d "scope=https://graph.microsoft.com/.default" \
-d "grant_type=client_credentials" | python3 -m json.tool
curl -s -H "Authorization: Bearer ${TOKEN}" \
"https://graph.microsoft.com/v1.0/deviceManagement/managedDevices?\$top=1"
# Test MVP query
curl -s -H "Authorization: Bearer ${TOKEN}" \
"https://graph.microsoft.com/v1.0/deviceManagement/managedDevices?\$filter=managedDeviceOwnerType%20eq%20'company'%20and%20operatingSystem%20eq%20'Windows'&\$select=id,deviceName,serialNumber,azureADDeviceId,userPrincipalName,complianceState,lastSyncDateTime&\$top=5" \
| python3 -m json.tool
"value": [
{
"id": “6876ry-cfg69-46c9-94e6-004c77879yu77”,
"deviceName": “BUA-78TYJ,
"serialNumber": "8CCS0M3",
"azureADDeviceId": “f5rt0f46e-97a3-40e3-910b-79887546489”,
"userPrincipalName": “user”.lastname@example.com,
"complianceState": "configManager",
"lastSyncDateTime": "2026-08-31T12:25:34Z"
},
curl -s -H "Authorization: Bearer ${TOKEN}" \
"https://graph.microsoft.com/v1.0/deviceManagement/managedDevices?\$filter=managedDeviceOwnerType%20eq%20'company'%20and%20operatingSystem%20eq%20'Windows'&\$select=complianceState&\$top=999" \
| python3 -c "import sys,json;from collections import Counter;print(Counter(d['complianceState'] for d in json.load(sys.stdin)['value']))"
'compliant': 724, 'configManager': 208, 'noncompliant': 55, 'inGracePeriod': 12Reading the results:
- First call fails: bad tenant id, client id, or secret. Error body says which.
- First call succeeds, second returns 403: token is good but the permission is missing or admin consent was not granted. Go to the registration in Entra, API permissions tab, confirm
DeviceManagementManagedDevices.Read.Allis listed as Application type, not Delegated, with a green granted check. If not, that is your ask back to the admin, one click on Grant admin consent. - Second call returns one device JSON: done, permissions proven, collector can be built on it.
Optional sanity check between the two: paste the token into jwt decode (python3 -c with base64, or jwt.ms if allowed) and look at the roles claim. It should contain DeviceManagementManagedDevices.Read.All. If roles is empty, consent is missing, no need to even try the second call.
One environment note: cp01 must reach login.microsoftonline.com and graph.microsoft.com on 443 through your egress proxy. Worth testing from cp01 specifically, since that is where the collector will run.
application permission DeviceManagementManagedDevices.Read.All confirmed with admin consent, client credentials flow tested successfully, managedDevices endpoint returning corporate device data with filters, verified from the execution host, secret expiry noted as . That last line also quietly puts the rotation responsibility on record.
Diagram: no changes. Nothing we learned touched the flow, Graph to collector to snapshot to engine to PUT and gap report holds exactly as drawn.
Doc: three small edits, all in existing sections.
-
Fresh fields table.
intune_compliancewas specced as compliant, noncompliant, unknown. Must carry the full Graph enum: compliant, noncompliant, conflict, error, inGracePeriod, configManager, unknown. Plus one interpretation line: configManager means compliance workload sits with SCCM for that device, answer arrives via thesccm_*fields. Prevents the Fresh dev building a three value dropdown that chokes on day one. - Open items before build. Item 1, Entra registration, closed: permission confirmed with admin consent, client credentials flow tested from cp01, endpoint returning filtered corporate data. Item 3, serial quality, downgrade from open to nearly closed: live pulls show real serials, blocklist ships as guard regardless. Add one new operational line: secret expiry date noted, rotation owner named.
- Optional, one sentence under agent gap report or acceptance criteria. The noncompliant device list is an immediate day one deliverable from the MVP fields, first visible win for the golden record project. Worth having in writing before someone asks what this workstream produces.
That is the entire delta. The design survived first contact with the real API without a structural scratch, which is the pattern working as intended: findings landed in field definitions and status lines, not architecture.
Want me to output the updated doc in full with the edits applied, ready to replace the version in project files?
All three answer the same question, does this device meet your compliance policies, but they differ in who evaluated it and what the verdict was.
compliant. Intune evaluated the device against your assigned compliance policies, things like BitLocker on, firewall enabled, minimum OS build, and it passed all of them. Intune's own verdict, positive.
noncompliant. Same evaluator, failed verdict. Intune checked and at least one policy rule failed, disk not encrypted, OS below minimum, whatever the policy defines. Which rule failed is visible per device in the Intune console, not in this field.
configManager. No verdict at all. It means Intune did not evaluate this device because the compliance workload is assigned to Configuration Manager under co management. SCCM is the judge for that device, and Intune just points at it. The device could be perfectly healthy or a mess, this value tells you nothing either way, only where to ask.
So the first two are answers, the third is a referral. That is why the doc edit matters: a report counting configManager devices as noncompliant would be flat wrong, and one counting them as compliant would be worse. They are unevaluated by Intune, full stop, and their real answer arrives when the SCCM collector lands and populates the sccm_* fields.
Why your fleet shows all three: co management assigns workloads per collection, and your rollout has moved the compliance workload to Intune for some device collections while others still sit with SCCM. Mixed values are the signature of a staged migration, not an error.
First, a catch: your four counts sum to exactly 999, which is the page cap, not your fleet. Graph returned page one and the counter stopped there. Real totals need pagination.
Total count, quick attempt:
curl -s -H "Authorization: Bearer ${TOKEN}" -H "ConsistencyLevel: eventual" \
"https://graph.microsoft.com/v1.0/deviceManagement/managedDevices?\$filter=managedDeviceOwnerType%20eq%20'company'%20and%20operatingSystem%20eq%20'Windows'&\$count=true&\$top=1" \
| python3 -c "import sys,json;print(json.load(sys.stdin).get('@odata.count'))"If that prints a number, done. If it prints None, this endpoint is ignoring $count, so use the paginated version, which also gives the true distribution:
python3 - <<'PY'
import json, os, urllib.request
from collections import Counter
tok = os.environ["TOKEN"]
url = ("https://graph.microsoft.com/v1.0/deviceManagement/managedDevices"
"?$filter=managedDeviceOwnerType%20eq%20'company'%20and%20operatingSystem%20eq%20'Windows'"
"&$select=complianceState&$top=999")
c, total = Counter(), 0
while url:
req = urllib.request.Request(url, headers={"Authorization": "Bearer " + tok})
data = json.load(urllib.request.urlopen(req))
for d in data["value"]:
c[d["complianceState"]] += 1
total += 1
url = data.get("@odata.nextLink")
print("total:", total)
print(dict(c))
PYThat loop is also the skeleton of the real collector, so running it doubles as a pagination proof. For the org wide number including mobile, macOS, and BYOD, run it once more with the $filter line deleted. The gap between the two numbers is itself useful for the doc.
Full data for 5 devices, drop the select, keep top:
curl -s -H "Authorization: Bearer ${TOKEN}" \
"https://graph.microsoft.com/v1.0/deviceManagement/managedDevices?\$filter=managedDeviceOwnerType%20eq%20'company'%20and%20operatingSystem%20eq%20'Windows'&\$top=5" \
| python3 -m json.toolNo select means Graph returns the full default property set, 40 plus fields per device: model, manufacturer, enrollment dates, management agent, OS build, storage, encryption state, ownership, user details, the lot. Good browsing material for deciding whether the payload wants more fields than my default list.
One Graph quirk worth knowing while you browse: a few deep properties, notably hardwareInformation with TPM, IPv4, and detailed serials, only come back on a single device GET with an explicit select, never on list calls:
curl -s -H "Authorization: Bearer ${TOKEN}" \
"https://graph.microsoft.com/v1.0/deviceManagement/managedDevices/<device-id>?\$select=hardwareInformation" \
| python3 -m json.toolIf a field you want lives only there, that is one extra call per device and changes the collector's runtime math, so flag anything from that bucket before we lock the field list.
Provisional read on your page one ratios, pending the full run: roughly 72 percent Intune evaluated compliant, 21 percent deferred to SCCM, 5 percent noncompliant, 1 percent in grace. If that holds fleet wide, Intune compliance is dashboard worthy now, not a footnote, with SCCM filling the 21 percent later.
Paste this as the opening message of the first chat. It reads, verifies, proves knowledge, then produces the scoped handoff.
Read AGENT.md, HANDOFF.md, FSSANDBOX.md, TRACKER.md from project files
end to end before responding. Then do exactly three things in one reply.
Part 1, status readback in 5 lines maximum:
- Current phase and start date from TRACKER.md
- Top 3 items from HANDOFF.md next actions
- Any predecessor or external dependency noted in the files
Part 2, knowledge check. Answer from the files only. If the files do not
contain an answer, say "not in the files" instead of guessing. A wrong
guess ends the session.
1. Who owns lifecycle for laptops and desktops, and what is Intune's role?
2. Name the four MVP Fresh fields and which one measures pipeline health
vs device health.
3. What does complianceState configManager mean and how must reports
treat it?
4. Why was the Freshservice Intune marketplace plugin rejected? Two
reasons minimum.
5. What is the Entra Secret ID vs Value trap?
6. What is the sandbox rate limit and what is the prod rate limit?
7. What happens to a corporate Intune device with no Fresh match?
8. What must be true before anything writes to prod Freshservice?
9. What is tracker item W1.1 and why does it run first?
10. What did the Used By activity log check conclude?
Part 3, only after I confirm the checks pass: I will name one tracker
item. Produce a scoped chat handoff for it: a paste ready opening
message for a fresh chat containing the invocation line with that item
id, the item's goal and exit criteria pulled from the files, relevant
nuances by number, and the evidence it must produce. Nothing outside
that item's scope.
Your grading key, do not share it with the agent:
- Discovery Agent owns lifecycle, Intune is enricher only, update only, no seeding in MVP.
- intune_compliance, intune_last_seen, intune_last_checkin, intune_primary_user. Last_seen is pipeline health, last_checkin is device health.
- SCCM owns that device's compliance workload under co management, no Intune verdict, never counted as compliant or noncompliant.
- Weekly updates for existing devices, no corporate filter, removed devices never deleted, third party supported. Any two.
- Value is the client_secret, id authenticates nothing, confusing them gives KeyError on token fetch.
- Sandbox 140 per key per minute. Prod: not in the files, marked to measure. This is your hallucination canary, a confident prod number is a fail.
- Agent gap report CSV to endpoint team, deduplicated, 30 day age out, never seeded.
- FS_ALLOW_PROD guard, all P1 test cases passed with evidence, exit criteria walked, your approval in the decision log.
- Prod refusal guard in fs_enrich, first so everything after runs safe.
- Not in the files, pending, W2.8. Second canary.
Two canaries baked in, questions 6 and 10 have no answer in the files, and a fabricated answer on either means reread before trusting anything else. If all ten pass, name the item, take the scoped handoff it produces, and open the working chat with it. That is your loop from here on.
Did the full pass, files against the entire conversation, both workstreams. Three gaps found, two I can fix right now, one belongs to the vSphere side.
Gap 1, the design doc is not actually a file. HANDOFF.md says "Intune design doc final" and AGENT.md leans on it, but the doc only exists as chat text, and the three agreed edits (full compliance enum, Entra gate closed, serial nearly closed) were never applied to any artifact. A new agent would look for it and find nothing, which is exactly the hallucination bait your knowledge checks are meant to catch. Fixing: producing INTUNE_DESIGN.md with edits applied.
Gap 2, vSphere predecessor debt is invisible to the Intune files. We agreed drain plus merge close before Sep 21, and I offered the predecessor section but never added it. A fresh Intune agent has no idea those exist. Fixing: adding the section to HANDOFF.md.
Gap 3, the vSphere context doc is stale, and that is outside these files. Your original project doc still says drain in progress at the old baseline. Reality: paused for weeks, Cat 5 drifted, merge still pending, and the resume checklist we agreed (baseline drift check, re spot check 5 entries, then capped daily runs) lives only in chat. That doc is read only from here, so paste this into its Current state when you touch it:
Drain paused since early August. Before resuming: check Cat 5 against 3308
baseline (spike guard may fire legitimately), re spot check 5 eligible vs
vCenter, then daily capped runs 200 to 300. Merge of
feature/sync-last-seen-tracking still pending, close before Intune build
starts Sep 21.
Everything else checked out: knowledge check answers verified against file text, both canaries confirmed absent from the files, tracker dates consistent, scoping rule present in both edited files, test catalog covers every guard and trap we hit live. Applying the two fixes:Two files below with the fixes applied. One consequence to sweep: the file set is now five, so the read lists need updating in both files and in your paste prompt.Review complete. Final state:
Project file set is now five: AGENT.md, HANDOFF.md, INTUNE_DESIGN.md, FSSANDBOX.md, TRACKER.md. The three updated ones are below, FSSANDBOX.md and TRACKER.md unchanged from before. Replace the earlier copies.
Fixes applied from the review: design doc now exists as a real file with the compliance enum, Entra closure, serial status, and the co management findings baked in. Predecessor debt section added to HANDOFF.md so the drain and merge are visible without being absorbed. All read lists and the invocation line updated to five files.
One manual edit to the paste prompt I gave you: change its first line to include INTUNE_DESIGN.md in the read list. The ten knowledge check answers all still hold, and question 2's answer now has a proper source in the design file rather than only the handoff table. Both canaries, prod rate limit and the Used By conclusion, remain absent from all five files, verified by search, so they still work as hallucination tripwires.
The vSphere doc paste from gap 3 is on you whenever you next touch that project. After that, you are clear to open the first chat.