Find unused Microsoft 365 licenses
DoubleCheck compares the licenses you bought with the licenses you assigned, per product, every day, and tells you where you pay for licenses nobody uses.
The problem
Licenses are bought when someone joins and often stay when someone leaves. The user is removed, the license goes back to the pool, and the subscription keeps renewing at the same count.
The admin center shows the numbers, but only if you go and look, per tenant, and compare available with assigned for every product.
Why it matters
Every unused license is a monthly cost without a user. On a tenant with a few products and some staff turnover, the leftovers add up without anyone deciding to keep them.
Seeing the spare licenses before a renewal date is the moment you can still lower the count.
How to check it manually
In the Microsoft 365 admin center, go to Billing, Licenses. For each product, compare the number of licenses with the number assigned.
With Microsoft Graph PowerShell you can list it for all products at once:
Connect-MgGraph -Scopes Organization.Read.All
Get-MgSubscribedSku | Select-Object SkuPartNumber, CapabilityStatus, ConsumedUnits,
@{n='Purchased'; e={$_.PrepaidUnits.Enabled}}
Purchased minus ConsumedUnits is the number of unused licenses for that product.
How DoubleCheck checks it
Once a day DoubleCheck reads the license products of your tenant from Microsoft Graph and works out, per product, how many licenses are bought but not assigned.
You set how many spare licenses per product are fine, for example one or two for new staff. A product with more unused licenses than that puts the check in error, and the result names the products and how many are unused.
Free and trial products can go on an ignore list, so they do not cause alerts. Products whose subscription is suspended or disabled are not counted, because there is nothing to act on.
- Measures
- The license products of your tenant from Microsoft Graph (
/subscribedSkus): per product the licenses bought minus the licenses assigned. Only products with status Enabled count. - Runs
- Once a day by default
- Status
-
- Lower is better.
- Error when the number of unused licenses of a product is above 0 (default advice).
- You can change the levels per check.
- What you need
-
- An app registration in your tenant with a client secret.
- Microsoft Graph application permission
Organization.Read.All(orLicenseAssignment.Read.All), with admin consent. Read only.
Frequently asked questions
Which permission does DoubleCheck need?
The Microsoft Graph application permission Organization.Read.All, or LicenseAssignment.Read.All, approved by a Global Administrator or Privileged Role Administrator. Both only read.
Why is there no warning level?
A license is either used or paid for without a user. The only setting is how many spare licenses per product you accept before the check goes into error.
Can I ignore free or trial licenses?
Yes. Products on the ignore list stay visible in the overview, marked as ignored, but do not count towards an alert.
Does DoubleCheck remove or change licenses?
No. It only reads. Removing or lowering licenses is something you do in the admin center or with your license provider.
The product names look like SPB or ENTERPRISEPACK. What are they?
Those are Microsoft's technical product names (SKU part numbers), the names Microsoft Graph returns. The DoubleCheck documentation has a list that translates each code into the product name, based on Microsoft's own published mapping.
How are my credentials stored?
The tenant ID, application ID and client secret are kept in a dedicated, encrypted secrets vault, separate from the DoubleCheck database. Access to it is restricted to DoubleCheck's own services under strict access policies.
Start monitoring with DoubleCheck
Alerts by email, SMS, Slack, Microsoft Teams or Discord. You pay per check run: cost management in the app shows what every check costs, and a longer interval lowers it.