Removing Messages by Message Class (Updated)

powershellRecently, I was asked if it is possible to remove stub items. The reason was they were going to transition to a newer version of Exchange and they wouldn’t be using the archiving solution in the new environment. When required, vendor tooling would be used to search through the existing archives.

In such cases it makes sense to remove the stubs from the mailbox, which are shortcut messages that points to a copy of the original message in the archive solution. The new environment won’t contain the required Outlook plugins or extensions to retrieve the original message from the archive using the stub, making the stub lead to a partial or empty message.

To identify stubs, one can filter on an attribute of each item, MessageClass. This attribute defines which kind of item it is (in fact, determines what form Outlook should use in order to present or process the information). Examples of MessageClass definitions are IPM.Note (regular e-mail messages), IPM.Note.EnterpriseVault.Shortcut (message archived by Enterprise Vault) or IPM.ixos-archive (message archived by Opentext/IXOS LiveLink E-Mail Archive).

To identify stubs from Outlook, add the Message Class field to your Outlook view, e.g.:


When you want to remove the stubs using Outlook, you can utilize the Advanced Find function of Outlook, but that is a very labor intensive, tedious and non-centralized per-mailbox procedure:


Now I wouldn’t have started this article if the same thing wasn’t possible with a little bit of scripting against Exchange Web Services and so the script Remove-MessagesClassItems.ps1 was born. Using this script requires Exchange 2007 or later and Exchange Web Services Managed API 1.2 (or later) which you can download here or you can copy the Microsoft.Exchange.WebServices.DLL locally and adjust the DLL path mentioned in the script when necessary. The script has been developed and tested against Exchange 2007, meaning it’s a PowerShell 1.0 script which should be compatible with later versions of PowerShell or Exchange.

Also take notice that since you’ll be processing user mailboxes, you’ll need to have full mailbox access or impersonation permissions; the latter is preferred. For details on how to configure impersonation for Exchange 2010 using RBAC, see this article or check here for details on how to configure impersonation for Exchange 2007.

The script Remove-MessagesClassItems.ps1 uses the following syntax:

Remove-MessageClassItems.ps1 [-Mailbox] <String> [-MessageClass] <String> [-Server <String>] [-Impersonation] [-DeleteMode <String>] [-WhatIf] [-Confirm] [<CommonParameters>]

A quick walk-through on the parameters and switches:

  • Mailbox is the name of the mailbox of which to fix the folder structure;
  • MessageClass specifies the Message Class to remove, for example IPM.Note.EnterpriseVault.Shortcut (EnterpriseVault);
  • Server is the name of the Client Access Server to access for Exchange Web Services. When omitted, the script will attempt to use Autodiscover;
  • When the Impersonation switch is specified, impersonation will be used for mailbox access, otherwise the current user context will be used;
  • DeleteMode specifies how to remove messages. Possible values are HardDelete (permanently deleted), SoftDelete (use dumpster, default) or MoveToDeletedItems (move to Deleted Items folder).

So for example, suppose you want to remove  IPM.Note.EnterpriseVault.Shortcut items from the mailbox of user1, moving the items to the DeletedItems by Impersonation. In such case, you could use the following cmdlet:

Remove-MessageClassItems.ps1 -Mailbox user1 -MessageClass IPM.Note.EnterpriseVault.Shortcut -DeleteMode MoveToDeletedItems -Impersonation –Verbose 


Note: By default, Remove-MessageClassItems.ps1 will only process IPF.Note class folders (i.e. containing mail items), so you’ll only see those being processed. If you want all folders scanned (also class-less), use the ScanAllFolders switch.

In case you want to process multiple mailboxes, you can use a CSV file which needs to contain the Mailbox field. An example of how the CSV could look:


The cmdlet could then be something like:

Import-CSV users.csv1 | Remove-MessageClassItems.ps1 -MessageClass IPM.Note.EnterpriseVault.Shortcut -DeleteMode HardDelete -Impersonation

You’re feedback is welcomed through the comments; if you got scripting suggestions, please use the contact form.

You can download the script from the Technet Gallery here.

Revision History
See TechNet Gallery page.

54 thoughts on “Removing Messages by Message Class (Updated)

  1. Pingback: Removing Messages by Message Class (Updated) | EighTwOne (821) | JC's Blog-O-Gibberish

  2. Michel, I would appreciate to learn more about the migration from EV to Exchange archiving. We are planning on moving from an Exchange 2007/EV 9.0.2 implementation to Exchange 2013 and the road block is the 34TB of data in EV and how best to get it back to exchange. On the plus side, we are not stubbing our emails.

    • Transvault :)
      Nb : when you say 34TB is y compressed and duplicated DATA (singleinstanceless) ? Or just the DATA in disk ?

    • Depends on scenario and setup. This customer cut off their archived data (ie isolated, available when required using vendor tools) and will move not (yet) archived mailbox data to the new e-mail environment, facilitating large mailboxes. Otherwise, they’d have to inject the original mail items using the stubs from the archive, but as that data is rarely consulted but has a significant storage footprint, they chose to cut it off.

  3. Fantastic script! This is extremely useful in removing third-party voice mail messages from the Exchange environment. Not being very adept at EWS scripitng(but learning); I’d like to know how would the script be modified to find messages of a specific class older than, let’s say, 45-days?
    (define in base property set and then filter?)

    • The line with “[Microsoft.Exchange.WebServices.Data.ItemSchema]::ItemClass, $MessageClass)” defines the item filter. It’s a single condition; if you want to combine multiple conditions, you need to utilize a SearchFilterCollection where you add all the individual conditions. Then, pass that collection instead of the single condition to FindItems, e.g.

      $ItemSearchFilter1= New-Object Microsoft.Exchange.WebServices.Data.SearchFilter+IsEqualTo( [Microsoft.Exchange.WebServices.Data.ItemSchema]::ItemClass, $MessageClass)
      $ItemSearchFilter2= New-Object Microsoft.Exchange.WebServices.Data.SearchFilter+IsLessThanOrEqualTo( [Microsoft.Exchange.WebServices.Data.ItemSchema]::DateTimeReceived, (Get-Date).AddDays(-45))
      $SearchFilterCollection= New Microsoft.Exchange.WebServices.Data.SearchFilter+SearchFilterCollection([Microsoft.Exchange.WebServices.Data.LogicalOperator]::And);
      $SearchFilterCollection.add( $ItemSearchFilter1)
      $SearchFilterCollection.add( $ItemSearchFilter2)

      Do {
      $ItemSearchResults= $SubFolder.FindItems( $ItemSearchFilterCollection, $ItemView)

      Let me know if there’s more animo for this; I could make item age a parameter of course :)

      • Thank you for the prompt reply. When I replace the code to create the SearchFilterCollection..I get an error regarding “New”…seems to run, then remove all messages, not just message class specified. (Of course, working in a test environment)
        [PS] E:\hold\msgclass>.\Remove-MessageClassItems45days.ps1 -Mailbox mbxrestore -MessageClass IPM.Note.Adomo.VM -DeleteMode MoveToDeletedItems -Impersonation -Verbose
        Processing mailbox mbxrestore
        VERBOSE: Loading Microsoft.Exchange.WebServices.dll
        VERBOSE: Set to trust all certificates
        VERBOSE: Using MBX.Restore@xxxxxx.xxxfor impersonation
        VERBOSE: Looking up EWS URL using Autodiscover for
        VERBOSE: Using EWS on CAS
        VERBOSE: DeleteMode is MoveToDeletedItems
        VERBOSE: Removing messages of class IPM.Note.Adomo.VM
        VERBOSE: Processing folder Drafts
        The term ‘New’ is not recognized as the name of a cmdlet, function, script file, or operable program. Check the spellin
        g of the name, or if a path was included, verify that the path is correct and try again.
        At E:\hold\msgclass\Remove-MessageClassItems45days.ps1:166 char:38
        + $SearchFilterCollection= New <<<< Microsoft.Exchange.WebServices.Data.SearchFilter+SearchFilterCollec
        + CategoryInfo : ObjectNotFound: (New:String) [], CommandNotFoundException
        + FullyQualifiedErrorId : CommandNotFoundException

        You cannot call a method on a null-valued expression.
        At E:\hold\msgclass\Remove-MessageClassItems45days.ps1:167 char:35
        + $SearchFilterCollection.add <<<< ( $ItemSearchFilter1)
        + CategoryInfo : InvalidOperation: (add:String) [], RuntimeException
        + FullyQualifiedErrorId : InvokeMethodOnNull

        • Change:

          $ItemSearchFilter= New-Object Microsoft.Exchange.WebServices.Data.SearchFilter+IsEqualTo( [Microsoft.Exchange.WebServices.Data.ItemSchema]::ItemClass, $MessageClass)

          Do {
          $ItemSearchResults= $SubFolder.FindItems( $ItemSearchFilter, $ItemView)


          $ItemSearchFilter1= New-Object Microsoft.Exchange.WebServices.Data.SearchFilter+IsEqualTo( [Microsoft.Exchange.WebServices.Data.ItemSchema]::ItemClass, $MessageClass)
          $ItemSearchFilter2= New-Object Microsoft.Exchange.WebServices.Data.SearchFilter+IsLessThanOrEqualTo( [Microsoft.Exchange.WebServices.Data.ItemSchema]::DateTimeReceived, (Get-Date).AddDays(-45))
          $ItemSearchFilterCollection= New-Object Microsoft.Exchange.WebServices.Data.SearchFilter+SearchFilterCollection([Microsoft.Exchange.WebServices.Data.LogicalOperator]::And);
          $ItemSearchFilterCollection.add( $ItemSearchFilter1)
          $ItemSearchFilterCollection.add( $ItemSearchFilter2)

          Do {
          $ItemSearchResults= $SubFolder.FindItems( $ItemSearchFilterCollection, $ItemView)

          • Fantastic! Works perfectly! Thanks so much for your effort. I enjoy your blog and the UC Podcasts.

  4. Pingback: The UC Architects » Episode 22: A Game of Clouds

  5. Fantastic !! Can’t thank you enough. Is it possible to make age as one of the parameters and also parameter to include a specific folder.


      • Can you give me some pointer as how to exclude messages in “Managed Folder” and its subfolders. We use Exchange Managed Folders for classifying important emails.


  6. Thanks for the script. I am going to try to use it to see if my novice PS skills can change it from deleting items with a specified message class to changing them to a similiar but different message class. If you happen to catch this and have suggestions they would be appreciated.

    Thanks again for your contributions to date.


  7. I found a similar solution using the search-mailbox command. I chose this meathod because I’m not using Exchange Web Services.

    Search-Mailbox -Identity $UserName -SearchQuery “IPM.NOTE.EnterpriseVault.Shortcut” -DeleteContent

    You can test with the following commands.

    This produces a report with the number of hits
    Search-Mailbox -Identity $UserName -SearchQuery “IPM.NOTE.EnterpriseVault.Shortcut” –EstimateResultOnly

    This will search a mailbox, and all hits will be copied to another mailbox. You can look at that mailbox and see exactly what would be deleted if you used the -DeleteContent command.

    Search-Mailbox -Identity $UserName -SearchQuery “IPM.NOTE.EnterpriseVault.Shortcut” -TargetMailbox “TestMB” -TargetFolder “$UserName”

  8. Just tried this script today but unfortunately it’s not working for me. In verbose mode I can see it’s processing all the folders, but it didn’t find any e-mails with the class “IPM.Note.EnterpriseVault.Shortcut”.

      • After reading the comment below, I know why it’s not working for me. The stub mail items are all in the archive mailbox. Because the script did show all folders including the ones in the archive mailbox I thought it was also working for the archive. I will use the search-mailbox cmdlet for now. If you include the option to process the archive it will be a great script. Keep up the good work!

  9. This is an awesome script. Thank you so much for taking the time to publish it.

    What if the stub is already in the user’s Personal Archive? Can we use the script to remove stubs that have already been moved to the user’s archive?

  10. Hello Michel,

    when I run the script I get the following. I’m running the script as a Exchange Administrator and I have full mailbox rights on this test account. I doesn’t remove any of the EAS messageClass items.

    [PS] C:\>.\Remove-MessageClassItems.ps1 -Mailbox Training6 -DeleteMode SoftDelete -MessageClass IPM.Note.EAS -ScanAllFolders -verbose
    Processing mailbox Training6
    VERBOSE: Loading Microsoft.Exchange.WebServices.dll
    VERBOSE: Set to trust all certificates
    VERBOSE: Looking up EWS URL using Autodiscover for Training6@****.com
    VERBOSE: Using EWS on CAS https://mail.****.com/EWS/Exchange.asmx
    VERBOSE: DeleteMode is SoftDelete
    VERBOSE: Removing messages of class IPM.Note.EAS
    VERBOSE: Scanning all folders
    VERBOSE: Processing folder Calendar
    VERBOSE: Processing folder Contacts
    VERBOSE: Processing folder Conversation Action Settings
    VERBOSE: Processing folder Drafts
    VERBOSE: Processing folder Inbox
    VERBOSE: Processing folder Journal
    VERBOSE: Processing folder Junk E-Mail
    VERBOSE: Processing folder Notes
    VERBOSE: Processing folder Outbox
    VERBOSE: Processing folder Quick Step Settings
    VERBOSE: Processing folder RSS Feeds
    VERBOSE: Processing folder Sent Items
    VERBOSE: Processing folder Suggested Contacts
    VERBOSE: Processing folder Sync Issues
    VERBOSE: Processing folder Conflicts
    VERBOSE: Processing folder Local Failures
    VERBOSE: Processing folder Server Failures
    VERBOSE: Processing folder Tasks

  11. Great script. Thanks for it. I have a few suggestions that you can take or leave.

    Enhancement Suggestions:
    - Include a parameter to specify the EWS API location of not installed to the default location
    - Include a parameter to run as a desired account to impersonate.
    -Include a parameter to change the default EWS Exchange2007_SP1 config to other versions of Exchange
    + I did this as part of Troubleshooting my issues and it appeared to not be the root cause of the issue. Perhaps this is not required because of it.
    - Provide a parameter to output verbose output to a log file (I know we can pipe it… but it would be nice to have on screen and off screen output)

    Thanks again for the hard work and responsive assistance

  12. Hi,

    I’ve managed to get the script to run without error now (had various syntax issues), but it doesn’t seem to be doing anything. After hitting enter, I just get a line showing ‘>>’ and no further action happens.

    I am trying to permanently remove EV stubs in Exchange 2010:

    .\Remove-MessageClassItems.ps1″ -Mailbox TomU -MessageClass IPM.Note.EnterpriseVault.Shortcut -DeleteMode HardDelete -Impersonation –Verbose

    Any ideas please?

    • You shouldn’t have to edit the script. If you copied/pasted the exact cmdlet, there’s a quote mismatch (the >> means its waiting for more input since you didn’t enter a closing quote). Try this:
      .\Remove-MessageClassItems.ps1 -Mailbox TomU -MessageClass IPM.Note.EnterpriseVault.Shortcut -DeleteMode HardDelete -Impersonation –Verbose

      • My bad, I didn’t edit the script, just the cmdlet. Your amendment now generates an error telling me it is not digitally signed? Appreciate your help.

        File C:\2010\Remove-MessageClassItems.ps1 cannot be loaded. The file C:\2010\Remove-MessageClassItems.ps1 is not digitally signed. The script will not execute on the system. Please see “get-help about_signing” for more details..
        At line:1 char:31
        + .\Remove-MessageClassItems.ps1 <<<< -Mailbox TomU -MessageClass IPM.Note.EnterpriseVault.Shortcut -DeleteMode HardDe
        lete -Impersonation -Verbose
        + CategoryInfo : NotSpecified: (:) [], PSSecurityException
        + FullyQualifiedErrorId : RuntimeException

          • Thanks, getting there…. A whole host of errors now though. Appreciate your time but understand if you have better things to be doing!

            Processing mailbox TomU
            VERBOSE: Loading Microsoft.Exchange.WebServices.dll
            Exception calling “LoadFile” with “1″ argument(s): “The system cannot find the file specified. (Exception from HRESULT:
            At C:\2010\Remove-MessageClassItems.ps1:253 char:46
            + [void][Reflection.Assembly]::LoadFile <<<< ( $EwsDll)
            + CategoryInfo : NotSpecified: (:) [], MethodInvocationException
            + FullyQualifiedErrorId : DotNetMethodException

            Unable to find type [Microsoft.Exchange.WebServices.Data.ExchangeVersion]: make sure that the assembly containing this
            type is loaded.
            At C:\2010\Remove-MessageClassItems.ps1:257 char:80
            + $ExchangeVersion= [Microsoft.Exchange.WebServices.Data.ExchangeVersion] <<<< ::Exchange2007_SP1
            + CategoryInfo : InvalidOperation: (Microsoft.Excha…ExchangeVersion:String) [], RuntimeException
            + FullyQualifiedErrorId : TypeNotFound

            New-Object : Cannot find type [Microsoft.Exchange.WebServices.Data.ExchangeService]: make sure the assembly containing
            this type is loaded.
            At C:\2010\Remove-MessageClassItems.ps1:258 char:32
            + $EwsService= New-Object <<<< Microsoft.Exchange.WebServices.Data.ExchangeService( $ExchangeVersion)
            + CategoryInfo : InvalidType: (:) [New-Object], PSArgumentException
            + FullyQualifiedErrorId : TypeNotFound,Microsoft.PowerShell.Commands.NewObjectCommand

            Property 'UseDefaultCredentials' cannot be found on this object; make sure it exists and is settable.
            At C:\2010\Remove-MessageClassItems.ps1:259 char:21
            + $EwsService. <<<< UseDefaultCredentials= $true
            + CategoryInfo : InvalidOperation: (UseDefaultCredentials:String) [], RuntimeException
            + FullyQualifiedErrorId : PropertyNotFound

            VERBOSE: Set to trust all certificates
            VERBOSE: Using for impersonation
            Unable to find type [Microsoft.Exchange.WebServices.Data.ConnectingIdType]: make sure that the assembly containing this
            type is loaded.
            At C:\2010\Remove-MessageClassItems.ps1:270 char:165
            + $EwsService.ImpersonatedUserId= New-Object Microsoft.Exchange.WebServices.Data.ImpersonatedUserId([Micros
            oft.Exchange.WebServices.Data.ConnectingIdType] <<<< ::SmtpAddress, $EmailAddress)
            + CategoryInfo : InvalidOperation: (Microsoft.Excha…onnectingIdType:String) [], RuntimeException
            + FullyQualifiedErrorId : TypeNotFound

            VERBOSE: Looking up EWS URL using Autodiscover for
            Write-Error : A positional parameter cannot be found that accepts argument 'You cannot call a method on a null-valued e
            At C:\2010\Remove-MessageClassItems.ps1:286 char:28
            + Write-Error <<<< "Autodiscover failed: " $error[0]
            + CategoryInfo : InvalidArgument: (:) [Write-Error], ParentContainsErrorRecordException
            + FullyQualifiedErrorId : PositionalParameterNotFound,Microsoft.PowerShell.Commands.WriteErrorCommand

          • @Tom: “Using this script requires Exchange 2007 or later and Exchange Web Services Managed API 1.2 (or later) which you can download here or you can copy the Microsoft.Exchange.WebServices.DLL locally and adjust the DLL path mentioned in the script when necessary. “

          • Ah ok thanks, we are using 2010 but I will need to discuss with our main admin about the web services DLLs ec.

            Appreciate your time.

          • @No need to install; you can install it elsewhere, copy the Microsoft.Exchange.WebServices.DLL in the same location as the script and change the location (in the script) accordingly. Updated version of script will have additional logic to look for that DLL by the way.

          • Me again…. Now I’ve got this far… “Can’t access mailbox information store” ?


            Processing mailbox TomU
            VERBOSE: Loading Microsoft.Exchange.WebServices.dll
            VERBOSE: Set to trust all certificates
            VERBOSE: Using for impersonation
            VERBOSE: Looking up EWS URL using Autodiscover for TomU@xxx
            VERBOSE: Using EWS on CAS https://xxx/EWS/Exchange.asmx
            C:\2010\Remove-MessageClassItems.ps1 : Can’t access mailbox information store
            At line:1 char:31
            + .\Remove-MessageClassItems.ps1 <<<< -Mailbox TomU -MessageClass IPM.Note.EnterpriseVault.Shortcut -DeleteMode HardDe
            lete -Impersonation -Verbose
            + CategoryInfo : NotSpecified: (:) [Write-Error], WriteErrorException
            + FullyQualifiedErrorId : Microsoft.PowerShell.Commands.WriteErrorException,Remove-MessageClassItems.ps1

          • Ok , I did see that just thought I’d have permissions on my own mailbox already 8)

            Thanks again! By the way, is there anyway to delete (or edit) this thread? I fear leaving a full email address showing from the scripts wasn’t ideal.

          • I have tried on a couple of users who’s mailboxes I have full access permissions on, however I am still getting an error stating it can’t access the mailbox information store.

            Do I have to use impersonation in 2010 or something?


  13. Is it possible to target only one folder? For instance I want to delete the
    IPM.Note.Microsoft.Conversation files from the ConversationHistory folder on 139 mailboxes. I have my csv file ready but I was hoping to avoid running the script against all of the folders in each mailbox.


  14. Hi,

    I wanted to know whether this script will work on a 2013 exchange evironment? i have followed the directions posted in reference to impersonation, but still running into errors whether i excute the script from a CAS or MBX server. I am trying to delete stubs from a legacy email archive solution.


  15. Thanks for this script.. I’m trying to purge EV Shortcuts from a broken EV environment before a mail migration to O365. Script works fine for the most part but for a few users I get the “Can’t access mailbox information store” at line 1 char:31. These are users that are on the exact same mailbox database that users that the script works fine for. Impersonation is set on the account I’m using to run the script and again seems to work for the other users. Size issue? (these are fairly large Exchange 2010 mailboxes) I went so far as to specifically add Full right and send as to these problem mailboxes with no luck

      • Figured it out.. I did a

        get-mailbox -identity “username” | export-csv user.csv -notype

        On the users that were not working and discovered that despite showing as a regular User Mailbox in Exchange 2010 Management Console these mailboxes were apparently Linked Mailboxes. I discovered this by comparing all the properties of the mailboxes that did not work vs the ones that did and discovered that the failing mailboxes had a Linked Maser Account value (I have a very old Exchange environment that originally started as Exchange 5.5 so somewhere along the way during the migrations some mailboxes came across as Linked)

        So I ran

        Set-User -Identity -LinkedMasterAccount $null

        against the failing mailboxes and now the script is working.

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s