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