Quick PowerShell Hunt to Reclaim Unused Microsoft 365 Licenses

  • Thread Author
A surprising number of Microsoft 365 tenants keep paying for licenses that are never used, and the short, practical PowerShell approach highlighted in a recent German post on BornCity underscores how simple discovery can unlock real cost savings — but also how easy it is to make mistakes if you rely on one metric alone. The script described inspects assigned licenses, cross-checks users’ last successful sign-in activity and flags accounts that are disabled or that have never logged in; it’s a useful starting point for license hygiene, but it needs to be embedded in a careful operational process that accounts for data-retention windows, service-only sign-ins, compliance holds, and Microsoft’s evolving administration toolset.

Background​

Microsoft 365 licensing is a recurring, fixed subscription cost for most organizations. Licenses are assigned to user objects in a tenant and can be reclaimed or reassigned, but many tenants lack an effective, automated process for identifying true non‑users. The BornCity write-up walks through a community PowerShell script that reports:
  • Assigned licenses for users who have not performed a successful interactive login,
  • Licenses assigned to disabled user accounts, and
  • Licenses assigned to accounts that have never logged in.
That’s exactly the kind of audit many admins should run periodically. The key caveat is that login metadata is only one signal, and single-signal decisions can produce false positives that cause service disruption or data loss.

How the detection approach works (technical overview)​

What the script checks​

At a high level, the typical script does three things:
  • Queries the user directory for accounts with assigned license SKUs.
  • Pulls sign-in / last-login metadata for each account.
  • Marks for review accounts meeting one or more of these criteria:
  • Last successful interactive login older than a configurable threshold (commonly 30–180 days).
  • Never logged in (no recorded successful sign-in).
  • Account is disabled but still holds an assigned license.
This approach is effective for flagging obvious waste: an account with an assigned license and no successful login in a year is almost certainly a candidate for license reclaim — but not always.

Where the login information comes from​

There are several data sources admins use to determine a user’s activity:
  • Microsoft Graph user properties exposing sign-in activity (SignInActivity and related attributes — many Graph properties require the beta profile and appropriate scopes).
  • Azure AD / Entra sign-in logs and Audit logs (tenant-level logs of authentication events).
  • Service-specific usage reports in the Microsoft 365 admin center (Exchange mailbox activity, OneDrive file activity, Teams usage reports).
  • Legacy PowerShell modules or APIs (MSOnline, AzureAD) that are being deprecated and replaced by Microsoft Graph PowerShell.
Be aware: some of the properties and module behaviors have changed in recent years. Microsoft has deprecated older modules (MSOnline, AzureAD) and actively recommends migrating to the Microsoft Graph PowerShell SDK. Sign-in-related properties are sometimes only available via the Graph beta endpoints, and some attributes are populated only if sign-in telemetry exists or if certain audit logging is enabled.

Strengths of the PowerShell-based approach​

  • Fast, low-cost discovery: A small script can generate a prioritized list of license candidates for reclamation in minutes, often revealing immediate savings.
  • Actionable output: The script produces concrete records (user principal name, assigned SKU, lastSignIn timestamp) that feed into an operational workflow.
  • Customizable thresholds: You can tune the inactivity windows (30/60/90/180+ days), exclude service accounts or contract employees, and integrate tenant-specific rules.
  • Automatable: Identified accounts can be fed into automation to notify users, apply hold processes, or remove licenses after approvals.
  • Audit-friendly: The script can produce CSVs or structured logs that document the basis for license actions, useful for finance and compliance review.

Important limitations and risks​

  • Single-signal fallacy: Relying only on last successful login produces false positives. Examples:
  • Users who access only a single service (e.g., a mailbox accessed by protocols that don’t produce the same sign-in telemetry).
  • Accounts that perform non-interactive service authentications (app-provisioned accounts, service principals, sync accounts).
  • Recently migrated or provisioned accounts that haven’t logged in yet but must retain data.
  • Sign-in telemetry gaps and retention windows: Sign-in data availability depends on tenant logging configurations and Microsoft’s retention periods. Older sign-ins may have been purged or never captured.
  • Deprecated modules and changing APIs: Scripts written against MSOnline/AzureAD modules can break as Microsoft retires those modules; Graph-based queries are the supported direction, but some sign-in attributes remain in the beta API surface and can change.
  • Legal / compliance constraints: Mailbox data, legal holds, litigation holds, or retention policies (eDiscovery, retention labels) may prevent license removal without legal/records-team approval.
  • Service disruption risk: Removing a license can cause immediate loss of mailbox access, OneDrive files, or Teams data access if not properly transitioned or archived.
  • Permission scope and privacy: Queries that read sign-in and license data require elevated admin consent. Audit the least privilege necessary and log who runs these checks.
Because of these risks, an automated script should never be the only step in the reclamation workflow: it should produce candidates that are validated through a multi-step human + system process.

Modern toolset and recommended APIs​

Microsoft’s recommended administrative interfaces have changed. Administrators should transition off legacy modules and use Microsoft Graph PowerShell or Microsoft Entra PowerShell (as available). Key operational points:
  • Migrate scripts away from MSOnline and AzureAD modules. These modules were deprecated and have defined retirement windows; rely on Microsoft Graph PowerShell for long-term compatibility.
  • Use Microsoft Graph for sign-in activity when possible. The Graph SDK exposes sign-in metadata (SignInActivity). Some sign-in attributes have historically been in the beta profile — plan for API surface changes and test against your tenant.
  • Use the Microsoft 365 Admin Center and service reports as supplementary sources. Product-level reports (Exchange, SharePoint/OneDrive, Teams, and Azure AD sign-ins) provide complementary usage signals that are rooted in actual service activity.
Administrators must ensure their scripts request appropriate Graph scopes (for example, Directory.Read.All, AuditLog.Read.All, User.Read.All) and that those scopes are granted in a controlled, audited manner.

Practical playbook: how to safely find and reclaim unused licenses​

Below is a recommended step-by-step playbook combining automated detection with human checks and governance. Treat this as an operational checklist you can adapt to your tenant policies.

1. Discovery (automated)​

  • Collect a complete list of licensed users and assigned SKU details.
  • Pull sign-in metadata (last successful interactive sign-in, non-interactive signins) and the disabled/enabled status for each account.
  • Pull service-specific usage where available (Exchange mailbox last activity, OneDrive file activity, Teams last activity).
  • Flag accounts meeting initial criteria (example: no successful interactive sign-in in 90 days or never logged in; disabled accounts with licenses).

2. Enrichment (automated + human)​

  • Cross-reference flagged accounts with HR/contractor systems to exclude recently departed or term-limited employees still under offboarding workflows.
  • Check for legal holds, eDiscovery holds, retention labels, or litigation flags that prevent license removal.
  • Identify service or automation accounts used for provisioning/synchronization; exclude these from reclamation (or convert to service principals where appropriate).
  • Create an “exceptions” list for VIPs, compliance-bound accounts, or accounts that must retain licenses.

3. Notification and verification (human process)​

  • Notify users and managers (templated email) that their account is identified as potentially unused, with a clear deadline and appeal process.
  • Allow self-remediation or confirmation (e.g., sign in within 14 days to preserve license).
  • For non-responsive cases, involve managers or HR to confirm termination/completion of engagement.

4. Safe reclaim (automated with checks)​

  • Convert mailboxes to a preserving state if needed (Shared Mailbox conversion where appropriate) to preserve content without a license.
  • If mailbox data must be retained intact and searchable for legal reasons, consider placing a preservation hold before removing a license.
  • Remove license only after approvals and after ensuring that any necessary data exports or archives are performed.
  • Log all license removals, including the script output, approver name, and timestamp.

5. Continuous governance​

  • Schedule discovery to run monthly or quarterly depending on tenant size and churn.
  • Use group-based licensing and dynamic groups where possible to reduce manual assignment drift.
  • Publish licensing governance policies (retention, on-boarding/off-boarding, grace periods).
  • Regularly reconcile billing statements against tenant assignment reports.

Example Microsoft Graph PowerShell pattern (conceptual)​

The following conceptual steps illustrate how to use the modern Graph approach. Do not run these verbatim without adapting to your tenant and least-privilege requirements.
  • Install and update the Microsoft Graph PowerShell SDK.
  • Use the beta Graph profile if you need sign-in attributes currently in beta.
  • Connect with the minimal admin scopes required (Directory.Read.All, AuditLog.Read.All, User.Read.All).
  • Query users with assigned SKUs and include SignInActivity properties.
  • Output candidates to CSV for human review.
A conceptual PowerShell flow:
  • Install-Module Microsoft.Graph
  • Select-MgProfile -Name "beta"
  • Connect-MgGraph -Scopes "User.Read.All","Directory.Read.All","AuditLog.Read.All"
  • $props = 'Id','UserPrincipalName','DisplayName','AssignedLicenses','SignInActivity'
  • $users = Get-MgUser -All -Property $props
  • $report = foreach ($u in $users) {
    $last = $u.SignInActivity.LastSuccessfulSignInDateTime
    [PSCustomObject]@{
    DisplayName = $u.DisplayName
    UserPrincipalName = $u.UserPrincipalName
    LastSignIn = $last
    Licenses = ($u.AssignedLicenses | ForEach-Object { $_.SkuId }) -join ';'
    AccountEnabled = $u.AccountEnabled
    }
    }
  • Export-Csv -Path unused-license-candidates.csv
Important notes: property names and availability may vary. Some tenants show empty SignInActivity fields due to data availability, and non-interactive sign-ins may incorrectly appear as activity for service-only logins. Always confirm on a per-account basis before taking action.

Avoiding common mistakes and edge cases​

  • Don’t remove licenses for accounts with only non-interactive sign-ins unless you’ve verified they are not service/service-principal accounts.
  • Check OneDrive and Exchange usage. A user might never have signed into the Microsoft 365 portal but may still have an active mailbox or files being accessed by other services.
  • Avoid bulk deletions without manager approvals. Human confirmation reduces the risk of business disruption.
  • If a user’s mailbox must be preserved for compliance, consider converting to a shared mailbox (if allowed by licensing and policy) or placing a preservation hold rather than immediate license removal.
  • Be cautious with “never logged in” flags — some migration scenarios create accounts that must be kept licensed for a period.

Governance: policy examples and recommended thresholds​

Every organization will have different tolerance for risk and different compliance obligations. Here are sample thresholds and a governance template to adapt:
  • Inactivity threshold: 90 days for general staff, 30 days for temporary contractors, 180 days for infrequent users.
  • Notification campaign: 14-day notice to the user and manager, then 7-day manager escalation, then reclaim.
  • Exceptions: HR-onboarded holds, pending eDiscovery or legal holds, VIPs, or accounts with pre-approved exceptions.
  • Reporting cadence: Monthly candidate report; quarterly executive summary for finance and procurement.
These thresholds are operational suggestions — tailor them to your org’s churn rate and service usage patterns.

Cost-saving realism: calculate before you act​

Estimated savings from reclaiming licenses are straightforward math: per-license unit price × number of licenses reclaimed × months. But actual savings depend on your contract terms (annual commitments, Enterprise Agreement, CSP monthly billing), so coordinate with procurement or your Microsoft account team before aggressive reclaim actions to avoid contract complications.

Automation patterns that work​

  • Group-based licensing with dynamic groups: Define dynamic rules for active staff and contractors so licenses are automatically assigned/removed as attributes change (e.g., employeeStatus).
  • Azure Automation / Power Automate workflows: After a human approval step, run an Azure Automation runbook or a Power Automate flow that calls Graph APIs to remove licenses and log the action.
  • Scheduled hygiene job: Monthly run that creates a “Candidates for Review” report and sends an automated digest to helpdesk and finance.
  • Integration with HR systems: Tie the onboarding/offboarding lifecycle to license assignment to prevent orphaned licenses.

When to use commercial tools​

Large enterprises with complex license estates and many SKU types may prefer third-party license management tools. Commercial tools often add:
  • Advanced analytics and dashboards,
  • Automated reclamation workflows with multi-step approvals,
  • Pre-built connectors to HR and finance systems,
  • Billing and contract-aware cost modelling.
These tools can accelerate governance at scale, but they add cost — evaluate ROI against your reclaim potential.

Final recommendations — an operational checklist​

  • Inventory: Get a complete list of licensed users and assigned SKUs.
  • Detect: Run Graph-based or admin-center reports to flag potential unused licenses.
  • Enrich: Cross-check with service-specific usage, HR records, and legal holds.
  • Notify: Implement a two-step notification process (user, manager) with a grace period and appeal.
  • Approve: Require manager/HR/legal sign-off for reclaim decisions that could affect data retention.
  • Reclaim: Use automated, logged processes to remove licenses and archive or convert resources where required.
  • Monitor: Schedule recurring checks and maintain an exceptions register.
  • Migrate tooling: Move scripts to Microsoft Graph PowerShell and avoid deprecated modules.

Conclusion​

Detecting unused Microsoft 365 licenses is low-hanging fruit for reducing recurring cloud spend, and community-contributed PowerShell scripts — like the one described in the BornCity post — are an excellent starting point. However, license hygiene is an operational discipline, not a single technical query. The robust approach combines Graph-based detection, service usage correlation, HR/finance ties, legal/retention checks, manager confirmation, and controlled automation. Adopt conservative thresholds, preserve data where required, and migrate your tooling to supported APIs before relying on automation. When done carefully, a recurring license-hygiene process reduces waste, protects business continuity, and keeps procurement and IT aligned on licensing efficiency.

Source: BornCity Microsoft 365: Find unused licenses | Born's Tech and Windows World