Wipe-ExchangeOnlineMailbox.ps1
<#PSScriptInfo
.VERSION 2.1.1 .GUID a4a6d2f0-aced-4492-8776-10da1d9338c4 .AUTHOR Aaron Guilmette .COMPANYNAME Microsoft .COPYRIGHT 2021 .TAGS .LICENSEURI .PROJECTURI https://www.undocumented-features.com/2017/10/19/update-to-wipe-exchange-online-mailbox-script/ .ICONURI .EXTERNALMODULEDEPENDENCIES .REQUIREDSCRIPTS .EXTERNALSCRIPTDEPENDENCIES .RELEASENOTES .DESCRIPTION Wipe out all content in an Exchange Online mailbox. .PRIVATEDATA #> <# .SYNOPSIS Remove all contents in an Office 365 / Exchange Online mailbox. .PARAMETER Credential Credential of user to perform mailbox wipe. User identity must have a mailbox. .PARAMETER Identity Use to specify one or more identities. .PARAMETER TargetOptions Specify which operational mode to use for content wipe: MailboxOnly, ArchiveOnly, or MailboxAndArchive. .PARAMETER DeleteItemOptions Process selected folders for deletions. Valid options include: - DeletedItemsOnly : Uses EWS to process items in Deleted Items ONLY. - DraftsOnly : Uses EWS to process items in Drafts ONLY. - RecoverableItemsOnly : Uses EWS to process items in Recoverable Items ONLY. - Normal : Default. Processes all folders using EWS and then runs Search-Mailbox. .EXAMPLE .\WipeExchangeOnlineMailbox.ps1 -Identity testuser@contoso.com Remove mailbox contents for testuser@contoso.com .EXAMPLE .\WipeExchangeOnlineMailbox.ps1 -Identity testuser@contoso.com -Credential $Cred Remove mailbox contents for testuser@contoso.com using stored credential $cred .EXAMPLE .\WipeExchangeOnlineMailbox.ps1 -Identity user1@contoso.com,user2@contoso.com -TargetOptions ArchiveOnly -Credential $Cred Remove contents of archives for user1@contoso.com and user2@contoso.com .EXAMPLE .\WipeExchangeOnlineMailbox.ps1 -Identity user1@contoso.com -TargetOptions MailboxOnly -Credential $Cred Remove contents of mailbox only for user1@contoso.com and user2@contoso.com .EXAMPLE .\WipeExchangeOnlineMailbox.ps1 -Identity user1@contoso.com -DeleteItemOptions DeletedItemsOnly -Credential $Cred Remove only deleted items folder content for user1@contoso.com .LINK For an updated version of this script, check the my blog or the PowerShell Gallery. https://www.undocumented-features.com/2017/10/19/update-to-wipe-exchange-online-mailbox-script/ .NOTES All environments perform differently. Please test this code before using it in production. THIS CODE AND ANY ASSOCIATED INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK OF USE, INABILITY TO USE, OR RESULTS FROM THE USE OF THIS CODE REMAINS WITH THE USER. Updates: - 2021-11-29 - Added 'DraftsOnly' delete option - Switched default delete option to DeletedItemsOnly - 2021-01-23 - Added 'RecoverableItems' delete option - 2017-10-29 - Added options to locate managed EWS API - 2015-09-23 - Initial release. Author: Aaron Guilmette aaron.guilmette@microsoft.com #> Param( [Parameter(Mandatory=$true,HelpMessage="Enter UPN of mailbox user or users")] [array]$Identity, [Parameter(Mandatory=$true,HelpMessage="Enter Admin Credential with ApplicationImpersonation and Mailbox Import Export roles")] [System.Management.Automation.CredentialAttribute()] $Credential = (Get-Credential), [ValidateSet('MailboxOnly','ArchiveOnly','MailboxAndArchive')] [string]$TargetOptions = "MailboxOnly", #[switch]$OnlyRecoverableItems, [ValidateSet('Normal','RecoverableItemsOnly','DeletedItemsOnly','DraftsOnly')] [string]$DeleteItemOptions = "DeletedItemsOnly", [string]$LogFile = (Get-Date -Format yyyy-MM-dd) + "_WipeMailbox.txt", [switch]$DebugLogging ) function Write-Log([string[]]$Message, [string]$LogFile = $Script:LogFile, [switch]$ConsoleOutput, [ValidateSet("SUCCESS", "INFO", "WARN", "ERROR", "DEBUG")][string]$LogLevel) { $Message = $Message + $Input If (!$LogLevel) { $LogLevel = "INFO" } switch ($LogLevel) { SUCCESS { $Color = "Green" } INFO { $Color = "White" } WARN { $Color = "Yellow" } ERROR { $Color = "Red" } DEBUG { $Color = "Gray" } } if ($Message -ne $null -and $Message.Length -gt 0) { $TimeStamp = [System.DateTime]::Now.ToString("yyyy-MM-dd HH:mm:ss") if ($LogFile -ne $null -and $LogFile -ne [System.String]::Empty) { Out-File -Append -FilePath $LogFile -InputObject "[$TimeStamp] [$LogLevel] :: $Message" } if ($ConsoleOutput -eq $true) { Write-Host "[$TimeStamp] [$LogLevel] :: $Message" -ForegroundColor $Color } } } # Locating EWS Managed API and loading Write-Host -Fore Yellow "Locating EWS installation ..." If (Test-Path 'C:\Program Files\Microsoft\Exchange\Web Services\2.2\Microsoft.Exchange.WebServices.dll') { Write-Log -ConsoleOutput -LogFile $LogFile -LogLevel SUCCESS -Message "Found Exchange Web Services DLL." $WebServicesDLL = "C:\Program Files\Microsoft\Exchange\Web Services\2.2\Microsoft.Exchange.WebServices.dll" Import-Module $WebServicesDLL } ElseIf ( $filename = Get-ChildItem 'C:\Program Files' -Recurse -ea silentlycontinue | where { $_.name -eq 'Microsoft.Exchange.WebServices.dll' }) { Write-Log -ConsoleOutput -LogFile $LogFile -LogLevel SUCCESS -Message "Found Exchange Web Services DLL at $($filename).FullName." $WebServicesDLL = $filename.FullName Import-Module $WebServicesDLL } ElseIf (!(Test-Path 'C:\Program Files\Microsoft\Exchange\Web Services\2.2\Microsoft.Exchange.WebServices.dll')) { Write-Log -ConsoleOutput -LogFile $LogFile -LogLevel WARN -Message "This requires the Exchange Web Services Managed API. Attempting to download and install." wget http://download.microsoft.com/download/8/9/9/899EEF2C-55ED-4C66-9613-EE808FCF861C/EwsManagedApi.msi -OutFile ./EwsManagedApi.msi msiexec /i EwsManagedApi.msi /qb Sleep 60 If (Test-Path 'C:\Program Files\Microsoft\Exchange\Web Services\2.2\Microsoft.Exchange.WebServices.dll') { Write-Log -ConsoleOutput -LogFile $LogFile -LogLevel SUCCESS -Message "Found Exchange Web Services DLL." $WebServicesDLL = "C:\Program Files\Microsoft\Exchange\Web Services\2.2\Microsoft.Exchange.WebServices.dll" Import-Module $WebServicesDLL } Else { Write-Log -ConsoleOutput -LogFile $LogFile -LogLevel ERROR -Message "Please download the Exchange Web Services API and try again." Break } } If (!($Credential)) { $Credential = Get-Credential -Message "Enter your Office 365 User Global Admin User Credential with the Mailbox Import/Export Role" } If (!($Identity)) { [array]$Identity = Read-Host "Enter user mailbox to wipe" } # Check Management Roles $ManagementRoles = Get-ManagementRoleAssignment -AssignmentMethod Direct -RoleAssignee $Credential.UserName If ($ManagementRoles -match "ApplicationImpersonation" -and $ManagementRoles -match "Mailbox Import Export") { Write-Log -ConsoleOutput -LogFile $LogFile -LogLevel SUCCESS -Message "Correct management roles are granted." } Else { If (!($ManagementRoles -match "Mailbox Import Export")) { Write-Log -ConsoleOutput -LogFile $LogFile -LogLevel INFO -Message "You do not currently have the Mailbox Import Export Role." New-ManagementRoleAssignment -User $Credential.UserName -Role "Mailbox Import Export" } If (!($ManagementRoles -match "ApplicationImpersonation")) { Write-Log -ConsoleOutput -LogFile $LogFile -LogLevel INFO -Message "You do not currently have the ApplicationImpersonation Role." New-ManagementRoleAssignment -User $Credential.UserName -Role "ApplicationImpersonation" } Write-Log -ConsoleOutput -LogFile $LogFile -LogLevel WARN -Message "We have attempted to grant you the required roles. Please log out of your Office 365 session, log back in, and try again." Break } ## Create Exchange Service Object Write-Log -ConsoleOutput -LogFile $LogFile -LogLevel INFO -Message " Connecting to AutoDiscover endpoint for $($Credential.UserName)." $ExchangeVersion = [Microsoft.Exchange.WebServices.Data.ExchangeVersion]::Exchange2013 $Service = New-Object Microsoft.Exchange.WebServices.Data.ExchangeService($ExchangeVersion) $creds = New-Object System.Net.NetworkCredential($Credential.UserName.ToString(),$Credential.GetNetworkCredential().password.ToString()) $Service.Credentials = $creds $Service.AutodiscoverUrl($Credential.Username, {$true}) foreach ($User in $Identity) { Write-Log -ConsoleOutput -LogFile $LogFile -LogLevel INFO -Message " Connecting to mailbox $($User)" # Grant full mailbox access Write-Log -ConsoleOutput -LogFile $LogFile -LogLevel INFO -Message " Granting mailbox access for $User to $($Credential.UserName) ...." If (Get-Mailbox $User -EA SilentlyContinue) { $PermissionResult = Add-MailboxPermission -Identity $User -User $Credential.UserName -AccessRights FullAccess -Automapping $false -wa silentlycontinue Write-Log -ConsoleOutput -LogFile $LogFile -LogLevel INFO -Message " Content from $($User) will be erased." } Else { Write-Log -ConsoleOutput -LogFile $LogFile -LogLevel ERROR -Message " Mailbox $($User) not found. Skipping user." Continue } $Service.ImpersonatedUserId = New-Object Microsoft.Exchange.WebServices.Data.ImpersonatedUserId([Microsoft.Exchange.WebServices.Data.ConnectingIdType]::SmtpAddress, $User) Switch ($TargetOptions) { MailboxOnly { switch ($DeleteItemOptions) { DeletedItemsOnly { Write-Log -ConsoleOutput -LogFile $LogFile -LogLevel INFO -Message " Processing Deleted Items with EWS." $Root = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($service, [Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::Root) $FolderView = New-Object Microsoft.Exchange.WebServices.Data.FolderView(10000) $FolderView.Traversal = [Microsoft.Exchange.WebServices.Data.FolderTraversal]::Deep $FolderList = $Root.FindFolders($FolderView) $FolderList = $FolderList | ? { $_.DisplayName -match 'Deleted Items' } } RecoverableItemsOnly { Write-Log -ConsoleOutput -LogFile $LogFile -LogLevel INFO -Message " Processing Recoverable Items with EWS." $Root = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($service, [Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::RecoverableItemsRoot) $FolderView = New-Object Microsoft.Exchange.WebServices.Data.FolderView(10000) $FolderView.Traversal = [Microsoft.Exchange.WebServices.Data.FolderTraversal]::Deep $FolderList = $Root.FindFolders($FolderView) } Normal { Write-Log -ConsoleOutput -LogFile $LogFile -LogLevel INFO -Message " Processing All Items with EWS and Search-Mailbox." $Root = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($service, [Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::Root) $FolderView = New-Object Microsoft.Exchange.WebServices.Data.FolderView(10000) $FolderView.Traversal = [Microsoft.Exchange.WebServices.Data.FolderTraversal]::Deep $FolderList = $Root.FindFolders($FolderView) } DraftsOnly { Write-Log -ConsoleOutput -LogFile $LogFile -LogLevel INFO -Message " Processing All Items with EWS and Search-Mailbox." $Root = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($service, [Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::Drafts) $FolderView = New-Object Microsoft.Exchange.WebServices.Data.FolderView(10000) $FolderView.Traversal = [Microsoft.Exchange.WebServices.Data.FolderTraversal]::Deep $FolderList = $Root.FindFolders($FolderView) } default { Write-Log -ConsoleOutput -LogFile $LogFile -LogLevel INFO -Message " Processing All Items with EWS and Search-Mailbox." $Root = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($service, [Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::Root) $FolderView = New-Object Microsoft.Exchange.WebServices.Data.FolderView(10000) $FolderView.Traversal = [Microsoft.Exchange.WebServices.Data.FolderTraversal]::Deep $FolderList = $Root.FindFolders($FolderView) } } # Original Foreach # ForEach ($Folder in $FolderList.Folders) ForEach ($Folder in $FolderList) { $Error.Clear() If ($DebugLogging) { Write-Log -ConsoleOutput -LogFile $LogFile -LogLevel DEBUG -Message " Processing $($Folder.DisplayName) ..." } Try { If ((!($Folder.DisplayName -eq "Deleted Items")) -or (!($Folder.DisplayName -eq "Inbox"))) { If ($DebugLogging) { Write-Log -LogFile $LogFile -LogLevel DEBUG -Message "Trying Folder.Delete() method" } $Folder.Delete([Microsoft.Exchange.WebServices.Data.DeleteMode]::HardDelete) | Out-Null $Folder.Update() | Out-Null } } Catch { Write-Log -LogFile $LogFile -LogLevel ERROR -Message $Error[0].Exception.Message.ToString() $Error.Clear() } Try { If ($DebugLogging) { Write-Log -LogFile $LogFile -LogLevel DEBUG -Message "Trying folder.Empty HardDelete, include subfolders" } $Folder.Empty([Microsoft.Exchange.WebServices.Data.DeleteMode]::HardDelete, $true) | Out-Null $Folder.Update() | Out-Null If ($DebugLogging) { Write-Log -ConsoleOutput -LogFile $LogFile -LogLevel DEBUG -Message "Trying Folder.Empty HardDelete, no subfolders" } $Folder.Empty([Microsoft.Exchange.WebServices.Data.DeleteMode]::HardDelete) | Out-Null $Folder.Update() | Out-Null } Catch { Write-Log -LogFile $LogFile -LogLevel ERROR -Message $Error[0].Exception.Message.ToString() $Error.Clear() } } # Deleting remaining inbox content via Search-Mailbox cmdlet If ($DeleteItemOptions -eq "Normal" -or $DeleteItemOptions -eq "Default") { Write-Log -ConsoleOutput -LogFile $LogFile -LogLevel INFO -Message " Running Search-Mailbox to finsh up." Search-Mailbox -Identity $User -DeleteContent -Force -DoNotIncludeArchive -wa silentlycontinue } } # End MailboxOnly ArchiveOnly { $ArchiveGuid = (Get-Mailbox $User).ArchiveGuid switch ($DeleteItemOptions) { DeletedItemsOnly { Write-Log -ConsoleOutput -LogFile $LogFile -LogLevel INFO -Message " Processing Deleted Items with EWS." $Root = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($service, [Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::ArchiveRoot) $FolderView = New-Object Microsoft.Exchange.WebServices.Data.FolderView(10000) $FolderView.Traversal = [Microsoft.Exchange.WebServices.Data.FolderTraversal]::Deep $FolderList = $Root.FindFolders($FolderView) $FolderList = $FolderList | ? { $_.DisplayName -match 'Deleted Items' } } RecoverableItemsOnly { Write-Log -ConsoleOutput -LogFile $LogFile -LogLevel INFO -Message " Processing Recoverable Items with EWS." $Root = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($service, [Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::ArchiveRecoverableItemsRoot) $FolderView = New-Object Microsoft.Exchange.WebServices.Data.FolderView(10000) $FolderView.Traversal = [Microsoft.Exchange.WebServices.Data.FolderTraversal]::Deep $FolderList = $Root.FindFolders($FolderView) } Normal { Write-Log -ConsoleOutput -LogFile $LogFile -LogLevel INFO -Message " Processing All Items with EWS and Search-Mailbox." $Root = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($service, [Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::ArchiveRoot) $FolderView = New-Object Microsoft.Exchange.WebServices.Data.FolderView(10000) $FolderView.Traversal = [Microsoft.Exchange.WebServices.Data.FolderTraversal]::Deep $FolderList = $Root.FindFolders($FolderView) } DraftsOnly { Write-Log -ConsoleOutput -LogFile $LogFile -LogLevel INFO -Message " Processing All Items with EWS and Search-Mailbox." $Root = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($service, [Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::Drafts) $FolderView = New-Object Microsoft.Exchange.WebServices.Data.FolderView(10000) $FolderView.Traversal = [Microsoft.Exchange.WebServices.Data.FolderTraversal]::Deep $FolderList = $Root.FindFolders($FolderView) } default { Write-Log -ConsoleOutput -LogFile $LogFile -LogLevel INFO -Message " Processing All Items with EWS and Search-Mailbox." $Root = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($service, [Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::ArchiveRoot) $FolderView = New-Object Microsoft.Exchange.WebServices.Data.FolderView(10000) $FolderView.Traversal = [Microsoft.Exchange.WebServices.Data.FolderTraversal]::Deep $FolderList = $Root.FindFolders($FolderView) } } ForEach ($Folder in $FolderList) { $Error.Clear() If ($DebugLogging) { Write-Log -ConsoleOutput -LogFile $LogFile -LogLevel DEBUG -Message " Processing $($Folder.DisplayName) ..." } Try { If ((!($Folder.DisplayName -eq "Deleted Items")) -or (!($Folder.DisplayName -eq "Inbox"))) { If ($DebugLogging) { Write-Log -LogFile $LogFile -LogLevel DEBUG -Message "Trying Folder.Delete() method" } $Folder.Delete([Microsoft.Exchange.WebServices.Data.DeleteMode]::HardDelete) | Out-Null $Folder.Update() | Out-Null } } Catch { Write-Log -LogFile $LogFile -LogLevel ERROR -Message $Error[0].Exception.Message.ToString() $Error.Clear() } Try { If ($DebugLogging) { Write-Log -LogFile $LogFile -LogLevel DEBUG -Message "Trying folder.Empty HardDelete, include subfolders" } $Folder.Empty([Microsoft.Exchange.WebServices.Data.DeleteMode]::HardDelete, $true) | Out-Null $Folder.Update() | Out-Null If ($DebugLogging) { Write-Log -ConsoleOutput -LogFile $LogFile -LogLevel DEBUG -Message "Trying Folder.Empty HardDelete, no subfolders" } $Folder.Empty([Microsoft.Exchange.WebServices.Data.DeleteMode]::HardDelete) | Out-Null $Folder.Update() | Out-Null } Catch { Write-Log -LogFile $LogFile -LogLevel ERROR -Message $Error[0].Exception.Message.ToString() $Error.Clear() } } # Deleting remaining content via Search-Mailbox cmdlet If ($DeleteItemOptions -eq "Normal" -or $DeleteItemOptions -eq "Default") { Write-Log -ConsoleOutput -LogFile $LogFile -LogLevel INFO -Message " Running Search-Mailbox to finsh up." Search-Mailbox -Identity $ArchiveGuid.Guid -DeleteContent -Force -wa silentlycontinue } } # End ArchiveOnly MailboxAndArchive { If ($DeleteItemOptions -eq "Normal") { Write-Log -ConsoleOutput -LogFile $LogFile -LogLevel INFO " Processing normal delete operation for mailbox and archive." { $MailboxGuid = (Get-Mailbox $User).ExchangeGuid $ArchiveGuid = (Get-Mailbox $User).ArchiveGuid $FolderRoots = ('Root', 'ArchiveRoot') foreach ($Inbox in $FolderRoots) { $Root = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($service, [Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::$Inbox) $FolderView = New-Object Microsoft.Exchange.WebServices.Data.FolderView(1000) $FolderView.Traversal = [Microsoft.Exchange.WebServices.Data.FolderTraversal]::Deep $FolderList = $Root.FindFolders($FolderView) ForEach ($Folder in $FolderList.Folders) { $Error.Clear() If ($DebugLogging) { Write-Log -ConsoleOutput -LogFile $LogFile -LogLevel DEBUG -Message " Processing $($Folder.DisplayName) ..." } Try { $Folder.Delete([Microsoft.Exchange.WebServices.Data.DeleteMode]::HardDelete) | Out-Null $Folder.Empty([Microsoft.Exchange.WebServices.Data.DeleteMode]::HardDelete, $true) | Out-Null $Folder.Update() } Catch { Write-Log -LogFile $LogFile -LogLevel ERROR -Message $Error[0].Exception.Message.ToString() $Error.Clear() } Finally { } } } Write-Log -ConsoleOutput -LogFile $LogFile -LogLevel INFO -Message " Running Search-Mailbox to finsh up." Search-Mailbox -Identity $MailboxGuid.Guid -DeleteContent -Force -wa silentlycontinue Search-Mailbox -Identity $ArchiveGuid.Guid -DeleteContent -Force -wa silentlycontinue } } } Else { Write-Host -ForegroundColor Red "DeleteItemOptions parameter values other than Normal only works with either -ArchiveOnly or -MailboxOnly options individually." } } } |