Remove OneDrive From Shared Mailboxes
Use this PowerShell script after an offboarding review has confirmed that OneDrive sites owned by shared mailboxes are no longer required. Start with -WhatIf, verify every mailbox-to-site match, and use -Purge only after the retention and recovery decision is final.
The script finds shared mailboxes in Exchange Online, matches their user principal names to OneDrive owners in SharePoint Online, and asks before moving each matching site to Deleted Sites.
Best For
- Tenant-wide checks for OneDrive sites still owned by shared mailboxes.
- Offboarding processes where mailbox, account, and OneDrive decisions are tracked separately.
- Administrators who want an interactive confirmation for every matched site.
- Controlled cleanup that must support
-WhatIfbefore any change is made.
Do Not Use It For
- Removing OneDrive sites before the data owner, retention owner, and IT have agreed what must happen to the content.
- Deleting the OneDrive of one known account when a direct, individually reviewed command is clearer.
- Automatically excluding sites from retention policies or eDiscovery holds.
- Unattended automation. The script uses interactive Exchange Online and SharePoint Online connections.
Requirements And Permissions
Run the script in Windows PowerShell 5.1 with these modules installed:
Install-Module -Name ExchangeOnlineManagement, Microsoft.Online.SharePoint.PowerShell -Scope CurrentUser
The signed-in account needs permission to read shared mailboxes in Exchange Online and administer the relevant OneDrive sites in SharePoint Online. Microsoft documents that Connect-SPOService requires a SharePoint Administrator or SharePoint Embedded Administrator role. Role-based access control determines which Exchange Online cmdlets and objects the account can use.
Run It Safely
-
Open Windows PowerShell 5.1 and review the script before running it.
-
Start with
-WhatIf:.\remove-onedrive-from-shared-mailboxes.ps1 -WhatIf -
Check that each displayed shared mailbox belongs to the displayed OneDrive URL.
-
Run without
-WhatIfto move individually approved sites to Deleted Sites. -
Use
-Purgeonly when permanent removal is approved. Add-SkipConfirmationonly in a separately controlled process where the reviewed input and evidence are retained.
Specify -SharePointAdminUrl https://contoso-admin.sharepoint.com when the script cannot derive the tenant admin URL reliably.
-Purge calls Remove-SPODeletedSite after a selected site reaches Deleted Sites. Microsoft describes that operation as permanent. A retention policy or eDiscovery hold can also block deletion; investigate that policy instead of weakening it simply to make the script succeed.
How The Matching Works
The script loads all personal sites, indexes them by the lowercase Owner value, and then requests all Exchange Online shared mailboxes. A site is selected only when its owner exactly matches the shared mailbox user principal name.
That exact match reduces guesswork but does not prove that the content may be deleted. Record the owner, purpose, retention decision, and outcome outside the script so the cleanup remains auditable.
Full Script
The code block and download use the same repository file. Updating the .ps1 file updates this displayed source on the next development refresh or site build.
#Requires -Version 5.1
#Requires -Modules ExchangeOnlineManagement
#Requires -Modules Microsoft.Online.SharePoint.PowerShell
<#
.SYNOPSIS
Removes OneDrive sites that belong to shared mailboxes.
.DESCRIPTION
Finds shared mailboxes in Exchange Online, looks up their associated OneDrive
sites, and removes the selected sites. Use -Purge to permanently remove sites
from Deleted Sites.
This script is provided as-is, without guarantees. Test it thoroughly in a
safe environment before using it in production.
Install the required modules once before first use:
Install-Module -Name ExchangeOnlineManagement, Microsoft.Online.SharePoint.PowerShell -Scope CurrentUser
.PARAMETER SharePointAdminUrl
Optional SharePoint Online admin center URL. When omitted, the script derives
the URL from the Exchange Online tenant.
.PARAMETER SkipConfirmation
Skips confirmations for all deletions. When combined with -Purge, it also skips
confirmations for permanent removal.
.PARAMETER Purge
After all selected OneDrive sites have been moved to Deleted Sites, permanently
removes them. Unless -SkipConfirmation is specified, the script asks for
confirmation for every site that is purged.
.NOTES
Author: Dwayne Selsig
Website: https://m365wizard.com
#>
[CmdletBinding(SupportsShouldProcess)]
param(
[string]$SharePointAdminUrl,
[switch]$SkipConfirmation,
[switch]$Purge
)
$ErrorActionPreference = "Stop"
function Confirm-Deletion {
[CmdletBinding(SupportsShouldProcess)]
param(
[Parameter(Mandatory)]
[System.Management.Automation.PSCmdlet]$Cmdlet,
[Parameter(Mandatory)]
[string]$Target,
[Parameter(Mandatory)]
[string]$Action,
[Parameter(Mandatory)]
[bool]$SkipConfirmation
)
if ($WhatIfPreference) {
return $Cmdlet.ShouldProcess($Target, $Action)
}
if (-not $SkipConfirmation) {
$question = "$Action`n$Target"
if (-not $Cmdlet.ShouldContinue($question, "Confirm deletion")) {
return $false
}
}
return $Cmdlet.ShouldProcess($Target, $Action)
}
Connect-ExchangeOnline -ShowBanner:$false
if ([string]::IsNullOrWhiteSpace($SharePointAdminUrl)) {
$initialDomain = Get-AcceptedDomain |
Where-Object {
$_.DomainName -like "*.onmicrosoft.com" -and
$_.DomainName -notlike "*.mail.onmicrosoft.com"
} |
Select-Object -First 1 -ExpandProperty DomainName
if ([string]::IsNullOrWhiteSpace($initialDomain)) {
throw "Could not determine the tenant's .onmicrosoft.com domain. Specify -SharePointAdminUrl explicitly."
}
$tenantName = $initialDomain -replace '\.onmicrosoft\.com$', ''
$SharePointAdminUrl = "https://$tenantName-admin.sharepoint.com"
}
Write-Verbose "Using SharePoint admin URL: $SharePointAdminUrl"
Connect-SPOService -Url $SharePointAdminUrl
Write-Progress `
-Activity "Loading OneDrive sites" `
-Status "Retrieving personal sites from SharePoint Online" `
-PercentComplete 0
[array]$personalSites = @(Get-SPOSite `
-Filter "Url -like '-my.sharepoint.com/personal/'" `
-IncludePersonalSite $true `
-Limit All)
$personalSitesByOwner = @{}
foreach ($personalSite in $personalSites) {
$owner = [string]$personalSite.Owner
if (-not [string]::IsNullOrWhiteSpace($owner)) {
$personalSitesByOwner[$owner.ToLowerInvariant()] = $personalSite
}
}
Write-Progress -Activity "Loading OneDrive sites" -Completed
[array]$sharedMailboxes = @(Get-EXOMailbox `
-RecipientTypeDetails SharedMailbox `
-ResultSize Unlimited `
-Properties UserPrincipalName, PrimarySmtpAddress)
$mailboxCount = $sharedMailboxes.Count
$mailboxIndex = 0
$removedSites = [System.Collections.Generic.List[object]]::new()
foreach ($mailbox in $sharedMailboxes) {
$mailboxIndex++
$percentComplete = [math]::Round(($mailboxIndex / $mailboxCount) * 100)
Write-Progress `
-Activity "Processing shared mailbox OneDrives" `
-Status "$mailboxIndex of ${mailboxCount}: $($mailbox.PrimarySmtpAddress)" `
-PercentComplete $percentComplete
$upn = [string]$mailbox.UserPrincipalName
if ([string]::IsNullOrWhiteSpace($upn)) {
continue
}
$site = $personalSitesByOwner[$upn.ToLowerInvariant()]
if (-not $site) {
Write-Host "No personal site found for $upn."
continue
}
$personalUrl = [string]$site.Url
Write-Host ""
Write-Host "Shared mailbox: $($mailbox.PrimarySmtpAddress)"
Write-Host "OneDrive URL: $personalUrl"
Write-Host "Storage used: $($site.StorageUsageCurrent) MB"
$removeApproved = Confirm-Deletion `
-Cmdlet $PSCmdlet `
-Target $personalUrl `
-Action "Remove OneDrive personal site" `
-SkipConfirmation ([bool]$SkipConfirmation)
if ($removeApproved) {
Remove-SPOSite `
-Identity $personalUrl `
-Confirm:$false
Write-Host "Moved to Deleted Sites."
$removedSites.Add([pscustomobject]@{
SharedMailbox = [string]$mailbox.PrimarySmtpAddress
OneDriveUrl = $personalUrl
})
}
elseif ($WhatIfPreference -and $Purge) {
$removedSites.Add([pscustomobject]@{
SharedMailbox = [string]$mailbox.PrimarySmtpAddress
OneDriveUrl = $personalUrl
})
}
}
Write-Progress -Activity "Processing shared mailbox OneDrives" -Completed
if ($removedSites.Count -gt 0) {
Write-Host ""
if ($WhatIfPreference) {
Write-Host "OneDrive sites that would be moved to Deleted Sites:"
}
else {
Write-Host "OneDrive sites moved to Deleted Sites:"
}
$removedSites | Format-Table SharedMailbox, OneDriveUrl -AutoSize | Out-Host
}
if ($Purge -and $removedSites.Count -gt 0) {
$removedSiteCount = $removedSites.Count
$removedSiteIndex = 0
foreach ($removedSite in $removedSites) {
$removedSiteIndex++
$percentComplete = [math]::Round(($removedSiteIndex / $removedSiteCount) * 100)
Write-Progress `
-Activity "Purging deleted OneDrive sites" `
-Status "$removedSiteIndex of ${removedSiteCount}: $($removedSite.SharedMailbox)" `
-PercentComplete $percentComplete
if ($WhatIfPreference) {
Confirm-Deletion `
-Cmdlet $PSCmdlet `
-Target $removedSite.OneDriveUrl `
-Action "Permanently remove OneDrive personal site" `
-SkipConfirmation ([bool]$SkipConfirmation) | Out-Null
continue
}
do {
Start-Sleep -Seconds 5
$deletedSite = Get-SPODeletedSite `
-Identity $removedSite.OneDriveUrl `
-ErrorAction SilentlyContinue
}
until ($deletedSite)
if (Confirm-Deletion `
-Cmdlet $PSCmdlet `
-Target $removedSite.OneDriveUrl `
-Action "Permanently remove OneDrive personal site" `
-SkipConfirmation ([bool]$SkipConfirmation)) {
Remove-SPODeletedSite `
-Identity $removedSite.OneDriveUrl `
-Confirm:$false
Write-Host "Permanently removed: $($removedSite.OneDriveUrl)"
}
}
Write-Progress -Activity "Purging deleted OneDrive sites" -Completed
}
Disconnect-ExchangeOnline -Confirm:$false
Disconnect-SPOService
Source And Documentation
- Download remove-onedrive-from-shared-mailboxes.ps1
- Connect to Exchange Online PowerShell
- Get-EXOMailbox
- Get started with the SharePoint Online Management Shell
- Remove-SPOSite
- Remove-SPODeletedSite
- Site deletion blocked by retention or eDiscovery