One page that answers: how healthy is our security posture?
Ask the Contoso security team how healthy its security posture is, and the answer takes a tour of the portal. The device inventory, filtered by hand. The onboarding page. The security recommendations. The incidents queue. The attack surface reduction report. Every page answers part of the question, none of them answers all of it, and a manager asking for a weekly view gets a set of screenshots.
This series builds the alternative: a single page in the Microsoft Defender portal that answers the whole question, keeps itself up to date, and needs nothing extra installed to make it work.
The finished dashboard - sensor health from the lab tenant
Who this series is for
The series is written for two kinds of reader, and each part works for both.
If you lead or manage security, read this guide and the In plain terms box and Key takeaways in each part. They explain what each section of the dashboard tells you and what good looks like - no code required.
If you run Defender day to day, each part gives you the queries, the settings and real screenshots to build the dashboard yourself - or import the finished version from GitHub in a few minutes.
What the dashboard shows
Part
Section
Question it answers
1
Device health
Are all our devices reporting in, and are the servers that control our identities covered?
2
Protection controls
Are antivirus, tamper protection, attack surface reduction rules, BitLocker and the firewall actually switched on?
3
Alerts and attack surface reduction
What is raising alerts, and which attack-blocking rules are ready to move from test mode to blocking?
Each part adds one section to the same dashboard. Together they answer the question a manager actually asks: are we watching every device, are the protections on, and what is happening right now?
The control compliance grid from Part 2 - antivirus green, attack surface reduction rules red
The jargon, in one line each
Term
What it means
Microsoft Defender portal
Microsoft’s security console (security.microsoft.com), where alerts, devices and investigations live
Workbook
A dashboard page inside the portal, built from charts and tables that update themselves
Advanced hunting
The portal’s search engine over the last 30 days of security data
KQL
Kusto Query Language - the language advanced hunting uses to ask questions of that data
Sensor
The part of Defender for Endpoint on each device that reports what happens on it
Tier 0 servers
The few servers that control identity - domain controllers, AD CS, AD FS and Entra Connect. Compromise one and an attacker can reach everything
Tamper protection
A setting that stops anyone, including an attacker, switching Defender off
EDR
Endpoint detection and response - Defender’s ability to spot an attack in progress and act on it
ASR rules
Attack surface reduction rules - switches that block common attack techniques, such as an Office document launching a program
Defender Vulnerability Management
The part of Defender that checks every device’s settings against Microsoft’s recommended configuration
Why a workbook in the Defender portal
It reads advanced hunting directly. Every table Contoso already uses for hunting - device, vulnerability management, alert and event data - can drive a section. There is no data export, no Log Analytics workspace and no Azure resource to build first.
It is live. Each section runs its query whenever the workbook opens or refreshes, and auto refresh can rerun everything on a schedule, from every five minutes to once a day.
It respects each viewer’s access. A viewer sees only the data their own permissions allow, so the same workbook works for the central security team and for a regional team limited to its own devices.
It is shareable and portable. Workbooks are saved for the whole organisation, and the dashboard exports as one file that imports into another tenant in seconds.
Live data and history
Device, alert and event data is current to within minutes. The protection control sections read Defender Vulnerability Management assessments, which Defender updates on its own schedule rather than continuously. Advanced hunting keeps 30 days of data, so the dashboard shows the current state and the last month - which is exactly what a weekly review needs.
An example, not the limit
The three sections in this series are an example of what a Defender workbook can do, not a boundary. Any table in the advanced hunting schema can become a section, so the same approach extends well beyond device health and alerts. A few directions Contoso could take next:
Use case
Advanced hunting table
What it could show
Sensitive file activity on devices
DeviceFileEvents
Files with a sensitivity label that were created, modified, renamed or copied, by label and by device
Purview data security signals
DataSecurityEvents (preview)
Activity matched to sensitive information types and DLP policies, shared from Insider Risk Management
Generative AI app usage
CloudAppEvents, DeviceNetworkEvents
Which users and devices use generative AI apps and services, and how often
Local AI agents
AgentsInfo
AI agents discovered on onboarded devices, with their versions
Sign-in and email risk
IdentityLogonEvents, EmailEvents
Failed sign-ins on Tier 0 accounts, and phishing delivered or blocked
Each use case follows the same pattern as the sections in this series: write the query in advanced hunting, check the results, then add it to the workbook as a new section. Some tables depend on other products or settings - DataSecurityEvents, for example, is populated only when Insider Risk Management shares its alerts with Defender - so confirm a table returns data in your own tenant first.
Where the dashboard lives
The finished dashboard sits in the Defender portal under Workbooks (security.microsoft.com/sentinel/workbooks), alongside Microsoft’s example workbooks. Part 1 shows how to create it, save it and open it again.
What you need
Workbooks in the Defender portal. Available to tenants using Microsoft Sentinel in the Defender portal, and to tenants on the Integrated Security Operations Center (ISOC) preview - Microsoft Defender Suite, Microsoft 365 E5 or E7 without an active Sentinel workspace.
To build it: the Workbooks Unified RBAC permission with Manage access, or the Security Administrator role.
To view it: the Workbooks permission with Reader access, or the Security Reader role.
Data: devices onboarded to Defender for Endpoint, and Defender Vulnerability Management for the Part 2 controls.
How the series works
Every query is shown in full and explained in the post that uses it. Every screenshot comes from a real lab tenant, so you will see what the dashboard looks like with real gaps in it - including a device that has stopped reporting and a set of attack surface reduction rules that were never switched on.
If you would rather start with the finished product, the companion repository has the complete workbook as a single file, ready to import, together with every query as a .kql file.
Series:This is Part 1 of 3 in my series on building a Defender security dashboard with workbooks. New to the series? Start with the series guide, which explains what the dashboard shows and what you need.
In plain terms: Every device in the organisation should have a Defender sensor reporting in. A device that goes quiet is a blind spot: if it is attacked, nobody sees it, and Defender does not raise an alert to say it has stopped reporting. This part builds the first page of the dashboard, which answers two questions a manager can act on: are all our devices reporting, and are the servers that control our identities covered?
The series guide set out the goal: one page in the Defender portal that shows how healthy Contoso’s security posture is, built entirely on advanced hunting. This part creates the workbook and builds its first section - device health.
Workbooks in the Defender portal
A workbook is a dashboard page made of sections: text, parameters and queries. Each query section asks advanced hunting a question in KQL and shows the answer as a table, chart or tiles.
Workbooks in the Defender portal navigation
Workbooks sits in the Defender portal navigation. Tenants that use Microsoft Sentinel in the Defender portal reach it under Microsoft Sentinel; tenants on the Integrated Security Operations Center (ISOC) preview get it as a top-level item, as shown above, without needing a workspace.
Create the workbook
Go to Workbooks and select + Add workbook, then Edit.
Replace the default text with a heading such as Contoso Defender Dashboard.
The workbook in edit mode, with Advanced editor in the toolbar
Save once, then reopen. In testing, every Save on an unsaved new workbook page created a separate copy. Save the first time, then open the workbook from the Workbooks list before making further changes.
Add a parameter for Tier 0 servers
Domain controllers, AD CS, AD FS and Entra Connect servers deserve their own view, and only Contoso knows their names. Add a parameters section with one text parameter called IdentityServers, and enter the short host names separated by commas, for example dc01,dc02,adfs01,ca01,aadc01. Queries in this series read it as {IdentityServers}.
The IdentityServers parameter under the workbook header
Device health
Each of the following is a query section: select Add, choose a query section, set Data source to Advanced hunting, leave Time range as Set in query, paste the query, select Run query, choose the visualisation and select Apply changes.
A query section in edit mode with Advanced hunting as the data source
Why 30 days? A device that stops reporting also stops appearing in short time windows, so a 7-day query quietly drops the very devices you are looking for. These queries look back 30 days for each device’s last record, and flag any device not seen for more than 7 days.
1. Sensor health at a glance
The headline: how many devices are onboarded, how many are reporting normally, and how many have gone quiet or report a problem.
DeviceInfo
| where Timestamp > ago(30d)
| summarize arg_max(Timestamp, *) by DeviceId
| where OnboardingStatus == "Onboarded"
| extend Health = iff(Timestamp < ago(7d), "Not seen for 7+ days", SensorHealthState)
| summarize Onboarded = count(),
Active = countif(Health == "Active"),
NotSeen = countif(Health == "Not seen for 7+ days"),
Unhealthy = countif(Health has "Inactive" or Health has "Misconfigured" or Health has "Impaired")
| project Pairs = pack_array(pack("Status", "Onboarded", "Count", Onboarded), pack("Status", "Active", "Count", Active),
pack("Status", "Not seen for 7+ days", "Count", NotSeen), pack("Status", "Unhealthy", "Count", Unhealthy))
| mv-expand Pairs
| project Status = tostring(Pairs.Status), Count = toint(Pairs.Count)
Show this as tiles. The last two numbers should be close to zero. In the lab, the single test device has not reported for over a week, and the tile shows it immediately.
Sensor health tiles
2. Sensor health by device class
The same numbers split by type of device, so it is clear where the problems sit.
DeviceInfo
| where Timestamp > ago(30d)
| summarize arg_max(Timestamp, *) by DeviceId
| where OnboardingStatus == "Onboarded"
| extend Health = iff(Timestamp < ago(7d), "Not seen for 7+ days", SensorHealthState)
| extend Class = case(DeviceType == "Server" and OSPlatform startswith "Windows", "Windows server",
OSPlatform == "Linux", "Linux",
DeviceType == "Workstation", strcat("Workstation - ", OSPlatform), OSPlatform)
| summarize Devices = count(),
Active = countif(Health == "Active"),
NotSeen = countif(Health == "Not seen for 7+ days"),
Inactive = countif(Health == "Inactive"),
Misconfigured = countif(Health has "Misconfigured"),
Impaired = countif(Health has "Impaired") by Class
| order by Devices desc
Workstations that have not been seen are often laptops left switched off; a server that has not been seen almost never has a good reason.
Sensor health by device class table
3. Devices that have stopped reporting
The list that turns a number into a task: every device that has gone quiet or reports a problem, and how long it has been silent.
DeviceInfo
| where Timestamp > ago(30d)
| summarize arg_max(Timestamp, DeviceName, OSPlatform, DeviceType, SensorHealthState, OnboardingStatus, MachineGroup) by DeviceId
| where OnboardingStatus == "Onboarded"
| extend DaysSinceLastSeen = datetime_diff('day', now(), Timestamp)
| where SensorHealthState != "Active" or DaysSinceLastSeen >= 7
| project DeviceName, OSPlatform, DeviceType, SensorHealthState, LastSeen = Timestamp, DaysSinceLastSeen, DeviceGroup = MachineGroup
| order by DaysSinceLastSeen desc
The lab device still shows Active as its last sensor state - it simply stopped reporting, which is exactly why the 7-day check matters. Sort by DaysSinceLastSeen and work from the top: a device unseen for weeks is either retired, rebuilt under a new name, or a blind spot.
Devices not reporting a healthy sensor
4. Tier 0 identity servers
Domain controllers and the other identity servers are the keys to the kingdom. This section checks each one by name, so a server that was never onboarded shows up as a gap instead of simply being missing from the results.
let names = split(tolower(replace_string("{IdentityServers}", " ", "")), ",");
let seen = DeviceInfo
| where Timestamp > ago(30d)
| summarize arg_max(Timestamp, DeviceName, OSPlatform, ClientVersion, SensorHealthState, OnboardingStatus, MachineGroup) by DeviceId
| extend Host = tolower(tostring(split(DeviceName, ".")[0]))
| where Host in (names)
| project Host, DeviceName, OnboardingStatus, SensorHealthState, ClientVersion, DeviceGroup = MachineGroup, LastSeen = Timestamp;
print Host = names
| mv-expand Host to typeof(string)
| where isnotempty(Host)
| join kind=leftouter seen on Host
| extend Status = iff(OnboardingStatus == "Onboarded" and SensorHealthState == "Active" and LastSeen > ago(7d), "Healthy", "Needs attention")
| project Host, Status, OnboardingStatus, SensorHealthState, ClientVersion, DeviceGroup, LastSeen
| order by Status desc, Host asc
Show the result as a table, and use an icon format on the Status column so problems stand out. A server counts as healthy only if it is onboarded, its sensor is active and it has reported in the last 7 days. The lab has no domain controllers, so every name in the parameter shows Needs attention - exactly what a missing server would look like.
Tier 0 identity server coverage
5. Devices that can be onboarded
Defender also discovers devices on the network that are not yet protected. This section counts them by platform - the unmanaged devices attackers look for first.
DeviceInfo
| where Timestamp > ago(30d)
| summarize arg_max(Timestamp, OnboardingStatus, OSPlatform, DeviceType) by DeviceId
| where OnboardingStatus == "Can be onboarded"
| summarize Devices = count() by OSPlatform, DeviceType
| order by Devices desc
An empty result is good news. In the lab, device discovery found nothing waiting to be onboarded.
Devices that can be onboarded, by platform
Save it
Select Done editing, then Save, and enter a title. The workbook is saved for the whole organisation and appears in the Workbooks list for anyone with Workbooks Reader access.
Save Workbook dialog
Opening the dashboard
Sign in to the Microsoft Defender portal at security.microsoft.com.
Select Workbooks in the navigation (under Microsoft Sentinel in Sentinel tenants), or browse directly to security.microsoft.com/sentinel/workbooks.
Under My workbooks, select Contoso Defender Dashboard, then View saved workbook in the side panel.
The saved dashboard in the Workbooks list
Select the star next to the workbook to add it to your favourites, so it stays at the top of the list. Anyone with Workbooks Reader access, or the Security Reader role, sees the same workbook in their own list.
The shortcut: import the finished workbook
The complete dashboard from all three parts is in the companion repository as DefenderDashboard.workbook. Create a new workbook, select Edit, then Advanced editor, replace the JSON with the file’s contents and select Apply. Every query section uses "queryType": "advancedHunting", so it runs in any tenant with the same tables.
The workbook Advanced editor
Every query is also in the repository as its own .kql file, ready to run in advanced hunting. Those files cannot read a workbook parameter, so the two queries that use {IdentityServers} have a let line marked // EDIT instead, where you type your Tier 0 host names.
A device that stops reporting does not raise an alert - this section makes the silence visible.
Look back 30 days, not 7, or the devices you most need to find drop out of the results.
Check Tier 0 servers by name, so a server that was never onboarded shows up as a gap.
Next: Part 2
A device that reports is not the same as a device that is protected. Part 2 adds a single grid showing whether each protection is actually switched on, for every type of device.
Series:This is Part 2 of 3 in my series on building a Defender security dashboard with workbooks. New to the series? Start with the series guide, which explains what the dashboard shows and what you need.
In plain terms: A device can be reporting in and still be unprotected - its antivirus standing back behind another product, its tamper protection off, or its attack-blocking rules never switched on. Defender already checks every one of these settings on every device. This part turns those checks into one colour-coded grid: green where a protection is on, red where it is not.
Part 1 showed which of Contoso’s devices are reporting - and caught one that is not. But a sensor that reports is not the same as a device that is protected.
Defender Vulnerability Management assesses each device’s protection settings against Microsoft’s recommended configuration. The results are in two advanced hunting tables, DeviceTvmSecureConfigurationAssessment and its reference table DeviceTvmSecureConfigurationAssessmentKB. This part turns them into one grid and three supporting views.
Protection controls
Add each query below as a query section, exactly as in Part 1: data source Advanced hunting, time range Set in query.
1. The control grid
One grid for every protection control and every type of device. Each cell is the percentage of devices where that control is on.
let dcNames = split(tolower(replace_string("{IdentityServers}", " ", "")), ",");
let cls = DeviceInfo
| where Timestamp > ago(30d)
| summarize arg_max(Timestamp, DeviceName, DeviceType, OSPlatform) by DeviceId
| extend Host = tolower(tostring(split(DeviceName, ".")[0]))
| extend Class = case(Host in (dcNames), "Identity server", OSPlatform == "Linux", "Linux",
DeviceType == "Server", "Windows server", OSPlatform startswith "Windows", "Workstation", "Other")
| project DeviceId, Class;
DeviceTvmSecureConfigurationAssessment
| where IsApplicable
| join kind=inner cls on DeviceId
| join kind=inner (DeviceTvmSecureConfigurationAssessmentKB
| project ConfigurationId, ConfigurationName, ConfigurationSubcategory) on ConfigurationId
| where ConfigurationSubcategory in ("Antivirus", "Attack Surface Reduction", "Bitlocker", "EDR",
"Exploit Guard", "Firewall", "SmartScreen")
| summarize Pct = round(100.0 * countif(IsCompliant) / count(), 0) by Area = ConfigurationSubcategory, Control = ConfigurationName, Class
| evaluate pivot(Class, any(Pct), Area, Control)
| order by Area asc, Control asc
Show the result as a grid and apply a colour scale to the percentage columns - red at 0, green at 100. Identity servers get their own column through the IdentityServers parameter from Part 1, because a domain controller without these protections is a far bigger risk than a laptop without them.
In the lab, every antivirus control - including tamper protection - is green, and every attack surface reduction rule is red: Defender is running, but not one ASR rule is switched on.
Control compliance grid with colour scale
A note on the categories
The queries filter on ConfigurationSubcategory, and the match is case-sensitive. The security control subcategories in our test tenant were Antivirus, Attack Surface Reduction, Bitlocker, EDR, Exploit Guard, Firewall, Onboard Devices and SmartScreen. Check the list in your own tenant before relying on the filter:
DeviceTvmSecureConfigurationAssessmentKB
| where ConfigurationCategory == "Security controls"
| summarize Controls = count() by ConfigurationSubcategory
2. The weakest controls
The grid shows percentages; this chart shows volume - the controls failing on the most devices, where one policy change fixes the most.
DeviceTvmSecureConfigurationAssessment
| where IsApplicable
| join kind=inner (DeviceTvmSecureConfigurationAssessmentKB
| project ConfigurationId, ConfigurationName, ConfigurationSubcategory) on ConfigurationId
| where ConfigurationSubcategory in ("Antivirus", "Attack Surface Reduction", "Bitlocker", "EDR",
"Exploit Guard", "Firewall", "SmartScreen")
| summarize NonCompliant = countif(not(IsCompliant)), Applicable = count() by ConfigurationName
| where NonCompliant > 0
| top 15 by NonCompliant desc
| project Control = ConfigurationName, NonCompliant
Show it as a categorical bar chart. The standard bar chart treats the control names as numbers and draws a single bar.
Top 15 controls by non-compliant devices
3. Antivirus mode
Whether Defender Antivirus is actively protecting each device, or standing back in passive mode behind another antivirus product.
DeviceTvmInfoGathering
| extend AvMode = case(tostring(AdditionalFields.AvMode) == "0", "Active",
tostring(AdditionalFields.AvMode) == "1", "Passive",
tostring(AdditionalFields.AvMode) == "4", "EDR in block mode", "Unknown")
| join kind=inner (DeviceInfo
| where Timestamp > ago(30d)
| summarize arg_max(Timestamp, OSPlatform) by DeviceId) on DeviceId
| summarize Devices = count() by OSPlatform, AvMode
| order by OSPlatform asc, Devices desc
Passive mode can be intended - during a migration from another product, for example - but it should be a decision, not a surprise. EDR in block mode lets Defender step in behind the other product when it misses something. Every device in passive mode should match a known exception.
Antivirus mode by platform
4. Devices failing antivirus, tamper protection or EDR controls
A named list of devices failing the controls an attacker switches off first - antivirus and tamper protection - plus the EDR controls that let Defender see what happens next.
DeviceTvmSecureConfigurationAssessment
| where IsApplicable and not(IsCompliant)
| join kind=inner (DeviceTvmSecureConfigurationAssessmentKB
| project ConfigurationId, ConfigurationName, ConfigurationSubcategory) on ConfigurationId
| where ConfigurationSubcategory in ("Antivirus", "EDR")
| summarize FailedControls = make_set(ConfigurationName) by DeviceName, OSPlatform
| extend Failed = array_length(FailedControls)
| order by Failed desc, DeviceName asc
Tamper protection sits within the Antivirus subcategory, so two subcategories cover all three. In the lab, the test device fails three EDR controls, including impaired communications - the same device Part 1 flagged as not seen for a week.
Devices failing antivirus, tamper protection or EDR controls
Key takeaways
A device that reports is not necessarily protected - check the settings, not just the sensor.
In the lab, antivirus was fully on but no attack surface reduction rules were configured - a common gap that one policy closes.
A device failing EDR controls is a device Defender may not be able to see.
Next: Part 3
Part 3 completes the dashboard with what is actually happening: alerts by source, the alerts that keep coming back, attack surface reduction rules that are ready to block, and auto refresh.
Series:This is Part 3 of 3 in my series on building a Defender security dashboard with workbooks. New to the series? Start with the series guide, which explains what the dashboard shows and what you need.
In plain terms: The first two parts showed whether devices are being watched and whether their protections are on. This part shows what is actually happening: which security products are raising alerts, which alerts keep repeating, and which attack-blocking rules are ready to move from test mode (audit) to blocking. It also sets the dashboard to refresh itself, so it can sit on a screen in the security team’s office.
Alerts and attack surface reduction
As in Parts 1 and 2, add each query below as a query section with Advanced hunting as the data source and the time range Set in query.
1. Alerts by source and severity
Which products are raising alerts, and how serious those alerts are.
AlertInfo
| where Timestamp > ago(30d)
| summarize Alerts = dcount(AlertId) by ServiceSource, Severity
| order by Alerts desc
AlertInfo holds alerts from across Microsoft Defender XDR, and Microsoft Purview alerts, such as Data Loss Prevention and Insider Risk Management, can appear there too, each with its own ServiceSource. In a tenant that raises both, one section covers Defender and Purview together; our lab only had Defender for Endpoint alerts. Show it as a categorical bar chart, with ServiceSource on the x-axis and Severity as the series.
Alerts by source and severity
2. Alerts over time
Alert volume day by day, so a sudden spike - or a product that has gone quiet - stands out.
AlertInfo
| where Timestamp > ago(30d)
| make-series Alerts = dcount(AlertId) default = 0 on Timestamp from startofday(ago(30d)) to now() step 1d by ServiceSource
make-series fills days with no alerts with zero, so the line drops to the axis instead of jumping between busy days. Show it as a time chart.
Daily alerts by source over 30 days
3. The alerts that keep coming back
A recurring alert is either a real problem nobody has fixed or noise that needs tuning - either way it needs an owner. This section lists the 20 most frequent.
AlertInfo
| where Timestamp > ago(30d)
| summarize Alerts = dcount(AlertId), LastSeen = max(Timestamp) by Title, ServiceSource, Severity
| top 20 by Alerts desc
In the lab, one alert - a connection to a custom network indicator - fired seven times in a month. That is exactly the kind of repeat that should be either fixed or tuned.
Top 20 alert titles
4. ASR rules - blocked and audited
Attack surface reduction rules run in one of two modes: audit, which only records what a rule would have blocked, and block, which stops it. This section shows each rule’s mode and how often it fires.
DeviceEvents
| where Timestamp > ago(30d)
| where ActionType startswith "Asr"
| extend Mode = iff(ActionType endswith "Audited", "Audit", "Block"),
Rule = replace_regex(ActionType, @"^Asr|(Audited|Blocked)$", "")
| summarize Events = count(), Devices = dcount(DeviceId) by Rule, Mode
| order by Events desc
Rules that only ever appear in Audit are not protecting anything yet. Our lab returns no results here, which matches Part 2: with no ASR rules configured, there is nothing to audit or block. An empty section is a finding in itself.
ASR events by rule and mode
5. Which audit rules are ready to block
Moving a rule from audit to block is where the protection comes from. This list ranks audit-mode rules by how many devices they affect, so you can start with the safest.
DeviceEvents
| where Timestamp > ago(30d)
| where ActionType startswith "Asr" and ActionType endswith "Audited"
| summarize Events = count(), Devices = dcount(DeviceId), TopProcesses = make_set(InitiatingProcessFileName, 5) by Rule = ActionType
| order by Devices asc, Events asc
The rules at the top touch the fewest devices - the easiest to switch to block. The TopProcesses column shows what would be blocked, so Contoso can decide on exclusions before changing the rule, not after.
Audit-mode ASR rules by devices affected
Keep it current
Select Auto refresh and choose an interval: 5, 10, 15 or 30 minutes, 1, 2 or 4 hours, or 1 day. Refresh pauses while the workbook is being edited and stops when it is closed. For a wall display in a security operations centre, 15 minutes is a sensible balance.
Auto refresh interval options
Build the next section with Copilot
Tenants with Security Copilot and available Security Compute Units see an Edit with Copilot option and can add sections by describing them in plain English. In edit mode, select Edit with Copilot and describe the result you want rather than the query, for example: “Add a bar chart of email messages blocked by Defender for Office 365 in the last 7 days, by threat type.” Copilot writes the KQL and the chart; review the query before saving, exactly as you would one you wrote yourself.
Key takeaways
One page now shows alerts from across Defender - and Purview, where it raises them - instead of one portal page per product.
A recurring alert needs an owner, not another acknowledgement.
ASR rules only protect in block mode; their audit data tells you which rules are safe to switch.
The finished dashboard
Contoso now has one page in the Defender portal that shows whether every device is reporting, whether the identity servers are covered, whether the protections are switched on, and what is raising alerts - built entirely from advanced hunting, with nothing extra to deploy. The finished workbook and every query are in the companion repository, linked below.
Series:Part 1 introduced AI agent runtime protection in Microsoft Defender for Endpoint. Part 2 covered the lab build and validation. Part 3 found the agents running on your endpoints. Part 4 blocked an unsanctioned MCP server with a Defender indicator. Part 5 automated it. Part 6 moves from Defender controls on the endpoint to governance in Microsoft Agent 365.
From Defender controls to Agent 365 governance
Parts 1 to 5 of this series were about Defender controls. They covered seeing agents on endpoints, detecting prompt injection, blocking unsanctioned MCP servers and controlling where tools like Claude Code can connect. Those controls act at runtime, on the device, once an agent is already running.
This part moves up a level, to governance and control in Microsoft Agent 365. Agent 365 is where Microsoft now inventories every agent in a tenant, approves them before people can use them, and applies policy when they are published. Entra ID sits underneath it, giving the agents that have an identity their own Conditional Access, ID Protection and access governance.
Defender answers “what is this agent doing right now?” Agent 365 answers the questions that should come first: which agents do we have, who owns them, who approved them, and what are they allowed to reach?
Core concepts
A few terms come up throughout:
Agent: in Agent 365, anything in the registry, whatever platform built it: Copilot Studio, Microsoft, a third-party publisher or your own developers.
Agent identity: an agent that signs in to Entra with its own identity, much like a service principal. Only these agents can be targeted by Entra Conditional Access, ID Protection and access packages.
Agent identity blueprint: the template an agent identity is created from. It sets the identity, permissions, policies and naming for every agent built from it. Copilot Studio has its own blueprint, so every Copilot Studio agent with an identity comes from the same one.
Blueprint principal: the blueprint’s object in your tenant. Conditional Access uses it to include or exclude every agent built from that blueprint in one go.
Agent user: an agent that also has a user account, so it can work in Teams, Outlook, SharePoint and OneDrive. It is a separate identity type, so a policy for agent identities does not cover agent users.
Owner and sponsor: the people accountable for an agent. Owners handle the technical side; sponsors answer for why it exists.
User consent: the Entra setting that decides whether ordinary users can grant an app or agent access to their data without an administrator.
The diagram above shows how these fit together. Owners and sponsors maintain a blueprint. The blueprint creates agent identities, and can create agent users for agents that need Microsoft 365. Conditional Access and ID Protection then evaluate every token request those identities make. The point to take from it: set policy at the blueprint, and every agent built from it is covered.
The diagram below shows the four gates this post is built around. Each one asks a different question, and an agent has to pass all four before it is any use.
Gate
Question
Native control
Where
1
Who can build an agent?
Copilot Studio environments and licensing, Foundry RBAC, Agent ID Developer role, user consent
Power Platform, Azure, Entra
2
Is it approved for people to use?
Agent and plugin access, user access, policy templates, approval of requests
Microsoft 365 admin center (Agent 365)
3
What can it reach?
Owners and sponsors, access packages with approval and expiry
Entra ID Governance
4
Can it authenticate at all?
Conditional Access for agents, plus ID Protection
Entra
Gate 2 covers every agent in the registry. Gates 3 and 4 only reach agents with an Entra agent identity, which is why Agent 365 comes first.
What a dev tenant looks like
To write this part I reviewed a Microsoft 365 E7 dev tenant, read-only.
81 from the Copilot Studio blueprint, 1 from Purview
Waiting for approval
0 requests
–
Governance templates
None
–
Agent Conditional Access
–
2 policies, both report-only
These figures are from a dev tenant. Agent 365 and Entra count agents in different ways, so the totals do not line up exactly between the two portals.
The headline is the gap between the two columns. Most agents in the tenant have no Entra agent identity at all, so Entra’s Conditional Access, ID Protection and access packages cannot see them. Entra says so itself on the Agent identities page: it lists only agents with Entra identities, and points to Agent 365 for the rest. That is why the governance has to start in Agent 365.
The risks
Risk
What I saw
Why it matters
Agent sprawl
Hundreds of agents across several platforms
Nobody can review what they cannot count
Agents outside Entra
About three quarters of agents had no Entra agent identity
Identity controls (CA, ID Protection, access packages) do not reach them
Third-party agents
Two thirds of the registry
They cannot go through the Agent 365 approval workflow
No approval in use
Nothing in the Requests queue
Nothing had gone through the approval workflow, so there is no record of anyone signing agents off
Ownerless agents
An agent identity with no owner and no sponsor
No one to ask what it does, or to switch it off
Standing permissions
An agent holding a delegated permission granted by admin consent
Access granted once, never reviewed, never expires
Permissive defaults
External publishers allowed; no governance templates
Agents arrive without policy applied
Controls not enforced
Agent CA policies left in report-only
Risky agents would be logged, not blocked
Data exposure through users
Copilot Studio agents act with the signed-in user’s permissions
An over-shared agent exposes whatever its users can reach
None of this is unusual. Agent 365 and Entra Agent ID are new, the defaults are open, and agents have been arriving faster than anyone has been governing them.
Two ways to put the controls in place
Option 1: the portal walkthrough. The rest of this post walks through each control, with screenshots from the dev tenant, in the Microsoft 365 admin center and the Entra admin center. Use it to understand what each setting does, or to apply it by hand in a small tenant.
Option 2: the script. The companion PowerShell script, Invoke-Agent365Governance.ps1, asks for one thing, your tenant ID, and signs you in interactively. It then:
Reviews the tenant read-only: every agent identity grouped by blueprint, agents with no owner, agents holding granted permissions, legacy Copilot Studio service principals, and existing agent Conditional Access policies. Everything is exported to CSV.
Creates the Gate 4 policies (CA-AGT-001 to 003, plus the optional CA-WID policies for service principals) in report-only mode, skipping any that already exist.
Run it with -ReviewOnly to change nothing, or -WhatIf to see what it would create.
The README explains every parameter and the Microsoft 365 and Entra roles the account running it needs. The Agent 365 settings in Gate 2 have no published API yet, so the script cannot set them. The README and the script’s summary list them as manual steps. Gate 2, later in this post, walks through each one with screenshots.
Prerequisites
Licensing
Capability
Licence needed
Agent 365 registry, approvals, templates
Microsoft Agent 365, included in Microsoft 365 E7 (Learn)
Entra Agent ID (agent identities and blueprints)
Included for Microsoft Entra customers
Conditional Access for agents
Microsoft 365 E7, or Agent 365 plus Entra ID P1 or Microsoft 365 E3 (Learn)
Risk-based Conditional Access for agents
As above, plus Entra ID P2 or Microsoft 365 E5 (Learn)
Global Reader (check it can open the Agent 365 pages in your tenant)
Tip: Security defaults must be off, or Conditional Access will not apply to agents at all.
Step 1: Take stock
1a. Agent 365: the full inventory
In the Microsoft 365 admin center, browse to Agents > Overview. The cards show the registry total, active users, connected platforms, pending requests, agents at risk and agents without owners.
Open Agents > All agents. The Registry tab lists every agent with its status, platform, risks, users and sessions; filter by Status, Publisher type, Platform or Channel.
Open Agents > Agent map to see the agents clustered by platform. This is the quickest way to show leadership where agents are coming from.
1b. Entra: the agents with an identity
In the Entra admin center, browse to Entra ID > Agents > Overview.
Open Agent identities for the list, and Agent blueprints to see which blueprints they come from.
Compare the two totals. The difference is the number of agents your identity controls cannot see.
1c. Find legacy Copilot Studio service principals
Older Copilot Studio agents use plain service principals rather than agent identities. Their service principals carry consistent tags such as AgenticApp, AIAgentBuilder and power-virtual-agents-<id> (Learn):
<#.SYNOPSIS Lists service principals tagged as Copilot Studio / agentic apps..NOTES Author: Seรกn O'Farrell Website: https://cloud-secure.ai/#>Connect-MgGraph-Scopes"Application.Read.All"-NoWelcome$agentTags=@('AgenticApp','AIAgentBuilder','AgenticInstance')$legacyAgents=Get-MgServicePrincipal-All-PropertyId,AppId,DisplayName,Tags,CreatedDateTime|Where-Object{$tags=$_.Tags($tags|Where-Object{$_-in$agentTags-or$_-like'power-virtual-agents-*'})}$legacyAgents|Select-ObjectDisplayName,AppId,CreatedDateTime,@{Name='CopilotStudioTag';Expression={$_.Tags|Where-Object{$_-like'power-virtual-agents-*'}}}|Sort-ObjectCreatedDateTime-Descending|Format-Table-AutoSizeWrite-Host"๐ Found $($legacyAgents.Count) agent-tagged service principals"
Don’t mass-disable. Blocking an agent or disabling a blueprint can break something the business relies on. Triage first, with the owners.
Gate 1: Control who can build
You cannot stop a Copilot Studio agent getting an identity, but you can control who can build agents in the first place (Learn):
Platform
Native control
Copilot Studio
Block self-service trials, assign Copilot Studio licences only to approved makers, restrict environment creation to Power Platform Administrators
Microsoft Foundry
Turn off self-service subscription creation; assign Azure AI Account Owner and Azure AI User only to approved teams
Custom agents
Limit the Agent ID Administrator and Agent ID Developer roles
Third-party agents
Tighten user consent so users cannot consent to new agent blueprints themselves
Microsoft Teams
Control which apps and agents users can add in the Teams admin center
Gate 2: Govern agents in Agent 365 (the main event)
Everything in this gate happens in the Microsoft 365 admin center, under Agents > Settings and Agents > All agents.
2a. Agent and plugin access
Decide which kinds of agent users can install. The dev tenant allowed agents from all external publishers, which is the opposite of where most organisations want to start.
Open Agents > Settings > Agent and plugin access.
Choose which types users can install: built by Microsoft, built by your organisation, or built by external publishers.
If you allow external publishers, consider Only certified external publishers rather than All external publishers.
Select Save.
2b. User access
Control who can use agents at all: All users, No users, or Specific users or groups. A pilot group is a sensible place to start while the rest of the controls go in. Microsoft notes that wider access can increase cost if you use pay-as-you-go billing.
2c. Policy templates
Policy templates apply licences and policies automatically when an agent is created or added. The dev tenant had none.
Open Agents > Settings > Policy template and select Add a new policy template.
On Details, give it a name and description, then choose What kind of agent can use this policy template? and pick Agents without their own identity or Agents with their own identity (preview).
On Security policies and protections, choose the policies to apply. For agents without their own identity, the dev tenant offered none, so expect this page to vary by agent type and licensing.
Review and create.
2d. Work the Requests queue
When a maker publishes an agent to the tenant, it should land in Agents > All agents > Requests for approval rather than going straight to users (Learn). An empty queue in a tenant with hundreds of agents is a warning sign, not a sign that all is well.
For each request, ask four questions (Learn): does it ask only for what it needs, is the publisher trusted, is the business purpose clear, and does it fit your AI policy? Then approve it with Publish to store, choosing the audience and template, or reject it.
Gap: third-party agents cannot be requested or approved through this workflow. In the dev tenant, that was two thirds of the registry. Your controls for them are Gate 1 (user consent) and Gate 2a (which publishers to allow), plus blocking anything you do not trust.
2e. Review an agent
Select any agent in the registry to open its details. The tabs tell you most of what you need: Details, Users, Data & tools, Security, Permissions and Activity. From here you can also Assign new owner and Block the agent.
The Security tab is where Agent 365 ties the other products together: Purview protections for the agent’s activity and data, and a link to its Entra identity.
2f. Block, delete and restore
Block stops an agent being surfaced to users. Delete soft-deletes it for 30 days, and you can restore it from Agents > Deleted agents (Learn). Both need a role that can manage agents. They were greyed out for the read-only account I used.
Gate 3: Owners, sponsors and access
This gate only applies to agents with an Entra agent identity, which was 84 of the 318 in the dev tenant. For those, Entra adds ownership and access governance. Open one from Entra ID > Agents > Agent identities.
3a. Owners and sponsors
Owners handle the technical side; sponsors are accountable for the agent’s purpose and lifecycle (Learn). Microsoft now requires sponsors when agent identities are created, but older agents can still have none. The one above had neither. Add them under Owners and sponsors, using a Microsoft 365 group as sponsor so ownership survives staff changes.
3b. Permissions and access packages
The same agent held a delegated permission granted by admin consent: access granted once and never reviewed. Check Granted permissions on each agent, and move standing access into access packages, which add approval and an expiry date.
Browse to ID Governance > Entitlement management > Access packages and create a package for a class of agent.
Add the resources it needs: groups, application permissions or Entra roles.
In the request policy, allow agent identities to request it, and require approval.
Set an expiry, such as 90 or 180 days, so access is re-justified (Learn).
Gate 4: Conditional Access and ID Protection
Like Gate 3, this gate only reaches agents with an Entra agent identity or agent user. Everything else is governed in Agent 365.
4a. Conditional Access for agents
Agent policies show up in the normal Conditional Access policy list; filter by Actors: Agents to see them. For agent identities the only condition is agent risk, and the only grant control is Block (Learn).
The starter set I recommend:
Policy
Assign to
Condition
Control
CA-AGT-001 Block agents from unapproved blueprints
All agent identities, excluding approved blueprint principals
None
Block
CA-AGT-002 Block high-risk agent identities
All agent identities
Agent risk: High
Block
CA-AGT-003 Block high-risk agent users
All agent users
Agent risk: High
Block
To create CA-AGT-002 in the portal:
Browse to Entra ID > Conditional Access > Policies and select New policy. Name it CA-AGT-002 Block high-risk agent identities.
Under Assignments, select Users, agents or workload identities, then under What does this policy apply to? select Agents and All agent identities.
Under Target resources, include All resources.
Under Conditions, set Agent risk to High.
Under Grant, select Block access, set Enable policy to Report-only, and select Create.
CA-AGT-003 is the same, but choose All agent users in step 2. CA-AGT-001 has no condition; instead, under Exclude choose Select individual agent identities and use the Agent blueprint principals tab to exclude the blueprints you approve (Learn). Run all three in report-only for 48 hours, check the results, then switch them on.
4b. ID Protection for agents
ID Protection scores agent risk, and the CA policies above act on it. Check ID Protection > Dashboard for agents flagged for risk, and Risky agents for the detail.
The gap E7 does not cover: Workload ID Premium
Everything so far covers agents with an Entra agent identity. But plenty of agents still authenticate as ordinary service principals: legacy Copilot Studio agents, custom agents built on standard app registrations, automation that never moved to Agent ID, and many third-party apps. Agent Conditional Access and ID Protection for agents do not reach them.
Protecting service principals needs Microsoft Entra Workload ID Premium. It is a standalone product at $3 per workload identity per month, and Microsoft states it is not part of any other SKU, so it is not included in Microsoft 365 E7, Entra ID P2 or the Entra Suite (Workload ID FAQ).
Capability
Free
Workload ID Premium
Why it matters for agents
Sign-in logs and audit trail for service principals
Yes
Yes
You can already see what they do
Managed identities and workload identity federation
Yes
Yes
The way to retire client secrets
Conditional Access for workload identities
No
Yes
Block a service principal outside named locations, or when it is risky
ID Protection for workload identities
No
Yes
Risk detection for leaked credentials and unusual sign-ins
Access reviews for service principals in privileged roles
No
Yes
Re-justify agents and automation holding admin roles
App health recommendations
No
Yes
Flags unused apps and expiring or risky credentials
What it covers, and what it does not
Conditional Access applies only to single-tenant service principals registered in your tenant. Multi-tenant and SaaS apps, Microsoft apps and managed identities are excluded, and service principals must be assigned to the policy directly, not through a group (Learn).
ID Protection covers both single-tenant and multi-tenant service principals, so it reaches some third-party apps that Conditional Access cannot.
As with agents, the only grant control is Block. Conditions are location and service principal risk.
Workload identity protection considerations
Separate licensing. These features need Workload ID Premium, which is separate from Entra ID P1 and P2. Having P2 through E5 or E7 does not unlock them.
The licence check cannot be worked around. It is enforced by the service, not just the portal, so creating or changing a workload identity policy through PowerShell or Microsoft Graph needs the licence too. Without it, existing policies keep working but cannot be changed (Learn).
Same place, different target. Workload identity policies live in the normal Conditional Access policy list. The difference is the assignment: under Users, agents or workload identities you choose Workload identities instead of users and groups, or agents.
Two policies worth having
Policy
Assign to
Condition
Control
CA-WID-001 Block service principals outside trusted locations
Selected single-tenant service principals
Any location, excluding your named locations
Block
CA-WID-002 Block risky service principals
Selected single-tenant service principals
Service principal risk: High
Block
Browse to Entra ID > Conditional Access > Policies and select New policy.
Under Assignments, select Users, agents or workload identities, then under What does this policy apply to? select Workload identities and Select service principals.
Under Target resources, include All resources.
Under Conditions, set Locations (include Any location, exclude your trusted named locations), or Service principal risk.
Under Grant, select Block access, and create the policy in Report-only.
Review the results under Sign-in logs > Service principal sign-ins, and risky service principals under ID Protection > Risky workload identities.
Is it worth it?
If your Step 1 inventory finds custom or legacy agents still running as service principals with client secrets, yes, at least until they move to agent identities or managed identities. A 90-day trial is available. Microsoft’s FAQ is inconsistent on how licences are counted. It quotes a per-identity price, but also says one licence unlocks the features tenant-wide, so confirm the count with your Microsoft partner before buying.
Monitor
Agent sign-ins are under Entra ID > Agents > Sign-in logs, already filtered to Is Agent: Yes. Agent identity sign-ins sit on the Service principal sign-ins tab; agent user sign-ins on the non-interactive tab.
To see what your report-only agent policies would block:
// Agent sign-ins that a CA-AGT report-only policy would have blockedunionAADServicePrincipalSignInLogs,AADNonInteractiveUserSignInLogs|whereTimeGenerated>ago(2d)|extendCAP=todynamic(ConditionalAccessPolicies)|mv-expandCAP|wheretostring(CAP.displayName)startswith"CA-AGT-"|wheretostring(CAP.result)=="reportOnlyFailure"|summarizeWouldBlock=count(),LastSeen=max(TimeGenerated)byPolicy=tostring(CAP.displayName),Identity=coalesce(ServicePrincipalName,UserPrincipalName),ResourceDisplayName|orderbyWouldBlockdesc
Blind spots
Gotcha
What happens
What to do
Agents without an Entra identity
Most of the registry is invisible to CA, ID Protection and access packages
Govern them in Agent 365: access settings, user access, approval, block
Third-party agents
They cannot be approved through the Requests workflow
Restrict external publishers and user consent; block what you do not trust
API keys
An agent using an API key never authenticates with Entra (Learn)
Move it to an agent identity and retire the key
Legacy Copilot Studio agents
Plain service principals, not agent identities
Find them with the script in Step 1c and migrate them; protect them meanwhile with Workload ID Premium
Custom agents on app registrations
Agent CA does not apply; workload identity CA needs a licence E7 does not include
License Workload ID Premium, or move them to agent identities or managed identities
Delegated access
Copilot Studio agents act as the user, so permission controls do not limit them
Rely on Gate 2 and Power Platform data policies
Security defaults
Conditional Access does not apply to agents
Replace security defaults with a CA baseline
Report-only forever
Policies log but never block
Set a date to enforce
Preview features
Several agent features are still in preview
Recheck status before rolling out to a customer
Checklist
Compare the Agent 365 registry total with the Entra agent identity total
Find legacy Copilot Studio service principals and plan their migration
Gate 1: restrict who can build, and tighten user consent
Gate 2: set Agent and plugin access and User access, create a policy template, and work the Requests queue
Gate 2: assign owners to ownerless agents; block what nobody will own
Gate 4: deploy CA-AGT-001, 002 and 003 in report-only
Monitor: review the report-only results after 48 hours, set an enforcement date, then switch the policies on
Service principals: decide on Workload ID Premium for agents and automation that are not agent identities
Or run the companion script for the review and Gate 4 policies, then work through the manual steps it lists
More to come
The series started on the endpoint with Defender and now covers governance in Agent 365. Microsoft is moving quickly here, and several features in this post are still in preview. I will update this post, and the script, as they change.