
It’s common for MSPs to convert Office 365 users to Shared Mailboxes when offboarding them. It’s essentially a free mailbox that you can redirect, attach to someone else, or convert to a regular mailbox if the user returns.
Unfortunately, if you’re manually offboarding users and you forget to remove the user’s Office 365 licence, you’ll continue to be billed for it. One way to prevent this is to use an offboarding script (I’ll upload a version of ours in the next week or so). However it’s also a good idea to do an audit of your customers’ users and their licences every now and then, just in case some have fallen through the cracks.
The script below will check all of your customers’ Office 365 tenants for licensed shared mailboxes.
If some are found, it’ll export a csv of them. If you’re happy to remove the licences from all of them at once, you can do so. Otherwise you can be asked to decide for each one.
Want to run this on a single Office 365 tenant?
This script is for Microsoft Partners. If you’d like to run this on a single Office 365 tenant, see here for a separate version of it.
Removed a licence from a shared mailbox by mistake?
The script will also export a CSV with a summary of all licences assigned to shared mailboxes, including a PowerShell cmdlet that you can use to reassign licences. If you use this script and remove a licence in error, just copy and paste the relevant cmdlet into a PowerShell session that’s connected to Office 365 as a delegated admin.
Some things to keep in mind
- This script does not support 2 factor authentication on the delegated admin account.
- This script is for Microsoft Partners managing multiple tenants via delegated administration. See here for a version of this script that works across your own Office 365 tenant.
Keep track of unused Office 365 licences
Once you’ve run this script, you may want to run this one as well. It’ll give you a report of all unused Office 365 licences in all customer tenants.
You can also follow our guide here to set up an Azure Function and Microsoft Flow to receive email alerts about unused licences.
How to run the script to find licensed shared mailboxes
- To run the script, copy and paste the code below into Visual Studio Code.
- Save it as a PowerShell (.ps1) file. Make sure you’ve installed the PowerShell extension in Visual Studio Code. You should be prompted for this when you save it if it’s not installed already.
- Press F5 to run it.
- Enter the credentials of an Office 365 admin with delegated access to customer tenants.
- Wait for it to complete. This one takes a while because it needs to connect to Exchange Online for each of your tenants and retrieve their mailbox details.

- When it completes, a couple of CSVs will be exported to C:\Temp. One will contain a list of all licensed shared mailboxes and their details. The other will contain a list of each licence attached to the shared mailbox, and a command that you can use to re-add it (see image above).

- You can choose to remove all shared mailboxes at once (not recommended until you’ve checked the exported CSV), or get prompted for each mailbox.


Complete script for reporting on licensed Shared Mailboxes in all Office 365 customer tenants
#Establish a PowerShell session with Office 365. You'll be prompted for your Delegated Admin credentials
$Cred = Get-Credential
Connect-MsolService -Credential $Cred
$customers = Get-MsolPartnerContract
Write-Host "Found $($customers.Count) customers for $((Get-MsolCompanyInformation).displayname)." -ForegroundColor DarkGreen
$CSVpath = "C:\Temp\LicensedSharedMailboxes.csv"
$LicenseReportpath = "C:\Temp\SharedMailboxLicenseReport.csv"
$licensedSharedMailboxes = @()
foreach ($customer in $customers) {
$licensedUsers = Get-MsolUser -TenantId $customer.TenantId | Where-Object {$_.islicensed}
$ScriptBlock = {Get-Mailbox -ResultSize Unlimited}
$InitialDomain = Get-MsolDomain -TenantId $customer.TenantId | Where-Object {$_.IsInitial -eq $true}
Write-Host "Checking Shared Mailboxes for $($Customer.Name)" -ForegroundColor Green
$DelegatedOrgURL = "https://outlook.office365.com/powershell-liveid?DelegatedOrg=" + $InitialDomain.Name
$sharedMailboxes = Invoke-Command -ConnectionUri $DelegatedOrgURL -Credential $Cred -Authentication Basic -ConfigurationName Microsoft.Exchange -AllowRedirection -ScriptBlock $ScriptBlock -HideComputerName -ErrorAction SilentlyContinue
$sharedMailboxes = $sharedMailboxes | Where-Object {$_.RecipientTypeDetails -contains "SharedMailbox"}
foreach ($mailbox in $sharedMailboxes) {
$licensedSharedMailboxProperties = $null
if ($licensedUsers.ObjectId -contains $mailbox.ExternalDirectoryObjectID) {
Write-Host "$($mailbox.displayname) is a licensed shared mailbox" -ForegroundColor Yellow
$licenses = ($licensedUsers | Where-Object {$_.objectid -contains $mailbox.ExternalDirectoryObjectId}).Licenses
$licenseArray = $licenses | foreach-Object {$_.AccountSkuId}
$licenseString = $licenseArray -join ","
Write-Host "$($mailbox.displayname) has $licenseString" -ForegroundColor Blue
$licensedSharedMailboxProperties = @{
CustomerName = $customer.Name
DisplayName = $mailbox.DisplayName
EmailAddress = $mailbox.PrimarySmtpAddress
Licenses = $licenseString
TenantId = $customer.TenantId
UserPrincipalName = ($licensedusers | Where-Object {$_.objectid -contains $mailbox.ExternalDirectoryObjectID}).UserPrincipalName
}
$forcsv = New-Object psobject -Property $licensedSharedMailboxProperties
$licensedSharedMailboxes += $forcsv
$forcsv | Select-Object CustomerName, DisplayName, EmailAddress, Licenses | Export-CSV -Path $CSVpath -Append -NoTypeInformation
# Create a CSV with a license report and PowerShell Cmdlets that you can use to quickly reassign licenses if you've removed them in error.
foreach ($license in $licenses) {
$licenseProperties = @{
CustomerName = $customer.Name
DisplayName = $licensedSharedMailboxProperties.DisplayName
EmailAddress = $licensedSharedMailboxProperties.UserPrincipalName
License = $license.AccountSkuId
TenantId = $customer.TenantId
ReAddlicense = "Set-MsolUserLicense -UserPrincipalName $($licensedSharedMailboxProperties.UserPrincipalName) -TenantId $($customer.TenantId) -AddLicenses $($license.AccountSkuId)"
}
$forcsv = New-Object psobject -Property $licenseProperties
$forcsv | Export-CSV -Path $LicenseReportpath -Append -NoTypeInformation
}
}
else {
Write-Host "$($mailbox.DisplayName) is unlicensed"
}
}
}
# Provide an option to remove the licenses from the shared mailboxes
Write-Host "`nFound $($licensedSharedMailboxes.Count) licensed shared mailboxes in your customers' tenants. A list has been exported to $csvpath
A license report has been exported to $licenseReportPath, just in case you need to restore these licenses to the shared mailboxes later." -ForegroundColor Yellow
Write-Host "r: Press 'r' to remove all licenses from all shared mailboxes."
Write-Host "a: Press 'a' to be asked for each mailbox."
Write-Host "l: Press 'q' to quit and leave all licensed."
do {
$input = Read-Host "Please make a selection"
switch ($input) {
"r" {
Clear-Host
Write-Host "Removing licenses from the following sharedmailboxes: `n$($licensedSharedMailboxes.userprincipalname -join ", ")"
foreach ($mailbox in $licensedSharedMailboxes) {
$currentLicenses = $null
$licenses = $mailbox.Licenses -split ","
foreach ($license in $licenses) {
Write-Host "Removing $license" -ForegroundColor Yellow
Set-MsolUserLicense -UserPrincipalName $($mailbox.UserPrincipalName) -TenantId $($mailbox.TenantId) -removelicenses $License
}
$currentLicenses = (Get-MsolUser -UserPrincipalName $($mailbox.UserPrincipalName) -TenantId $($mailbox.TenantId)).Licenses
if (!$currentLicenses) {
Write-Host "License successfully removed from $($Mailbox.displayname) ($($mailbox.UserPrincipalName)): $($mailbox.Licenses)" -ForegroundColor Green
}
else {
Write-Host "License was not successfully removed from $($Mailbox.displayname) ($($mailbox.UserPrincipalName)), please remove licenses via the Office 365 Portal: $($mailbox.Licenses)" -ForegroundColor Red
}
}
Read-Host "Enter q to quit."
$input = "q"
return
} "a" {
Clear-Host
foreach ($mailbox in $licensedSharedMailboxes) {
$mailboxChoice = $null
do {
$mailboxChoice = Read-Host "Would you like to remove $($mailbox.licenses) from $($Mailbox.displayname) ($($mailbox.UserPrincipalName))? [y,n]"
switch ($mailboxChoice) {
"y" {
$currentLicenses = $null
$licenses = $mailbox.Licenses -split ","
foreach ($license in $licenses) {
Write-Host "Removing $license" -ForegroundColor Yellow
Set-MsolUserLicense -UserPrincipalName $($mailbox.UserPrincipalName) -TenantId $($mailbox.TenantId) -removelicenses $License
}
$currentLicenses = (Get-MsolUser -UserPrincipalName $($mailbox.UserPrincipalName) -TenantId $($mailbox.TenantId)).Licenses
if (!$currentLicenses) {
Write-Host "License successfully removed from $($Mailbox.displayname) ($($mailbox.UserPrincipalName)): $($mailbox.Licenses)" -ForegroundColor Green
}
else {
Write-Host "License was not successfully removed from $($Mailbox.displayname) ($($mailbox.UserPrincipalName)), please remove licenses via the Office 365 Portal: $($mailbox.Licenses)" -ForegroundColor Red
}
}
"n" {
Write-Host "Leaving $($mailbox.licenses) on $($mailbox.EmailAddress)"
}
} Pause
}until($mailboxchoice -eq "y" -or $mailboxChoice -eq "n")
}
return
} "q" {
return
}
}
pause
}
until ($input -eq "q")



