Hidden cost of CSV reporting

10 Aug 2026

The hidden cost of manual admin-center reporting

One monthly report, one client, 240 seats: licence usage, mailbox and OneDrive storage, MFA coverage, sign-in anomalies, Azure spend. Done the way most people do it — sign in, filter, export, open in Excel — it took 3 hours and 28 minutes, and 22 of those minutes were spent looking at the numbers. The other 186 were spent getting the numbers into one place. Here is where they went, and why the split is structural rather than a skill problem.

208 min

One monthly report, start to send

22 min

Time actually spent on analysis

11

CSVs downloaded and joined by hand

41 h/yr

Same report, twelve months, one client

Where the 208 minutes went

The stopwatch breakdown is the least interesting part of this article and the most convincing one. Nothing in it is a mistake. Every segment is work the platform requires.

ONE MONTHLY CLIENT REPORT — 208 MINUTES, TIMED 26m 34m 41m 52m 33m 22m Sign in, switch portal Download, de-duplicate Format the deck Wait for exports to build Join rows on identity Analysis 186 minutes of logistics the product

The pattern

The single largest segment is not exporting and not writing — it is joining. Five surfaces each return a table keyed differently, and one of them has the identity column deliberately scrambled.

52 minutes on a join key that is a hash

By default, every Microsoft 365 usage report hides user information. The admin center is explicit about it: reports conceal usernames, display names, groups and sites so organisations can support local privacy law. It is a sensible default, and it is not confined to the portal — the same organisation setting also applies to the Microsoft Graph usage reports and to the Power BI content pack. So the mailbox-storage CSV comes back with an opaque identifier where the UPN should be, while the licence export from the users blade comes back with a real address, and the two do not join.

You have two ways out and both cost something. Turn the setting off — a global admin action, and showing identifiable user information is itself a logged event in the Purview audit log — or resolve the identifiers yourself against a directory export, which is the 52 minutes. Neither is a mistake in your process. Both are the price of a privacy default doing its job.

The API path is not automatically shorter, either. A call to getOffice365ActiveUserDetail does not return JSON: it answers 302 Found with a Location header pointing at a preauthenticated download URL that is valid for a few minutes, and what lands is a CSV with 24 columns. Fine for a scheduled job. Miserable for a human doing it once a month, which is why the "just script it" answer only pays off after you have built somewhere for the CSV to land.

Five surfaces, five ceilings

Each source has a limit that only shows up at the size a real report reaches.

Surface What you get The ceiling
M365 usage reports CSV per report Names concealed by default; only 7 / 30 / 90 / 180-day windows exist
Graph reports API 302 → CSV Download URL expires in minutes; same concealment setting applies
Entra sign-in logs CSV or JSON 100,000 records per file, then the browser download times out
Entra audit logs CSV or JSON 250,000 records per file; columns ignore whatever you customised on screen
Azure cost details CSV or Parquet Portal backfill is 13 months, one month at a time; files are always partitioned

That last one is worth dwelling on. Cost Management splits every export into partitions described by a manifest.json, partitioning cannot be disabled, and Microsoft's own guidance is to recombine the parts with Power BI, Spark or Fabric — noting that large datasets may exceed Excel's row limits. The tool the report is being assembled in is the tool the docs tell you not to use.

Four clocks, and none of them wait for you

The part that turns an expensive habit into an unrecoverable one: each source forgets at its own speed. Entra retains sign-in and audit logs for seven days on Free and 30 days on P1 or P2; risky sign-ins stretch to 90 days, but only with P2. Usage reports go back 180 days. Azure cost detail backfills 13 months through the portal.

HOW FAR BACK EACH SOURCE CAN STILL ANSWER — FROM TODAY today 7d 30d 90d 180d Sign-ins — Entra ID Free Sign-ins and audit — P1 / P2 Risky sign-ins — P2 only Microsoft 365 usage reports Azure cost details 13 months, one month per request

Two footnotes make that diagram sharper than it looks. Retention changes are not retroactive: upgrading from Free to P1 gives you 30 days going forward, and data already past the seven-day window is gone unless it was archived. And when a user account is deleted, Microsoft removes that user's usage data within 30 days — the totals still count them for the days they were active, but the per-user row disappears. The month you skipped the report because you were busy is a month you cannot reconstruct later, and the leaver whose licence you meant to reclaim takes their evidence with them.

What 3h 28m actually buys

A deck. Not a baseline, not a trend, not a diff — a deck, describing one moment, assembled from sources that had already begun forgetting. Next month you start from zero, and the comparison you would most like to make ("what changed since October?") is only available if last month's spreadsheet still exists and still parses. That is the real cost, and it is not measured in hours.

The arithmetic is unkind at portfolio scale. One client, twelve months, is 41 hours. Ten clients is a part-time job that produces no artefact anyone keeps — the same failure mode as managing twenty tenants through twenty browser tabs, where the answer dies with the session. And it is self-defeating in the most literal way: the licence waste sitting in these exports is typically worth more per month than the time spent looking for it, which is precisely why the audit that would find it never gets run twice.

The fix is not a faster human. It is moving the 186 minutes to a schedule: collect on a timer, store what you collected, and let the join happen once instead of monthly. That is what GraphPaaS does — every domain above, per tenant, retained past the window Microsoft keeps it in, so the monthly report is the 22 minutes and nothing else.

Sources

GraphPaaS

Keep the 22 minutes. Give the other 186 to a scheduler.

See it on your tenant →

Newsletter

Get the next article by email

Practical M365 & Azure cost pieces like this one. No spam, unsubscribe anytime.