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:

  • period accepts D7, D30, D90, or D180. These are the only values. There is no D365.
  • 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/json to 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 the Microsoft.Graph.Reports module (for example Get-MgReportOffice365ActiveUserDetail -Period D30 -OutFile ./users.csv). The cmdlets are convenient interactively; Invoke-MgGraphRequest is 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:

  1. 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.
  2. 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.

Dark data center aisle with rows of glowing blue storage arrays, one rack door open

<#
  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.All is 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

  1. Audit your report settings today. Run Get-MgAdminReportSetting and confirm the concealment state matches what your scripts and dashboards assume.
  2. 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.
  3. Deploy the monthly snapshot runbook into an Azure Automation account with a managed identity, and schedule it for the third of each month.
  4. Right-size reporting roles. Replace any Global Administrator assignment handed out for reporting with Reports Reader or Usage Summary Reports Reader.
  5. 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.

Related Articles

Thorsteinn Halldorsson Senior Cloud Engineer

Senior Cloud Engineer with 25+ years of hands-on experience across the datacenter-to-cloud stack: fiber SAN and disk storage, IBM/Lenovo blade and Dell/HP/Lenovo servers, Hyper-V and VMware clusters, and SQL and Remote Desktop Services (RDS) clusters. Deep in the Microsoft platform — Active Directory, PKI/certificate services, SQL, Power BI, Dynamics 365 Business Central (NAV) and AX (Axapta), Microsoft 365, Entra, and Intune — with a focus on Azure operations, FinOps, and applying AI tools like GitHub Copilot and Claude in real workflows. Writes practical, no-nonsense guides for IT professionals who need to ship real solutions.

Leave a Reply

Your email address will not be published. Required fields are marked *