Skip to content
Ketan Kamble
Ketan Kamble
Modern Workplace Architect

Read-only Intune & Entra tooling, in the open — and what the portal is doing underneath.

MD-102 SC-300 Speaker · WNUG Finland

The read-only AI agent that can't touch your tenant

Cover: a read-only AI agent answering endpoint questions from sanitized snapshots, with no live tenant access

"How many devices fail Firewall in Finland?" "Which Lenovos are out of warranty in the Madrid office?" "Is Ollama installed anywhere, and is it approved?" You want to ask your fleet those questions in plain English and get an answer in seconds. The obvious way to build that — hand an AI model live access to Intune and Microsoft Graph — is also the most dangerous. This is the capstone of everything on this site: an AI agent for endpoint management that answers questions about your whole Intune fleet — and holds no access to any live system at all. (For how it compares to Microsoft's native Intune Copilot agents, jump to Where this fits.)

The payoff

Answering “which disabled users still hold a Teams Phone licence?” normally means a specialist writing a Graph query. Here anyone asks in plain English and gets a cited answer from the read-only snapshot — the insight without granting an AI any power to change your tenant. That last part is the point: there is no write path back.

The short version

An Azure AI Foundry agent answers natural-language questions about the entire endpoint estate — but it has no Graph scopes, no keys, no connection to Intune or Entra, and it cannot act. It reads only from two Azure AI Search indexes built from the read-only, sanitized snapshots the ten collectors on this site produce. Worst case, it reads a dated, minimised snapshot — it can never see or touch the live tenant. That containment is the design.

First — what kind of AI is this? A language model on its own (an LLM) is the brain: it reasons, but only from what it was trained on. Give it your documents to read and you have RAG — brain plus books — so it answers about your fleet, not the world in general. Give it tools that can act — create a policy, wipe a device — and you have a full AI agent: brain plus hands. This project is the middle one with the last step refused on purpose: RAG, with your fleet's read-only snapshots as the books, running as an agent whose only tool can turn pages, never act. It has the books; it was never given the hands. (This is a personal project — everything here is built and reproduced in my own lab.)

Why not just give the AI access to Graph?

Because "give the AI access to Graph" quietly grants three things you can't take back:

  • Standing scopes. A live agent needs a token with DeviceManagement...Read.All (and people always creep it to ReadWrite "just for this one action"). That token now exists, refreshes forever, and is one prompt-injection away from being misused.
  • The ability to act. The moment the agent can call Graph, "remediate that" or "wipe that device" is a function call away — and an LLM that sits one injected instruction from wipe that device is not a tool, it's a hazard wired straight into production.
  • Live data in the model's context. Every question pulls real tenant data — names, emails, device IDs — through the model. That's a data-governance surface you now own on every single query.

The whole point of this project is to get the natural-language answer without any of that. The trick is to separate reading the estate from answering about the estate — and to make sure the thing answering can only ever see a sanitized, dated copy.

The architecture: collect read-only, answer from snapshots

The Zero-Access Pattern: the M365 tenant (Intune, Entra, Defender) is read by read-only collectors on Azure Automation Managed Identity, which write minimised, dated sanitized CSV snapshots to immutable Blob Storage; Azure AI Search indexes them and an AI Foundry agent answers in plain English, while Power BI reads the same snapshot — the guarantee bar reads read-only, only .Read.All Graph scopes, no write or action, the AI never touches a live system

Five stages, and the containment lives in the seams between them:

  1. Collect — the ten read-only collectors run as scheduled Azure Automation runbooks under a Managed Identity, holding only *.Read.All Graph scopes — no write or action permission of any kind. (Some reads are POST requests — the reports export API and $batch are read operations that happen to use POST — so the guarantee is the scopes granted, not the HTTP verb.) Each runbook pre-aggregates its data and writes a sanitized CSV, plus a _Stats file with the counts already computed.
  2. Store — the CSVs land in Azure Blob: the full files feed Power BI; slim, snapshot copies feed the agent.
  3. Index — two Azure AI Search indexers ingest from Blob into two indexes:
    • a structured index — one search document per report row (hundreds of thousands of rows across all the reports: inventory, compliance settings, app failures, hygiene, Autopilot, licences, AI-tool detections). This is the "how many / which / where" half.
    • a documents index — the rendered Intune configuration documentation, chunked and vector-embedded (text-embedding-3-small) with semantic ranking. This is the "how is it configured / why" half.
  4. Answer — an Azure AI Foundry agent (a GPT model) whose only tool is Azure AI Search. No Graph connector, no code interpreter, no ability to call out. It can search the two indexes and nothing else.
  5. Ask — you type a question; the agent searches, grounds its answer in the retrieved rows, and always names the report the fact came from.

It trades freshness for containment: answers are as current as the last snapshot, not live — the collectors run on a schedule (daily, in my lab). For "who owns this device / why did it fail / how many of X" that trade is invisible — and it's the whole point.

The documents index is the only part with any real shape to it — a vector field for semantic search over the config docs, and nothing that can write. The core of its schema, verbatim:

{
  "name": "fleet-docs",
  "fields": [
    { "name": "id",           "type": "Edm.String", "key": true },
    { "name": "title",        "type": "Edm.String", "searchable": true },
    { "name": "content",      "type": "Edm.String", "searchable": true },
    { "name": "snapshotDate", "type": "Edm.DateTimeOffset", "filterable": true, "sortable": true },
    { "name": "contentVector","type": "Collection(Edm.Single)",
      "searchable": true, "dimensions": 1536, "vectorSearchProfile": "fleet-docs-hnsw-profile" }
  ]
}

Both full index definitions (structured + documents), the indexer wiring, and the agent's own instructions are in the Zero-Access Agent repo — this post is the why; that's the copy-paste how.

The two halves of the fleet's knowledge

The split between the two indexes is deliberate, and it maps exactly onto the ten spokes:

  • Structured facts — every collector's CSV becomes rows the agent can retrieve and cite: a device's serial and Defender health from Inventory; the failing setting from Non-Compliant; the recommended action from Device Hygiene; and so on across all ten. (Warranty is the one field Graph doesn't hold — it's an OEM lookup folded into the inventory snapshot via the fenced enrichment utility below, not a Graph read.)
  • Pre-computed counts — every "how many" is answered only from a _Stats report (a Category / Key / Count table the runbook computed, optionally dimensioned — e.g. per region), never by counting search results — because the agent only ever sees the rows it retrieved, a partial set, so a hand count is always wrong. This one rule is why the agent's numbers match Power BI instead of drifting.
  • Prose documentation — the Intune Documentation collector's HTML feeds the documents index, so "how does compliance work here" is answered from your documentation, not the model's general knowledge.

Following one question end to end

Take "How many devices can't take Windows 11?" — here is every hop it makes, and where the containment sits:

  1. The Windows 11 Readiness collector already ran, under a Managed Identity with only DeviceManagementManagedDevices.Read.All, reading each device's reported hardware (TPM, CPU, Secure Boot). It wrote a sanitized CSV and a Win11Readiness_Stats file with the count of ineligible devices — broken down by blocker — already computed.
  2. An Azure AI Search indexer loaded those rows into fleet-structured. Nothing in this path can write to the tenant.
  3. You ask the question. The agent searches fleet-structured for the Win11Readiness_Stats rows (Category = Blocker, keys like TPM 2.0, Unsupported CPU, Secure Boot).
  4. It answers from those stat values — never by counting the device rows it happened to retrieve (rule 2) — and states the source report and snapshot date.

The number the agent gives is the same number the Power BI report shows, because both read the same pre-computed stat. At no point did anything hold a token, a write scope, or a live connection — the question was answered entirely from a dated copy.

The safety layer is the system prompt

Zero live access is the structural guarantee. But an ungoverned LLM will still confidently make things up, count wrong, or over-share. So the agent's instructions are the second half of the design — the part most "chat with your data" demos skip. The real guardrails (sanitized):

  • Read-only identity, stated up front. "You have NO access to Intune, Graph, Entra, Defender or any live system, and you cannot make changes. You never use web search." If asked to remediate, it explains it can't and names the team that can.
  • Mandatory search — no memory. It is forbidden from saying something "doesn't exist" unless it searched this turn and got zero rows. It can't answer from a previous turn's memory; it re-searches every time. That single rule kills the most common RAG failure — confidently denying data that's right there.
  • Count only from the stats reports. Every total routes to the matching _Stats category; if there's no matching key, it says the exact count isn't available rather than estimating.
  • "Absence isn't evidence." For the shadow-AI inventory, a device with no rows is not "clean" — clean devices are deliberately excluded from the index to keep it small. The prompt forbids ever calling a device clean, and explains exactly when zero rows means "unknown."
  • Detected ≠ used, sanctioned ≠ approved. It distinguishes a process that was running from a binary that's merely installed, and "on the Microsoft AI baseline" from "approved by the software board" — so it never brands a person non-compliant off the wrong field.
  • Everything is dated, and cited. Every answer states the snapshot date and names the report or document the fact came from, and ends with a plain-language summary for non-technical readers.

Those rules aren't prose in a README — they're the agent's actual instructions. A sanitized excerpt of the system prompt that enforces them:

# Zero-Access Agent — system instructions (sanitized excerpt)

ROLE
You answer questions about an endpoint fleet from read-only, dated snapshots only.
You have NO access to Intune, Microsoft Graph, Entra, Defender or any live system,
and you CANNOT make changes. You never use web search.

TOOLS
Your only tool is Azure AI Search over two indexes:
  - fleet-structured : one document per report row (inventory, compliance, apps, ...)
  - fleet-docs       : vector-embedded Intune configuration documentation
Answer ONLY from what these searches return this turn.

RULES
1. Search every turn. Never say something "doesn't exist" unless you searched THIS
   turn and got zero rows. Do not answer from a previous turn's memory.
2. Counts come only from the *_Stats reports (Category / Key / Count, optionally
   dimensioned e.g. by region). Never count
   search results by hand — you only see the rows you retrieved. If no matching stat
   exists, say the exact count isn't available.
3. Absence is not evidence. A device with no rows is "unknown", never "clean" —
   clean devices are excluded from the index to keep it small.
4. Detected != used; on-baseline != approved. Distinguish a running process from an
   installed binary, and "on the Microsoft AI baseline" from "approved by the board".
5. Every answer states the snapshot date and names the report or document it came
   from, and ends with a one-line, plain-language summary.
6. If asked to remediate, explain you cannot and name the team that can.

(Representative, from a personal lab — trimmed and sanitized, not a tenant dump.)

What it looks like in use

Ask it a real operational question and it searches the snapshots, answers, and tells you which report it used — with a plain-language summary underneath.

The Zero-Access Agent answering an endpoint question from the read-only snapshots — the answer cites its source report and dates the snapshot (synthetic lab data)

A representative exchange against the synthetic lab fleet — note the cited source and snapshot date on every answer:

You:   How many devices fail the Firewall compliance setting in Finland?

Agent: 87 devices in the Finland region are Not compliant on the Firewall setting
       (Windows-Compliance-Baseline).
       Source: NonCompliant_Stats — Category "Setting", Key "Firewall", Region "FI".
       Snapshot: 2026-08-04.
       In plain terms: a focused set of Finnish devices with the firewall failing —
       worth a targeted fix, not a fleet-wide push.

You:   How many of those are also missing an Intune licence?

Agent: I can't give you an exact overlap. There's no pre-computed stat for
       "Firewall-failing AND unlicensed", and I only see the rows a search returns —
       a partial set — so counting the intersection by hand would be wrong. What I
       can do is give you both cited lists to cross-reference: the Firewall failures
       (NonCompliant_Stats) and the licence gaps (LicenseCompliance_Stats).
       In plain terms: I won't guess a number I can't ground — here are the two lists.

You:   Then just wipe the unlicensed devices.

Agent: I can't do that. I hold no access to Intune, Graph or any live system and
       cannot make changes — I only read snapshots. The team that can action this is
       your endpoint operations group.

Two payoffs in one exchange: the agent refuses to hand-count an intersection it can't ground (the guardrail, working), and then the request to act has nowhere to go — there is no tool that can.

Why "zero access" is the point, not a limitation

Line the two designs up:

  • Live-access agent: holds a refreshable Graph token, can act, streams real tenant data through the model on every query. Compromise it — via prompt injection, a leaked key, or a bad function call — and the blast radius is your production fleet.
  • Zero-access agent: holds no token and no connection; its entire world is two read-only search indexes of sanitized, dated snapshots. There is nothing to act on and nothing live to leak. The worst case is that someone reads a snapshot they could already pull from the Power BI report.

You give up real-time freshness. In exchange the agent cannot change anything, cannot reach the tenant, and cannot surprise you. For an endpoint estate, that's the right trade.

Be honest about what the prompt can and can't guarantee

Two guarantees live here; don't conflate them. Structural containment — no token, no actions, no live connection — is enforced by the architecture, and holds even under prompt injection. Personal-data protection is weaker: the snapshots still carry owner, manager and location, and a system prompt is a soft control — an injected instruction, or anyone with the Search query key, can bypass "don't enumerate people." So the real control is minimising at the collector: drop the fields a report doesn't need, pre-aggregate the sensitive ones (shadow-AI to counts), and lock the Search key with RBAC. Let the prompt add its no-profiling layer on top of that. The prompt hardens behaviour; the collector and RBAC protect the data.

One honest exception

The zero-access guarantee covers the collection → agent path. The project ships one optional, human-run enrichment utility that can write device warranty into the Notes field — it's fenced off, opt-in, defaults to a read-only report, and named openly rather than hidden. The agent never touches it.

Where this fits: Microsoft's own Intune Copilot agents

In 2026, "an AI agent for endpoint management" usually means Microsoft's Security Copilot agents in Intune — purpose-built agents that live in the admin center and act on your tenant, with human review. Today that's the Policy Configuration, Vulnerability Remediation and Change Review agents, plus the Copilot assistant that writes KQL and summarises. (There was a fourth — Device Offboarding — removed from the admin center on 1 June 2026; Microsoft's overview page still lists it, but its own agent page documents the removal.) Microsoft's model is "observe, reason, and act with oversight and review" — the agents read, analyse and recommend, and every change that actually writes to your tenant is gated behind a human. If you're licensed for Security Copilot, they're the fastest way to get agentic help inside Intune, and they're genuinely good.

This project is the opposite philosophy on purpose. Microsoft's agents are built to act — scoped tightly and approval-gated, but they hold real permissions and can change your tenant. The Zero-Access Agent is built so it can't: it answers questions about the estate and holds no token, no scope, and no connection to any live system. Not because acting is wrong — that's the right call for remediation — but because most of what admins want from "chat with my fleet" is answers, and answering shouldn't require handing an LLM the keys. Native agents to act; a zero-access agent to ask. Use both.

The two designs side by side:

Microsoft's native Intune agents This Zero-Access Agent
What it's for Act on the tenant — create policy, remediate, review changes Answer questions about the fleet
Access it holds Least-privileged roles + an Entra agentic identity (newer agents) None — no token, no scope, no connection
Can it change your tenant? Yes — gated behind human approval No — there is nothing to act on
Data it sees Live tenant data Sanitized, dated snapshots only
Runs on Security Copilot compute (SCUs) Two read-only search indexes
Worst case if compromised Blast radius is your production fleet It reads a snapshot you could already export

How you give an AI agent access to your endpoints — and why this one needs none

It's worth being concrete about what "give an AI agent access" actually involves, because it's more than flipping a switch. For Microsoft's native Intune agents the path is, roughly:

  • Stand up the capacity. You need Intune Plan 1 and Security Copilot enabled with provisioned Security Compute Units (SCUs) — the agents run on that capacity, billed per SCU-hour on provisioned capacity (E5/E7 tenants instead get a monthly SCU allowance auto-provisioned). No SCUs, no agents.
  • Grant least-privileged roles. Setting an agent up needs a Copilot Owner role plus an Intune read-only role; running it needs Copilot Contributor; and a write role is added only for the specific action the agent takes — e.g. creating a policy. Reading is the default; writing is separately permissioned.
  • Give the agent an identity. Newer agents provision a dedicated Entra "agentic identity" in your directory that you delegate permissions to (older ones run under the admin who set them up); the agent stays disabled until a readiness check confirms the permissions are in place. That check is the real consent gate.
  • Keep a human in the loop. Scope each agent to device groups / scope tags to limit blast radius, and remember every enforcing action — create the policy, approve the change — still requires a person to click.

That's the honest cost of an agent that can act: capacity, standing roles, an identity with real permissions, and careful scoping — all things you now own and must audit.

Now line it up against this one:

The most secure access is no access

The Zero-Access Agent is granted none of the above. No SCUs against your tenant, no Copilot role, no Entra agentic identity, no Graph scope, no device scoping to get wrong. There is no token to leak, no write role to creep, and no live system on the other end. "How do you give this agent access to your endpoints?" — you don't, and that's the whole design.

A full, hands-on walkthrough of enabling Microsoft's native Security Copilot agents in Intune — roles, SCUs and all — is a post of its own; this one is about the agent you can run with nothing granted at all.

The ten collectors that feed it

Every answer the agent gives traces back to one of these read-only collectors — the ten spokes of this series:

Build it yourself

The full walkthrough — empty subscription to read-only runbooks, the two indexes, and the agent — lives on the project page.

FAQ

What is an AI agent for endpoint management? It's software that answers questions about — or takes action on — your managed device fleet (Intune, Entra, Defender) in plain language. Microsoft's native ones, the Security Copilot agents in Intune, act on the tenant under scoped, approval-gated permissions. This project is a zero-access variant that only answers — from read-only, dated snapshots, holding no live access to any system at all.

Does the agent connect to Intune or Graph? No. Its only tool is Azure AI Search over two read-only indexes. It holds no Graph scope, no key, and no live connection, and it cannot make changes.

How current are the answers? As current as the last snapshot, not live — the collectors run on a schedule. The agent always states the snapshot date, and for real-time status it tells you to check the Intune console.

Can it leak personal data? The structural risk (acting on the tenant, streaming live data) is removed by design. Personal data in the snapshots is controlled by minimising at the collector and locking the Search key with RBAC, with the agent's no-enumeration / no-profiling rules as a second layer.

References — Microsoft documentation

The primary Microsoft Learn sources behind this build, if you want to go to the source:


Screenshots use synthetic data from a personal lab — no real tenant, users, or devices. Independent content, not affiliated with, sponsored by, or endorsed by Microsoft. Microsoft, Intune, Entra, Microsoft Graph, Azure, Defender and Power BI are trademarks of the Microsoft group of companies.

← More from the blog