Private/Invoke-SPCSafeRecycleBinPurgeInternal.ps1
|
function Invoke-SPCSafeRecycleBinPurgeInternal { <# .SYNOPSIS Purges items in the 1st and 2nd stage recycle bins in batches with audit logging. .DESCRIPTION Processes items in chunks of 100 to prevent memory spikes and maintains detailed audit trails. .OUTPUTS [PSCustomObject] with DeletedCount, StorageFreedMB, ErrorCount #> [CmdletBinding()] param( [Parameter(Mandatory)] [string]$SiteUrl, [Parameter(Mandatory)] [int]$OlderThanDays, [Parameter(Mandatory)] [string]$Stage, [Parameter(Mandatory)] [bool]$DryRun, [Parameter(Mandatory)] [string]$AuditLogPath ) begin { $siteConn = Connect-SPCSiteInternal -SiteUrl $SiteUrl -Context $script:SPCContext $cutoffDate = (Get-Date).ToUniversalTime().AddDays(-$OlderThanDays) $deletedCount = 0 $freedBytes = [int64]0 $errorCount = 0 $operatorUPN = if ($script:SPCContext -and -not [string]::IsNullOrWhiteSpace($script:SPCContext.OperatorUPN)) { $script:SPCContext.OperatorUPN } elseif ($script:SPCContext -and $script:SPCContext.TenantName) { "admin@$($script:SPCContext.TenantName).onmicrosoft.com" } else { 'System' } } process { $itemsQuery = Get-PnPRecycleBinItem -Connection $siteConn -RowLimit 5000 $filteredItems = @() if ($null -ne $itemsQuery) { $filteredItems = @($itemsQuery | Where-Object { $itemDeletedDate = $_.DeletedDate.ToUniversalTime() $isOlder = $itemDeletedDate -le $cutoffDate $stageMatch = switch ($Stage) { '1stStage' { $_.ItemState -eq 'FirstStageRecycleBin' } '2ndStage' { $_.ItemState -eq 'SecondStageRecycleBin' } 'Both' { $true } } $isOlder -and $stageMatch }) } $batches = [System.Collections.Generic.List[object[]]]::new() $batchSize = 100 for ($i = 0; $i -lt $filteredItems.Count; $i += $batchSize) { $count = [Math]::Min($batchSize, ($filteredItems.Count - $i)) $batches.Add($filteredItems[$i..($i + $count - 1)]) } foreach ($batch in $batches) { foreach ($item in $batch) { $itemSize = [int64]$item.Size $itemIdStr = if ($null -ne $item.Id) { $item.Id.ToString() } else { '' } $itemTitle = if ($null -ne $item.Title) { $item.Title } else { '' } $dirName = if ($null -ne $item.DirName) { $item.DirName } else { '' } $deletedBy = if ($null -ne $item.DeletedByName) { $item.DeletedByName } else { 'Unknown' } $itemDate = if ($null -ne $item.DeletedDate) { $item.DeletedDate } else { (Get-Date) } if ($DryRun) { Write-SPCAuditLogInternal ` -LogPath $AuditLogPath ` -SiteUrl $SiteUrl ` -TargetType 'RecycleBinItem' ` -ItemId $itemIdStr ` -ItemTitle $itemTitle ` -FileRelativeUrl $dirName ` -SizeBytes $itemSize ` -DeletedDate $itemDate ` -DeletedByUPN $deletedBy ` -OperatorUPN $operatorUPN ` -ExecutionStatus 'SIMULATED' ` -ErrorMessage '' $freedBytes += $itemSize $deletedCount++ } else { try { Clear-PnPRecycleBinItem -Identity $item.Id -Force -Connection $siteConn -ErrorAction Stop Write-SPCAuditLogInternal ` -LogPath $AuditLogPath ` -SiteUrl $SiteUrl ` -TargetType 'RecycleBinItem' ` -ItemId $itemIdStr ` -ItemTitle $itemTitle ` -FileRelativeUrl $dirName ` -SizeBytes $itemSize ` -DeletedDate $itemDate ` -DeletedByUPN $deletedBy ` -OperatorUPN $operatorUPN ` -ExecutionStatus 'SUCCESS' ` -ErrorMessage '' $freedBytes += $itemSize $deletedCount++ } catch { $errorCount++ Write-SPCAuditLogInternal ` -LogPath $AuditLogPath ` -SiteUrl $SiteUrl ` -TargetType 'RecycleBinItem' ` -ItemId $itemIdStr ` -ItemTitle $itemTitle ` -FileRelativeUrl $dirName ` -SizeBytes $itemSize ` -DeletedDate $itemDate ` -DeletedByUPN $deletedBy ` -OperatorUPN $operatorUPN ` -ExecutionStatus 'FAILED' ` -ErrorMessage $_.Exception.Message } } } } } end { return [PSCustomObject]@{ DeletedCount = $deletedCount StorageFreedMB = [Math]::Round(($freedBytes / 1MB), 2) ErrorCount = $errorCount } } } |