#!/usr/bin/env pwsh # Add any email addresses that should be purged to the $Addresses array $Addresses = @() $MaxDate = "2023-06-30" # No modifications below this point. function Write-Log { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [string]$Message ) $timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss" Write-Output "${timestamp}: $Message" } # Build query for ComplianceSearch if (!$Addresses) { Write-Output "Search query is empty - make sure that Addresses array is not empty - exiting" exit 1 } $From = "from:" + ($Addresses -join " OR from:") $To = "to:" + ($Addresses -join " OR to:") $Participants = "$From OR $To" $Query = "($Participants) AND date<=$MaxDate AND (kind:email)" # Ask user for admin credentials $UserCredential = Get-Credential -Title "Office 365 Credentials" -Message "Enter your credentials for Office 365" # Load PowerShell session for Microsoft Exchange Import-Module ExchangeOnlineManagement Connect-IPPSSession -Credential $UserCredential Connect-ExchangeOnline -Credential $UserCredential # Define folders that should be ignored by ComplianceSearch $folders = "/Recoverable Items", "/Deletions", "/Purges", "/Versions" Write-Output "Finding folders to exclude, this may take a while..." $mailboxes = Get-Mailbox $folderQueries = @() foreach ($mailbox in $mailboxes) { $emailAddress = $mailbox.PrimarySmtpAddress $folderStatistics = Get-EXOMailboxFolderStatistics $emailAddress foreach ($folderStatistic in $folderStatistics) { $folderId = $folderStatistic.FolderId; $folderPath = $folderStatistic.FolderPath; if ($folders -contains $folderPath) { # Based on / shamelessly stolen from: # https://practical365.com/targeted-collection-content-search/ $encoding = [System.Text.Encoding]::GetEncoding("us-ascii") $nibbler = $encoding.GetBytes("0123456789ABCDEF"); $folderIdBytes = [Convert]::FromBase64String($folderId); $indexIdBytes = New-Object byte[] 48; $indexIdIdx = 0; $folderIdBytes | Select-Object -skip 23 -First 24 | % { $indexIdBytes[$indexIdIdx++] = $nibbler[$_ -shr 4]; $indexIdBytes[$indexIdIdx++] = $nibbler[$_ -band 0xF] } $folderQuery = "folderid:$($encoding.GetString($indexIdBytes))"; $folderStat = New-Object PSObject Add-Member -InputObject $folderStat -MemberType NoteProperty -Name Mailbox -Value $emailAddress Add-Member -InputObject $folderStat -MemberType NoteProperty -Name FolderPath -Value $folderPath Add-Member -InputObject $folderStat -MemberType NoteProperty -Name FolderQuery -Value $folderQuery $folderQueries += $folderStat } } } $Excluded = "" foreach ($folder in $folderQueries) { $q = $folder.folderQuery $Excluded += " NOT $q" } $Query += $Excluded $Looper = $true while ($Looper) { try { Write-Output "Creating new search" New-ComplianceSearch -Name "WAT Remover" -ContentMatchQuery $Query -ExchangeLocation all -AllowNotFoundExchangeLocationsEnabled $true -Force | Out-Null Start-ComplianceSearch -Identity "WAT Remover" | Out-Null Write-Output "Waiting for search to complete" while (!(Get-ComplianceSearch -Identity "WAT Remover" | Format-List -Property Status | Out-String | Select-String -Pattern "Completed")) { Start-Sleep -s 1 } $Results = (Get-ComplianceSearch "WAT Remover").Items if ($Results) { Write-Output "$Results results found, deleting" for ($i = 0; $i -lt 10; $i++) { Write-Output "Starting deletion run $i" New-ComplianceSearchAction -SearchName "WAT Remover" -Purge -PurgeType HardDelete -Force -Confirm:$false | Out-Null while (!(Get-ComplianceSearchAction -Identity "WAT Remover_Purge" | Out-String | Select-String -Pattern "Completed")) { Start-Sleep -s 1 } Write-Output "Results deleted" Remove-ComplianceSearchAction -Confirm:$false -Identity "WAT Remover_Purge" } } else { Write-Output "No results found, exiting after this run" $Looper = $false } } finally { Write-Output "Cleaning up" Remove-ComplianceSearchAction -Confirm:$false -Identity "WAT Remover_Purge" Remove-ComplianceSearch -Identity "WAT Remover" -Confirm:$false } } Write-Output "Done - exiting"