๐๏ธ Recipe ยท Microsoft 365 Governance
Generate a Comprehensive Microsoft 365 Tenant Health Summary
Aggregate license utilization, inactive users, privileged exposure, device compliance, risky sign-ins, credential expirations, and storage trends into a single audit-ready report
Complexity
Advanced
Impact
governance + security + cost-optimization + compliance + read-only + audit
Context
Why This Matters
A tenant health summary is a cross-domain snapshot of the signals that most commonly drive security incidents, license waste, and compliance findings in Microsoft 365. Rather than opening seven separate admin centers, this recipe aggregates the key indicators into a single report you can deliver to security, finance, and leadership stakeholders.
Run this recipe:
- Monthly as part of a standing governance review.
- Quarterly before license renewals to surface downgrade and reclamation opportunities.
- Ad hoc during M&A, audits (SOC 2 / ISO 27001), or after a security incident.
- On onboarding when you inherit a tenant and need a baseline.
The summary covers seven domains: license utilization efficiency, inactive users (90+ days), privileged account exposure, unmanaged/non-compliant devices, risky sign-ins (last 30 days), app registration certificate and secret expirations, and SharePoint storage growth trends.
Expected Outcomes
After completing this recipe you will have:
- A structured JSON/CSV report file containing all seven health domains with timestamps and tenant identifier.
- Quantified license utilization per SKU and a list of downgrade/reclamation candidates.
- A list of enabled accounts with no sign-in in 90+ days (license waste and attack-surface candidates).
- A full inventory of Global Administrators and other privileged role holders, with counts benchmarked against Microsoft's recommendation (โค4 Global Admins).
- A reconciliation of Entra-registered vs. Intune-managed devices, with unmanaged devices called out.
- Every risky sign-in in the last 30 days with user, IP, location, risk level, and remediation state.
- App registrations whose certificates or client secrets expire in the next 30โ90 days.
- SharePoint storage growth over the last 30 days, including any anomalous spikes.
- A prioritized list of remediation actions (High / Medium / Low).
Risks & Considerations
โ ๏ธ Warnings and Gotchas
- Read-only report โ safe to run, but the data it exposes (admin lists, user sign-in data, IP addresses) is highly sensitive. Store the output in a controlled location and share only with authorized reviewers.
- signInActivity is only populated for Entra ID P1/P2 tenants. Free-tier tenants will not have
lastSignInDateTimedata and the inactive-users section will be empty. - Identity Protection (riskyUsers / riskySignIns) requires Entra ID P2. Without P2, fall back to querying
auditLogs/signInswithriskLevelDuringSignIn ne 'none'. - Report anonymization: If
displayConcealedNamesis enabled in/admin/reportSettings, user and site names in usage reports will be hashed. Set it tofalse(temporarily) if you need readable names. - Do NOT act on inactive-user data without verification. Service accounts, shared mailboxes, and break-glass admins may legitimately have no interactive sign-ins. Cross-reference with group membership and license assignment before disabling.
- Do NOT reduce Global Admin count below 2. Always retain at least two break-glass accounts that are cloud-only, excluded from Conditional Access, and have complex stored credentials.
- Compliance: Sign-in log data is subject to data residency and retention policies. Do not export it to uncontrolled storage (personal OneDrive, email).
Required Permissions
| Permission | Why It's Needed |
|---|---|
| Organization.Read.All | Read subscribedSkus for license utilization |
| User.Read.All | Enumerate users and read signInActivity for inactive-user detection |
| AuditLog.Read.All | Read sign-in logs for risky sign-in analysis |
| Directory.Read.All | Read directory roles and privileged role members |
| DeviceManagementManagedDevices.Read.All | Read Intune managed device inventory and compliance state |
| Device.Read.All | Read Entra ID registered devices for unmanaged-device reconciliation |
| Application.Read.All | Read app registrations including keyCredentials and passwordCredentials expirations |
| Reports.Read.All | Read SharePoint, OneDrive, and mailbox usage reports |
| IdentityRiskyUser.Read.All | Read riskyUsers and riskyDetections from Identity Protection (Entra ID P2) |
The fastest way to get this done: just ask Dex. Copy the prompt below and paste it into your Dex conversation.
For IT Admins
Paste into Dex Pro
Manual walkthrough โ Microsoft 365 admin consoles
Collecting the full health summary by hand requires visiting seven admin surfaces. Use this path when you need a one-off review and don't want to automate.
1. License utilization
- Go to .
- For each subscription, note Assigned vs. Total. Flag any SKU below 70% utilization as a reclamation candidate.
2. Inactive users
- Go to .
- Click Columns and add Last sign-in date.
- Sort ascending by Last sign-in. Export users with last sign-in older than 90 days and Account enabled = Yes.
3. Privileged account exposure
- Go to .
- Open Global Administrator and record member count. Repeat for Privileged Role Admin, Security Admin, Exchange Admin, SharePoint Admin, Application Admin, Authentication Admin.
- Microsoft recommends โค 4 Global Admins.
4. Device management
- Go to and record total count and compliance breakdown.
- Go to . Compare counts โ Entra-registered devices not present in Intune are potentially unmanaged.
5. Risky sign-ins
- Go to .
- Set filter: Detection time = Last 30 days. Export to CSV.
6. App credential expirations
- Go to .
- For each app, open Certificates & secrets and note any credential expiring within 30โ90 days.
7. Storage growth trends
- Go to .
- Select Site usage, period = Last 30 days. Export the CSV and compare Storage Used over time.
8. Compile the report
Paste each section's findings into a single document. Add a Recommendations section ranking items by risk.
Estimated time: ~45 minutes for a small tenant, 2+ hours for a large one.
Graph API recipe
All seven domains can be collected with read-only Graph API calls. Use an app registration with the permissions listed above and app-only (client credentials) authentication.
1. License utilization
GET https://graph.microsoft.com/v1.0/subscribedSkus?$select=skuPartNumber,prepaidUnits,consumedUnitsFor each SKU, utilization = consumedUnits / prepaidUnits.enabled.
2. Inactive users (90+ days, still enabled)
GET /v1.0/users?$filter=signInActivity/lastSignInDateTime le {iso90DaysAgo} and accountEnabled eq true&$select=id,displayName,userPrincipalName,accountEnabled,signInActivity&$top=999Paginate via @odata.nextLink. Requires Entra ID P1+.
3. Privileged roles
GET /v1.0/directoryRoles?$expand=members($select=id,displayName,userPrincipalName)Filter to privileged roles: Global Administrator, Privileged Role Administrator, Security Administrator, Exchange Administrator, SharePoint Administrator, Application Administrator, Authentication Administrator.
4. Device inventory
GET /v1.0/deviceManagement/managedDevices?$select=id,deviceName,userPrincipalName,operatingSystem,complianceState,lastSyncDateTime
GET /v1.0/devices?$select=id,displayName,operatingSystem,trustType,isManagedReconcile by Entra device ID or device name to identify registered-but-not-managed devices.
5. Risky sign-ins (last 30 days)
GET /v1.0/auditLogs/signIns?$filter=createdDateTime ge {iso30DaysAgo} and riskLevelDuringSignIn ne 'none'&$select=userDisplayName,userPrincipalName,riskLevelDuringSignIn,riskState,createdDateTime,ipAddress,location&$top=200&$orderby=createdDateTime descFor P2 tenants, also query /v1.0/identityProtection/riskyUsers for users currently in an at-risk state.
6. App credential expirations
GET /v1.0/applications?$select=displayName,appId,keyCredentials,passwordCredentials&$top=999Iterate each app's keyCredentials[].endDateTime and passwordCredentials[].endDateTime; flag any within 30 days.
7. Storage growth (SharePoint)
GET /v1.0/reports/getSharePointSiteUsageStorage(period='D30')
GET /v1.0/reports/getSharePointSiteUsageDetail(period='D30')The first endpoint returns a daily storage-used time series as CSV. Compute: start MB, end MB, absolute growth, percent growth, and flag spikes > 5% day-over-day.
Response example (subscribedSkus)
{
"value": [
{
"skuPartNumber": "ENTERPRISEPACK",
"consumedUnits": 143,
"prepaidUnits": { "enabled": 200 }
}
]
}PowerShell โ Microsoft Graph SDK
The script below uses the Microsoft.Graph PowerShell SDK to collect all seven domains and produce a structured JSON report plus per-domain CSVs.
Prerequisites
- PowerShell 7+
Install-Module Microsoft.Graph -Scope CurrentUserInstall-Module Microsoft.Graph.Beta -Scope CurrentUser(for some Intune endpoints)- An account (or app registration) with the Graph scopes listed in Permissions.
Approach
- Connect with
Connect-MgGraphrequesting all needed scopes. - For each of the seven domains, call the corresponding cmdlet (
Get-MgSubscribedSku,Get-MgUser,Get-MgDirectoryRole, etc.). - Aggregate into a single
[pscustomobject]report. - Export to
TenantHealthSummary_{yyyy-MM-dd}.jsonand individual CSVs per section. - Print a color-coded console summary with priority recommendations.
Running
.\Get-TenantHealthSummary.ps1 -OutputPath "C:\Reports" -InactiveDays 90 -RiskyDays 30 -CertWarnDays 30The script is idempotent and read-only โ it performs no writes against the tenant.
Next steps