← Back to articles Teams

When Teams Phone Licenses Get Stuck

When Teams Phone Licenses Get Stuck

A real-world licensing investigation, told honestly, so you don’t lose hours like we did.

🎬 The short story

We had a few disabled users who still had Microsoft Teams Phone licenses assigned. No matter what we tried in the Microsoft 365 or Entra ID portal… the license just would not go away.

The portal refused. Scripts half‑worked. Errors everywhere. And yet — Microsoft Graph fixed everything in one clean pass.

This article explains why.

😤 What we saw in the UI (UX)

In the Microsoft 365 / Entra portal, we tried the obvious steps:

  • Remove Teams Phone from the user
  • Check the licensing group
  • Disable the account

And the portal responded with something like:

⚠️ “This license is assigned via group membership and cannot be removed.”

Sometimes the checkbox was greyed out. Sometimes the action “worked” but nothing changed.

The license looked stuck.

🧠 The first wrong assumption (very common)

We assumed this flow:

Licensing Group
   → Teams Phone (MCOEV)
       → User

So naturally, we searched for:

  • A group assigning MCOEV
  • A way to remove that license directly

But this assumption was wrong.

🔍 The real licensing flow (what was actually happening)

Here is the real flow inside Entra ID:

Licensing Group
   → Microsoft 365 E3 / E5
        → Service plans inside the license
             → Teams Phone (enabled switch)
                  → User

Important: Teams Phone was not assigned as a standalone license. It was enabled as a service plan inside E3 / E5.

That means:

  • No group visibly “assigned Teams Phone”
  • The portal could not map the source
  • Removing the license was blocked for safety

🧩 Why the license looked “stuck”

From the portal’s point of view:

  • The license was group-based ✅
  • The group did not obviously show Teams Phone ✅
  • The assignment path was unclear ❌

So the UX followed a conservative rule:

If any part of the license is group-based and unclear → block removal.

That’s not a bug. That’s the portal protecting you from accidental mass license removal.

🧠 The breakthrough: stop asking the group, ask the user

Microsoft Graph exposes something the portal hides:

LicenseAssignmentStates

This property answers one golden question:

“For THIS user, who assigned THIS license?”

When we queried it, we finally saw entries like:

AssignedByGroup = e6689047-728e-4422-8ca2-35ad9c7ab0de

In plain English:

“This license exists because of THIS group, even if the group UI doesn’t show the feature.”

😬 The extra twist: mixed assignments

One user had an even messier situation:

  • ✅ Teams Phone via group
  • ❗ Another SKU assigned directly months or years ago

The UX saw:

“Part group-based, part direct — I’m not touching that.”

Graph, however, exposed both paths clearly.

✅ The final working logic (the one that fixed everything)

Once we trusted LicenseAssignmentStates, the solution became obvious:

User
 → Check LicenseAssignmentStates
   → If AssignedByGroup → Remove user from THAT group
   → If Assigned directly → Remove license directly

No guessing. No brute force. No collateral damage.

Result: Teams Phone removed cleanly for all problematic users.

📊 Why this only worked with Graph (not the UI)

  • The UI does not expose license attribution details
  • The UI applies safety rules when attribution is unclear
  • Graph exposes the internal truth Entra ID already knows

So the final rule becomes:

If a license looks “stuck” in the UI, it usually means the UI cannot see the full assignment path — not that the system is broken.

🧠 The one lesson to remember

Groups advertise licenses. Users remember who actually assigned them.

When things get weird:

  • Start from the user
  • Read LicenseAssignmentStates
  • Remove the license using the same path it came from

🔒 How to prevent this forever

Make sure all licensing groups include a dynamic rule like:

(user.accountEnabled -eq true)

That way:

  • Disabled users auto-drop
  • Teams Phone, E3, E5 auto-remove
  • No “stuck” licenses ever again

✅ Final thought

The license was never stuck. We just needed to ask the right question — at the right place.

And that place was Microsoft Graph.

📜 PowerShell Script for License Removal

Here's a PowerShell script to manage license removal using Graph API:

Connect-MgGraph -Scopes "User.Read.All","Group.ReadWrite.All","Directory.ReadWrite.All"

$DomainFilter = "@domain.com"
$TeamsPhoneSkuPartNumber = "MCOEV"

# Resolve Teams Phone SKU ID
$TeamsPhoneSkuId = (Get-MgSubscribedSku |
    Where-Object { $_.SkuPartNumber -eq $TeamsPhoneSkuPartNumber }).SkuId

$Users = Get-MgUser -All `
 -Property Id,UserPrincipalName,AccountEnabled,LicenseAssignmentStates |
Where-Object {
    $_.AccountEnabled -eq $false -and
    $_.UserPrincipalName -like "*$DomainFilter"
}

foreach ($User in $Users) {

    # Detect Teams Phone presence (direct OR inherited)
    $PhoneAssignments = $User.LicenseAssignmentStates |
        Where-Object { $_.SkuId -eq $TeamsPhoneSkuId }

    if (-not $PhoneAssignments) { continue }

    Write-Host "`n==========================================" -ForegroundColor Cyan
    Write-Host "Disabled user with Teams Phone detected:" -ForegroundColor Yellow
    Write-Host "User: $($User.UserPrincipalName)"
    Write-Host ""

    # --- GROUP-BASED assignments ---
    $GroupIds = $PhoneAssignments |
        Where-Object { $_.AssignedByGroup } |
        Select-Object -ExpandProperty AssignedByGroup -Unique

    if ($GroupIds) {
        Write-Host "Inherited via licensing group(s):"
        foreach ($Gid in $GroupIds) {
            $G = Get-MgGroup -GroupId $Gid
            Write-Host " - $($G.DisplayName) ($Gid)"
        }
    }

    # --- DIRECT assignments ---
    $Direct = $PhoneAssignments |
        Where-Object { -not $_.AssignedByGroup }

    if ($Direct) {
        Write-Host "Direct Teams Phone assignment detected" -ForegroundColor Yellow
    }

    Write-Host ""
    $Confirm = Read-Host "Type YES to remove Teams Phone access for this user"

    if ($Confirm -ne "YES") {
        Write-Host "Skipped — no changes made"
        continue
    }

    # Remove from groups
    foreach ($Gid in $GroupIds) {
        Remove-MgGroupMemberByRef `
            -GroupId $Gid `
            -DirectoryObjectId $User.Id
        Write-Host "✅ Removed from group $Gid" -ForegroundColor Green
    }

    # Remove direct license if present
    if ($Direct) {
        Set-MgUserLicense `
            -UserId $User.Id `
            -RemoveLicenses @($TeamsPhoneSkuId) `
            -AddLicenses @()
        Write-Host "✅ Direct Teams Phone license removed" -ForegroundColor Green
    }
}

Disconnect-MgGraph

Usage Example: To remove stuck licenses from a user, run the script with the parameter:

.\Remove-License.ps1 -UserPrincipalName "user@domain.com"

🎓 Ready to go deeper?

Practice real MD-102 exam questions, get AI feedback on your weak areas, and fast-track your Intune certification.

Start Free Practice → Book a Session
Souhaiel Morhag
Souhaiel Morhag
Microsoft Endpoint & Modern Workplace Engineer

Souhaiel Morhag is a Microsoft Intune and endpoint management specialist with hands-on experience deploying and securing enterprise environments across Microsoft 365. He founded MSEndpoint.com to share practical, real-world guides for IT admins navigating Microsoft technologies — and built the MSEndpoint Academy at app.msendpoint.com/academy, a dedicated learning platform for professionals preparing for the MD-102 (Microsoft 365 Endpoint Administrator) certification. Through in-depth articles and AI-powered practice exams, Souhaiel helps IT teams move faster and certify with confidence.

Related Articles

Popular on MSEndpoint