Understanding the MSAL Token Broker Conflict in PowerShell

When automating tenant administration tasks such as user offboarding, admins frequently need to interact with both the Microsoft Graph PowerShell SDK (Microsoft.Graph) and the Exchange Online Management module (ExchangeOnlineManagement). However, running both in the same PowerShell session often results in a frustrating MSAL token acquisition error:

Error Acquiring Token:
System.NullReferenceException: Object reference not set to an instance of an object.
   at Microsoft.Identity.Client.Platforms.Features.RuntimeBroker.RuntimeBroker..ctor(...)

This error happens because both modules rely on the Microsoft Authentication Library (MSAL) and Windows Web Account Manager (WAM) for interactive authentication. When you load both modules or attempt to disconnect and reconnect interactive sessions within a single PowerShell process, the underlying MSAL assembly versions collide or corrupt the active broker UI handle.

Why Disconnecting and Reconnecting Breaks Your Session

In the script, calling Disconnect-MgGraph followed by Connect-ExchangeOnline and then Connect-MgGraph again causes MSAL to lose track of the interactive broker instance. Disconnecting from Graph does not unload the loaded MSAL binaries from the active PowerShell memory, leading to null reference exceptions when Exchange attempts to invoke its own interactive broker.

How to Solve the Issue

Here are the three best ways to resolve this issue and seamlessly run both Graph and Exchange Online cmdlets in one PowerShell session.

Solution 1: Disable WAM Login for Microsoft Graph

By default, modern versions of the Graph SDK use Web Account Manager (WAM) on Windows for single sign-on (SSO). Disabling WAM forces Graph to use a standard web browser login, preventing conflict with Exchange Online's authentication provider.

Add Set-MgGraphOption -DisableLoginByWAM $true before connecting to Microsoft Graph:

# Disable WAM integration before connecting
Set-MgGraphOption -DisableLoginByWAM $true

# Connect to both services at the beginning of the script
Connect-MgGraph -Scopes "User.ReadWrite.All", "Directory.ReadWrite.All"
Connect-ExchangeOnline

Solution 2: Connect to Both Services at the Start (Do Not Disconnect)

Rather than connecting and disconnecting repeatedly during script execution, authenticate to both services once at the top of your script. Both active sessions will happily coexist in memory as long as you do not tear down the connection state mid-script.

# Connect once at the start
Set-MgGraphOption -DisableLoginByWAM $true
Connect-MgGraph -Scopes "User.ReadWrite.All", "Directory.ReadWrite.All"
Connect-ExchangeOnline

# Perform Graph actions
# Perform Exchange actions
# Perform final Graph actions

# Disconnect everything at the end
Disconnect-ExchangeOnline -Confirm:$false
Disconnect-MgGraph

Solution 3: Use Non-Interactive App Authentication (Best Practice)

For administrative automation scripts like offboarding, interactive browser logins are prone to manual errors and session timeouts. Using App Registration with certificate-based authentication bypasses interactive brokers completely, eliminating WAM issues altogether.

Connect-MgGraph -ClientID "<App-ID>" -TenantId "<Tenant-ID>" -CertificateThumbprint "<Thumbprint>"
Connect-ExchangeOnline -AppID "<App-ID>" -Organization "yourdomain.onmicrosoft.com" -CertificateThumbprint "<Thumbprint>"

Refactored Offboarding Script

Here is an optimized version of the offboarding workflow incorporating these fixes along with the correct Microsoft Graph SDK v2 syntax for license removal:

# Disable WAM to prevent MSAL authentication broker errors
Set-MgGraphOption -DisableLoginByWAM $true

# 1. Connect to both endpoints up front
Write-Host "Connecting to Microsoft Graph and Exchange Online..." -ForegroundColor Cyan
Connect-MgGraph -Scopes "User.ReadWrite.All", "Directory.ReadWrite.All"
Connect-ExchangeOnline

# Get user input
$User = Read-Host "Enter the user principal name (UPN) to deactivate"

# 2. Disable Account in Graph
Write-Host "Disabling account $User..." -ForegroundColor Yellow
Update-MgUser -UserId $User -AccountEnabled:$false

# 3. Exchange Online Actions (Shared Mailbox, GAL Hide, Auto-Reply)
Write-Host "Converting mailbox to Shared and hiding from GAL..." -ForegroundColor Yellow
Set-Mailbox -Identity $User -Type Shared -HiddenFromAddressListsEnabled $true
Set-MailboxAutoReplyConfiguration -Identity $User -AutoReplyState Enabled `
    -InternalMessage "Thank you for your message. This account is no longer active." `
    -ExternalMessage "Thank you for your message. This account is no longer active."

# 4. Remove Licenses via Microsoft Graph
Write-Host "Removing licenses from $User..." -ForegroundColor Yellow
$UserLicenses = Get-MgUserLicenseDetail -UserId $User
if ($UserLicenses) {
    $LicensesToRemove = $UserLicenses.SkuId
    Set-MgUserLicense -UserId $User -RemoveLicenses $LicensesToRemove -AddLicenses @()
    Write-Host "Licenses successfully removed." -ForegroundColor Green
} else {
    Write-Host "No licenses found on account." -ForegroundColor Gray
}

# Clean up sessions
Disconnect-ExchangeOnline -Confirm:$false
Disconnect-MgGraph
Write-Host "Offboarding completed successfully!" -ForegroundColor Green

Summary

When combining Microsoft Graph and Exchange Online in PowerShell, remember to avoid toggling connections mid-script and set Set-MgGraphOption -DisableLoginByWAM $true to prevent MSAL broker collisions. For production environments, transitioning to certificate-based application authentication will provide the most reliable automated execution.