Microsoft setup commands
Every command to run in your Microsoft tenant for Agent 365, Microsoft 365 grounding, Purview and Sentinel, in order, with the role ids filled in.
Every command an organisation runs in its own Microsoft tenant for AIOE's Microsoft features, in
order: Agent 365 (Microsoft Agent 365), Microsoft 365 grounding through Work IQ (Memory),
Purview (Microsoft Purview) and Sentinel. Run them as a Global Administrator, in a shell signed in to
the organisation's tenant (az login --tenant <tenant id>; check with az account show).
Enterprise edition only.
Two rules Microsoft enforces shape what follows:
- A blueprint is barred from some calls whatever it holds. Graph answers it
403 UnknownErroron the Agent 365 registry and Purview's labels, refuses it file permissions outright, and refuses it app-only observability tokens (AADSTS82001). AIOE makes those calls as the AIOE API application (the registration people sign in to AIOE with) or as an agent identity, so some roles go on more than one principal. - Assign roles straight to service principals. The blueprint's registration declares no
permissions, so its admin-consent page fails with
AADSTS1003031. An assignment needs no consent step. A new role only reaches tokens minted after it: allow a few minutes, then use the panel's Try again or Connect, which ask for fresh tokens.
0. The ids
TENANT=$(az account show --query tenantId -o tsv)
GRAPH_SP=$(az ad sp show --id 00000003-0000-0000-c000-000000000000 --query id -o tsv) # Microsoft Graph in this tenant
# The AIOE API application: the console shows it as "API scope" under Settings (api://<id>/...).
API_APP=<AIOE API application (client) id>
API_SP=$(az ad sp show --id $API_APP --query id -o tsv)
# The deployment's managed identity (Azure): its principal (object) id.
MI_PRINCIPAL=$(az identity show -g <resource group> -n <identity name> --query principalId -o tsv)
# The person accountable for the agents (sponsor and owner).
SPONSOR=$(az ad user show --id <their UPN> --query id -o tsv)
# One helper for every application role below.
grant() { # grant <principal sp id> <resource sp id> <app role id>
az rest --method POST \
--url "https://graph.microsoft.com/v1.0/servicePrincipals/$1/appRoleAssignments" \
--body "{\"principalId\":\"$1\",\"resourceId\":\"$2\",\"appRoleId\":\"$3\"}" \
--query appRoleId -o tsv
}The application role ids are Microsoft's and the same in every tenant:
| Role | Resource | Id |
|---|---|---|
AgentRegistration.ReadWrite.All | Microsoft Graph | 39fb8c64-7bd3-4107-8515-14d6e55ddda4 |
InformationProtectionPolicy.Read.All | Microsoft Graph | 19da66cb-0fb0-4390-b071-ebc76a349482 |
ProtectionScopes.Compute.User | Microsoft Graph | fe696d63-5e1f-4515-8232-cccc316903c6 |
Content.Process.User | Microsoft Graph | 24ceb246-ad29-4680-90b4-3e91ffad15eb |
ContentActivity.Write | Microsoft Graph | 2932e07a-3c29-44e4-bb36-6d0fc176387f |
Files.ReadWrite.All | Microsoft Graph | 75359482-378d-4052-8f01-80520e7db3cd |
Agent365.Observability.OtelWrite | Agent 365 observability (app 9b975845-388f-4429-889e-eab1ef63949c, shown as "maven-prod") | 8f71190c-00c8-461d-a63b-f74abde9ba52 |
1. Agent 365: the blueprint
Create it in the Entra admin centre (Entra ID, Agents, Agent blueprints, New agent blueprint), or:
BLUEPRINT_APP=$(az rest --method POST --url "https://graph.microsoft.com/v1.0/applications/microsoft.graph.agentIdentityBlueprint" \
--headers "OData-Version=4.0" \
--body "{\"displayName\":\"AIOE agents\",\"sponsors@odata.bind\":[\"https://graph.microsoft.com/v1.0/users/$SPONSOR\"],\"owners@odata.bind\":[\"https://graph.microsoft.com/v1.0/users/$SPONSOR\"]}" \
--query appId -o tsv)
az rest --method POST --url "https://graph.microsoft.com/v1.0/serviceprincipals/microsoft.graph.agentIdentityBlueprintPrincipal" \
--headers "OData-Version=4.0" --body "{\"appId\":\"$BLUEPRINT_APP\"}"
BLUEPRINT_SP=$(az ad sp show --id $BLUEPRINT_APP --query id -o tsv)Give it the deployment's managed identity as its credential (no secret anywhere):
az rest --method POST \
--url "https://graph.microsoft.com/v1.0/applications(appId='$BLUEPRINT_APP')/federatedIdentityCredentials" \
--headers "OData-Version=4.0" \
--body "{\"name\":\"aioe-prod\",\"issuer\":\"https://login.microsoftonline.com/$TENANT/v2.0\",\"subject\":\"$MI_PRINCIPAL\",\"audiences\":[\"api://AzureADTokenExchange\"]}"Outside Azure, add a client secret instead (Entra admin centre, the blueprint, Certificates and secrets; needs the Agent ID Administrator role) and paste it into the panel.
2. Agent 365: the registry
Cards go as the AIOE API application, which the registry then records as their manager:
grant $API_SP $GRAPH_SP 39fb8c64-7bd3-4107-8515-14d6e55ddda4 # AgentRegistration.ReadWrite.All
grant $BLUEPRINT_SP $GRAPH_SP 39fb8c64-7bd3-4107-8515-14d6e55ddda4 # the same, for the blueprint's first tryThen in the console: Settings, Microsoft Agent 365, the blueprint's application id, This deployment's managed identity, Connect. Create an enterprise team on the Portfolio page; each agent member gets an identity and a card.
3. Agent 365: the licence row (optional)
A registered agent exports its runs without a further role. Only the panel's licence question needs the observability role, on each agent identity (the ids are shown on each member in the panel):
OBS_SP=$(az ad sp show --id 9b975845-388f-4429-889e-eab1ef63949c --query id -o tsv)
for AGENT in <agent identity id> <agent identity id>; do
grant $AGENT $OBS_SP 8f71190c-00c8-461d-a63b-f74abde9ba52
done4. Microsoft 365 grounding (Work IQ)
az ad sp create --id fdcc1f02-fc51-4226-8753-f668596af7f7 # Work IQ's service principal, once per tenant
az ad app permission add --id $API_APP --api fdcc1f02-fc51-4226-8753-f668596af7f7 \
--api-permissions 0b1715fd-f4bf-4c63-b16d-5be31f9847c2=Scope # WorkIQAgent.Ask (delegated)
az ad app permission admin-consent --id $API_APPSet a Copilot Credits spending policy in the Microsoft 365 admin centre, then Settings, Microsoft 365 Grounding, Switch on.
4a. The app AI Workbench signs in with (agents using Work IQ as a tool server)
Work IQ answers only a person's own delegated token, so agents reach Microsoft 365 through the Work IQ tool server with each person's own Microsoft sign-in on their workbench. That sign-in needs a public client registered for a desktop's loopback redirect. The console's own app is not registered for that, and is not used.
cat > /tmp/workbench-rra.json <<'JSON'
[
{"resourceAppId": "fdcc1f02-fc51-4226-8753-f668596af7f7", "resourceAccess": [{"id": "0b1715fd-f4bf-4c63-b16d-5be31f9847c2", "type": "Scope"}]},
{"resourceAppId": "00000003-0000-0000-c000-000000000000", "resourceAccess": [
{"id": "37f7f235-527c-4136-accd-4a02d197296e", "type": "Scope"},
{"id": "14dad69e-099b-42c9-810b-d002981feec1", "type": "Scope"},
{"id": "7427e0e9-2fba-42fe-b0c0-848c9e6a8182", "type": "Scope"}]}
]
JSON
WB_APP=$(az ad app create --display-name "AI Workbench" --sign-in-audience AzureADMyOrg \
--is-fallback-public-client true --public-client-redirect-uris "http://127.0.0.1" \
--required-resource-accesses @/tmp/workbench-rra.json --query appId -o tsv) # WorkIQAgent.Ask; openid, profile, offline_access
az ad sp create --id $WB_APP
az ad app permission grant --id $WB_APP --api fdcc1f02-fc51-4226-8753-f668596af7f7 --scope "WorkIQAgent.Ask"
az ad app permission grant --id $WB_APP --api 00000003-0000-0000-c000-000000000000 --scope "openid profile offline_access"
echo $WB_APPThen go to Settings, Microsoft 365 Grounding, The app AI Workbench signs in with, and paste the
id. Workbenches read it from the discovery document (microsoftSignIn, at the organisation's own
tenant), so nobody types it when they connect Work IQ. The redirect must be http://127.0.0.1
exactly: Entra treats http://localhost as a different address.
4b. Knowledge bases (Foundry IQ) and data agents (Fabric IQ) as tools (optional)
Publish them under Tools, Ready to Publish: Knowledge base (Foundry IQ) asks for the search service and the knowledge base, Data agent (Fabric IQ) for the workspace, the data agent and its tool name (Fabric, the data agent's Settings, Model Context Protocol). Each person reaches them as themselves, signed in with the app from 4a, so that app needs the delegated permissions too:
# Azure AI Search: user_impersonation, for knowledge bases.
az ad app permission add --id $WB_APP --api 880da380-985e-4198-81b9-e05b1cc53158 \
--api-permissions a4165a31-5d9e-4120-bd1e-9d88c66fd3b8=Scope
az ad app permission grant --id $WB_APP --api 880da380-985e-4198-81b9-e05b1cc53158 --scope "user_impersonation"
# Power BI Service (Fabric): DataAgent.Execute.All and DataAgent.Read.All, for data agents.
az ad app permission add --id $WB_APP --api 00000009-0000-0000-c000-000000000000 \
--api-permissions c6756612-6853-4145-a661-90c1d045b2dc=Scope 40fa91d5-73ef-412c-a8c8-c8658670d0eb=Scope
az ad app permission grant --id $WB_APP --api 00000009-0000-0000-c000-000000000000 --scope "DataAgent.Execute.All DataAgent.Read.All"
# Who may search a knowledge base: the Search Index Data Reader role on the service, to a group.
az role assignment create --role "Search Index Data Reader" --assignee <group-object-id> \
--scope $(az search service show -g <resource-group> -n <search-service> --query id -o tsv)A knowledge base filters results to what each person may read only when its index was built with
document permissions; otherwise everyone with the role reads all of it. A data agent answers under
Fabric's own permissions and row-level security for the person asking. Both need AI Workbench with
support for signing in to such servers (the auth field of a tool server definition).
4c. Microsoft 365 Copilot calls our agents (optional, Enterprise)
Copilot Studio agents can hand questions and tasks to the specialists and teams made callable on a project's page. Each call runs on the asker's own workbench (or a node they let run their work), as them. Three things in Entra, then one in Copilot Studio:
# 1. The AIOE API application offers the delegated scope Agents.Invoke (needs a Global Administrator).
API_OBJ=$(az ad app show --id $API_APP --query id -o tsv)
az ad app show --id $API_APP --query api -o json > /tmp/api.json
python3 - <<'PY'
import json, uuid
api = json.load(open("/tmp/api.json"))
scopes = api["oauth2PermissionScopes"]
if not any(s["value"] == "Agents.Invoke" for s in scopes):
scopes.append({"id": str(uuid.uuid4()), "value": "Agents.Invoke", "type": "User", "isEnabled": True,
"adminConsentDisplayName": "Ask the organisation's agents for work as the signed-in person",
"adminConsentDescription": "Lets an app such as a Copilot Studio agent hand questions and tasks to the organisation's AI Workbench specialists and teams, as the signed-in person.",
"userConsentDisplayName": "Ask your organisation's agents for work as you",
"userConsentDescription": "Lets the app hand questions and tasks to your organisation's agents as you."})
json.dump({"api": {"oauth2PermissionScopes": scopes}}, open("/tmp/api.json", "w"))
PY
az rest --method PATCH --url "https://graph.microsoft.com/v1.0/applications/$API_OBJ" \
--headers "Content-Type=application/json" --body @/tmp/api.json
# 2. An app for Copilot Studio's OAuth 2.0 connection: a confidential client with Copilot Studio's
# redirect address (Copilot Studio shows it when you create the connection).
CS_APP=$(az ad app create --display-name "Copilot Studio to AIOE" --sign-in-audience AzureADMyOrg \
--web-redirect-uris "<the redirect URL Copilot Studio shows>" --query appId -o tsv)
az ad sp create --id $CS_APP
SCOPE_ID=$(az ad app show --id $API_APP --query "api.oauth2PermissionScopes[?value=='Agents.Invoke'].id" -o tsv)
az ad app permission add --id $CS_APP --api $API_APP --api-permissions $SCOPE_ID=Scope
az ad app permission grant --id $CS_APP --api $API_APP --scope "Agents.Invoke"
az ad app credential reset --id $CS_APP --display-name copilot-studio --years 1 # the client secret for the connection
# 3. (Optional) Only this Copilot Studio connection may call: its application id goes in the policy.
echo $CS_APPIn Copilot Studio: your agent, Agents, Add an agent, Connect to an external agent,
Agent2Agent. Give the agent's address from the project's Microsoft 365 Copilot panel, and an
OAuth 2.0 connection: authorisation URL https://login.microsoftonline.com/<tenant>/oauth2/v2.0/authorize,
token URL …/oauth2/v2.0/token, the client id and secret from step 2, and the scope shown on the
panel (api://<API identifier>/Agents.Invoke). The agent card at
<address>/.well-known/agent-card.json fills in the name and description.
In the console: a policy that allows it (Policies, Microsoft 365 Copilot: on; optionally only
the application id from step 3, and whether a person approves first), and Make callable on the
project's page. Administrators decide whether project owners may do that too (Settings,
Microsoft 365 Copilot). Mojo Up's API application offers Agents.Invoke since 12 October 2026.
5. Purview: labels, verdicts and activity
The four on both principals; the platform tries the blueprint and falls back to the API application:
for ROLE in 19da66cb-0fb0-4390-b071-ebc76a349482 fe696d63-5e1f-4515-8232-cccc316903c6 \
24ceb246-ad29-4680-90b4-3e91ffad15eb 2932e07a-3c29-44e4-bb36-6d0fc176387f; do
grant $BLUEPRINT_SP $GRAPH_SP $ROLE
grant $API_SP $GRAPH_SP $ROLE
doneThen Settings, Microsoft Purview, Connect, and map the labels.
6. Purview: encrypting labels through SharePoint (optional)
grant $API_SP $GRAPH_SP 75359482-378d-4052-8f01-80520e7db3cd # Files.ReadWrite.All (the blueprint is refused it)
# Microsoft meters assignSensitivityLabel: link the API application to a subscription in the same tenant.
az provider register --namespace Microsoft.GraphServices
az provider show -n Microsoft.GraphServices --query registrationState -o tsv # repeat until "Registered"
az rest --method PUT \
--url "https://management.azure.com/subscriptions/$(az account show --query id -o tsv)/resourceGroups/<resource group>/providers/Microsoft.GraphServices/accounts/aioe-api-graph-billing?api-version=2023-04-13" \
--body "{\"location\":\"global\",\"properties\":{\"appId\":\"$API_APP\"}}"Authorization_IdentityNotFound on the last call means the provider is still registering: wait and
repeat it. Then tick Offer the SharePoint route in the panel and set a site.
7. Purview: Ask Purview itself (optional)
Turn on Purview pay-as-you-go once: the Purview portal, the rocket icon at the top right (or Settings, Account details), Get Started, a subscription and resource group in the same tenant. It takes a few hours.
The DLP policy is PowerShell only (the portal's policies cover Microsoft 365, not an app like this).
Windows PowerShell, or PowerShell 7 (pwsh) anywhere; not bash:
Install-Module ExchangeOnlineManagement -Scope CurrentUser
Connect-IPPSSession -UserPrincipalName <admin UPN> # in WSL or a terminal without a browser: Connect-IPPSSession -Device
$blueprint = "<blueprint application id>"
$apiApp = "<AIOE API application id>"
$loc = { param($id, $name) "{`"Workload`":`"Applications`",`"Location`":`"$id`",`"LocationDisplayName`":`"$name`",`"LocationSource`":`"Entra`",`"LocationType`":`"Individual`",`"Inclusions`":[{`"Type`":`"Tenant`",`"Identity`":`"All`"}]}" }
$locations = "[" + (& $loc $blueprint "Mojo Up AI Workbench") + "," + (& $loc $apiApp "Mojo Up AI Workbench (AIOE API)") + "]"
New-DlpCompliancePolicy -Name "AI Workbench prompts" -Mode Enable -Locations $locations -EnforcementPlanes @("Application")
New-DlpComplianceRule -Name "Block credentials in prompts" -Policy "AI Workbench prompts" `
-ContentContainsSensitiveInformation @{Name = "All Credential Types"} `
-RestrictAccess @(@{setting = "UploadText"; value = "Block"}) -GenerateAlert $true
Get-DlpCompliancePolicy "AI Workbench prompts" | Format-List Name, Mode, Locations # checkName both applications: Purview matches a policy against the application that calls it, and AIOE
calls as the blueprint where Microsoft allows it and as the AIOE API application otherwise (the
panel's Try a prompt says which). A policy made with one location can be replaced:
Remove-DlpCompliancePolicy -Identity "AI Workbench prompts", then create it again with both.
Allow an hour, then check it with Try a prompt in the panel (under Ask Purview itself): paste a fake credential and expect "In scope of a DLP policy" and "Blocked", naming the policy. Then set Ask Purview itself to "Prompts to hosted models". Optionally, in DSPM for AI (classic), Recommendations, Secure interactions from enterprise apps.
8. Sentinel (optional)
AIOE writes to Sentinel as the deployment itself, never the blueprint (Azure refuses a blueprint
any role assignment): its managed identity on Azure, otherwise the AIOE API application. Deploy the
table, endpoint, rule and detections from AI Workbench's docs/integrations/microsoft-sentinel
into the Sentinel workspace, naming that principal as the sender:
SENDER=$MI_PRINCIPAL # on Azure; otherwise SENDER=$API_SP
az deployment group create -g <sentinel resource group> -f mojoup-security-events.bicep \
-p workspaceName=<sentinel workspace> senderPrincipalId=$SENDER --query properties.outputs
az deployment group create -g <sentinel resource group> -f mojoup-detections.bicep -p workspaceName=<sentinel workspace>The first prints ingestionEndpoint and dcrImmutableId. Then Settings, Microsoft Sentinel:
enter both and Connect; AIOE proves it by sending an empty batch. A new role can take a few
minutes to apply. If the sender has to change later, grant the role directly:
DCR_ID=$(az monitor data-collection rule show -g <sentinel resource group> -n dcr-mojoup-security --query id -o tsv)
az role assignment create --assignee-object-id $SENDER --assignee-principal-type ServicePrincipal \
--role "Monitoring Metrics Publisher" --scope $DCR_IDCheck what is granted
for SP in $BLUEPRINT_SP $API_SP; do
echo "== $SP"
az rest --method GET --url "https://graph.microsoft.com/v1.0/servicePrincipals/$SP/appRoleAssignments" \
--query "value[].{role:appRoleId,resource:resourceDisplayName}" -o table
doneWhen something is refused
| Message | Meaning | What to do |
|---|---|---|
AADSTS1003031 on the consent page | The blueprint's registration declares no permissions | Use the grant commands; no consent page is needed |
cannot be granted to agent identity blueprint principals | A role Entra never gives a blueprint (files) | Grant it to the AIOE API application |
UnknownError (Graph answered 403) as the blueprint | Graph bars the blueprint from that endpoint | Expected: AIOE retries as the API application; grant the role there |
| The same 403 as the API application, minutes after a grant | The token predates the grant | Wait a few minutes, then Try again |
You do not have permission to create an agent registration managed by another AppId | A card naming another app as its manager | Fixed in AIOE 0.5.2: update |
AADSTS82001 | An agentic application asked for an app-only observability token | Expected: AIOE asks as an agent identity |
| Licence row: Microsoft did not answer (403) | The agent identities lack the observability role | Optional: step 3 |
Install-Module: command not found | PowerShell commands run in bash | Run them in PowerShell |
Principals of type agentIdentityBlueprintPrincipal cannot validly be used in role assignments | A blueprint named as Sentinel's sender | Use the managed identity or the AIOE API application (step 8) |
Authorization_IdentityNotFound on the billing account | Microsoft.GraphServices is still registering | Wait for "Registered", repeat |