Deleting Folders

from an Exchange Mailbox with PowerShell

I was given an escalation at a client where somehow a user managed to create thousands of folders all nested one inside the other.  So the task was how to delete these folders.

User and 1/2/3rd line support had already tried using Outlook, OWA and MFCMapi but none of them worked for the user anymore (plus personally the thought of using MFCMapi makes me shudder…)

First thought, surely you can just delete them with PowerShell…?  Alas, there is no Remove-MailboxFolder, nor does eDiscovery help with the search and destroy feature as this is only for mail, not folders.

Right so what is left…?  EWS to the rescue, however the thought of doing EWS with PowerShell did make me think twice.  I did a quick search on Google and found Delete Outlook Folders Bottom-Up.  To make it run you need the EWS Managed API, which can be downloaded from the Microsoft site.  The current version at time of writing is v2.2 found here

I took a quick look through the code though and found that actually it isn’t paging through the results properly and so won’t work for more than 1000 folders.  So I set about fixing it.

First was to add paging which was surprisingly simple.  The following code snippet shows how the paging works by adjusting the view’s offset

$MoreResults = $true
do{
   $Response = $RootFolder.FindFolders($FolderView)
   $AllSubFolders += $Response.Folders
   $MoreResults = $Response.MoreAvailable
   $FolderView.Offset += $Response.Folders.count
} while( $MoreResults -eq $true )

Then deleting the folders from the bottom up.  Since I have all of the folders in an array and the folders are pulled out in the order they were created (therefore the parent folder always exists before the lower level one), we can simply reverse our way through the array and delete them.

for( $i = ($AllSubFolders.Count - 1); $i -gt 0; $i-- ) {
   $AllSubFolders[$i].Delete([Microsoft.Exchange.WebServices.Data.DeleteMode]::HardDelete)
}

Even after doing those two pieces I struggled because the sheer number of folders meant that EWS was timing out.  Not to fear since we can catch that!

for( $i = ($AllSubFolders.Count - 1); $i -gt 0; $i-- ) {
   $error.clear()
   $AllSubFolders[$i].Delete([Microsoft.Exchange.WebServices.Data.DeleteMode]::HardDelete)

   if( $error[0] -like '*the operation has timed out*' ) { $i++ }
}

Finally I didn’t like how the original code tried to find the top level folder so changed it from looping through all folders to just searching for the one with the right display name

$SearchFilter = New-Object Microsoft.Exchange.WebServices.Data.SearchFilter+IsEqualTo( [Microsoft.Exchange.WebServices.Data.FolderSchema]::Displayname, $topFolderName )
$Response = $RootFolder.FindFolders($SearchFilter, $FolderView)

Putting it all together and we get a pretty cool (albeit dangerous) script.  Since the original script was on MS Technet Gallery I uploaded this revision there too.  Delete Outlook Folders Bottom-Up (v2)

The bottom line for me was that actually using EWS from Powershell isn’t actually that difficult, and adds some very powerful tools to the Exchange Admin’s toolbelt

Enjoy!

 

 

 

Twan van Beers

Twan is a senior consultant with over 20 years of experience. He has a wide range of skills including Messaging, Active Directory, SQL, Networking and Firewalls. Twan loves to write scripts and get deep and dirty into debugging code, in order to understand and resolve the most complex of problems.

This Post Has 15 Comments

  1. Hi Twan,

    Thanks for the great script…i’m in the same situation i tried many other tools with no luck.

    Just download the script and tried to run it but i’m getting this error “Missing or invalid array index expression.”

    Do you know what i’m missing?? Running it en Exchange 2010 SP3

    Thanks in advance

    1. Hi Jorge,

      I found an issue with the code pasted on the MS site (the download file should be fine)

      Change the line $AllSubFolders[$].Delete to $AllSubFolders[$i].Delete

      Cheers
      Twan

      1. Hi Twan,

        The script worked fantastic! I was able to delete those nested Folders/subfolders (1200+ Folders) after testing many tools.

        It was great…Awesome! Thanks again for your quick support and collaboration

        Cheers,
        Jorge

  2. Hi Twan,
    I am getting the following errors when running the script-

    Exception calling “AutodiscoverUrl” with “2” argument(s): “The Autodiscover service couldn’t be located.”
    At C:\MyO365Tennant\Myo365scripts\Deleting Outlook Folders Using EWS\DeleteFoldersBottomUp (2).ps1:13 char:1
    + $Service.AutodiscoverUrl($mbxName, {$True})
    + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo : NotSpecified: (:) [], MethodInvocationException
    + FullyQualifiedErrorId : AutodiscoverLocalException

    Exception calling “Bind” with “2” argument(s): “The Url property on the ExchangeService object must be set.”
    At C:\MyO365Tennant\Myo365scripts\Deleting Outlook Folders Using EWS\DeleteFoldersBottomUp (2).ps1:16 char:1
    + $RootFolder = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($Service, $Root …
    + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo : NotSpecified: (:) [], MethodInvocationException
    + FullyQualifiedErrorId : ServiceLocalException

    You cannot call a method on a null-valued expression.
    At C:\MyO365Tennant\Myo365scripts\Deleting Outlook Folders Using EWS\DeleteFoldersBottomUp (2).ps1:23 char:1
    + $Response = $RootFolder.FindFolders($SearchFilter, $FolderView)
    + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo : InvalidOperation: (:) [], RuntimeException
    + FullyQualifiedErrorId : InvokeMethodOnNull

    1. Hi Bosco,

      This means that the machine running the script is not able to autodiscover Exchange based on the mailbox name entered in the script (first few lines). Is that a valid mailbox name on your Exchange server?

      Regards
      Twan

    2. I have the same problems with autodiscover.

      It’s an valid email but it’s shared mailbox if that have something to do with it?

      Could you manually input the correct server if you look it up? 🙂

      1. Hi Frederic,

        A shared mailbox should really make a difference. You can replace the line
        $Service.AutodiscoverUrl($mbxName, {$True})
        with
        $Service.AutodiscoverUrl('http://autodiscover.xxx/autodiscover/autodiscover.xml')

        Regards
        Twan

  3. Hello,
    I found your script and it seems to be really interesting, however, I get some errors and I wonder if it could be related to my Exchange 2016 CU9 server version.
    Got :
    Exception calling “Bind” with “2” argument(s): “An internal server error occurred. The operation failed.”
    At E:\Scripts\Annick\DeleteFoldersBottomUp.ps1:23 char:1
    + $RootFolder = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($Ser …
    + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo : NotSpecified: (:) [], MethodInvocationException
    + FullyQualifiedErrorId : ServiceResponseException

    Get also
    You cannot call a method on a null-valued expression.
    At E:\Scripts\Annick\DeleteFoldersBottomUp.ps1:33 char:1
    + $Response = $RootFolder.FindFolders($SearchFilter, $FolderView)
    + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo : InvalidOperation: (:) [], RuntimeException
    + FullyQualifiedErrorId : InvokeMethodOnNull

    I red the previous post and my shared mailbox is correctly created and accessible by OWA
    I know this post is old, and I appreciate the time you spend to look at this question.
    Thanks!

    1. Hi Annick,

      Could you share the variables you’re using in the script, so I can see what it is trying to bind to, etc.? I don’t have an Exchange 2016 CU9 server at my disposal at the moment to check

      Cheers
      Twan

      1. Hello Twan,

        Thanks to answer, it’s really appreciated. Here is what I written, based on your code:
        # Script: DeleteFoldersBottomUp.ps1
        # Purpose: This scripts deletes every folder and subfolders for a specific top-folder starting from the last one
        # Author: Twan van Beers (based on original script from Nuno Mota)
        # Date: May2015

        [String] $mbxName = “RoomCLeanupReportPPD@mycompany.ca”
        [String] $topFolderName = “Deleted Items”

        [String] $dllPath = “C:\Windows\Temp\ExchangeSetup\Microsoft.Exchange.WebServices.dll”
        [Void] [Reflection.Assembly]::LoadFile($dllPath)
        $Service = New-Object Microsoft.Exchange.WebServices.Data.ExchangeService([Microsoft.Exchange.WebServices.Data.ExchangeVersion]::Exchange2010_SP1)
        $URL = “https://mail.cnmail.ca/Autodiscover/Autodiscover.xml”
        #$Service.AutodiscoverUrl(“https://autodiscover.cnppd.lab/autodiscover/autodiscover.xml”)
        #$Service.AutodiscoverUrl($URL)
        $Service.AutodiscoverUrl($mbxName, {$True})
        #$Service.AutodiscoverUrl($mbxName, {$True})
        echo “4”
        $RootFolderID = new-object Microsoft.Exchange.WebServices.Data.FolderId([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::Root, $mbxName)
        echo “5 + Service: $Service – Root: $RootFolderID”
        $RootFolder = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($Service, $RootFolderID)

        Here, $Service = Microsoft.Exchange.WebServices.Data.ExchangeService
        $RootFolderID = Root (RoomCLeanupReportPPD@mycompany.ca) (here I don’t know why there is a prefix of “Root” in front of the name of the shared mailbox)

        Note: I did not changed Exchange2010_SP1 value in the script to replace it by my server version… Not sure if I should, I have Exchange 2016 CU9, don’t know what to put exactly.

        Thanks again!
        Annick

        1. Hi Annick,

          It sounds like Autodiscover isn’t working for that email address on this server. Instead of calling $Service.AutodiscoverURL you could set the $Service.URL property to $URL and then running the script to see if the bind succeeds

          Regards
          Twan

          1. Hello Twan,

            I ran the script in my prod environment, it executed successfully without any error, but the folders are still under Deleted Items in the shared mailbox.

            Any suggestion ?

            Best Regards,
            Annick

          2. Hi Annick,

            After the bind the script sets up the search for the top level folder


            $FolderView = New-Object Microsoft.Exchange.WebServices.Data.FolderView(1000)
            $FolderView.Traversal = [Microsoft.Exchange.WebServices.Data.FolderTraversal]::Deep

            # set up the search filter to look for the folder we want
            $SearchFilter = New-Object Microsoft.Exchange.WebServices.Data.SearchFilter+IsEqualTo( [Microsoft.Exchange.WebServices.Data.FolderSchema]::Displayname, $topFolderName )
            $Response = $RootFolder.FindFolders($SearchFilter, $FolderView)

            Can you check that $Response includes the folder you are after. Perhaps the Display Name for that folder isn’t what you expect?

            Regards
            Twan

  4. Hello Twan,

    I got it ! It works like a charm ! I changed a little bit the IF statement because the value was not recognize and the Exchange version. I don’t know if that have any effect or not about the version.

    Here are my changes:
    $Service = New-Object Microsoft.Exchange.WebServices.Data.ExchangeService([Microsoft.Exchange.WebServices.Data.ExchangeVersion]::Exchange2016_CU9)

    just before the first If:
    $Count = $Response.folders.count
    $Counti = [int]$Count
    if( $Counti -ge 1) {
    $AllSubFolders = @()

    The script deleted over 4000 folders without timeout.

    So thank you very much to have shared your script! Much appreciated !
    Annick

Leave a Reply

Your email address will not be published. Required fields are marked *

Search