Current version
This commit is contained in:
commit
0a59e028c9
3 changed files with 183 additions and 0 deletions
19
LICENSE
Normal file
19
LICENSE
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
Copyright (c) 2020-2023 Kumi Mitterer <office365-mail-purger@kumi.email>
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
28
README.md
Normal file
28
README.md
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
# Office 365 email purger
|
||||||
|
|
||||||
|
This PowerShell script will use Compliance Search to purge all content older than a specified date involving one or more specified users from all mailboxes in an Office 365 tenant.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
1. Download the script and save it to your computer.
|
||||||
|
2. Edit the script and change the following lines to match your environment:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
$Addresses = @("user@example.com", "user2@example.com")
|
||||||
|
$MaxDate = "2016-01-01"
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Run the script. You will be prompted for your Office 365 credentials.
|
||||||
|
4. Get a cup of coffee, and then another. This script will take a while to run. As in, hours or days, depending on the size of your organization.
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
* This script will delete all content older than the specified date, including items in the user's mailbox that were sent to or received from other users in the organization.
|
||||||
|
* The larger your organization, the longer this script will take to run.
|
||||||
|
* This is quite a dangerous script. Use it at your own risk.
|
||||||
|
* This script may delete more or less than you expect.
|
||||||
|
* The script is provided as-is. I am not responsible for any damage caused by running it.
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
This script is licensed under the [MIT License](LICENSE).
|
136
purger.ps1
Normal file
136
purger.ps1
Normal file
|
@ -0,0 +1,136 @@
|
||||||
|
#!/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"
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue