Executive Snapshot
| Category | Recommendation |
|---|---|
| Ad-hoc questions | Microsoft 365 admin center → Reports → Usage (fastest path, zero setup) |
| Automation surface | Microsoft Graph /reports/* endpoints with app-only certificate auth |
| Least-privilege role | Reports Reader — or Usage Summary Reports Reader when per-user detail is not needed |
| Longest usage history | Power BI Microsoft 365 usage analytics (rolling 12 months) or your own snapshots |
| Identity reporting | Entra sign-in and audit logs, streamed to Log Analytics for retention beyond 30 days |
| Biggest trap | Concealed (pseudonymised) user names, on by default since 2021 — it silently breaks per-user reporting |
| Hard limit to plan around | Usage report data stops at 180 days. Snapshot monthly or the history is gone |
| Data latency | Usage reports lag reality by up to 48 hours; never report on "yesterday" |
TL;DR
- Microsoft 365 reporting is five separate surfaces, not one — admin center usage reports, the Graph reports API, Entra ID monitoring, the Purview audit log, and Power BI. Each has its own retention ceiling and its own permission model.
- Nothing retains usage data beyond 180 days except the Power BI template app (12 months) and whatever you archive yourself. If leadership wants year-over-year adoption trends, you have to build that history.
- Concealed user names are the default. Since September 2021 Microsoft pseudonymises user, group, and site names in usage reports, and the setting applies to the Graph API too — app-only automation does not bypass it.
- Entra sign-in and audit logs are licence-gated: 7 days on Entra ID Free, 30 days on P1 or P2. Diagnostic settings to Log Analytics is the only supported way to keep more.
- A monthly snapshot runbook is the whole game. One scheduled script pulling seven Graph endpoints into dated CSVs turns a set of disposable dashboards into a real reporting asset.
Introduction
Every Microsoft 365 admin gets the same request eventually. Finance wants to know how many of the 1,400 E5 licences are actually being used. Security wants a list of accounts that have not signed in for 90 days. A department head wants to prove Teams adoption is up since the March rollout. Legal wants to know who opened a specific SharePoint site in April.
Those four questions live in four different places, with four different retention windows and four different permission models. Answer them from the wrong surface and you either get numbers that quietly disagree with each other, or you discover the data expired weeks ago.
This guide maps the entire Microsoft 365 reporting landscape as it stands in 2026: what each surface actually holds, how long it holds it, the exact Graph endpoints and PowerShell to pull it, the privacy setting that breaks per-user reporting by default, and how to assemble the whole thing into a monthly cadence that survives audits. Every endpoint here is the current v1.0 path unless explicitly marked as beta.
Prerequisites
- A Microsoft 365 tenant with at least one workload in production (Exchange Online, Teams, or SharePoint Online).
- Reports Reader or Global Reader in Microsoft Entra ID for reading. Global Administrator is required only to change tenant report settings.
- PowerShell 7.4+ with the Microsoft Graph SDK:
Install-Module Microsoft.Graph -Scope CurrentUser. - Microsoft Entra ID P1 or P2 if you need sign-in log retention beyond 7 days, the sign-in logs Graph API, or diagnostic settings export.
- An app registration with a certificate for anything unattended, or an Azure Automation account with a managed identity.
- Familiarity with delegated versus application permissions in Graph — see the companion guide on automating Microsoft 365 with PowerShell for the auth patterns used throughout this article.
1. The Five Reporting Surfaces
Before writing a line of PowerShell, get the mental model right. Microsoft 365 telemetry splits into two fundamentally different streams — usage (what people did in the workloads) and identity/audit (who authenticated and what changed) — and each stream fans out into consumer surfaces with different limits.
flowchart LR
W[Microsoft 365 workloads] --> U[Usage telemetry]
W --> S[Entra sign-in and audit logs]
U --> A[Admin center usage reports]
U --> G[Graph reports API]
U --> P[Power BI usage analytics]
S --> E[Entra monitoring blade]
S --> D[Diagnostic settings]
D --> L[Log Analytics workspace]
G --> M[Monthly CSV snapshot]
M --> T[Your long-term trend model]
L --> T
The practical consequence: usage data and sign-in data can never be joined reliably at the user level unless you control the name concealment setting, and neither stream keeps enough history to answer a year-over-year question on its own.
| Surface | Best for | Retention ceiling |
|---|---|---|
| Admin center → Reports → Usage | Fast answers, exec screenshots | 7 / 30 / 90 / 180-day rolling windows |
Microsoft Graph /reports/* |
Automation, bulk exports, custom joins | Same D7–D180 windows |
| Power BI Microsoft 365 usage analytics | Trended adoption dashboards | Rolling 12 months |
| Entra ID monitoring (sign-ins, audit) | Authentication and directory-change forensics | 7 days Free, 30 days P1/P2 |
Purview audit log (Search-UnifiedAuditLog) |
Per-item activity, eDiscovery, investigations | 180 days Standard, 1 year Premium |
2. Admin Center Usage Reports: Fast, Shallow, Well Governed
The Microsoft 365 admin center is at Reports → Usage. It gives you the same aggregate charts every tenant sees — active users, email activity, mailbox usage, OneDrive and SharePoint storage, Teams activity, Microsoft 365 Apps usage, Groups activity — with a period selector for the last 7, 30, 90, or 180 days.
Two things matter more than the charts themselves.
First, the role model. You do not need Global Administrator to read reports, and handing it out for this is a common least-privilege failure.
| Role | What it sees |
|---|---|
| Reports Reader | All usage reports, including per-user detail rows |
| Usage Summary Reports Reader | Tenant-level aggregates only — no per-user rows at all |
| Global Reader | Usage reports plus read-only access to most admin surfaces |
| Exchange / Teams / SharePoint Administrator | Their own workload's reports |
| Global Administrator | Everything, including the tenant report settings toggle |
Give analysts and finance partners Usage Summary Reports Reader. It answers "how many people used Teams last month" without exposing who did what.
Second, the latency. Microsoft publishes usage report data on a delay — plan for up to 48 hours. Every report exposes a Report Refresh Date column; treat that value, not today's date, as the true edge of your data. Reporting on "yesterday" produces numbers that will change under you.
Note: The admin center charts and the Graph reports API read the same underlying dataset. If a number differs between them, it is almost always a period boundary or a time zone difference (the API works in UTC), not a data problem.
3. Concealed User Names: The Setting That Breaks Everything Quietly
This is the single most important thing to understand about Microsoft 365 reporting, and it catches teams every year.
Since 1 September 2021, Microsoft 365 usage reports conceal user, group, and site names by default. Instead of dana.reyes@contoso.com you get an opaque pseudonymised identifier. The change was a privacy-by-default move, and it applies to the admin center reports, the Microsoft Graph reports API, and the Power BI usage analytics app alike. Application permissions do not bypass it — a certificate-authenticated daemon sees exactly the same pseudonyms an admin does in the portal.
The result is a specific, nasty failure mode: a licence-reclamation script that joins usage rows to UPNs will not error. It will simply match nothing, and report that every user is inactive.
3.1 Check and Change the Setting in the Portal
Go to Microsoft 365 admin center → Settings → Org settings → Services → Reports. The checkbox is labelled Display concealed user, group, and site names in all reports. Cleared means real names are shown; selected means pseudonymised. Changing it requires Global Administrator.
3.2 Check and Change It with PowerShell
The tenant-wide setting is exposed at /admin/reportSettings in Graph, so it can be read in an audit script and flipped in a controlled, logged way rather than clicked in a portal.
# Read and (optionally) change tenant-wide report name concealment.
# Read requires: ReportSettings.Read.All
# Write requires: ReportSettings.ReadWrite.All + Global Administrator
Connect-MgGraph -Scopes "ReportSettings.ReadWrite.All" -NoWelcome
# Current state: displayConcealedNames = True means names ARE pseudonymised.
$current = Get-MgAdminReportSetting
"Concealed names currently: {0}" -f $current.DisplayConcealedNames
# Reveal real names tenant-wide (affects admin center, Graph API, and Power BI).
Update-MgAdminReportSetting -BodyParameter @{ displayConcealedNames = $false }
# Verify the change took effect before running any dependent export.
(Get-MgAdminReportSetting).DisplayConcealedNames
The equivalent raw call, useful if you are scripting outside PowerShell:
PATCH https://graph.microsoft.com/v1.0/admin/reportSettings
Content-Type: application/json
{
"displayConcealedNames": false
}
A successful PATCH returns 204 No Content.
Important: This is a tenant-wide, all-or-nothing switch. There is no per-report or per-consumer exception — revealing names for a licensing audit reveals them for every admin, every script, and every Power BI viewer at the same time. Decide deliberately, document the decision, and if you switch it temporarily, put the revert in the same change ticket.
Do not assume the pseudonyms are stable identifiers you can join across report periods or across reports. Treat them as non-durable and verify behaviour in your own tenant before designing anything around them.
4. The Microsoft Graph Usage Reports API
This is where real reporting happens. Every chart in the admin center has an equivalent Graph endpoint, and the API is the only sane way to export, join, and archive the data.
4.1 Endpoint Anatomy
All usage endpoints live under /reports/ and take an aggregation window as a function parameter:
GET https://graph.microsoft.com/v1.0/reports/getOffice365ActiveUserDetail(period='D30')
Three behaviours to internalise:
periodacceptsD7,D30,D90, orD180. These are the only values. There is noD365.- Some reports also accept
date={YYYY-MM-DD}for a single day, but only within the past 30 days, and not every report supports it. - The default response is a 302 redirect to a pre-authenticated CSV download URL. Add
?$format=application/jsonto get a paged JSON object instead.
The permission required is Reports.Read.All (delegated or application). Because that permission exposes tenant-wide usage detail, treat any app registration holding it as a sensitive identity: certificate auth only, no client secrets, and a Conditional Access policy for workload identities if you are licensed for it.
4.2 Pull a Report to CSV
The SDK's Invoke-MgGraphRequest follows the redirect and streams the file for you.
# Export the 30-day active user detail report to CSV.
# Requires: Reports.Read.All
Connect-MgGraph -Scopes "Reports.Read.All" -NoWelcome
$outFile = Join-Path $PWD "office365-active-user-detail-D30.csv"
Invoke-MgGraphRequest `
-Method GET `
-Uri "https://graph.microsoft.com/v1.0/reports/getOffice365ActiveUserDetail(period='D30')" `
-OutputFilePath $outFile
$rows = Import-Csv $outFile
"Rows: {0} | Report refresh date: {1}" -f $rows.Count, $rows[0].'Report Refresh Date'
Always echo the Report Refresh Date into your log. When someone challenges a number six weeks later, that column is the evidence of exactly which day's dataset produced it.
4.3 Work with JSON and Find Genuinely Dormant Accounts
For analysis, JSON is easier than CSV — but it pages, so honour @odata.nextLink.
# Find licensed accounts with no activity in ANY workload over 90 days.
# Requires: Reports.Read.All (and concealed names OFF for usable UPNs)
$uri = "https://graph.microsoft.com/v1.0/reports/getOffice365ActiveUserDetail(period='D90')?`$format=application/json"
$all = [System.Collections.Generic.List[object]]::new()
do {
$page = Invoke-MgGraphRequest -Method GET -Uri $uri
$all.AddRange($page.value)
$uri = $page.'@odata.nextLink'
} while ($uri)
$dormant = $all | Where-Object {
-not $_.isDeleted -and
($_.hasExchangeLicense -or $_.hasTeamsLicense -or $_.hasSharePointLicense) -and
-not $_.exchangeLastActivityDate -and
-not $_.teamsLastActivityDate -and
-not $_.sharePointLastActivityDate -and
-not $_.oneDriveLastActivityDate
}
$dormant |
Select-Object userPrincipalName, displayName, assignedProducts, reportRefreshDate |
Export-Csv -Path "./dormant-licensed-users-$(Get-Date -Format yyyyMM).csv" -NoTypeInformation
"Candidates for licence review: {0} of {1} accounts" -f $dormant.Count, $all.Count
Hand that CSV to whoever owns the licence budget, not to an automated deprovisioning job. A 90-day gap is a strong signal, not proof — long-term leave, seasonal staff, and shared mailboxes all show up here. Pair it with the assignment data covered in the Microsoft 365 licensing comparison before anyone removes a seat.
4.4 Endpoint Reference
The reports worth knowing, all under https://graph.microsoft.com/v1.0/reports/:
| Question | Endpoint |
|---|---|
| Who is active in which workload | getOffice365ActiveUserDetail(period='D30') |
| Tenant-level active user trend | getOffice365ActiveUserCounts(period='D90') |
| Which services users touch | getOffice365ServicesUserCounts(period='D30') |
| Mail send/read/receive per user | getEmailActivityUserDetail(period='D30') |
| Mailbox size against quota | getMailboxUsageDetail(period='D30') |
| OneDrive storage per account | getOneDriveUsageAccountDetail(period='D30') |
| SharePoint site storage and activity | getSharePointSiteUsageDetail(period='D30') |
| Teams activity per user | getTeamsUserActivityUserDetail(period='D30') |
| Teams client and device mix | getTeamsDeviceUsageUserDetail(period='D30') |
| Desktop vs web app usage per user | getM365AppUserDetail(period='D30') |
| Microsoft 365 Groups activity | getOffice365GroupsActivityDetail(period='D30') |
Copilot usage has its own reporting surface, exposed under the beta endpoint getMicrosoft365CopilotUsageUserDetail(period='D30'). Because it is still beta, the path can change without notice — pin your scheduled automation to v1.0 endpoints and treat Copilot reporting as best-effort until it graduates.
Tip: Every endpoint above also has a
Get-MgReport*cmdlet in theMicrosoft.Graph.Reportsmodule (for exampleGet-MgReportOffice365ActiveUserDetail -Period D30 -OutFile ./users.csv). The cmdlets are convenient interactively;Invoke-MgGraphRequestis more predictable in runbooks because it does not change signature between SDK releases.
5. Entra ID Sign-In and Audit Reporting
Usage reports tell you what people did inside the workloads. They tell you nothing about authentication. For that you need Microsoft Entra ID's monitoring surfaces — and here, retention is licence-gated.
| Log | Entra ID Free | Entra ID P1 / P2 |
|---|---|---|
| Audit logs (directory changes) | 7 days | 30 days |
| Sign-in logs | 7 days | 30 days |
| Risky sign-ins | 7 days | 30 days |
| Multifactor authentication usage | 30 days | 30 days |
Provisioning logs, Microsoft Graph activity logs, and the Usage & insights reports require P1 or P2 outright. So does the sign-in logs Graph API — Get-MgAuditLogSignIn returns an error on a Free tenant regardless of the admin role you hold.
5.1 Query Sign-Ins with Graph
# Failed interactive sign-ins in the last 7 days, grouped by failure reason.
# Requires: AuditLog.Read.All (+ Directory.Read.All in some tenants)
# Requires: Microsoft Entra ID P1 or P2
Connect-MgGraph -Scopes "AuditLog.Read.All","Directory.Read.All" -NoWelcome
$since = (Get-Date).AddDays(-7).ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ")
$failures = Get-MgAuditLogSignIn `
-Filter "createdDateTime ge $since and status/errorCode ne 0" `
-All
$failures |
Group-Object { $_.Status.ErrorCode } |
Sort-Object Count -Descending |
Select-Object @{n='ErrorCode';e={$_.Name}},
Count,
@{n='Example';e={$_.Group[0].Status.FailureReason}} |
Format-Table -AutoSize
Sign-in log filtering is more restricted than the rest of Graph: createdDateTime, userPrincipalName, appDisplayName, and status/errorCode are supported, but arbitrary property filters are not. Pull broadly, filter in PowerShell, and expect HTTP 429 on large tenants — honour the Retry-After header rather than sleeping a fixed interval.
5.2 Escape the 30-Day Ceiling with Diagnostic Settings
Thirty days does not survive an audit. The supported answer is Entra ID → Monitoring & health → Diagnostic settings, streaming SignInLogs, NonInteractiveUserSignInLogs, and AuditLogs to a Log Analytics workspace, a storage account, or an event hub. Once the data is in Log Analytics you set the retention, from 30 days up to two years interactive with archive well beyond that.
// Monthly active users by application over the last 180 days.
// Requires Entra sign-in logs streaming to this Log Analytics workspace.
SigninLogs
| where TimeGenerated > ago(180d)
| where ResultType == 0
| summarize ActiveUsers = dcount(UserPrincipalName)
by AppDisplayName, Month = startofmonth(TimeGenerated)
| order by Month asc, ActiveUsers desc
Two caveats worth budgeting for. Diagnostic settings for sign-in logs require P1 or P2, and the day you enable streaming is day one of your history — there is no backfill. Turn it on before you need it.
For directory-object and content-level activity (who deleted the file, who changed the mail flow rule), the Purview audit log is the correct surface: 180 days on Audit Standard, one year on Audit Premium with E5, and up to ten years with the add-on. It is queried through the Purview portal or Search-UnifiedAuditLog, not through the reports API.
6. Power BI Microsoft 365 Usage Analytics
The Power BI template app — Microsoft 365 usage analytics — is the only first-party surface that holds more than 180 days. It provides a rolling 12 months of tenant usage across adoption, communication, collaboration, storage, and mobility, refreshed daily.
Enabling it is a two-step job:
- In the admin center, open Reports → Usage → Microsoft 365 usage analytics and turn on the option that makes tenant data available to the template app. Note the tenant ID it displays.
- In Power BI, install the Microsoft 365 usage analytics template app and connect it using that tenant ID. Installing and sharing requires a Power BI Pro licence.
Expect the first full data load to take a day or two rather than minutes, and expect the app to honour your concealment setting — if names are concealed tenant-wide, the dashboards show pseudonyms too.
Caution: Treat the template app as a convenience layer, not as your system of record. Microsoft's investment in first-party template apps has shifted toward Fabric-based patterns, and template apps have been retired before — check the Microsoft 365 Message center for retirement or replacement notices before making it a long-term dependency.
The durable alternative costs an afternoon: archive the Graph CSVs yourself and point Power BI or Fabric at the archive. You own the schema, the retention, and the refresh — and nothing Microsoft deprecates can take the history away.
7. Build the Monthly Reporting Cadence
Everything above becomes valuable only when it runs on a schedule. The pattern that works is simple: one runbook, one dated folder, one immutable archive.

<#
Monthly Microsoft 365 usage snapshot.
Runs as an Azure Automation runbook using a system-assigned managed identity.
Managed identity needs the Reports.Read.All APPLICATION permission (admin-consented)
and Storage Blob Data Contributor on the archive account.
#>
Connect-MgGraph -Identity -NoWelcome
$period = 'D30'
$stamp = (Get-Date).AddDays(-1).ToString('yyyy-MM')
$workDir = Join-Path $env:TEMP "m365-usage-$stamp"
New-Item -ItemType Directory -Path $workDir -Force | Out-Null
$reports = @(
'getOffice365ActiveUserDetail'
'getOffice365ServicesUserCounts'
'getEmailActivityUserDetail'
'getMailboxUsageDetail'
'getOneDriveUsageAccountDetail'
'getSharePointSiteUsageDetail'
'getTeamsUserActivityUserDetail'
'getM365AppUserDetail'
)
foreach ($report in $reports) {
$target = Join-Path $workDir "$report-$stamp.csv"
$uri = "https://graph.microsoft.com/v1.0/reports/$report(period='$period')"
for ($attempt = 1; $attempt -le 3; $attempt++) {
try {
Invoke-MgGraphRequest -Method GET -Uri $uri -OutputFilePath $target
Write-Output "OK $report -> $(Split-Path $target -Leaf)"
break
}
catch {
$wait = [math]::Pow(2, $attempt) * 5 # 10s, 20s, 40s
Write-Warning "Attempt $attempt failed for ${report}: $($_.Exception.Message). Retrying in ${wait}s."
Start-Sleep -Seconds $wait
}
}
}
# Archive to immutable blob storage — this is the history nobody else keeps.
$ctx = New-AzStorageContext -StorageAccountName 'contosoreporting' -UseConnectedAccount
Get-ChildItem -Path $workDir -Filter *.csv | ForEach-Object {
Set-AzStorageBlobContent `
-File $_.FullName `
-Container 'm365-usage' `
-Blob "$stamp/$($_.Name)" `
-Context $ctx `
-Force | Out-Null
}
Remove-Item -Path $workDir -Recurse -Force
Write-Output "Snapshot $stamp archived."
Schedule it for the third or fourth day of each month, not the first. That clears the 48-hour publication lag and guarantees the D30 window covers the whole preceding month.
A cadence that holds up in practice:
| Frequency | Output | Audience |
|---|---|---|
| Daily (automated) | Sign-in failure and risky sign-in alerts from Log Analytics | Security operations |
| Weekly | Dormant licensed accounts, storage approaching quota | Service desk, storage owners |
| Monthly | The archived snapshot above plus a one-page adoption summary | IT leadership |
| Quarterly | Licence reclamation review and trend comparison against archive | Finance, IT leadership |
| Annually | Year-over-year adoption from your own archive | Executive and budget planning |
8. Pitfalls to Avoid
1. Treating the 180-day window as an archive. It is a rolling window. Data outside it is not slow to retrieve — it is gone. The month you skip the snapshot is the month you can never report on again.
2. Building automation on names while concealment is on. Scripts do not fail; they return pseudonyms and match nothing. Assert displayConcealedNames at the top of any per-user job and abort with a clear message if it is not what the script expects.
3. Comparing periods that overlap. Pulling D30 weekly and stacking the results double-counts activity. Pick one non-overlapping cadence per metric and stick to it.
4. Confusing "active" across workloads. Each workload defines activity differently — a Teams active user may have only reacted to a message, while Exchange counts send, read, or receive. Never sum active users across workloads into a single headline number.
5. Reading licence entitlement from usage reports. The has*License columns describe the report period, not the current assignment state. For entitlement, query the licence assignment data directly.
6. Forgetting non-interactive sign-ins. Service principals and refresh-token flows land in NonInteractiveUserSignInLogs, a separate category in diagnostic settings. Enable it or your sign-in volume will look implausibly low.
7. Over-permissioning the reporting identity. Reports.Read.All as an application permission exposes tenant-wide usage detail with no user context. Certificate auth, no secrets, scoped Conditional Access, and an annual review of who holds it.
8. Ignoring the Report Refresh Date. Two people running the same query on the same afternoon can get different numbers if the dataset refreshed between them. Record the refresh date with every export.
Troubleshooting
Graph returns 403 Forbidden on any /reports/ call
Cause: Missing Reports.Read.All, or the permission was granted as delegated while the script runs app-only.
Fix: Confirm the consented scopes with (Get-MgContext).Scopes. For app-only, verify the application permission is granted and admin-consented on the app registration, then acquire a fresh token — permission changes do not apply to cached tokens.
The CSV contains opaque identifiers instead of user names
Cause: Concealed names are enabled (the default).
Fix: Read the current state with Get-MgAdminReportSetting. If your reporting genuinely requires named data, change it deliberately with Update-MgAdminReportSetting and document the decision — it is tenant-wide.
Get-MgAuditLogSignIn fails on a licence error
Cause: The sign-in logs API requires Microsoft Entra ID P1 or P2. No admin role substitutes for the licence.
Fix: Confirm tenant licensing. Directory audit logs (Get-MgAuditLogDirectoryAudit) remain available on Free tiers with 7-day retention.
Report data is missing the last day or two
Cause: Normal publication latency of up to 48 hours.
Fix: Check the Report Refresh Date column and shift your reporting window back. Schedule monthly jobs a few days into the following month.
Repeated HTTP 429 responses during a bulk export
Cause: Graph throttling, most often from tight loops over per-user endpoints.
Fix: Honour the Retry-After response header, add exponential backoff as in the runbook above, and prefer one bulk report pull over thousands of per-user calls.
Admin center and Graph numbers disagree
Cause: Different period boundaries or time zone handling — the API works in UTC, the portal renders in your locale.
Fix: Compare on the same period value and the same Report Refresh Date before investigating further. Small, consistent differences are almost always boundary effects.
Key Takeaways
- Microsoft 365 reporting spans five surfaces, and the first skill is knowing which one owns the question you are being asked.
- 180 days is the hard ceiling for usage data. Only the Power BI template app (12 months) and your own archive go further.
- Concealed user names are on by default and apply to the Graph API too — check the setting before trusting any per-user output.
- Entra sign-in and audit log retention is licence-gated at 7 or 30 days. Diagnostic settings to Log Analytics is the only supported way past it, and it never backfills.
Reports.Read.Allis a sensitive permission. Certificate or managed-identity auth, no client secrets, reviewed annually.- The monthly snapshot runbook is the highest-value hour you will spend on reporting — it is what turns disposable dashboards into an auditable history.
- Record the Report Refresh Date with every export. It is the difference between a number you can defend and one you merely remember producing.
Next Steps
- Audit your report settings today. Run
Get-MgAdminReportSettingand confirm the concealment state matches what your scripts and dashboards assume. - Check your Entra retention. If sign-in logs are not streaming to Log Analytics, configure diagnostic settings now — every day of delay is a day of history you cannot recover.
- Deploy the monthly snapshot runbook into an Azure Automation account with a managed identity, and schedule it for the third of each month.
- Right-size reporting roles. Replace any Global Administrator assignment handed out for reporting with Reports Reader or Usage Summary Reports Reader.
- Decide your long-term store. Either commit to the Power BI template app or point Power BI at your own blob archive — but pick one and make it the system of record.
Leave a Reply