Skip to main content

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 -WhatIf before 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

  1. Download the PowerShell script.

  2. Open Windows PowerShell 5.1 and review the script before running it.

  3. Start with -WhatIf:

    .\remove-onedrive-from-shared-mailboxes.ps1 -WhatIf
  4. Check that each displayed shared mailbox belongs to the displayed OneDrive URL.

  5. Run without -WhatIf to move individually approved sites to Deleted Sites.

  6. Use -Purge only when permanent removal is approved. Add -SkipConfirmation only 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.

Permanent Removal

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

remove-onedrive-from-shared-mailboxes.ps1
#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