Add on SKUs nobody opens

16 Sept 2026

Visio, Project and the add-on SKUs nobody opens

Somebody asked for Visio in 2022. Procurement bought a block of seats because a block was cheaper per seat than four, and the block has renewed every year since without anyone looking at it. The same story exists for Project, for the Power BI Pro seats that came with a pilot, and for whatever your tenant's equivalent is.

The question is trivially easy to ask — of these forty Visio seats, how many people opened Visio this quarter? — and surprisingly hard to answer. Not because the data is buried. Because for these two products, the report that measures "opened it" does not have a column for them.

The gap you are about to fall into

Microsoft 365 has two per-user reports that sound like they cover this, and they cover different halves of it.

The Microsoft 365 Apps Activations report is explicit that it breaks down "Microsoft 365 Apps for enterprise, Project, and Visio Pro for Microsoft 365 subscription activations". Your add-ons are in there, as their own product rows. What it records is that a user "activated their Office subscription on at least one device" — an install event, once, possibly years ago.

The Microsoft 365 Apps Usage report is the one that measures real behaviour: active users are "any users who perform any intentional actions within these apps". The apps are named on the same page — "Outlook, Word, Excel, PowerPoint, OneNote, and Teams". Read the per-user column list in the Graph version, getM365AppUserDetail, and you get thirty-odd columns of app-by-platform detail for exactly those six products. There is no Visio column. There is no Project column. There is no $select that conjures one.

The pattern

Microsoft gives you activation for the add-ons and usage for the core apps. It never gives you usage for the add-ons. Every honest add-on audit is therefore built out of a weaker signal — and the trick is knowing exactly how weak, so you don't reclaim a seat you can't defend reclaiming.

WHICH SIGNAL SEES WHICH PRODUCT — THE GAP IS ONE CELL WIDE Activations report install event, once Apps usage report intentional actions Entra sign-in logs P1, last 30 days Word, Excel, Teams the core six Counted one row per product Measured six app columns Logged when signed in on web Visio, Project the add-on seats Counted own Product Type Nothing no such column exists Partial web front door only The invoice line lives in column 1. The word "used" only ever lives in column 2. So an add-on audit is built from columns 1 and 3, and has to say out loud which one each conclusion came from. Shared computer activations are excluded from the usage report entirely — another reason column 2 under-reports.

The audit, in five steps

1
List what you are paying for.

GET /subscribedSkus returns one subscribedSku per purchased product, with consumedUnits against prepaidUnits and the full servicePlans list. Filter to the add-on part numbers — this is the invoice, restated as an API response. Done when you have a row per add-on with two numbers on it: bought, and assigned.

2
Pull the activation rows, per product.

getOffice365ActivationsUserDetail returns a CSV with a Product Type column next to User Principal Name, Last Activated Date, and per-platform counts for Windows, Mac, iOS, Android and Activated On Shared Computer. One row per user per product — which is what makes Visio separable at all. Done when you can subtract: assigned seats minus users with an activation row for that product.

3
Reclaim the never-activated seats first.

A licensed user with no activation row for that product never installed it. That is not a judgement call about how much they use it — they don't have it. This bucket needs no second signal, no conversation and no 30-day watch period, and in most tenants it is the largest of the three. Done when those seats are unassigned and the difference is visible in consumedUnits on the next run.

4
For the rest, go to the sign-in logs.

The signIn resource carries appDisplayName and resourceDisplayName, both $filter-able, plus createdDateTime. Filtering on the Visio or Project web application gives you the closest thing to "opened it recently" that this tenant can produce. Two hard limits, both worth stating in the output: you need Entra ID P1 or P2 to read sign-in logs through Graph at all, and retention is 30 days on P1/P2 and seven on Free. Done when every still-assigned seat has a last-seen date or an explicit "no signal".

5
Hold the list against the renewal date.

Deciding to drop a seat and being able to drop it are different dates. On an annual commitment the seat is yours until the term ends whether it was ever opened or not — see annual versus monthly commitment. Done when the list has a date attached to it and lands in someone's calendar before the renewal window, not during it.

What each number actually proves

The reason add-on audits get argued down in meetings is that someone presents one column and calls it usage. Label them honestly and the argument goes away.

Signal What it proves What it costs
consumedUnits A seat is assigned. Nothing about a human. One call, no report pipeline.
No activation row The strongest evidence available: never installed on any device. Reports.Read.All; a 302 to a short-lived CSV URL, no period parameter.
Last Activated Date When they installed it. Not when they last opened it. Same call as above.
Sign-in in last 30 days Somebody opened the web app. Silence proves nothing about the desktop app. Entra ID P1/P2, and the window closes after 30 days.

Where this goes wrong

Reading silence as absence. A desktop-only Visio user who signs in once and stays signed in generates no fresh sign-in events and no new activation. They are the most likely person to appear on a reclaim list and the most likely to escalate when the licence disappears. Step 3 exists precisely so the easy wins don't have to share a spreadsheet with this case.

Auditing the SKU instead of the service plan. Some Visio and Project entitlements ride inside a bundle rather than arriving as a standalone purchase, which means the seat count and the entitlement count are different questions — the same distinction covered in the service plans nobody uses. Check servicePlans on the SKU before concluding a product isn't licensed anywhere.

Trusting the display name. The string your tenant reports is a part number, not a marketing name, and several of these products differ by a tier digit. The product names and service plan identifiers table is the authority for what maps to what; its String ID column is documented as the value behind skuPartNumber. A filter written from memory reports beautifully on the wrong SKU.

Running it once. An add-on audit expires. Seats get reassigned, projects end, the thirty-day sign-in window rolls forward and takes your evidence with it. A finding from March is not evidence in September.

Make it a standing column, not a project

The useful artefact is small: one row per add-on seat, with the assigned user, whether an activation row exists for that product, the activation date if it does, and a last-seen date from sign-in logs if there is one. Four columns, recomputed monthly, sorted so the never-activated seats float to the top.

Nobody builds it because it is three different Graph surfaces — a /subscribedSkus call, a CSV behind a redirect with no period parameter, and a paged log query with a retention clock on it — joined on userPrincipalName and worth redoing every month. That is a scheduled job, not an afternoon, and the afternoon version is what everyone tries first.

GraphPaaS collects the licence counts and the per-product activation counts per tenant on a schedule, so "how many of these seats has anyone ever installed" is a number on a page rather than a CSV you have to go and fetch before the download URL expires.

Sources
  • Microsoft 365 Apps Activations report — the explicit breakdown of "Microsoft 365 Apps for enterprise, Project, and Visio Pro for Microsoft 365" activations, and the definition of activation as installing on at least one device.
  • Microsoft 365 Apps Usage report — "intentional actions" as the definition of active, the six apps it covers, and the note that shared computer activations are excluded.
  • getM365AppUserDetail — the full per-user column list: Outlook, Word, Excel, PowerPoint, OneNote and Teams across four platforms, and nothing else.
  • getOffice365ActivationsUserDetail — the Product Type and Last Activated Date columns, Reports.Read.All, and the 302 redirect to a short-lived download URL.
  • signIn resource type — the filterable appDisplayName and resourceDisplayName, and the statement that reading sign-in logs via Graph requires Entra ID P1 or P2.
  • Microsoft Entra data retention — sign-ins retained seven days on Free and 30 days on P1 and P2, and the fact that upgrading does not recover expired data.
GraphPaaS

Add-on seats bought, assigned and ever activated — side by side, per tenant, every month.

See your add-on seats →

Newsletter

Get the next article by email

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