Removing 1000’s of folders in an on oprem Exchange Mailbox via EWS

Recently our helpdesk received a report of a user leveraging Outlook 2013 (x86) in a VM on a Mac to access a local mailbox (i.e. Not O365) crashing with an out of memory error. Before they reached out to my team (which are the stewards of Exchange 2016 and O365) they tired the following

The issue only went away once we removed all attached shared mailboxes (2 total). When adding them back, we noticed one of the shared mailboxes generated a lot of entries in the ‘Sync Issues’ folder. When we investigated the shared mailbox in question, it had 4207 user created folders. And while the user had access to this mailbox for years, it wasn’t until the gained access to another shared mailbox that this issue starting to occur. So we surmised that this mailbox in particular was the cause and was ripe for clean-up due to the amount of folders it contained. Using the get-mailboxfolderstatistics cmdlet we generated a CSV of all the user created folders along with some metrics around them for the owners of the shared mailbox to review for deletion

get-mailboxfolderstatistics PROBLEM_MAILBOX | Where-Object FolderType -eq "User Created" | Select Name, FolderPath, ItemsInFolder, ItemsInFolderAndSubfolders,@{N='FolderSize(MB)';E={$_.FolderSize.tomb()}}, @{N="FolderAndSubfolderSize(MB)";e={$_.FolderAndSubfolderSize.toMB()}}, NewestItemReceivedDate, OldestItemReceivedDate | Export-CsV -Path C:\Temp\FolderReview.csv

The owners then edited that list down to 3551 folders to delete. At that point we leverage EWS (Exchange Web Services) in PowerShell to delete the folders in question . This was a quick and dirty process as I didn’t have much time to create something more elegant. One of wish list items I had was figuring out how to properly weed out only top level folder paths to delete, but after spending 30 minutes trying to figure that out I decided it wasn’t worth the effort for this one off task. If I did delete a top level folder and the list had multiple child folders then our deletion attempt would just error out. We also used an account setup for application impersonation for this effort. Bare bones script below:

Make an Exchange EMS Connection
$ExchSession = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri YOUR_EXCHANGE_PS_URL -Authentication Kerberos -Name Exchange -ErrorAction Stop
Import-PSSession $ExchSession -allowclobber -ErrorAction Stop -DisableNameChecking -WarningAction SilentlyContinue *> $NULL

#Variables for EWS connection
$ExchangeVersion = "Exchange2013"
[String]$EWSManagedApiPath = "C:\Program Files\Microsoft\Exchange\Web Services\2.2\Microsoft.Exchange.WebServices.dll"

#Impersonation Account
$ImpersonationAccountNAme = "YOUR IMPERSONATION ACCOUNT"
$ImpersanationAccountPassword = "YOUR IMPERSONATION ACCOUNT"

#Mailbox clean up info
$Mailbox = 'PROBLEM_MAILBOX@YOURDOMAIN.COM'
$TaggedFolders = Import-Csv -Path 'YOUR_CSV.CSV'
$AllFolders = get-mailboxfolderstatistics $Mailbox |
    Where-Object FolderType -eq "User Created"
$FoldersToRemove = $AllFolders |
    Where-Object FolderPath -in $TaggedFolders.FolderPath

# Load the needed dlls
Add-Type -Path $EWSManagedApiPath
Add-Type -AssemblyName System.Web

# Set EWS Service
$ExchangeVersion = [Microsoft.Exchange.WebServices.Data.ExchangeVersion]::$ExchangeVersion
$service = New-Object Microsoft.Exchange.WebServices.Data.ExchangeService($ExchangeVersion)  

# Setup impersonation
$Service.Credentials = New-Object Microsoft.Exchange.WebServices.Data.WebCredentials($ImpersonationUserName, $ImpersonationPassword) -ErrorAction Stop
$Service.ImpersonatedUserId = New-Object -TypeName Microsoft.Exchange.WebServices.Data.ImpersonatedUserId -ArgumentList "SmtpAddress", $Mailbox

$service.AutodiscoverUrl($Mailbox, { $true })  

#Function to convert the folderID from the Exchange Management shell to one we can us in EWS
function ConvertId {    
    param (
        $OwaId = "$( throw 'OWAId is a mandatory Parameter' )"
          )
    process {
        $aiItem = New-Object Microsoft.Exchange.WebServices.Data.AlternateId      
        $aiItem.Mailbox = $Mailbox     
        $aiItem.UniqueId = $OwaId   
        $aiItem.Format = [Microsoft.Exchange.WebServices.Data.IdFormat]::OwaId 
        $convertedId = $service.ConvertId($aiItem, [Microsoft.Exchange.WebServices.Data.IdFormat]::EwsId) 
        return $convertedId.UniqueId
    }
}

# Run through the folders ot delete
$FoldersToRemove | 
    ForEach-Object {
        Write-Output "Deleting Folder $($_.FolderPath)"
        try {
            $urlEncodedId = [System.Web.HttpUtility]::UrlEncode($_.FolderId.ToString())
            $folderid = New-Object Microsoft.Exchange.WebServices.Data.FolderId((Convertid $urlEncodedId))  
            $ewsFolder = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($service, $folderid)        

            $ewsFolder.Delete([Microsoft.Exchange.WebServices.Data.DeleteMode]::HardDelete)
            "Folder Deleted"
        }
        catch {
            Write-Warning "Problems deleting $($_.FolderPath)"
        }
    }

About mell9185

IT proffesional. Tech, video game, anime, and punk aficionado.
This entry was posted in Uncategorized and tagged , . Bookmark the permalink.

Leave a Reply