Book a demo

How to generate Microsoft 365 License Expiry Reports

Microsoft 365

19 Mins Read

|

Nadeem Ahamed
Nadeem Ahamed Azure FinOps Specialist

Most Microsoft 365 admins find out about an expired subscription the same way: someone opens a ticket because Outlook stopped working. That used to come with a cushion. Since February 9, 2026, license-based subscriptions bought directly through a Microsoft Customer Agreement no longer get the Expired grace state, so for a lot of tenants the end date on the bill is now the day users lose access. Renewal is also where the money leaks. A good Microsoft 365 license expiry report fixes both problems. It shows what ends when, what happens on that date given how you bought it, and how many seats you really need before you pay again.

Key takeaways

  • An expiry report and a usage report answer different questions. Have both open before any renewal.
  • Subscriptions move from Active to Expired to Disabled to Deleted. Once a subscription is deleted, it can’t be reactivated and the data is gone.
  • MCA-direct subscriptions lost the Expired grace state on February 9, 2026.
  • Grace periods depend on how you bought the license: 30 days for most direct terms, 90 for multi-year EA or Volume Licensing, and only 30 for some Copilot VL subscriptions.
  • You can pull expiry dates from Billing > Your products, or with Get-MgDirectorySubscription. (Get-MgSubscribedSku won’t show them.)
  • Start acting 90 days out. Cancellation windows close long before the end date does.

What is a Microsoft 365 license expiry report?

A Microsoft 365 license expiry report lists every subscription in your tenant with its end date, lifecycle status, and renewal setting, so you can decide what to renew, reduce, or let lapse before Microsoft decides for you.

The report works at the subscription level, not the user level. Each row is one SKU you’ve bought: Microsoft 365 E3, Business Premium, a Teams Phone add-on, the Copilot seats finance signed off on last spring. Trials and free SKUs belong in it too. Trials especially, because when a trial ends, Microsoft starts billing you automatically.

People confuse this with the license usage report all the time. They’re different reports.

A usage report tells you who holds a license and whether they actually use it. An expiry report tells you when the contract behind those licenses ends and what happens after that. At renewal, neither one is enough on its own. If you know the date but not the usage, you renew 500 seats when 380 would have done. If you know the usage but not the date, you find the waste a week after the cancellation window closed.

The data source trips people up as well. Get-MgSubscribedSku is where most license scripts start, and it gives you consumed and prepaid units but no expiry date. For dates you need Get-MgDirectorySubscription. (Both come up again in the how-to section below.)

A useful expiry report answers three questions:

  • What ends, and when?
  • What actually happens on that date, given whether you bought through MCA, an Enterprise Agreement, Volume Licensing, or a CSP?
  • How many of those seats should you renew at all?

Most free scripts and admin tools stop at the first question. Turbo360 Tenant Manager answers all three in one view, with expiry dates, license consumption, and cost side by side.

What happens when a Microsoft 365 subscription expires?

When a Microsoft 365 subscription expires, it moves through four lifecycle states (Active, Expired, Disabled, and Deleted), and how long it stays in each one depends on how you bought it.

That second half is the part most guides skip. And since February 2026, it’s the part that bites.

The four lifecycle states

Active. This is the normal state. Users work, admins assign licenses, and nothing is wrong.

Expired. The subscription has passed its end date, but nobody notices. Users keep full access to apps, mail, and files. A Global or Billing admin can still reactivate it from the admin center. This is the grace period people mean when they say “we have 30 days.”

Disabled. Now people notice. Users can’t get into Microsoft 365 apps, and installed apps drop into a read-only, reduced-functionality mode with Unlicensed Product banners. Only admins can reach the data, so this is your backup window. Admins can still sign in to the admin center, but they can’t assign licenses from that subscription. Reactivation is still possible.

Deleted. It’s over at this point. Customer data is deleted, and Microsoft Entra ID is removed if no other service uses it. You can’t reactivate the subscription. Buying a new subscription of the same type later won’t bring the old data back either.

The February 2026 change for MCA subscriptions

As of February 9, 2026, license-based subscriptions bought directly from Microsoft under a Microsoft Customer Agreement no longer go through the Expired state.

Think about what that does to your report. For those subscriptions there’s no quiet 30 days where everything keeps working. The end date is the date that matters, full stop.

Microsoft’s answer for MCA customers who need more time is the Extended Service Term (EST). An EST keeps the subscription running month to month after the term ends, until you cancel or convert it to a standard subscription. It’s useful when a renewal decision is stuck in procurement, but you have to choose it before the end date. Your expiry report should flag MCA subscriptions early for exactly that reason.

How the purchase channel changes the timeline

The same SKU can follow a different clock depending on where you bought it. Your report needs a channel column, or the dates in it will mislead you.

Purchase channel and term Expired (grace) Disabled After that
Direct from Microsoft, most offers 30 days 90 days Deleted
Direct under MCA, license-based (from Feb 9, 2026) None Disabled period applies Deleted
EA, Volume Licensing, or Open Value, monthly or annual term 30 days 90 days (called Inactive) Deleted
EA, Volume Licensing, or Open Value, multi-year term 90 days 90 days (called Inactive) Deleted
Some Microsoft 365 Copilot subscriptions via VL 30 days only Per agreement Deleted
Cloud Solution Provider (CSP) Per Partner Center rules Per Partner Center rules Deleted

Microsoft adds a caveat for agreement-based purchases: your actual end date may be tied to the agreement end date rather than the individual subscription term, and grace periods can vary by program. If you buy through EA or VL, confirm the numbers with your licensing partner instead of trusting a blog table. (Including this one.)

Volume Licensing has one more quirk worth knowing. A VL subscription only enters Expired once all its licenses are removed. If you renew with fewer seats than before, there’s no Expired state. Instead, you get 90 days to sort out users who are assigned more licenses than you now own, with no interruption for per-user subscriptions. Non-user subscriptions behave differently. Cut the quantity on something like Office 365 Extra File Storage, and the storage drops right away.

When the grace period doesn’t apply at all

There are three ways to skip the Expired state, and only one of them is a policy change.

  1. Cancelling a monthly subscription inside the cancellation window. It goes straight to Disabled. Users lose access immediately, and admins keep access to back up data.
  2. Explicitly deleting a subscription. This skips Expired and Disabled, and SharePoint Online and OneDrive data is deleted immediately. If someone on your team “cleans up” the billing page by deleting instead of letting a subscription lapse, there’s no undo.
  3. Being an MCA-direct customer after February 9, 2026. Covered above.

Even after a normal cancellation, the clock keeps running. Microsoft says data you leave behind may be deleted after 90 days, and will be deleted no later than 180 days after cancellation.

So the question your expiry report really has to answer isn’t only “when does this end?” It’s also “which of these rules applies to this row?”

How do you check Microsoft 365 license expiry dates?

You can check Microsoft 365 license expiry dates in the admin center under Billing > Your products, with the Get-MgDirectorySubscription PowerShell cmdlet, or through the Microsoft Graph /directory/subscriptions endpoint.

The admin center is fine for a quick look. Anything you want to run every month belongs in PowerShell or Graph.

In the Microsoft 365 admin center

Sign in with at least the Billing admin role. (Global admin works, but you don’t need it for this.) Go to Billing > Your products. Each subscription shows its status and expiry date, and you can open it to check whether recurring billing is on.

It’s quick, but it’s a screen, not a report. You can’t sort a 40-subscription tenant by end date, you can’t schedule it, and it tells you nothing about how many of those seats people actually use.

With PowerShell (Get-MgDirectorySubscription)

The Microsoft Graph PowerShell SDK gives you the same data in a form you can sort, filter, and export. Read-only access is all it needs.

Connect-MgGraph -Scopes "Organization.Read.All"

Get-MgDirectorySubscription |
Select-Object SkuPartNumber, Status, IsTrial, TotalLicenses,
CreatedDateTime, NextLifecycleDateTime |
Sort-Object NextLifecycleDateTime

The field that matters is NextLifecycleDateTime, but read it alongside Status. It’s the date the subscription moves to its next state, which isn’t always the date you’d call “expiry.” For an active subscription, it’s the term end. For one that’s already Expired, it’s the date it turns Disabled. A report that shows the date without the status will mislead whoever reads it.

Remember the trap from earlier: Get-MgSubscribedSku has no expiry dates. It does have something the subscription object lacks, though, which is how many units are assigned. Join the two and the report starts doing real work:

$assigned = @{}
Get-MgSubscribedSku | ForEach-Object { $assigned[$_.SkuId] = $_.ConsumedUnits }

Get-MgDirectorySubscription | ForEach-Object {
[pscustomobject]@{
Sku = $_.SkuPartNumber
Status = $_.Status
Trial = $_.IsTrial
Purchased = $_.TotalLicenses
Assigned = $assigned[$_.SkuId]
NextLifecycle = $_.NextLifecycleDateTime
}
} | Sort-Object NextLifecycle |
Export-Csv .\m365-expiry-report.csv -NoTypeInformation

One caveat. ConsumedUnits is counted per SKU, not per subscription. If you hold two subscriptions of the same SKU (say, an annual E3 plus a monthly top-up), both rows will show the same assigned count. Flag those rows and look at them by hand.

With the Microsoft Graph API

For automation, call Graph directly:

GET https://graph.microsoft.com/v1.0/directory/subscriptions

It returns the same companySubscription objects, as JSON: SKU, status, trial flag, total licenses, and nextLifecycleDateTime. The least-privileged application permission is Organization.Read.All. For delegated access, a reader role like Global Reader or Directory Readers is enough, so nobody needs Global admin to run the report.

This is the route if you want the report to land in someone’s inbox, feed a dashboard, or raise an alert at 90 days. Run it from an Azure Automation runbook with a managed identity and you don’t have to store any credentials. (That setup is covered in the automation section.)

Which method should you use?

Method Least privilege What you get Schedulable Best for
Admin center Billing admin Status, end date, recurring billing, one subscription at a time No Spot checks in small tenants
PowerShell Organization.Read.All Status, NextLifecycleDateTime, trial flag, purchased and assigned units, CSV export With a scheduler or runbook Monthly reports for a single tenant
Graph API Organization.Read.All (application) The same fields as JSON, ready for other systems Yes Alerts, dashboards, multi-tenant setups

Whichever method you pick, the hard part isn’t pulling the dates. It’s deciding which columns the report needs so the person reading it can act on it.

What should a license expiry report include?

A Microsoft 365 license expiry report should show each subscription’s end date and lifecycle status next to its purchase channel, seat counts, actual usage, cost, and owner, so that every row ends in a renewal decision.

Most reports stop at the date. That tells you when the problem arrives, not what to do about it.

Here’s the column set that holds up in a real renewal meeting. Some of it comes straight from Graph. The rest you’ll have to add yourself, and that’s normal: the parts Graph doesn’t give you are usually the parts finance asks about.

Column Where it comes from Why it matters
SKU / subscription name Graph (skuPartNumber) or admin center The friendly name, so finance can read it. Map part numbers like SPE_E3 to “Microsoft 365 E3.”
Purchase channel Admin center, invoice, or your partner Decides which lifecycle rules apply: MCA direct, EA/VL, or CSP.
Term and billing frequency Admin center or invoice Monthly, annual, or multi-year changes both the grace period and how much room you have to reduce.
Status Graph (status) Tells you how to read the date column.
Next lifecycle date Graph (nextLifecycleDateTime) The date something happens.
Days remaining Calculated Lets you sort the report and band rows at 90, 60, and 30 days.
Recurring billing Admin center If it’s on, the subscription renews by itself. Sometimes that’s exactly what you don’t want.
Cancellation window Purchase terms Whether you can still cancel or reduce. Once it closes, you’re committed until the term ends.
Trial Graph (isTrial) Trials turn into paid subscriptions automatically.
Purchased units Graph (totalLicenses) What you’re paying for.
Assigned units Graph (consumedUnits, via Get-MgSubscribedSku) What you’ve handed out.
Active users Microsoft 365 usage reports What people actually use. This is the column that saves money.
Annual cost Invoice or price sheet Turns seat gaps into a number someone will act on.
Owner You Who signs off on the renewal. Rows without an owner tend to auto-renew.
Decision You Renew, reduce, switch SKU, move to EST, or let it lapse.

A few of these deserve a word.

Purchased, assigned, and active are three different numbers. Purchased minus assigned is shelfware: seats nobody holds. Assigned minus active is quieter waste: licenses sitting on people who left, moved roles, or never opened the app. Most tenants have both kinds. The second kind is harder to see because the license looks “in use.”

The cancellation window column is the one people forget. For some subscriptions, you can only cancel during a short window after you buy or renew. After that, turning off recurring billing just stops the next renewal. Your report needs to show when that window closes, not only when the term ends. Otherwise the waste you find in March is waste you’re paying for until next year.

Owner and Decision turn this into a report rather than a list. A row reading “E3, 500 purchased, 412 assigned, 371 active, ends in 74 days, owner: IT Ops, decision: reduce to 400” is something a person can act on. The same row without the last two fields is something a person can only look at.

Should usage and cost live in the same file as the dates? Ideally, yes. Pulling usage from one script, dates from another, and prices from an invoice PDF is how these reports go stale, which brings up timing.

How far ahead should you act on an expiring license?

Start acting on an expiring Microsoft 365 license at least 90 days before its end date, with checkpoints at 90, 60, and 30 days, because cancellation windows and seat reductions have to be settled long before the subscription actually ends.

Thirty days is when most teams start. It’s also when most of the options are already gone.

Here’s why. By 30 days out, procurement has a quote in motion, recurring billing is still on, and nobody has checked usage. The renewal goes through at last year’s seat count because there’s no time to argue for a smaller one. Microsoft does send warning emails and admin center alerts as the end date gets closer, but they go to billing admins, who often aren’t the people who know whether 120 Visio seats are still needed.

So build the cadence backward from the end date.

90 days out: find the waste. Pull the report and compare purchased, assigned, and active seats for everything ending this quarter. Reclaim licenses from leavers and long-inactive accounts now, so the assigned count drops before anyone quotes a renewal. Look for trials too. Any trial that ends in this window will start billing unless someone turns it off.

60 days out: make the decision. Each row gets an owner and a call: renew as-is, reduce, change SKU, move to EST, or let it lapse. This is where finance comes in. The admin knows the usage and finance knows the budget, and the decision needs both of them in the same meeting (or at least the same spreadsheet).

30 days out: execute and check. Change seat counts, set recurring billing to match the decision, and confirm with your partner if you buy through one. Anything still unresolved at this point gets escalated. Don’t let it default.

Day 0 and after: confirm the status changed the way you expected. If a subscription was meant to lapse, check that it’s in the right state and that data from it has been backed up. Don’t delete it to tidy up the billing page. (We covered why.)

Who owns which checkpoint?

It’s split between roles, and that’s where expiries fall through.

The M365 admin owns the data: seat counts, usage, reclaiming licenses, and the technical risk of anything lapsing. Finance or the billing owner owns the money: budget, invoice, and whether a reduction actually shows up on the next bill. If you buy through an EA or a CSP, your licensing partner owns part of the timeline as well, and they’ll need notice before they can change anything.

Name a single owner for each subscription, even if three people are involved. “IT” isn’t an owner.

Adjust the cadence by channel

Ninety days is the default, not a rule.

EA and Volume Licensing renewals usually need more runway than that. They involve negotiation and a partner, and often a true-up, so start the 90-day review a quarter earlier for the big agreement. CSP subscriptions run on your partner’s terms, and their cancellation windows can be short, so ask the partner for exact dates rather than assuming.

MCA-direct subscriptions are the ones to watch closest now. There’s no Expired grace state behind them anymore. If your decision isn’t made by the end date, the main way to buy time is the Extended Service Term, and you need to choose it before the term ends, not after.

Get the timing right and the 90-day review stops being a clean-up chore. It becomes the point where you actually cut the bill.

How do you automate Microsoft 365 license expiry reporting?

To automate Microsoft 365 license expiry reporting, run a Microsoft Graph PowerShell script as an Azure Automation runbook with a managed identity, give it read-only Organization.Read.All access, and schedule it to flag anything ending within 90 days.

No stored passwords, no app secrets to rotate, no laptop that has to stay on.

If your FinOps team already works in Azure, this is the natural home for it. The runbook lives in a subscription you already govern, alongside the rest of your cost tooling. And M365 licensing gets the same treatment as your Azure spend instead of sitting in someone’s Downloads folder.

Step 1: Create the automation account and identity

Create an Azure Automation account with a system-assigned managed identity turned on. Then import two modules as resources on the account: Microsoft.Graph.Authentication and Microsoft.Graph.Identity.DirectoryManagement. The second one holds the subscription and SKU cmdlets. Skip it and the runbook fails on its first line of real work.

Step 2: Grant the Graph permission

This step catches people out. The Entra admin center will show the managed identity’s permissions, but it won’t let you assign Graph application permissions to it. You do that once, from your own machine, with PowerShell:

Connect-MgGraph -Scopes "AppRoleAssignment.ReadWrite.All","Application.Read.All"

$graph = Get-MgServicePrincipal -Filter "AppId eq '00000003-0000-0000-c000-000000000000'"
$mi = Get-MgServicePrincipal -Filter "displayName eq 'your-automation-account'"
$role = $graph.AppRoles | Where-Object {
$_.Value -eq "Organization.Read.All" -and
$_.AllowedMemberTypes -contains "Application" }

New-MgServicePrincipalAppRoleAssignment -ServicePrincipalId $mi.Id `
-PrincipalId $mi.Id -ResourceId $graph.Id -AppRoleId $role.Id

Organization.Read.All is enough. Don’t grant Directory.Read.All “just in case.”

Step 3: Write the runbook

Create a PowerShell 7 runbook. It’s the same logic as the export script earlier, plus a days-remaining calculation and a filter for the rows someone actually needs to see:

Connect-MgGraph -Identity -NoWelcome

$assigned = @{}
Get-MgSubscribedSku | ForEach-Object { $assigned[$_.SkuId] = $_.ConsumedUnits }

$today = Get-Date
$report = Get-MgDirectorySubscription | ForEach-Object {
$daysLeft = if ($_.NextLifecycleDateTime) {
[int]($_.NextLifecycleDateTime - $today).TotalDays } else { $null }
[pscustomobject]@{
Sku = $_.SkuPartNumber
Status = $_.Status
Trial = $_.IsTrial
Purchased = $_.TotalLicenses
Assigned = $assigned[$_.SkuId]
NextLifecycle = $_.NextLifecycleDateTime
DaysLeft = $daysLeft
}
}

$flagged = $report | Where-Object {
($_.DaysLeft -ne $null -and $_.DaysLeft -le 90) -or
$_.Trial -or $_.Status -ne "Enabled"
} | Sort-Object DaysLeft

$flagged | Format-Table -AutoSize

The filter catches three things: anything inside the 90-day window, every trial (because trials convert to paid), and anything whose status isn’t Enabled. That last one means a subscription is already past its end date and nobody noticed.

Step 4: Schedule it and send it somewhere

Link a schedule to the runbook. Monthly is enough for the full report. Weekly is better once anything enters the 90-day band.

Then send the output to wherever the owners will actually see it. Writing the CSV to a storage account works. Posting to a Teams channel works better, because people read Teams. If you’d rather email it, the identity needs Mail.Send. Scope that to a single sending mailbox rather than leaving it tenant-wide.

Where the script stops

Be honest about what this covers. The runbook gives you dates, status, trials, and purchased versus assigned seats, and that alone is more than most tenants track today.

It doesn’t know your purchase channel, cancellation windows, or what each seat costs. It doesn’t know who’s actually using their license, as opposed to just holding one. And if you manage more than one tenant, you’re now maintaining a runbook, an identity, and a permission grant for each of them.

That gap, between “which licenses are expiring” and “which licenses are worth renewing,” is what Turbo360 Tenant Manager is built for. It analyzes every SKU in the tenant (paid, free, and trial) for consumption and right-sizing, so the renewal conversation starts from usage data instead of last year’s seat count.

FAQ

How do I find my Microsoft 365 license expiry date?

In the Microsoft 365 admin center, go to Billing > Your products, where each subscription shows its status and end date. You need at least the Billing admin role. For a sortable list, run Get-MgDirectorySubscription in PowerShell and read the NextLifecycleDateTime and Status fields together.

Is there still a 30-day grace period after a Microsoft 365 subscription expires?

It depends on how you bought it. Most subscriptions still get a 30-day Expired state with full access. But since February 9, 2026, license-based subscriptions bought directly through a Microsoft Customer Agreement no longer go through the Expired state. Multi-year EA and Volume Licensing terms get 90 days.

What happens to user data when a Microsoft 365 subscription expires?

During the Expired state, users keep normal access. In the Disabled state, users lose access and only admins can reach the data, typically for 90 days. Once the subscription is Deleted, customer data is removed and can’t be recovered, even if you buy the same subscription again.

Can I reactivate an expired Microsoft 365 subscription?

Yes, while it’s in the Expired or Disabled state. A Global admin or Billing admin can reactivate it from the admin center. A Deleted subscription can’t be reactivated.

Does turning off recurring billing cancel a Microsoft 365 subscription immediately?

No. Turning off recurring billing lets the subscription run to the end of its current term, then it ends. For yearly and three-year subscriptions, the Expired state starts on the end date, not the day you turned recurring billing off.

What happens if I delete a Microsoft 365 subscription instead of letting it expire?

Deleting a subscription skips both the Expired and Disabled states, and SharePoint Online and OneDrive data is deleted immediately. To end a subscription you no longer need, turn off recurring billing and let it lapse, so you keep the window to back up data.

How do I export Microsoft 365 license expiry dates to CSV?

Connect with Connect-MgGraph using the Organization.Read.All scope. Then pipe Get-MgDirectorySubscription through Select-Object (SkuPartNumber, Status, IsTrial, TotalLicenses, NextLifecycleDateTime) into Export-Csv. Join it with Get-MgSubscribedSku if you also want assigned seat counts.

Why doesn’t Get-MgSubscribedSku show license expiry dates?

Get-MgSubscribedSku reports licenses at the SKU level, with purchased and consumed units, but it has no expiry field. Expiry and lifecycle dates live on the subscription object, which you read with Get-MgDirectorySubscription or the Graph /directory/subscriptions endpoint.

Do Microsoft 365 trials renew automatically?

Yes. When a trial ends, Microsoft automatically starts billing for the subscription. If you don’t want to keep it, turn off recurring billing before the trial ends, and back up any data first, because trial data is permanently deleted once the trial ends without a purchase.

Are expiry rules different for EA, Volume Licensing, and CSP?

Yes. EA, Volume Licensing, and Open Value subscriptions get 30 days Expired on monthly or annual terms and 90 days on multi-year terms, though some Copilot VL subscriptions get only 30. CSP subscriptions follow Partner Center lifecycle rules. Always confirm exact dates with your licensing partner.

What is an Extended Service Term in Microsoft 365?

An Extended Service Term (EST) is an option for subscriptions bought under a Microsoft Customer Agreement. It keeps service running month to month after the term ends, until you cancel or convert to a standard subscription. It’s useful when a renewal decision isn’t finished by the end date.

What permissions do I need to run a license expiry report?

In the admin center, Billing admin is the least-privileged role. With Microsoft Graph, Organization.Read.All is the least-privileged permission for both delegated and application access. Delegated users also need a reader role such as Global Reader or Directory Readers.

Advanced Cloud Management Platform - Request Demo CTA

Related Articles