Public/Remediate/Clear-SPCRecycleBin.ps1
|
function Clear-SPCRecycleBin { <# .SYNOPSIS Safely clears 1st and 2nd stage recycle bins with audit logging and Microsoft Purview hold protection. .DESCRIPTION Clear-SPCRecycleBin purges deleted items older than OlderThanDays from the first-stage (End-User) and/or second-stage (Site Collection Administrator) recycle bins across one or more SharePoint Online site collections. Safety & Compliance Guarantees: - Purview Hold Immunity: Automatically checks for active Microsoft Purview Retention Policies, Retention Labels, or Litigation Holds using Test-SPCPurviewHoldInternal. Protected sites are skipped to prevent compliance violations. - Non-Destructive Simulation: Fully supports -DryRun and -WhatIf to preview items and storage that would be freed without deleting anything. - Immutable Audit Logging: Every purge action (simulated or real) is recorded in an append-only CSV audit file. - License Enforcement: Requires a Pro or Consultant license for live execution (simulations are unrestricted). .PARAMETER SiteUrl One or more SharePoint Online site collection URLs to process. Supports pipeline input by value and property name. .PARAMETER OlderThanDays The minimum age threshold (in days) since item deletion for items to be purged. Default is 30 days. Items deleted more recently than this value are preserved. Range: 0 to 180 days. .PARAMETER SecondStageOnly Purges items only from the second-stage (Site Collection Administrator) recycle bin. .PARAMETER FirstStageOnly Purges items only from the first-stage (End-User) recycle bin. .PARAMETER DryRun Executes a non-destructive simulation. No items are deleted from SharePoint, but potential storage savings and item counts are calculated and written to the audit log. .PARAMETER AuditLogPath Custom file path for the append-only CSV audit log. If omitted, an auto-named timestamped CSV file is generated in the current working directory. .PARAMETER Force Suppresses interactive confirmation prompts during execution. .INPUTS System.String[] Accepts site collection URLs from the pipeline. .OUTPUTS SPC.RecycleBinClearResult Returns custom objects containing site URL, stage processed, items deleted count, storage freed in MB, monthly cost savings in USD, simulation flag, audit log file path, execution timestamp, and status. .EXAMPLE Clear-SPCRecycleBin -SiteUrl 'https://contoso.sharepoint.com/sites/Marketing' -OlderThanDays 30 -DryRun Simulates purging items older than 30 days from both recycle bin stages on the Marketing site without making changes. .EXAMPLE Get-SPCStorageWaste -Top 5 | ForEach-Object { Clear-SPCRecycleBin -SiteUrl $_.SiteUrl -SecondStageOnly -OlderThanDays 14 -Force } Discovers top 5 sites with highest storage waste and permanently empties second-stage recycle bin items older than 14 days. .EXAMPLE Clear-SPCRecycleBin -SiteUrl 'https://contoso.sharepoint.com/sites/Legal' -OlderThanDays 60 -AuditLogPath 'C:\Audit\RecycleBin_Log.csv' Purges recycle bin items on the Legal site, automatically validating Purview hold immunity and logging results to a custom audit file. .NOTES Requires an active SPClean connection initialized via Connect-SPCTenant. Live execution requires a Pro or Consultant tier license. .LINK Get-SPCStorageWaste Optimize-SPCFileVersion Export-SPCStorageReport #> [CmdletBinding(DefaultParameterSetName = 'AllStages', SupportsShouldProcess = $true, ConfirmImpact = 'High')] [OutputType('SPC.RecycleBinClearResult')] param( [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)] [ValidateNotNullOrEmpty()] [string[]]$SiteUrl, [Parameter()] [ValidateRange(0, 180)] [int]$OlderThanDays = 30, [Parameter(ParameterSetName = 'SecondStage')] [switch]$SecondStageOnly, [Parameter(ParameterSetName = 'FirstStage')] [switch]$FirstStageOnly, [Parameter()] [switch]$DryRun, [Parameter()] [string]$AuditLogPath, [Parameter()] [switch]$Force ) begin { Test-SPCConnection $isSimulation = $DryRun.IsPresent -or $WhatIfPreference if (-not $isSimulation) { # Enforce Pro/Consultant License Gate Assert-SPCProLicense -Feature 'Clear-SPCRecycleBin' } if ([string]::IsNullOrWhiteSpace($AuditLogPath)) { $timestamp = (Get-Date).ToString("yyyyMMdd_HHmmss") $AuditLogPath = Join-Path (Get-Location) "SPClean_RecycleBin_Audit_$timestamp.csv" } } process { foreach ($url in $SiteUrl) { $stage = if ($SecondStageOnly) { '2ndStage' } elseif ($FirstStageOnly) { '1stStage' } else { 'Both' } # Check Purview Hold status $holdCheck = Test-SPCPurviewHoldInternal -SiteUrl $url if ($holdCheck.IsHoldActive) { Write-Warning "Clear-SPCRecycleBin: Site '$url' is protected by Purview Hold ($($holdCheck.HoldType)). Skipping purge." $opName = if ($script:SPCContext -and $script:SPCContext.TenantName) { $script:SPCContext.TenantName } else { 'System' } Write-SPCAuditLogInternal -LogPath $AuditLogPath -SiteUrl $url -TargetType 'RecycleBin' -ItemId 'N/A' -ItemTitle 'Site-Level Hold' -FileRelativeUrl 'N/A' -SizeBytes 0 -DeletedDate (Get-Date) -DeletedByUPN 'System' -OperatorUPN $opName -ExecutionStatus 'SKIPPED_COMPLIANCE_HOLD' -ErrorMessage "Site has active Purview Hold: $($holdCheck.HoldType)" continue } if (-not $Force -and -not $isSimulation) { if (-not $PSCmdlet.ShouldProcess("Site: $url [Stage: $stage, OlderThan: $OlderThanDays days]", "Clear Recycle Bin")) { continue } } $purgeResult = Invoke-SPCSafeRecycleBinPurgeInternal ` -SiteUrl $url ` -OlderThanDays $OlderThanDays ` -Stage $stage ` -DryRun $isSimulation ` -AuditLogPath $AuditLogPath $status = if ($isSimulation) { 'Simulated' } elseif ($purgeResult.ErrorCount -gt 0) { 'PartialSuccess' } else { 'Success' } $monthlySaved = [Math]::Round((($purgeResult.StorageFreedMB / 1024) * 0.20), 2) [PSCustomObject][ordered]@{ PSTypeName = 'SPC.RecycleBinClearResult' SiteUrl = $url StageProcessed = $stage ItemsDeletedCount = $purgeResult.DeletedCount StorageFreedMB = $purgeResult.StorageFreedMB MonthlyCostSavedUSD = $monthlySaved IsDryRun = $isSimulation AuditLogFilePath = $AuditLogPath ExecutedAt = (Get-Date).ToUniversalTime() Status = $status } } } } |