Find and remove unnecessary licenses on shared mailboxes in Office 365 customer tenants

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 license, 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 licenses 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 licenses 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 license from a shared mailbox by mistake?

The script will also export a CSV with a summary of all licenses assigned to shared mailboxes, including a PowerShell cmdlet that you can use to reassign licenses. If you use this script and remove a license 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 licenses

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 licenses 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 licenses.

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 license 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")