Prerequisites - Copilot Agent Inventory¶
Requirements for deploying the Copilot Agent Inventory (CAI) solution and running the discovery scanner. This solution targets the US commercial Microsoft 365 / Power Platform cloud.
Python Requirements¶
The schema deployment script and the discovery scanner are Python.
| Requirement | Version | Purpose |
|---|---|---|
| Python | 3.9+ | Schema deployment and discovery scanner runtime |
| msal | 1.30+ | Microsoft Entra ID authentication (token acquisition) |
| requests | 2.32+ | ARG / BAP / Dataverse Web API HTTP client |
| azure-identity | 1.15+ | Managed-identity-first auth (DefaultAzureCredential / ManagedIdentityCredential) |
Install Python dependencies:
Authentication (managed-identity-first)¶
All CAI scripts acquire tokens in the following priority order. Pick the strongest method available in your environment; the scanner and the shared Dataverse client accept a token from any source.
- System-assigned managed identity when running inside an Azure-hosted
runner or Function (
DefaultAzureCredential/ManagedIdentityCredential). Recommended for production. - User-assigned managed identity for cross-resource scenarios (pass the client ID).
- Workload identity federation (GitHub Actions OIDC → Entra app) for CI.
- Interactive / device-code for one-off admin-workstation runs.
- Client secret — dev-only fallback. Store the secret in Azure Key Vault and read it via the managed identity; never ship a client secret as the recommended production path.
Microsoft Entra ID App Registration (scanner service principal)¶
A single-tenant app registration backs the discovery scanner's service
principal. Follow least-privilege: the scanner needs read access to enumerate
environments and read bot / botcomponent records — it does not need write
access to target environments.
Registration Steps¶
- Navigate to Azure Portal > Microsoft Entra ID > App registrations > New registration.
- Name:
CAI-CopilotAgentInventory(or your naming convention). - Supported account types: Single tenant.
- For interactive runs, add redirect URI
http://localhost. - Configure a federated credential (workload identity) for CI, or a certificate; use a client secret only for local development.
Token Scopes¶
The scanner requests these resource scopes (all /.default, delegated or
application depending on the auth mode):
| Resource | Scope | Purpose |
|---|---|---|
Power Platform API (https://api.powerplatform.com) |
https://api.powerplatform.com/.default |
Layer 1 — ARG resourcequery against PowerPlatformResources |
Power Platform Admin / BAP (https://api.bap.microsoft.com) |
https://api.bap.microsoft.com/.default |
Environment enumeration |
| Dataverse (per environment) | https://<org>.crm.dynamics.com/.default |
Layer 2 — the scanner reads bot / botcomponent (read-only). The CAI inventory tables are written by the Power Automate flow's Dataverse connection (the flow-writer identity), not by the scanner. |
Microsoft Graph (https://graph.microsoft.com) |
https://graph.microsoft.com/.default |
Layer 4 — Package Management API (CopilotPackages.Read.All); owner licensing queries (User.Read.All, Organization.Read.All, GroupMember.Read.All) |
Note: After configuring permissions, a Microsoft Entra admin must grant tenant consent.
Roles and Permissions — the three governance identities¶
CAI deliberately separates three identities so no single principal holds both schema-authoring rights and tenant-wide scan rights, and so the read-only scanner never holds write access to the inventory. Keep each identity scoped to only what its stage requires. This separation supports the least-privilege expectations of OCC 2011-12 and Fed SR 11-7 (privileged-identity segregation); it does not by itself satisfy any regulation.
| Identity | Who / what | Where | Rights | Used for |
|---|---|---|---|---|
| Deployer | Interactive admin (a person) | Governance environment only | System Customizer (or System Administrator) | Deploy the Dataverse schema — create_cai_dataverse_schema.py (create tables, columns, option sets, alternate keys). One-time / on schema change. |
| Scanner | App-only service principal (or managed identity) | Every in-scope environment | (1) Registered as a Power Platform management application for environment enumeration; (2) read-only application user on bot / botcomponent. No CAI-table write. |
Run discover_agents.py. The scanner emits JSON only and never writes to Dataverse. |
| Flow-writer | Power Automate Dataverse connection | Governance environment only | Dataverse user with Create / Write on the CAI tables (fsi_copilotagent, fsi_caiagentfeature, …) |
The CAI-DailyDiscovery flow persists the scanner JSON into the inventory tables. |
Deployer (schema only)¶
The deployer is an interactive administrator running the schema script from an admin workstation. It needs schema-authoring rights (System Customizer or System Administrator) in the governance environment only — the environment that hosts the CAI tables. It is not used at scan time and needs no access to the in-scope environments being inventoried.
Scanner (read-only, app-only)¶
The scanner is the least-privilege discovery principal. It requires two distinct grants, and no Dataverse write anywhere:
- Environment enumeration — register the scanner app as a Power Platform
management application (for example with
New-PowerAppManagementApp -ApplicationId <appId>from theMicrosoft.PowerApps.Administration.PowerShellmodule, or thepac adminequivalent). This lets the app-only principal call the BAP admin environment-list API without granting it a Power Platform Admin user role. ARM access is also required for the Layer 1 ARG query. - Per-environment read — register the scanner app as an application user
in every in-scope environment, with a read-only security role scoped to
botandbotcomponent.
Least-privilege note: granting the scanner System Administrator in every environment ("sys-admin-everywhere") is a standing privileged-identity risk and is not required for read-only discovery. The scanner never needs write access to the CAI tables — writing the inventory is the flow-writer's job.
Flow-writer (the only writer)¶
The Power Automate flow authenticates with its own Dataverse connection. That connection's identity is the only identity that writes the CAI tables, and it needs Create / Write on those tables in the governance environment only. See flow-configuration.md.
Scanner environment-coverage verification and stop condition¶
Before go-live, verify the scanner service principal ("SP under test") is present as a read-only application user in every environment that must be inventoried:
- Enumerate the in-scope environments (the environments the scan is expected to cover) and record them as the coverage baseline.
- For each in-scope environment, confirm the scanner SP exists as an application
user and can read
bot/botcomponent— for example, a targeted read returns HTTP 200 (not 403). A first live run is a convenient way to exercise this across all environments at once. - Inspect the scan result: a healthy run reports
summary.status == "Complete",summary.environmentEnumeration.status == "Success", and an emptysummary.environmentFailures[].
Stop condition (required before promotion): if the scanner SP is missing from any in-scope environment, that environment now surfaces under
summary.environmentFailures[](typically HTTP 403 at thebotsstage) and the overallsummary.statusbecomesIncompleteorFailed— it is not reported as a clean, agent-free environment. Treat any such coverage gap as a stop-and-fix condition: grant the missing read-only application user and re-run until every in-scope environment reads cleanly andsummary.status == "Complete". Do not treat an inventory produced by anIncomplete/Failedscan as complete.
Dataverse (per environment)¶
| Role | Identity | Environment | Purpose |
|---|---|---|---|
Read-only role covering bot, botcomponent |
Scanner | Every in-scope environment | Layer 2 feature enumeration (read-only) |
| Dataverse user with Create / Write on CAI tables | Flow-writer | Governance environment | Persist the inventory system-of-record (flow only) |
Register the scanner app as an application user in each environment it reads, and grant it the minimum read-only role that allows the reads above.
Azure Key Vault (secret storage for dev fallback)¶
When a client secret is used (development only), store it in Azure Key Vault and
grant the managed identity get on secrets. The scanner reads the secret at
runtime via the managed identity rather than from an environment variable or a
file. This is required to keep secrets out of source control and CI logs.
Network Requirements¶
| Endpoint | Protocol | Purpose |
|---|---|---|
login.microsoftonline.com |
HTTPS | OAuth token acquisition |
api.powerplatform.com |
HTTPS | Layer 1 — ARG resourcequery |
api.bap.microsoft.com |
HTTPS | Environment enumeration |
*.crm.dynamics.com |
HTTPS | Layer 2 — bot / botcomponent reads; CAI table writes |
graph.microsoft.com |
HTTPS | Layer 4 — Package Management API; owner licensing queries |
*.vault.azure.net |
HTTPS | Key Vault secret retrieval (dev fallback) |
Live-confirm (🔎):
microsoft.copilotstudio/agentsis absent from the standard ARG supported-types reference. Before the first production run, confirm the type resolves withaz graph query -q "PowerPlatformResources | where type == 'microsoft.copilotstudio/agents'". If conditional access enforces ARM MFA, allow the PPAC client ID00b46ad5-e4ae-43ac-a878-281fc03d0839and "Microsoft Azure Management".
Dataverse Schema Deployment¶
Deploy the canonical 8-table schema with the Python schema script. The script is idempotent (it checks for existing tables, columns, and option sets) and supports a dry run.
# Install Python dependencies
pip install -r scripts/requirements.txt
# Preview the schema deployment (reads hit the live tenant; no writes)
python scripts/create_cai_dataverse_schema.py \
--environment-url https://governance.crm.dynamics.com \
--tenant-id <your-tenant-id> \
--interactive \
--dry-run
# Deploy for real
python scripts/create_cai_dataverse_schema.py \
--environment-url https://governance.crm.dynamics.com \
--tenant-id <your-tenant-id> \
--interactive
# Regenerate the schema documentation after any schema change
python scripts/create_cai_dataverse_schema.py --output-docs
The deployed schema is nine tables — the ninth, fsi_caiscanrun, and its
scan-run option sets are added during the v0.4 schema integration. See
dataverse-schema.md for the full reference, including the
11 solution-specific option sets and 1 shared option set (regenerated during
integration).
Package Management API — Layer 4 Prerequisites¶
Layer 4 is governed by the license-aware Agent 365 mode
(--agent365 present|absent|auto, environment variable CAI_AGENT365, default
absent; the deprecated --enable-package-api flag is a one-release alias
for --agent365 present). It is additive and, at the default absent, does
not run. It discovers Microsoft 365 Copilot Agent Builder packages only;
Copilot Studio agents are intentionally excluded because existing layers (ARG,
per-environment Dataverse, and PPAC) already cover Copilot Studio agents, and
package-to-bot joins are not strong enough to prevent duplicates. When Layer 4
runs, it requires the gates below — each satisfied independently, at different
administrative levels. Activate this layer only in the US commercial
Microsoft 365 cloud (see the cloud-scope statement at the top of this
document).
Mode precedence: explicit
--agent365flag >CAI_AGENT365environment variable > deprecated--enable-package-apialias > default (absent). Invalid values or contradictions (for example--agent365 absenttogether with--enable-package-api) fail argument validation. See architecture.md.Reference: List packages — Microsoft Learn · copilotPackage resource type · subscribedSku resource type
Gate 1 — Agent 365 mode and the Microsoft Agent 365 license (tenant product gate)¶
The Package Management API requires a tenant Microsoft Agent 365 license. This is a tenant-level product gate set by Microsoft licensing or the enterprise agreement — not an admin configuration step. How Layer 4 treats licensing depends on the selected mode:
absent(default): the operator authoritatively declares Agent 365 is out of scope. The scanner calls neithersubscribedSkusnor the Package API, records Layer 4 asDeferred, and keeps Layers 1–3, registry owner attribution, and entitlement resolution available. ADeferredLayer 4 is not "zero Agent Builder agents" — it means the package catalog was not observed. ARG (Layer 1) can classify authoring surface throughcreatedIn; Layer 2 still inventoriesbotrows but does not supply that field. If ARG is unavailable, authoring-surface classification remains unknown.present: the scanner attempts the Package API directly (no license probe). Provision the Agent 365 license and the Gate 2 permission first.auto: the scanner performs a conservative license probe first (see Gate 1a). It attempts the Package API when the probe matches or, as a best-effort fallback, when the probe is inconclusive. An inconclusive probe keepsresolvedState = Inconclusiveeven if the package request succeeds.
API errors are typed, never absence. An HTTP
401/403/404/429/5xxfrom the Package API is classified as aPartial/Failed/Unsupportedlayer outcome and surfaced insummary.agent365. It is never interpreted as "the tenant has no Agent 365 license" and never recorded asAbsent/NotDetected/Deferred. Only an explicit operatorabsentdeclaration yieldsAbsent; only a successfulautoprobe with no SKU match yields a heuristicNotDetected.
- How to verify (before selecting
present): confirm the tenant holds a Microsoft Agent 365 license assignment (Microsoft Agent 365 service description).
Gate 1a — auto-mode license probe permission (only for --agent365 auto)¶
In auto mode the scanner calls Graph
GET https://graph.microsoft.com/v1.0/subscribedSkus to look for an Agent 365
SKU. subscribedSkus supports only $select (no $filter), so the scanner
enumerates the tenant's SKUs and matches locally.
| Permission | Type | Purpose | Recommendation |
|---|---|---|---|
LicenseAssignment.Read.All |
Application | List subscribed SKUs for the probe | Recommended — least privileged for GET /subscribedSkus per current Microsoft Learn |
Organization.Read.All |
Application | Also authorizes GET /subscribedSkus |
Supported but broader; already granted for owner-entitlement queries, so it is compatible if you prefer not to add a permission |
Least-privilege guidance. Current Microsoft Learn lists
LicenseAssignment.Read.Allas the least-privileged application permission forGET /subscribedSkus;Organization.Read.Allis also supported but broader. GrantLicenseAssignment.Read.Allfor the probe where practical. If your app already holdsOrganization.Read.Allfor entitlement classification, the probe works without adding a permission.Heuristic, not authoritative. The public Microsoft licensing service-plan reference does not currently publish
skuPartNumber/servicePlanNamemappings for Agent 365, Agent 365 Frontier, or Microsoft 365 E7. Automatic matching is therefore a conservative exact-name heuristic plus an operator override list. A successful probe that finds no match isNotDetectedwith heuristic confidence — not authoritative absence.
Gate 2 — CopilotPackages.Read.All Application Permission (API gate)¶
The scanner uses application (app-only) permission to read the package
catalog, which allows unattended automation without a signed-in user. This gate
applies when the resolved mode attempts the Package API (present, or auto
with a SKU match).
| Permission | Type | Purpose |
|---|---|---|
CopilotPackages.Read.All |
Application | Read all Agent Builder packages from the catalog |
Who grants this: A Microsoft Entra Global Admin or Privileged Role Administrator must grant tenant-wide admin consent.
How to add the permission:
- In Azure Portal > Microsoft Entra ID > App registrations, open the CAI scanner app registration.
- Navigate to API permissions > Add a permission > Microsoft Graph > Application permissions.
- Search for and add
CopilotPackages.Read.All. - Click Grant admin consent for \<tenant> (requires Global Admin or Privileged Role Administrator).
Additional Graph permissions for owner licensing queries (required by
resolve_owner_entitlement.py, and reused by the auto-mode probe):
| Permission | Type | Purpose |
|---|---|---|
User.Read.All |
Application | Read owner profile and license assignments |
Organization.Read.All |
Application | Read tenant SKU information for entitlement classification (also authorizes the auto probe) |
GroupMember.Read.All |
Application | Resolve security-group memberships for sharing audience expansion |
Grant admin consent for these at the same time as CopilotPackages.Read.All.
Gate 3 — AI Administrator Entra Role (portal / delegated gate)¶
The AI Administrator role in Microsoft Entra ID grants access to the Copilot admin portal and is required for interactive/delegated workflows and for granting consent to Copilot-scoped permissions in the portal. For the app-only scanner running under a managed identity or service principal, this role is not required per-run — Gate 2 (admin-consented application permission) is sufficient for unattended automation.
- When this role is needed: Interactive portal workflows; granting Copilot-scoped admin consent via the M365 admin center; reviewing or managing the package catalog in the portal UI.
- Who holds this role: Typically the Copilot governance admin, not the scanner service principal.
PowerShell Core (pwsh) Requirement¶
resolve_owner_entitlement.py invokes
copilot-billing-governance/scripts/Get-CopilotEntitlement.ps1 as a
subprocess. PowerShell Core (pwsh) must be installed in the execution
environment (Azure Automation, GitHub Actions runner, or admin workstation).
Verify availability:
Install from aka.ms/install-powershell if not present.
# Dry run (no Dataverse writes; logs the calls that would be made)
python scripts/discover_agents.py \
--tenant-id <your-tenant-id> \
--dry-run \
--output scan.json
# Production run (managed identity preferred)
python scripts/discover_agents.py \
--tenant-id <your-tenant-id> \
--auth-mode managed-identity
# Full integrated scan — Package API + registry correlation + entitlement
# (US commercial Microsoft 365 cloud only; requires pwsh for entitlement resolution)
python scripts/discover_agents.py \
--auth-mode managed-identity \
--tenant-id <your-tenant-id> \
--agent365 present \
--registry-export registry-export.xlsx \
--columnmap templates/registry-columnmap.sample.json \
--as-of 2026-07-21T18:00:00Z \
--resolve-entitlement \
--output scan.json
Python Requirements: Python 3.9+, packages listed in scripts/requirements.txt.