Integrations

OneDrive Cleanup

Safely Deleting OneDrive Libraries

2025-09-11

Deleting user data can be a daunting task. When it's hosted in the cloud with sharing settings intact, this can be even more difficult. But sooner or later, admins are forced by cost or capacity to pull the trigger on deletion.

When I first approached this issue in SharePoint and OneDrive, I went down the path of crawling every site for shared content to try to identify items that may still be in use. I'll save that for another post, but it's probably overkill in most cases. Fortunately, SharePoint has a built-in mechanism to help simplify the cleanup process.

SharePoint LockState

Each SharePoint site has a LockState attribute that dictates how the site can be accessed.

  • Unlock: This is the default state for all active sites. Content can be read and written by all users with access to content.
  • ReadOnly: This restricts all modifications to the site content.
  • NoAccess: This blocks access and returns a 403 error by default.

The attribute can be set with the cmdlet below:

Set-SPOSite -Identity "<SiteURL>" -LockState "<state>"

There is an optional -NoAccessRedirectUrl parameter that can route the user to a custom response. Since this is a per-site setting, this could be routed to a site-specific URL that notifies IT what site collection needs to be released.

LockState Workflow

Leveraging this setting allows stale sites to be gracefully retired, first by setting them to ReadOnly for a period of time to allow content to be copied elsewhere, and then by setting them to NoAccess to block all access before finally purging the content through either account deletion or the Remove-SPOSite cmdlet.

We needed to keep accounts active for other services while purging OneDrive libraries, so we used the Remove-SPOSite method. Note that if your sites are set to NoAccess, they will need to be unlocked prior to deletion. The script below takes a .csv file with a url column header, confirms the listed sites are locked, unlocks them, and finally purges them. Note that the sites will still be recoverable for 93 days according to the default site retention policy, so it may take a few months to reclaim your storage.

$tenantUrl = "https://<tenant>.sharepoint.com"
$siteCsv = "C:\Path\To\sites.csv"

Connect-SPOService $tenantUrl

$csv = Import-Csv $siteCsv

foreach($site in $csv){
    Write-Host "Processing site: $($site.url)"
    try{
        $sposite = Get-SPOSite $site.url -ErrorAction Stop
    }
    catch{
        Write-Host "Error fetching site. Exception: $($_.Exception.Message)"
    }
    if($sposite){
        Write-Host "Site lockstate: $($sposite.lockstate)"
        if($sposite.lockstate -eq "NoAccess"){
            Write-Host "Unlocking site for removal: $($site.url)"
            $unlocked = $false
            try{
                Set-SPOSite $site.url -LockState "Unlock" -ErrorAction Stop
                Write-Host "Unlocked site: $($site.url)"
                $unlocked = $true
            }
            catch{
                Write-Host "Failed to unlock site: $($site.url). Exception: $($_.exception.message)"
            }

            if($unlocked){
                try{
                    Remove-SPOSite $site.url -NoWait -Confirm:$false -ErrorAction stop
                    Write-Host "Removed site $($site.url)"
                }
                catch{
                    Write-Host "Failed to remove site: $($site.url). Exception: $($_.exception.message)"
                }
            }
        }
        else{
            Write-Host "Site is not locked. Continuing to the next record without removal"
        }
    }
    Write-Host "Done processing site: $($site.url)"
 }

If you happen to need to recover a deleted site, refer to the documentation for the Get-SPODeletedSite and Restore-SPODeletedSite cmdlets.