Wipe-ExchangeOnlineMailbox.ps1

<#PSScriptInfo
 
.VERSION 2.0
 
.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.
- 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.
 
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')]
    [string]$DeleteItemOptions = "Normal",
    [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)
                }
                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)
                }
                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."
        }
    }
}