Managing the Agent Lifecycle: Approvals, Security Boundaries, and Power Platform Admin Center Controls
1. Study log: who approves an agent, and who can stop one
The question for this entry: when someone in your tenant builds a custom agent, which admin approves it, which admin can block it, and which portal holds each control? The Microsoft 365 admin center decides where an agent is available and who can install it. The Power Platform admin center decides what a Copilot Studio agent can connect to and where it can publish. Admin roles, mostly Microsoft Entra roles, decide who can act in each.[3][12][14]
I didn’t connect to a Microsoft 365 tenant, approve an agent, change a role, or create a data policy for this article. I wrote a read-only PowerShell role review script and ran it locally on PowerShell 7.4.6 against a synthetic JSON file (section 4). Every user, group and principal ID in the sample data is fake. The role template IDs are Microsoft’s real built-in IDs.[4] The portal steps are a lab plan built from Microsoft Learn, and I have no tenant results to report.
Blueprint version: the AB-900 study guide lists skills measured as of October 14, 2026, and the credential page shows a last update of 10/14/2026.[1][2] That version is now in effect, so this article targets it. Exams taken before October 14, 2026 used the prior version, and the change log lists only a minor change, in “Identify the core security features of Microsoft 365 services”.[1] In the October 14 version, “Perform basic administrative tasks for Copilot and agents” carries 25-30% of the exam, and its agent objectives read:[1]
- “Identify how to configure user access to agents”
- “Create an agent”
- “Understand approval process for agents”
- “Monitor agents, including usage, operational insights, and agent lifecycle, by working with the Microsoft 365 admin center and the Microsoft Power Platform admin center”
| Default approach | AI-hardened approach |
|---|---|
| Approve and block agents with a Global Administrator account. | Use AI Administrator for agent governance and keep Global Administrator for emergency access. Microsoft recommends fewer than five Global Administrators.[3][5] |
| Treat “published in Copilot Studio” as done. | Org-wide publishing goes through the Requests list, where the admin picks the audience, a policy template and permissions.[8] |
| Let every user install every agent. | Restrict User access to a pilot group and turn off agent types you don’t allow yet.[7] |
| Block agents one at a time after a complaint. | Set data policies in the Power Platform admin center first, so an agent that breaks policy can’t publish. Block or quarantine as the exception.[14][17] |
| Ignore agents whose owner left. | Watch Agents without owners, then reassign, block or delete.[9][10] |
2. Lab architecture and prerequisites
The control path for the lab looks like this:
Entra role (who can act) → Power Platform admin center (what a Copilot Studio agent can connect to and publish) → Requests in the Microsoft 365 admin center (approve or reject) → Agent settings (who can install and share) → Registry and Overview (monitor, block, reassign, delete)
Two builders feed that path, and Microsoft splits their management. Agent Builder agents are “managed primarily through the Microsoft 365 admin center”, and Copilot Studio agents are “managed through the Power Platform admin center with finer-grained controls for enterprise scenarios.”[12] Both show up in the Microsoft 365 admin center registry, which has a Platform filter for each, and an agent can appear more than once.[9] One Copilot Studio agent can appear as Shared by creator and Published by your org, and Microsoft says those duplicate-looking rows are by design.[10]
Least-privilege role map for this lab:
| Task | Role to use | Notes |
|---|---|---|
| View agent insights and registry | AI Reader or Global Reader | Read-only for tools and settings.[3] |
| Install, modify, approve and manage agent configurations | AI Administrator | Tenant-wide agent governance, the same as Global Administrator in this table.[3] The permissions reference lists “Approve and publish line-of-business copilot agents”.[4] |
| Data policies, environments, sharing limits for Copilot Studio | Power Platform Administrator, or Environment Admin for data policies on their own environments | Product-scoped governance only.[3][14] |
| Quarantine a Copilot Studio agent through the API | Global Administrator, AI Administrator, or Power Platform Administrator | Also needs an app registration with CopilotStudio.AdminActions.Invoke.[17] |
| Conditional Access or access package policies inside an agent template | A more privileged role | Microsoft says AI Administrators aren’t authorized for these.[9] |
| Run the role review script in section 4 | Directory Readers, Global Reader or Privileged Role Administrator, with Graph permission RoleManagement.Read.Directory | Least privilege per the Graph reference.[20] |
| Emergency access | Global Administrator | Microsoft recommends two cloud-only emergency access accounts permanently assigned this role.[5] |
AI Administrator and AI Reader are both marked as privileged roles in the Entra permissions reference, so treat the reader role with care too.[4] Microsoft recommends Privileged Identity Management (PIM) so an admin is eligible for a role and activates it for a limited time.[5]
Prerequisites checklist:
- A non-production tenant with a documented owner. Don’t try this in production first.
- Licensing for agent management: Microsoft 365, Microsoft Copilot or Microsoft Agent 365, and licenses for the users who create, publish or use agents.[11] Registry risk signals need an E7 or Agent 365 license.[10]
- For Copilot Studio makers, a tenant license plus a per user license. People who only use a published agent don’t need a special license.[19]
- A decision on trials. A Copilot Studio trial lets a user build and test but not publish, and you can turn self-service trial sign-up on or off with the AllowAdHocSubscriptions flag.[19]
- Microsoft Graph PowerShell, if you want to run the role review against a tenant. Microsoft recommends PowerShell 7 or later for the SDK, and you sign in with
Connect-MgGraph -Scopes.[22][24]
3. Implementation steps
Step 1 is read-only. The other steps change tenant settings, so run them only in the lab tenant under an approved change. Each step ends with its most likely failure point.
-
Review who holds the agent roles. Run the script in section 4 with
-WhatIf, then for real. It lists active assignments for Global Administrator, AI Administrator, AI Reader, Global Reader, Power Platform Administrator and Agent ID Administrator, and flags patterns that Microsoft’s role guidance warns about.[4][5] Failure point: reading the CSV as the full picture. The script listsroleAssignmentsand doesn’t read PIM eligibility schedules, so check PIM separately before you decide who can hold a role. -
Move agent approvals off Global Administrator. Assign AI Administrator to the people who approve and block agents, eligible through PIM, and give AI Reader or Global Reader to reviewers who only need to look.[3][5] Failure point: expecting AI Administrator to cover everything. If your agent template needs Conditional Access or access package policies, someone with a higher-privileged role has to set those.[9]
-
Gate Copilot Studio in the Power Platform admin center before anyone publishes. Go to Security > Data and privacy > Data policy, then create or edit a policy and block the Copilot Studio connectors you don’t want. Scope it with Add all environments so it also applies to every new environment.[14] For a first lab policy I’d block these two:
- Chat without Microsoft Entra ID authentication in Copilot Studio, so makers can’t publish agents that skip authentication.[14]
- Direct Line channels in Copilot Studio, unless you need the demo website or custom web channels.[14]
Data policy enforcement has applied to all tenants since early 2025, and agent exemptions are no longer supported.[14] When an agent breaks the policy, the Publish button becomes unavailable.[14] Failure point: blocking every channel. Microsoft notes that if no unblocked channel is configured, or no channel is allowed, agents can’t be published at all.[14] Connectors in different data groups also can’t share data, and connectors introduced after 2019 are likely to sit in the default Non-business group.[14]
-
Decide on generative AI publishing. Microsoft says an admin can use the Power Platform admin center to turn off publishing of agents that use generative AI features for the tenant.[13] For a lab I’d leave it on and rely on data policies, because the switch is tenant-wide. Failure point: flipping it in production without telling makers, which would stop agents that use generative AI features from publishing tenant-wide.
-
Give makers a safe place to build. Turn on environment routing under Manage > Tenant settings > Environment routing. It’s off by default.[16] Routed makers land in a personal developer environment that is a managed environment by default, with sharing preset to exclude security groups and allow five individuals.[16] Makers get the admin role in their own developer environment, and those environments inherit tenant-level data policies, which is why step 3 comes first.[16] Failure point: assuming routing locks makers in. Makers can still switch to other environments.[16]
-
Limit agent sharing in managed environments. In the Power Platform admin center, go to Manage > Environments, select a managed environment, choose Edit Managed Environments, and use the Manage sharing section.[15] The agent rules control Editor and Viewer sharing, sharing with security groups, and a viewer limit.[15] Failure point: expecting the rule to remove access. Sharing rules apply when someone shares. Users who already had access keep it, and enforcement can take up to an hour to start.[15]
-
Restrict who can install agents in the Microsoft 365 admin center. Go to Agents > Settings > User access and switch from All users (the default) to Specific users or groups for your pilot group.[7] Under Agent and plugin access, clear any agent type you don’t allow yet: Microsoft, your organization, or external publishers.[7] Failure point: Microsoft-built agents stay visible even when you turn that option off. Users just can’t install them.[7] Researcher and Analyst aren’t covered by agent settings at all; block them tenant-wide with the Block action if you need to.[6][9]
-
Restrict Agent Builder sharing. Under Agents > Settings > Sharing, choose Specific users or No users.[7] Failure point: this control only applies to agents built with Agent Builder, and No users still lets people share directly with specific individuals.[7] Copilot Studio sharing is the step 6 control.
-
Work the approval queue. Go to Agents > All agents > Requests. Requests show as Pending review, Pending update or Pending activate.[8] For a new agent, review its capabilities, data sources, permissions and custom actions, then select Publish to store. The wizard asks for the users or groups who can install it, optional preinstall targets, a policy template, and admin consent for the permissions it requests. If you don’t want it, select Reject submission.[8] Failure point: a pending update isn’t live. Users keep the previous version until you approve the update with Update in store.[8] To keep the queue clean, add an agent management rule that rejects publish requests older than a set number of days.[7]
-
Block or quarantine an unapproved agent. In Agents > All agents, select the agent, then Block. Blocking an Agent Builder or Copilot Studio agent affects Copilot and other hosts such as Outlook and Teams. Blocking a SharePoint or Microsoft Foundry agent only affects Copilot Chat.[9] For a Copilot Studio agent you also want isolated at the source, the Power Platform API offers quarantine. Makers can still test a quarantined agent, but they can’t use it in any other channel.[17]
# Lab plan, not executed. Power Platform API. GET https://api.powerplatform.com/copilotstudio/environments/{EnvironmentId}/bots/{BotId}/api/botQuarantine?api-version=1 POST https://api.powerplatform.com/copilotstudio/environments/{EnvironmentId}/bots/{BotId}/api/botQuarantine/SetAsQuarantined?api-version=1 # Rollback: POST https://api.powerplatform.com/copilotstudio/environments/{EnvironmentId}/bots/{BotId}/api/botQuarantine/SetAsUnquarantined?api-version=1Failure points: the quarantine calls return
405 Method Not Allowedfor classic chatbots.[17] If the agent’s environment uses Power Platform Firewall in active enforcement mode, actions from the Microsoft 365 admin center fail, and Microsoft points you to the Power Platform API instead.[9] In the registry, removing one entry of a duplicated agent doesn’t remove the other.[10] -
Monitor usage and lifecycle in both portals. In the Microsoft 365 admin center, Agents > Overview shows a 30-day snapshot, usage trends, pending requests and agents without owners.[11] The registry shows Total agents, Agents without owners and Unmanaged agents, and Export writes the list to a CSV file.[10] In the Power Platform admin center, the Copilot area’s Copilot Studio page shows active agents (sessions in the last 28 days), session success rate and the top 10 agents by monthly active sessions.[18] Failure point: comparing the two counts. The Power Platform page doesn’t include Agent Builder agents.[18]
-
Retire agents on purpose. Reassign ownerless agents with Assign new owner, or use the rule that reassigns ownerless Agent Builder agents to the previous owner’s manager.[7][9] Block ownerless agents with no usage through an agent management rule.[7] Delete agent is a soft delete with a 30-day recovery window, and soft-delete, restore and permanent-delete events go to the audit log.[9] Failure point: reassigning ownership to “share” an agent. The previous owner loses all access, including read rights.[9]
Rollback, in reverse order: unblock or restore the agent, set User access and Agent and plugin access back to their earlier values, remove the managed environment sharing limits, turn off environment routing, and edit the data policy to unblock the connectors. Then run the review script with -Rollback to delete its local files.
4. Engineering deliverable: read-only agent admin role review
This script lists who holds the Entra roles that govern agents and flags patterns from Microsoft’s role guidance: more than four Global Administrators, no AI Administrator at all, the same person holding Global Administrator and AI Administrator, roles assigned to groups or service principals, and ten or more privileged assignments within the watched roles.[5] It never writes to the tenant. The role template IDs come from the Entra permissions reference.[4]
What I tested, on Linux with PowerShell 7.4.6 and synthetic input only: -WhatIf created nothing; a normal run wrote a CSV, a findings file, a manifest and an audit log; a second identical run made no changes; a different input without -Force stopped with an error, and with -Force it rewrote the files; a bad role ID and a bad principal type both failed validation; and -Rollback removed the outputs but skipped a file I had edited, logging a hash mismatch. For the Graph path, I ran it against mocked Get-MgRoleManagementDirectoryRoleAssignment and Get-MgContext functions, with Microsoft.Graph.Identity.Governance 2.41.2 installed so the principal used the module’s real directory object type. I didn’t run it against a real tenant. PSScriptAnalyzer reported only information-level positional-parameter notes.
#Requires -Version 7.2
<#
.SYNOPSIS
Read-only review of who holds the Microsoft Entra roles that govern Copilot agents.
.DESCRIPTION
-FromGraph reads active directory role assignments with Microsoft Graph (RoleManagement.Read.Directory).
-InputPath reads the same shape from a JSON file, such as the synthetic sample in this article.
The script never writes to the tenant. It writes a CSV, a findings file, a manifest and an audit log locally.
.NOTES
Reads roleAssignments only. It does not read PIM eligibility schedules.
#>
[CmdletBinding(SupportsShouldProcess, DefaultParameterSetName = 'Offline')]
param(
[Parameter(Mandatory, ParameterSetName = 'Offline')]
[ValidateScript({ Test-Path -LiteralPath $_ -PathType Leaf })]
[string] $InputPath,
[Parameter(Mandatory, ParameterSetName = 'Graph')]
[switch] $FromGraph,
[Parameter(Mandatory, ParameterSetName = 'Rollback')]
[switch] $Rollback,
[Parameter(Mandatory)]
[ValidateNotNullOrEmpty()]
[string] $OutputPath,
# Microsoft recommends fewer than five Global Administrators.
[Parameter(ParameterSetName = 'Offline')]
[Parameter(ParameterSetName = 'Graph')]
[ValidateRange(1, 4)]
[int] $MaxGlobalAdmins = 4,
[Parameter(ParameterSetName = 'Offline')]
[Parameter(ParameterSetName = 'Graph')]
[switch] $Force
)
Set-StrictMode -Version Latest
$ErrorActionPreference = 'Stop'
# Built-in role template IDs from the Microsoft Entra permissions reference.
$watch = [ordered]@{
'62e90394-69f5-4237-9190-012177145e10' = 'Global Administrator'
'd2562ede-74db-457e-a7b6-544e236ebb61' = 'AI Administrator'
'1fe13547-53f6-408d-ac04-7f8eed167b38' = 'AI Reader'
'f2ef992c-3afb-46b9-b7cf-a126ee74c451' = 'Global Reader'
'11648597-926c-4cf3-9c36-bcebb0ba8dcc' = 'Power Platform Administrator'
'db506228-d27e-4b7d-95e5-295956d6615f' = 'Agent ID Administrator'
}
# Roles in this list that the permissions reference marks as privileged.
$privileged = @('Global Administrator', 'AI Administrator', 'AI Reader', 'Global Reader', 'Agent ID Administrator')
$root = [System.IO.Path]::GetFullPath($OutputPath)
$manifestPath = Join-Path $root 'review-manifest.json'
$auditPath = Join-Path $root 'review-audit.jsonl'
$guidPattern = '^[0-9a-fA-F]{8}-([0-9a-fA-F]{4}-){3}[0-9a-fA-F]{12}$'
function Write-Audit([string] $Action, [string] $Target, [string] $Result) {
if ($WhatIfPreference) { return }
if (-not (Test-Path -LiteralPath $root)) { return }
[ordered]@{ utc = (Get-Date).ToUniversalTime().ToString('o'); action = $Action; target = $Target; result = $Result } |
ConvertTo-Json -Compress | Add-Content -LiteralPath $auditPath -Encoding utf8NoBOM
}
function Get-Sha256([string] $Path) { (Get-FileHash -LiteralPath $Path -Algorithm SHA256).Hash.ToLowerInvariant() }
function Get-TextSha256([string] $Text) {
$bytes = [System.Text.Encoding]::UTF8.GetBytes($Text)
([System.Convert]::ToHexString([System.Security.Cryptography.SHA256]::HashData($bytes))).ToLowerInvariant()
}
if ($Rollback) {
if (-not (Test-Path -LiteralPath $manifestPath)) { Write-Verbose 'No manifest found. Nothing to roll back.'; return }
$manifest = Get-Content -LiteralPath $manifestPath -Raw | ConvertFrom-Json
foreach ($file in $manifest.files) {
$full = Join-Path $root $file.name
if (-not (Test-Path -LiteralPath $full)) { continue }
if ((Get-Sha256 $full) -ne $file.sha256) {
Write-Warning "Skipping $($file.name): it changed after the review wrote it."
Write-Audit 'rollback-skip' $file.name 'hash-mismatch'
continue
}
if ($PSCmdlet.ShouldProcess($full, 'Remove review output')) {
Remove-Item -LiteralPath $full
Write-Audit 'rollback-remove' $file.name 'removed'
}
}
if ($PSCmdlet.ShouldProcess($manifestPath, 'Remove review manifest')) {
Remove-Item -LiteralPath $manifestPath
Write-Audit 'rollback-remove' 'review-manifest.json' 'removed'
}
return
}
# Collect assignments in one shape, whatever the source.
if ($FromGraph) {
if (-not (Get-Command Get-MgRoleManagementDirectoryRoleAssignment -ErrorAction SilentlyContinue)) {
throw 'Install the Microsoft.Graph.Identity.Governance module first.'
}
$ctx = Get-MgContext
if (-not $ctx) { throw 'Run Connect-MgGraph -Scopes "RoleManagement.Read.Directory" first.' }
$raw = foreach ($id in $watch.Keys) {
Get-MgRoleManagementDirectoryRoleAssignment -Filter "roleDefinitionId eq '$id'" -ExpandProperty principal -All |
ForEach-Object {
$p = $_.Principal.AdditionalProperties
[pscustomobject]@{
roleTemplateId = $id
principalId = $_.PrincipalId
principalName = [string]$p['displayName']
principalType = ([string]$p['@odata.type']) -replace '^#microsoft\.graph\.', ''
directoryScopeId = $_.DirectoryScopeId
}
}
}
$source = "graph:$($ctx.TenantId)"
}
else {
$raw = Get-Content -LiteralPath $InputPath -Raw | ConvertFrom-Json
$source = "file:$([System.IO.Path]::GetFileName($InputPath))"
}
# Input validation: refuse anything that doesn't fit the expected shape.
$required = 'roleTemplateId', 'principalId', 'principalName', 'principalType', 'directoryScopeId'
$rows = foreach ($r in @($raw)) {
foreach ($field in $required) {
if (-not ($r.PSObject.Properties.Name -contains $field)) { throw "Assignment is missing field '$field'." }
}
if ($r.roleTemplateId -notmatch $guidPattern -or -not $watch.Contains([string]$r.roleTemplateId)) {
throw "Unexpected roleTemplateId '$($r.roleTemplateId)'."
}
if ($r.principalId -notmatch $guidPattern) { throw "Invalid principalId '$($r.principalId)'." }
if ($r.principalType -notin 'user', 'group', 'servicePrincipal') { throw "Unexpected principalType '$($r.principalType)'." }
[pscustomobject]@{
Role = $watch[[string]$r.roleTemplateId]
RoleTemplateId = [string]$r.roleTemplateId
PrincipalName = [string]$r.principalName
PrincipalId = [string]$r.principalId
PrincipalType = [string]$r.principalType
DirectoryScopeId = [string]$r.directoryScopeId
}
}
$rows = @($rows | Sort-Object Role, PrincipalName, PrincipalId)
# Findings. Each one maps to published Microsoft guidance; none of them changes anything.
$findings = [System.Collections.Generic.List[object]]::new()
function Add-Finding([string] $Id, [string] $Severity, [string] $Detail) {
$findings.Add([pscustomobject]@{ id = $Id; severity = $Severity; detail = $Detail })
}
$ga = @($rows | Where-Object Role -eq 'Global Administrator')
$aiAdmin = @($rows | Where-Object Role -eq 'AI Administrator')
if ($ga.Count -gt $MaxGlobalAdmins) {
Add-Finding 'GA-COUNT' 'high' "$($ga.Count) active Global Administrator assignments. Microsoft recommends fewer than five."
}
if ($ga.Count -gt 0 -and $aiAdmin.Count -eq 0) {
Add-Finding 'NO-AI-ADMIN' 'medium' 'No AI Administrator assignments. Agent approvals and blocks probably run under Global Administrator.'
}
$gaIds = @(foreach ($x in $ga) { $x.PrincipalId })
$aiAdminIds = @(foreach ($x in $aiAdmin) { $x.PrincipalId })
foreach ($id in ($gaIds | Where-Object { $_ -in $aiAdminIds } | Sort-Object -Unique)) {
$name = ($ga | Where-Object PrincipalId -eq $id | Select-Object -First 1).PrincipalName
Add-Finding 'GA-PLUS-AI-ADMIN' 'medium' "$name holds Global Administrator and AI Administrator. Keep Global Administrator for emergencies."
}
foreach ($g in ($rows | Where-Object PrincipalType -eq 'group')) {
Add-Finding 'GROUP-ASSIGNMENT' 'info' "$($g.Role) is assigned to group $($g.PrincipalName). Review its owners and members."
}
foreach ($s in ($rows | Where-Object PrincipalType -eq 'servicePrincipal')) {
Add-Finding 'APP-ASSIGNMENT' 'medium' "$($s.Role) is assigned to service principal $($s.PrincipalName). Confirm it still needs the role."
}
$privCount = @($rows | Where-Object { $_.Role -in $privileged }).Count
if ($privCount -ge 10) {
Add-Finding 'PRIVILEGED-COUNT' 'medium' "$privCount privileged assignments in this watch list alone. Microsoft suggests fewer than 10 tenant-wide."
}
# Idempotency: same input and unchanged outputs means nothing to do.
$inputHash = Get-TextSha256 ($rows | ConvertTo-Json -Depth 3 -Compress)
if (Test-Path -LiteralPath $manifestPath) {
$existing = Get-Content -LiteralPath $manifestPath -Raw | ConvertFrom-Json
$unchanged = $existing.inputHash -eq $inputHash -and -not ($existing.files | Where-Object {
$p = Join-Path $root $_.name
-not (Test-Path -LiteralPath $p) -or (Get-Sha256 $p) -ne $_.sha256
})
if ($unchanged) {
Write-Verbose 'Review outputs already match this input. No changes made.'
Write-Audit 'review' $source 'no-op'
return $findings
}
if (-not $Force) { throw 'Existing review outputs differ from this input. Use -Rollback first, or -Force to overwrite.' }
}
if (-not (Test-Path -LiteralPath $root)) {
if ($PSCmdlet.ShouldProcess($root, 'Create review folder')) { New-Item -ItemType Directory -Path $root | Out-Null }
}
$outputs = [ordered]@{
'role-assignments.csv' = ($rows | ConvertTo-Csv -NoTypeInformation)
'findings.json' = (ConvertTo-Json -InputObject @($findings) -Depth 3)
}
$written = foreach ($name in $outputs.Keys) {
$full = Join-Path $root $name
if ($PSCmdlet.ShouldProcess($full, 'Write review output')) {
Set-Content -LiteralPath $full -Value $outputs[$name] -Encoding utf8NoBOM
Write-Audit 'write' $name 'ok'
[pscustomobject]@{ name = $name; sha256 = Get-Sha256 $full }
}
}
if ($written -and $PSCmdlet.ShouldProcess($manifestPath, 'Write review manifest')) {
[ordered]@{ source = $source; inputHash = $inputHash; createdUtc = (Get-Date).ToUniversalTime().ToString('o'); files = @($written) } |
ConvertTo-Json -Depth 4 | Set-Content -LiteralPath $manifestPath -Encoding utf8NoBOM
Write-Audit 'manifest' 'review-manifest.json' 'ok'
}
$findings
The synthetic input (synthetic-role-assignments.json):
[
{ "roleTemplateId": "62e90394-69f5-4237-9190-012177145e10", "principalId": "00000000-0000-4000-8000-000000000001", "principalName": "ZKLAB Break Glass 01", "principalType": "user", "directoryScopeId": "/" },
{ "roleTemplateId": "62e90394-69f5-4237-9190-012177145e10", "principalId": "00000000-0000-4000-8000-000000000002", "principalName": "ZKLAB Break Glass 02", "principalType": "user", "directoryScopeId": "/" },
{ "roleTemplateId": "62e90394-69f5-4237-9190-012177145e10", "principalId": "00000000-0000-4000-8000-000000000003", "principalName": "ZKLAB Admin Avery", "principalType": "user", "directoryScopeId": "/" },
{ "roleTemplateId": "62e90394-69f5-4237-9190-012177145e10", "principalId": "00000000-0000-4000-8000-000000000004", "principalName": "ZKLAB Admin Blake", "principalType": "user", "directoryScopeId": "/" },
{ "roleTemplateId": "62e90394-69f5-4237-9190-012177145e10", "principalId": "00000000-0000-4000-8000-000000000005", "principalName": "ZKLAB Admin Casey", "principalType": "user", "directoryScopeId": "/" },
{ "roleTemplateId": "d2562ede-74db-457e-a7b6-544e236ebb61", "principalId": "00000000-0000-4000-8000-000000000003", "principalName": "ZKLAB Admin Avery", "principalType": "user", "directoryScopeId": "/" },
{ "roleTemplateId": "1fe13547-53f6-408d-ac04-7f8eed167b38", "principalId": "00000000-0000-4000-8000-000000000010", "principalName": "ZKLAB Agent Reviewers", "principalType": "group", "directoryScopeId": "/" },
{ "roleTemplateId": "11648597-926c-4cf3-9c36-bcebb0ba8dcc", "principalId": "00000000-0000-4000-8000-000000000006", "principalName": "ZKLAB Admin Devon", "principalType": "user", "directoryScopeId": "/" }
]
Usage:
# Offline, synthetic data
./Get-AgentAdminRoleReview.ps1 -InputPath ./synthetic-role-assignments.json -OutputPath ./ab900-04-review -WhatIf
./Get-AgentAdminRoleReview.ps1 -InputPath ./synthetic-role-assignments.json -OutputPath ./ab900-04-review -Verbose
./Get-AgentAdminRoleReview.ps1 -Rollback -OutputPath ./ab900-04-review
# Lab tenant, read-only (not executed)
Install-Module Microsoft.Graph.Identity.Governance -Scope CurrentUser
Connect-MgGraph -Scopes "RoleManagement.Read.Directory"
./Get-AgentAdminRoleReview.ps1 -FromGraph -OutputPath ./ab900-04-review-tenant
Disconnect-MgGraph
The findings from my local synthetic run:
id severity detail
-- -------- ------
GA-COUNT high 5 active Global Administrator assignments. Microsoft recommends fewer than five.
GA-PLUS-AI-ADMIN medium ZKLAB Admin Avery holds Global Administrator and AI Administrator. Keep Global Administrator for emergencies.
GROUP-ASSIGNMENT info AI Reader is assigned to group ZKLAB Agent Reviewers. Review its owners and members.
Why it’s built this way:
- It asks for one role at a time with
-Filter "roleDefinitionId eq '<id>'"and-ExpandProperty principal, the same shape as Microsoft’s Graph example, and-Allto read every page.[20][21] RoleManagement.Read.Directoryis the least-privileged Graph permission for listing directory role assignments.[20] The script calls no write cmdlets.- The
MaxGlobalAdminsdefault of 4 matches Microsoft’s “fewer than five” guidance. Microsoft also recommends two cloud-only emergency access accounts with Global Administrator, so plan the rest around them.[5] - Group assignments get an info-level finding. Microsoft supports assigning roles to groups and suggests managing role-assignable groups in PIM so they have no standing owners or members.[5]
- Rollback deletes only files the script wrote and whose SHA-256 hash still matches the manifest. The audit log records each write and removal as JSON lines with UTC timestamps, and
-WhatIfruns log nothing.
5. Public-objective concepts to retain
These map to the October 14, 2026 AB-900 skills list.[1]
- Approval process: an agent published to the organization needs admin approval before it’s available tenant-wide, and the admin scopes the audience during publishing.[8] Know the three request states.[8]
- User access: All users, No users, or Specific users or groups under Agents > Settings > User access.[7] Sharing in the same area applies to Agent Builder agents only.[7]
- Which portal: Agent Builder agents are managed mostly in the Microsoft 365 admin center, Copilot Studio agents in the Power Platform admin center.[12]
- Monitoring: the Microsoft 365 admin center Overview and registry for inventory, ownership and lifecycle; the Power Platform admin center Copilot area for Copilot Studio usage.[10][11][18]
- Roles: AI Administrator acts, AI Reader and Global Reader look, Power Platform Administrator governs within Power Platform only.[3] I’d memorize the role table in section 2 before exam day.
- Identity for agents: Microsoft Entra Agent ID is the identity framework for agents, and extending Entra security features to agents requires Microsoft Agent 365.[23] It isn’t named in the AB-900 skills list, so treat it as context, not exam scope.[1]
This is objective-aligned study material. It doesn’t reproduce exam questions.
6. Next step in the study journey
Next in the series is the AB-900 exam review entry: a lab blueprint recap, public-objective concepts and a pre-exam cheat sheet across all three domains. Until then, the related reading on ZakitPro is the AB-900 Day One Conditional Access lab blueprint, the read-only SharePoint oversharing report, the Purview DSPM for AI lab, and the AB-900 certification review.
Sources
[1] https://learn.microsoft.com/en-us/credentials/certifications/resources/study-guides/ab-900 (Study guide for Exam AB-900) [2] https://learn.microsoft.com/en-us/credentials/certifications/copilot-and-agent-administration-fundamentals (Copilot and Agent Administration Fundamentals credential page) [3] https://learn.microsoft.com/en-us/microsoft-365/admin/manage/agent-roles-perms (Agent management roles and permissions in the Microsoft 365 admin center) [4] https://learn.microsoft.com/en-us/entra/identity/role-based-access-control/permissions-reference (Microsoft Entra built-in roles) [5] https://learn.microsoft.com/en-us/entra/identity/role-based-access-control/best-practices (Best practices for Microsoft Entra roles) [6] https://learn.microsoft.com/en-us/microsoft-365/admin/manage/manage-copilot-agents-integrated-apps (Manage agents in the Microsoft 365 admin center) [7] https://learn.microsoft.com/en-us/microsoft-365/admin/manage/agent-settings (Agent settings in the Microsoft 365 admin center) [8] https://learn.microsoft.com/en-us/microsoft-365/admin/manage/agent-requests (Agent requests in the Microsoft 365 admin center) [9] https://learn.microsoft.com/en-us/microsoft-365/admin/manage/agent-actions (Governance and lifecycle actions for agents) [10] https://learn.microsoft.com/en-us/microsoft-365/admin/manage/agent-registry (Agent Registry in the Microsoft 365 admin center) [11] https://learn.microsoft.com/en-us/microsoft-365/admin/manage/agent-365-overview (Agent overview in the Microsoft 365 admin center) [12] https://learn.microsoft.com/en-us/microsoft-365/copilot/extensibility/copilot-studio-experience (Choose between Agent Builder and Copilot Studio) [13] https://learn.microsoft.com/en-us/microsoft-copilot-studio/security-and-governance (Copilot Studio security and governance) [14] https://learn.microsoft.com/en-us/microsoft-copilot-studio/admin-data-loss-prevention (Configure data policies for agents) [15] https://learn.microsoft.com/en-us/power-platform/admin/managed-environment-sharing-limits (Limit sharing in managed environments) [16] https://learn.microsoft.com/en-us/power-platform/admin/default-environment-routing (Environment routing) [17] https://learn.microsoft.com/en-us/microsoft-copilot-studio/admin-api-quarantine (Use the Power Platform API to quarantine agents) [18] https://learn.microsoft.com/en-us/power-platform/admin/copilot/copilot-hub (Copilot hub in the Power Platform admin center) [19] https://learn.microsoft.com/en-us/microsoft-copilot-studio/requirements-licensing (Copilot Studio: assign user licenses and manage access) [20] https://learn.microsoft.com/en-us/graph/api/rbacapplication-list-roleassignments (List unifiedRoleAssignments) [21] https://learn.microsoft.com/en-us/powershell/module/microsoft.graph.identity.governance/get-mgrolemanagementdirectoryroleassignment (Get-MgRoleManagementDirectoryRoleAssignment) [22] https://learn.microsoft.com/en-us/powershell/microsoftgraph/installation (Install the Microsoft Graph PowerShell SDK) [23] https://learn.microsoft.com/en-us/entra/agent-id/what-is-microsoft-entra-agent-id (What is Microsoft Entra Agent ID?) [24] https://learn.microsoft.com/en-us/powershell/microsoftgraph/authentication-commands (Microsoft Graph PowerShell authentication commands)