CIES scans your Azure environment with Resource Graph and Cost Management, then publishes what it finds as a report that refreshes on its own — so the answer to “what do we have, and what does it cost” is always sitting somewhere, instead of living in whoever last exported a spreadsheet.
Granting Graph permissionsCIES runs a set of KQL queries against Azure Resource Graph and Azure Cost Management across the subscriptions you point it at, then publishes the results as a structured report — resource inventory, cost breakdowns, network topology, KeyVault access, App Registration credential expiry, Service Bus health, Azure Monitor alert coverage, and configuration drift. Each scheduled run overwrites the last, so the report is never more than one cycle out of date.
Deployed as an Azure Marketplace Solution Template, it runs with no App Registration and no client secret: a scheduled Container Apps Job authenticates as its own system-assigned managed identity, and the report is published to a Static Web App in a resource group you own.
Most teams don’t lack Azure data — they lack a standing place to look at it. The usual substitute is a one-off export or a spreadsheet that’s accurate for exactly as long as nobody deploys anything new.
nobody notices spend creep until finance escalates
Resource counts and spend drift apart from what anyone remembers approving.
With CIES: a recurring, cost-enriched inventory tied to real Resource Graph data.forgotten resources and silent config changes pile up
Test resources outlive their purpose; configuration quietly diverges from what was intended.
With CIES: a dedicated drift report surfaces what changed.a secret expires in production with no warning
App Registration secrets and certificates expire on their own schedule, not yours.
With CIES: an expiry-monitored App Registration inventory.privileged access history is scattered or unavailable
Who activated what, and whether it was approved, is hard to reconstruct after the fact.
With CIES: PIM request history merged with its approval outcome.It’s built for the people who own that visibility gap:
On a Marketplace deployment, the App Registration Inventory and PIM Activity sections come back empty by default: the managed identity only holds Reader on your subscriptions, and these two sections additionally need Microsoft Graph permissions the deployment wizard can’t request on your behalf. That’s expected — not a broken job. Grant the five permissions below to populate them.
Before you start: you need to be signed in to the Azure Portal as a Privileged Role Administrator, Global Administrator, or Cloud Application Administrator. These are the only roles that can grant this permission.
Everything below stays inside portal.azure.com. The one part that needs a command instead of a button runs in the Portal’s own Cloud Shell — nothing is installed on your machine.
You’ll need this ID to target the script at the right identity.
Click the Cloud Shell icon in the Portal’s top toolbar, and choose PowerShell if prompted. First time in this tenant, follow its one-time storage prompt — that only happens once.
Paste the script below, replacing the placeholder with the object ID from step 1. It looks up what’s already granted and only adds what’s missing, so it’s safe to run more than once. All five permissions are read-only — this cannot let the identity modify app registrations, roles, group memberships, or PIM assignments.
$miObjectId = "<object-id-from-step-1>"
# Installs the Graph module if it isn't already present in this Cloud Shell session
if (-not (Get-Module -ListAvailable Microsoft.Graph.Applications)) {
Install-Module Microsoft.Graph.Applications -Scope CurrentUser -Force
}
Connect-MgGraph -Scopes "AppRoleAssignment.ReadWrite.All","Application.Read.All" -NoWelcome
# The Microsoft Graph service principal, and what the identity already holds on it
$graphSp = Get-MgServicePrincipal -Filter "appId eq '00000003-0000-0000-c000-000000000000'"
$existing = Get-MgServicePrincipalAppRoleAssignment -ServicePrincipalId $miObjectId |
Where-Object { $_.ResourceId -eq $graphSp.Id } |
Select-Object -ExpandProperty AppRoleId
$roles = @{
"Directory.Read.All" = "7ab1d382-f21e-4acd-a863-ba3e13f7da61"
"AuditLog.Read.All" = "b0afded3-3588-46d8-8b3d-9842eff778da"
"RoleManagement.Read.Directory" = "483bed4a-2ad3-4361-a73b-c83ccdbdc53c"
"PrivilegedAssignmentSchedule.Read.AzureADGroup" = "cd4161cb-f098-48f8-a884-1eda9a42434c"
"PrivilegedEligibilitySchedule.Read.AzureADGroup" = "8f44f93d-ecef-46ae-a9bf-338508d44d6b"
}
# Grants only what's missing, then reports the outcome for all five
$results = foreach ($name in $roles.Keys) {
if ($existing -contains $roles[$name]) {
[pscustomobject]@{ Permission = $name; Status = "Already granted" }
} else {
New-MgServicePrincipalAppRoleAssignment `
-ServicePrincipalId $miObjectId `
-PrincipalId $miObjectId `
-ResourceId $graphSp.Id `
-AppRoleId $roles[$name] | Out-Null
[pscustomobject]@{ Permission = $name; Status = "Granted" }
}
}
$results | Format-Table -AutoSize
There’s no separate admin-consent screen — this run is the grant. A successful run ends with a table showing all five permissions as Granted or Already granted.
No redeploy or restart needed — the managed identity’s next Graph API call picks up the new permissions automatically. Role assignments are typically effective within a few minutes, occasionally longer. If the two sections are still empty after the next scheduled run, wait for one more before troubleshooting.
To trigger a run immediately instead of waiting: copy the az containerapp job start command from the deployment’s Outputs tab and run it in the same Cloud Shell pane.
All five are read-only (*.Read.*) — two power App Registration Inventory, three power PIM Activity.
| Permission | Used by | Why |
|---|---|---|
| Directory.Read.All | App Reg Inventory | Reads app registrations and credential metadata. |
| AuditLog.Read.All | App Reg Inventory | Sign-in activity per app — optional, degrades gracefully if missing. |
| RoleManagement.Read.Directory | PIM Activity | Entra directory-role PIM requests: activations, eligibility, approvals. |
| PrivilegedAssignmentSchedule.Read.AzureADGroup | PIM Activity | PIM-for-Groups assignment requests. |
| PrivilegedEligibilitySchedule.Read.AzureADGroup | PIM Activity | PIM-for-Groups eligibility requests. |
The signed-in account doesn’t hold one of the roles listed under “Before you start.” Have someone with Global Administrator or Privileged Role Administrator run the script instead.
Re-run the script — it’s safe to run again, and its output table confirms whether all five permissions are actually present. A partial grant (e.g. only Directory.Read.All) still leaves the PIM section empty. Also check the Container Apps Job’s execution history for the specific Graph error.
These two sections will stay empty on this deployment. Running CIES outside the Marketplace offer, with a customer-created App Registration, is the alternative path.
Every step above stays inside the Azure Portal you already deployed into — nothing is installed locally, and nothing is sent to the publisher.