If you would like to read the first part in this article series please go to Preventing AutoReply Storms (Part 1).

Introduction

In the second part of this article we will improve the e-mail sent to administrators, learn how we can schedule the script to run every 15 minutes and see how we can automatically put an end to a storm once it is detected!

Please, make sure you first check part 1 otherwise the code below will not make much sense.

Scheduling the Script

There are several methods of scheduling PowerShell scripts. The one I use is the following:

  1. Place the code in a .ps1 file (e.g. PreventStorm.ps1);
  2. In the same folder, create a batch file (e.g. PreventStorm.bat) with the following code (make sure you update all the folder paths and names accordingly):PowerShell.exe -PSConsoleFile "C:\Program Files\Microsoft\Exchange Server\V14\Bin\ExShell.psc1" -Command ". 'E:\Scripts\PreventStorm.ps1'"
  3. Use Windows Task Scheduler to create a new task that runs the batch file every 15 minutes.

Preventing Storms

Once the administrators receive the e-mail, all they have to do is either let the user know of what is happening or login to the user’s mailbox and disable the rule themselves. Both are not ideal as a storm might happen during the night or over the weekend…

So far I found two ways of trying to prevent these storms:

  1. Use the new Exchange 2010 *-InboxRule cmdlet;
  2. Create a Transport Rule to drop/redirect these e-mails.

Let’s have a look at both methods.

*-InboxRule cmdlet

Using the Get-InboxRule –Mailbox <user> cmdlet we can see all the rules that a user has on his/hers mailbox, including the AutoReply rule that caused the storm:


Figure 2.1:
Inbox Rules

We can even explore the rules and see what they exactly do:


Figure 2.2:
Rule Details

The bad news is that in all this detail there is nothing that tells us specifically that a certain rule is an AutoReply rule (notice that the Description is empty)...


Figure 2.3 -
AutoReply Rule Details

We could hope for users to use a name for the rule that would give us some indication of what it does like in this example. If this was the case, we could disable any rule that has a name of *auto*:

Get-InboxRule –Mailbox <user> | ? {$_.Name –match “auto”} | Disable-InboxRule

But there is no guarantee that we will not disable a rule called “Delete AutoTrader Emails” for example... For this reason we cannot use this method to search and disable these rules.

Transport Rule

Another option is to create a transport rule to drop or redirect e-mails that match the e-mails found in the hash table. Once you are confident the script only picks up on actual storms, and then you can implement this method.

I will give the syntax of creating these rules in Exchange 2007 and 2010 since they are very different.

When we go through the hash table we have all the details we need to create the rule. So, let’s add the suitable code for your Exchange version right before the Send-MailMessage cmdlet:

# Get the local part of the e-mail address to use as part of the name for the transport rule

$strName= [Regex]::Split($arrDetails[0], "@")[0]

# Exchange 2010

New-TransportRule -Name "Prevent Storm - $strName" -Comments "Prevent Outlook AutoReply Storm" -From $arrDetails[0] -SentToScope "NotInOrganization" -RecipientAddressContainsWords $arrDetails[1] -SubjectContainsWords $arrDetails[2] -RedirectMessageTo "quarantine@letsexchange.com" -Enabled $True -Priority 0

# Exchange 2007

# For every e-mail sent by that user

$condition1= Get-TransportRulePredicate From

$condition1.Addresses = @(Get-Mailbox $arrDetails[0])

# only when the e-mail is going Outside the organization

$condition2= Get-TransportRulePredicate SentToScope

$condition2.Scope = @("NotInOrganization")

# only for e-mails that contain the subject we want

$condition3= Get-TransportRulePredicate SubjectContains

$condition3.Words = @($arrDetails[2])

# Redirect the e-mails e-mail to the Quarantine mailbox

$action= Get-TransportRuleAction RedirectMessage

$action.Addresses = @(Get-Mailbox Quarantine)

# Create the Transport Rule itself

New-TransportRule -Name "Prevent Storm - $strName" -Comments "Prevent Outlook AutoReply Storm" -Conditions @($condition1, $condition2, $condition3) -Actions @($action) -Enabled $True -Priority 0

The disadvantage of Exchange 2007 is that we have no easy way of specifying the recipient, which means it will block all automatic replies sent from the user to external recipients... The only way around this would be to create a contact for each recipient and then create the transport rule using that contact. In Exchange 2010 we can simply use the RecipientAddressContainsWords parameter.

Note:
With 2010, we can also tell the Transport Rule to only block e-mails of the AutomaticReply type by specifying -MessageTypeMatches "OOF" but this only matches OOF e-mails...

As we only need to “stop” an AutoReply rule for a few minutes to put an end to the storm, we can delete any existing transport rules we created previously every time the script starts. This will ensure that every rule is only in place for 15 minutes which is normally enough time:

Get-TransportRule | ? {$_.Name –match "Prevent Storm"} | Remove-TransportRule –Confirm:$False

Final Script

Below is the script with everything we discussed so far. Alternatively you can download it here. I left the deletion and creation of the transport rules commented so you can safely test the script first. Don't forget to replace letsexchange.com with your domain name.

# Script:   PreventStorm.ps1

# Purpose:  Analyze the Transport Logs for possible AutoReply Storms

# Author:   Nuno Mota

# Version:  3.0 (Jan 2012)

# Initialize variables

[Int] $intTime= 15

[Int] $intNumEmails= 25

[Bool] $boolSendEmail=$False

# Get the date and time for 15 minutes ago. This will be the starting point to search the transport logs

[DateTime] $dateStartFrom= (Get-Date).AddMinutes(-$intTime)

$hashEmails= @{}

[String] $strEmailBody=""

$strEmailBody+="`n**********************************"

$strEmailBody+="`n*                                *"

$strEmailBody+="`n*    WARNING: Storm Detected!    *"

$strEmailBody+="`n*                                *"

$strEmailBody+="`n**********************************"

# Check if we previously created any Transport Rule to prevent a storm. If yes, delete it

#Get-TransportRule | ? {$_.Name -match "Prevent Storm"} | Remove-TransportRule -Confirm:$False

# Get all the users who sent 25 or more e-mails in the last 15 minutes

# If you have multiple AD sites or are running in a co-existance scenario with multiple Exchange versions, you might want to include -and $_.Recipients -notmatch "letsexchange.com

$logEntries= Get-TransportServer | Get-MessageTrackingLog -ResultSize Unlimited -Start $dateStartFrom -EventId SEND | ? {$_.Source -eq"SMTP"-and$_.Recipients -notmatch"letsexchange.com"} | GroupSender | ? {$_.Count -ge$intNumEmails}

If ($logEntries-eq$null) { Exit }

# For each sender, analyze all the e-mails they sent and put them in the HashTable

ForEach ($logEntryin$logEntries)

{

ForEach ($emailin (Get-TransportServer | Get-MessageTrackingLog -ResultSize Unlimited -Start $dateStartFrom -Sender $logEntry.Name -EventId SEND | ? {$_.Source -eq"SMTP"-and$_.Recipients -notmatch"letsexchange.com"} | Select MessageSubject, Sender, Recipients))

{

$hashEmails["$($email.Sender) ô $($email.Recipients) ô $($email.MessageSubject)"] += 1

}

}

# To sort/filter a Hash Table, we need to transform its content into individual objects by using the GetEnumerator method

# If we find any key with a value of at least 25, then we have detected a storm

ForEach ($stormin ($hashEmails.GetEnumerator() | ? {$_.Value -ge$intNumEmails} | SortName))

{

$boolSendEmail=$True

$arrDetails= [Regex]::Split($storm.Name, " ô ")

$strEmailBody+="`n`nSender:     ", $arrDetails[0]

$strEmailBody+="`nRecipient:  ", $arrDetails[1]

$strEmailBody+="`nSubject:    ", $arrDetails[2]

$strEmailBody+="`n# e-mails:  ", $storm.Value

# Get the local part of the e-mail address to use as part of the name for the transport rule

#     $strName = [regex]::split($arrDetails[0], "@")[0]

#     $ruleResult = New-TransportRule -Name "Prevent Storm - $strName" -Comments "Prevent Outlook AutoReply Storm" -From $arrDetails[0] -SentToScope "NotInOrganization" -RecipientAddressContainsWords $arrDetails[1] -SubjectContainsWords $arrDetails[2] -RedirectMessageTo "quarantine@letsexchange.com" -Enabled $True -Priority 0

#     If (!$ruleResult)

#     {

#           $strEmailBody += "`nRule Created? NO"

#     } Else {

#           $strEmailBody += "`nRule Created? Yes"

#     }

#     # For every e-mail sent by that user

#     $condition1 = Get-TransportRulePredicate From

#     $condition1.Addresses = @(Get-Mailbox $arrDetails[0])

#

#     # only when the e-mail is going Outside the organization

#     $condition2 = Get-TransportRulePredicate SentToScope

#     $condition2.Scope = @("NotInOrganization")

#

#     # only for e-mails that contain the subject we want

#     $condition3 = Get-TransportRulePredicate SubjectContains

#     $condition3.Words = @($arrDetails[2])

#

#     # Redirect the e-mails e-mail to the Quarantine mailbox

#     $action = Get-TransportRuleAction RedirectMessage

#     $action.Addresses = @(Get-Mailbox Quarantine)

#

#     # Get the local part of the e-mail address to use as part of the name for the transport rule

#     $strName = [regex]::split($email.Sender, "@")[0]

#

#     # Create the Transport Rule itself

#     New-TransportRule -Name "Prevent Storm - $strName" -Comments "Prevent Outlook AutoReply Storm" -Conditions @($condition1, $condition2, $condition3) -Actions @($action) -Enabled $True -Priority 0

}

# Send an e-mail to the administrator(s)

If ($boolSendEmail) { Send-MailMessage-From"Administrator@letsexchange.com"-To"AdminNuno@letsexchange.com"-Subject"Storm Detected!"-Body$strEmailBody-SMTPserver"smtp.letsexchange.com"-DeliveryNotificationOptiononFailure }

Conclusion

Storms caused by Outlook AutoReply rules are a common issue many Exchange Administrators face. With no easy way of preventing them without disabling the whole functionality altogether, in this article I provided a script to try to help administrators detect and prevent these storms. Hopefully Exchange will make it easier to prevent these in the future.

If you would like to read the first part in this article series please go to Preventing AutoReply Storms (Part 1).

Nuno Mota

Nuno Mota is an Exchange MVP working as a Microsoft Messaging Specialist for a financial institution. He is passionate about Exchange, Lync, Active Directory, PowerShell, and Security. Besides writing his personal Exchange blog, LetsExchange.blogspot.com, he regularly participates in the Exchange TechNet forums and is the author of the book “Microsoft Exchange Server 2013 High Availability.”

Share
Published by
Nuno Mota

Recent Posts

What are the potential disadvantages of SSL/TLS?

There’s wide consensus on the benefits of SSL/TLS. However, not as much attention has been given to SSL/TLS disadvantages.

1 day ago

Exploring native software inventory logging in Windows Server

Windows Server has built-software inventory logging that can be very useful. Here’s how to use this little-known feature.

2 days ago

Passwordless authentication: Safer, better, and about time

Passwordless authentication has quickly become one of the primary means by which users access their laptops, phones, and tablets because…

2 days ago

Automated Incident Response in Office 365 ATP simplifies cybersecurity

Microsoft has pumped up Office 365 Advanced Threat Protection with a new feature, Automated Incident Response. Here’s what you need…

2 days ago

IFA 2019: Smart TVs and even smarter wearables unveiled

What will be in your living room or on your wrist this year? It may very likely be one of…

3 days ago

Consider these SD-WAN technologies for faster, more reliable networking

As virtualization becomes a major part of organizations’ infrastructure, these SD-WAN technologies provide faster and more reliable networking solutions.

3 days ago