Remove Specific E-Mail or E-Mails From All Exchange Mailboxes

Have you ever been asked to remove an unwanted e-mail from all mailboxes in your Exchange environment? Did someone send out an e-mail to the whole company and then tried to retract it? Or perhaps a new virus made its way to all mailboxes by way of an attachment. This scenario tends to be brought to my plate quite often so I will walk you through the steps of scanning all mailboxes and removing an unwanted e-mail. There are different strategies to accomplish this but the following should work for your needs.

All highlighted examples use a ForEach loop with the mailbox databases as large environments will often run out of memory retrieving all the mailboxes. This assumes you have less than 1000 mailboxes per database. The power of this command revolves around the Search Query and being able to include only the e-mails that fit your criteria for action. All examples assume you are connected to the Exchange Management Shell.

Step #1: The first thing that is required is a user account with the proper access role to search and delete. I use a secure service account to perform these tasks so that they are not being completed by my own account. The two roles that are required are Mailbox Import Export and Mailbox Search. By default, the Mailbox Import Export management role isn’t included in any of the built-in role groups. As a result this shows you how to create a role group with the required 2 roles.

New-RoleGroup -Name "Mailbox Scanning" -Roles "Mailbox Import Export", "Mailbox Search" -Members YourServiceAccount

Step #2a: At this point we can either search for an e-mail and log the results, search and delete the results, or both. This example searches all mailboxes and deletes the e-mail according to your search query. The search query can be adjusted according to your needs and more information can be found on the syntax here.

$databases = Get-MailboxDatabase
ForEach ($database in $databases) {
	$mailboxes = Get-Mailbox -Database $database
	ForEach ($mailbox in $mailboxes)
	{ Search-Mailbox $mailbox.identity -SearchQuery Subject:'The Subject Of The Email You Want To Delete', From:'The Sender', Received:'Today' -DeleteContent -Force}
}

Step #2b: The following will simply log the results of your search in case you want to test the search and delete first.

$databases = Get-MailboxDatabase
ForEach ($database in $databases) {
	$mailboxes = Get-Mailbox -Database $database
	ForEach ($mailbox in $mailboxes)
	{ Search-Mailbox $mailbox.identity -SearchQuery Subject:'The Subject Of The Email You Want To Log', From:'The Sender', Received:'Today' -TargetMailbox administrator -TargetFolder "SearchAndDeleteLog" -LogOnly -LogLevel Full}
}

Step #2c: The following searches all mailboxes and both removes the e-mail and logs the results. I changed the Search Query to highlight the searching of an attachment named ‘ILoveYou*’. This will look for any attachment with ILoveYou at the prefix of the file name.

$databases = Get-MailboxDatabase
ForEach ($database in $databases) {
	$mailboxes = Get-Mailbox -Database $database
	ForEach ($mailbox in $mailboxes)
	{ Search-Mailbox $mailbox.identity -SearchQuery attachment:ILoveYou* -TargetMailbox administrator -TargetFolder "SearchAndDeleteLog" -LogOnly -LogLevel Full -DeleteContent -Force}
}

Step #2d: The following will delete all e-mails matching your search query but will prompt you before each deletion takes place. This is accomplished by removing the -Force from the Search-Mailbox command.

$databases = Get-MailboxDatabase
ForEach ($database in $databases) {
	$mailboxes = Get-Mailbox -Database $database
	ForEach ($mailbox in $mailboxes)
	{ Search-Mailbox $mailbox.identity -SearchQuery Subject:'The Subject Of The Email You Want To Delete', From:'The Sender' -DeleteContent}
}

Step #2e: The following highlights a simpler method for environments with a smaller footprint of mailboxes without the need to loop through the databases. By default the result size for the Get-Mailbox command is 1000 mailboxes. Setting -ResultSize to Unlimited allows us to search all mailboxes.

Get-Mailbox -ResultSize Unlimited | Search-Mailbox -SearchQuery Subject:'The Subject Of The Email You Want To Delete', From:'The Sender' -DeleteContent

Step #2f: The following highlights utilizing server names instead of the mailboxes as per request. I prefer to use mailbox databases since they usually contain less users. If servers work better in your environment you could use the following method:

$servers = Get-MailboxServer
ForEach ($server in $servers) {
	$mailboxes = Get-Mailbox -Server $server
	ForEach ($mailbox in $mailboxes)
	{ Search-Mailbox $mailbox.identity -SearchQuery Subject:'The Subject Of The Email You Want To Delete', From:'The Sender' -DeleteContent}
}

As always, leave a comment with your scenario and I will update the blog post with the solution.

14 Responses

  1. Dustin says:

    Excellent information! thanks! What about larger environments where multiple mailbox servers are used, can we recurse each server too, by looping it with get-mailboxserver?

  2. Adrian Rodriguez says:

    Nice work. To remedy the memory issue involved with using the foreach statement consider using ForEach-Object instead.

    • Steve Parankewich says:

      Yeah the ForEach-Object is a good one to use as well, and is even more memory efficient. I like using ForEach since it has better performance.

  3. Jimmy says:

    Great information. Does anyone know the exact string to enter into powershell if you want to delete all mail sent by a specific accross all mailboxes?

  4. Joshua McAlister says:

    For my usage, we needed to quickly find which mailboxes received an e-mail from a sender recently, so that it may be removed. Searching 7000+ mailboxes takes way too much time, so we use Get-MessageTrackingLogs.

    THIS ONLY WORKS WHEN THE INFORMATION IS STILL IN THE MESSAGE TRACKING LOGS

    #

    # Enable Exchange Management Powershell from standard PowerShell
    Add-PSSnapin Microsoft.Exchange.Management.PowerShell.SnapIn;

    $mySender = “malware@contoso.com”

    # Fast Search Recent E-mails using the Message Tracking Log
    $myMailboxServers = Get-MailboxServer
    ForEach ($Server in $myMailboxServers)
    {
    $Server = $Server.Name
    $mySearchResults = Get-MessageTrackingLog -Server $Server -Sender $mySender
    # Include the -MessageSubject in the line above if known and you want to filter by Subject:
    # $mySubject = “secure document”
    # $mySearchResults = Get-MessageTrackingLog -Server $Server -MessageSubject $mySubject
    # Optionally filter by Event: ( -EventId “RECEIVE” )
    $mySearchResults
    }

    Then the $mySearchResults can be used to search only those mailboxes.

    Good Luck!
    Josh

    • Steve Parankewich says:

      Thanks for adding your solution! Yes we have 15,000 mailboxes so we span multiple search sessions or we first track down the recipients which is the same as your solution.

  5. Patrick Smythe says:

    How can this be done by Message ID instead of subject? Search and destroy by subject also gets communications to end users which needs to remain, and since we don’t always know if Security sends out the notices with non-matching subject lines, this approach does not work for us. Our Security department has a use case for search/delete via Message ID specifically, however I’m not finding that this works with the search-mailbox cmdlet.

    • Steve Parankewich says:

      Are you saying that you know the MessageID of the e-mail you would like to delete and would like to know the search syntax to find it by that MessageID?

  6. Scott says:

    Hello! Great stuff here. I am able to get the following script to run, but I have to enter a mailbox folder and user manually for every mailbox encountered. Is there a way to get this to auto populate for each mailbox searched?

    Also, I could not get this to work without putting quotes around the search terms. Also, I seem to be getting false positives in my results.

    Any help? Thanks! Scott

    $databases = Get-MailboxDatabase
    ForEach ($database in $databases) {
    $mailboxes = Get-Mailbox -Database $database
    ForEach ($mailbox in $mailboxes)
    { Search-Mailbox $mailbox.identity -SearchQuery attachment:”attachment search*” -DoNotIncludeArchive -LogOnly -LogLevel Full}
    }

    • Steve Parankewich says:

      The reason why is because you are specifying LogOnly so you just need to add the target user you want to send the logs to. That is why you have that issue.

  1. October 2, 2015

    […] power comes great responsibility. For a full run down on how to accomplish this head on over to PowerShellBlogger.com. I look forward to seeing everyone again next […]

  2. October 14, 2015

    […] http://powershellblogger.com/2015/10…nge-mailboxes/ […]

Leave a Reply

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