SharePoint (2003 thru Online): 2026

Monday, February 23, 2026

M365 Tenant - SharePoint Online Storage Cleanup

Items in the SharePoint Online Recycle Bin, including both the first and second stages, contribute to the site's storage quota for 93 days. Storage space is only released once items are permanently deleted. 

If there is a retention policy, deleted items may be moved to the Preservation Hold Library, which also consumes storage. 

To free up space right away, delete items from the second-stage Recycle Bin, and then clean up the Preservation Hold Library.

The PowerShell script below is the most effective way to carry out these tasks across all site collections in the tenant.

#==================================================================================================
# Retention Exception Update + PHL Cleanup + Recycle Bin Cleanup (PnP)
#==================================================================================================

#------------------------------ CONFIG -------------------------------------
$PolicyName               = "Document Retention Policy"   # Purview retention policy name
$ExceptionsCsvPath        = "E:\AReports\RP_Sites_DEL_2026_3.csv"
$SitesCsvPath             = "E:\AReports\Sites_DEL_2026_3.csv"

$IPPSSessionUPN           = "spadmin@spadmins.onmicrosoft.com"
$PnPClientId              = "12a34567-f123-4567-890e-1cch23456789"

# Batch size for policy exception updates (helps manage large lists)
$PolicyBatchSize          = 50

# PHL paging size
$PHLPageSize              = 2000

# Recycle bin cleanup controls
$EnableRecycleBinCleanup  = $true
$RecycleBinRowLimit       = 0        # 0 = no limit; otherwise e.g. 10000 (PnP supports -RowLimit).

#------------------------------ SECTION 1: RETENTION POLICY EXCEPTIONS ---------------------------
Write-Host "=== Updating Retention Policy Exceptions ===" -ForegroundColor Cyan

# Load, normalize, and de-dupe site URLs from CSV
[array]$excludeSites = Import-Csv -Path $ExceptionsCsvPath |
    Select-Object -ExpandProperty URL |
    ForEach-Object { $_.Trim().TrimEnd("/") } |
    Where-Object { $_ } |
    Sort-Object -Unique

Write-Host "Loaded $($excludeSites.Count) exception site URLs from $ExceptionsCsvPath"

# Connect to Purview / Security & Compliance PowerShell
Connect-IPPSSession -UserPrincipalName $IPPSSessionUPN

# IMPORTANT:
# - Microsoft notes Set-RetentionCompliancePolicy triggers a full orgsync and recommends waiting for distribution between updates.
# - Static scoping limit: 100 SharePoint sites per retention policy when specifying sites.
# This script batches updates to reduce risk; adjust batching per your change control.

for ($i = 0; $i -lt $excludeSites.Count; $i += $PolicyBatchSize) {
    $end = [Math]::Min($i + $PolicyBatchSize - 1, $excludeSites.Count - 1)
    $batch = $excludeSites[$i..$end]

    try {
        Set-RetentionCompliancePolicy -Identity $PolicyName -AddSharePointLocationException $batch
        Write-Host "Added exception batch: $($i+1) - $($end+1)" -ForegroundColor Green
    }
    catch {
        Write-Host "FAILED adding exception batch: $($i+1) - $($end+1). Error: $($_.Exception.Message)" -ForegroundColor Red
    }
}

#------------------------------ SECTION 2: PHL CLEANUP + RECYCLE BIN CLEANUP ---------------------
Write-Host "`n=== PHL Cleanup + Recycle Bin Cleanup ===" -ForegroundColor Cyan

$sites = Import-Csv -Path $SitesCsvPath
Write-Host "Loaded $($sites.Count) sites from $SitesCsvPath"

# Check if Clear-PnPRecycleBinItem exists in the current PnP.PowerShell install
# PnP docs indicate this cmdlet is available in the Nightly release.
$hasClearRecycleCmd = $null -ne (Get-Command Clear-PnPRecycleBinItem -ErrorAction SilentlyContinue)

foreach ($site in $sites) {
    $siteUrl = $site.URL.Trim().TrimEnd("/")
    if (-not $siteUrl) { continue }

    Write-Host "'nProcessing site: $siteUrl" -ForegroundColor Yellow

    # Connect to the site
    try {
        Connect-PnPOnline -Url $siteUrl -Interactive -ClientId $PnPClientId -ErrorAction Stop
    }
    catch {
        Write-Host "Failed to connect to $siteUrl : $($_.Exception.Message)" -ForegroundColor Red
        continue
    }

    # Check if Preservation Hold Library exists
    $phl = Get-PnPList -Identity "Preservation Hold Library" -ErrorAction SilentlyContinue
    if (-not $phl) {
        Write-Host "No Preservation Hold Library found at $siteUrl" -ForegroundColor DarkGray
    }
    else {
        Write-Host "Preservation Hold Library found at $siteUrl" -ForegroundColor Green

        # Retrieve items in pages idle large libraries (PnP supports -PageSize and -ScriptBlock pattern).
        $items = @()
        try {
            $items = Get-PnPListItem -List "Preservation Hold Library" -PageSize $PHLPageSize -ScriptBlock {
                param($pagedItems)
                $pagedItems.Context.ExecuteQuery()
            }
        }
        catch {
            Write-Host "Failed to list PHL items at $siteUrl : $($_.Exception.Message)" -ForegroundColor Red
            $items = @()
        }

        if ($items.Count -gt 0) {
            Write-Host "$($items.Count) items found in Preservation Hold Library" -ForegroundColor Cyan

            foreach ($item in $items) {
                try {
                    Remove-PnPListItem -List "Preservation Hold Library" -Identity $item.Id -Force -ErrorAction Stop
                }
                catch {
                    Write-Host "Failed to delete PHL item ID $($item.Id): $($_.Exception.Message)" -ForegroundColor DarkYellow
                }
            }
        }
        else {
            Write-Host "No items found in Preservation Hold Library" -ForegroundColor DarkGray
        }
    }

    # Clear Recycle Bins (1st + 2nd stage) to free storage immediately.
    # Your internal recycle bin guidance notes both stages exist; second stage typically needs site collection admin.
    if ($EnableRecycleBinCleanup) {
        if (-not $hasClearRecycleCmd) {
            Write-Host "Clear-PnPRecycleBinItem cmdlet not found. Per PnP docs, it may require PnP.PowerShell Nightly. Skipping recycle bin cleanup." -ForegroundColor DarkYellow
        }
        else {
            try {
                if ($RecycleBinRowLimit -gt 0) {
                    # -RowLimit supported by PnP.
                    Clear-PnPRecycleBinItem -All -Force -RowLimit $RecycleBinRowLimit
                }
                else {
                    # Clears all items; -Force suppresses confirmation.
                    Clear-PnPRecycleBinItem -All -Force
                }
                Write-Host "Recycle bins cleared for: $siteUrl" -ForegroundColor Green
            }
            catch {
                Write-Host "Failed to clear recycle bins for $siteUrl : $($_.Exception.Message)" -ForegroundColor Red
            }
        }
    }
}

Write-Host "`n=== DONE ===" -ForegroundColor Cyan

'Filter by' option for the Name column in SPO Document Library.

When I received this requirement, I reviewed several blogs and Google AI instructions, but none addressed my goal. After further research, I was able to achieve it and would like to share an article that aims to enable the 'Filter by' option for the Name column.

When a document library is first created in Modern SharePoint Online, you'll see the Name, Modified, and Modified by columns, with All Documents set as the default view.

The 'Filter by' option is available for the Modified and Modified by columns, but not for the Name column.



Top right down to '+ Create or upload' you will see Options (click on it) > Edit View.

In Edit View, under columns, after scrolling down you will notice three types of Name columns as shown below.

1. In Display, Uncheck, Name (linked to document with edit menu).
2. In Display, Check, Name (for use in forms) replace the Position from left.
3. Click OK to save the modifications.


Once redirected to the view, you will notice Filter by option for the Name column.