Post by Paul C on Jul 21, 2012 18:27:14 GMT 5.5
AutoReply Storms - Mail Stroms - How to stop
To detect and prevent e-mail storms caused by Outlook AutoReply rules.
An e-mail storm is normally associated with a big spike in the number of e-mails being sent due to the Reply All functionality on a large Distribution List [DL]. These storms start when multiple members of the DL reply to the entire list around the same time in response to the original e-mail followed by other members also replying asking to be removed from the DL or answering someone’s question. When enough members reply to these messages, a chain reaction of e-mails is triggered, and this dramatically increases the generated traffic and can bring e-mail servers to a halt.
we will focus on storms not created by DLs but by an AutoReply rule in Outlook (not to be confused with Out-Of-Office [OOF] messages).
A Microsoft Outlook AutoReply rule is a feature only available through Outlook and Exchange that allows users to send automated replies to incoming messages. The biggest difference between an AutoReply and an OOF e-mail is that the OOF only replies once per sender when they first e-mail the user, while the AutoReply rule will reply to every single e-mail received by the mailbox.
This rule is often used on generic mailboxes to inform senders that they will receive a response shortly or that the mailbox is no longer in use, for example, and is created using the have server reply using a specific message action
Storm Generating
The problem with an AutoReply rule is when both the sender and the recipient have one enabled. Imagine UserA and UserB:
As you can imagine, this can generate a huge number of e-mails per minute and the only way to stop them is for one of the users to temporarily disable the rule... This is known as an AutoReply Storm.
Exchange is smart enough to prevent these storms from happening when both sender and recipient are internal users.
However, if one of them is external, Exchange will not stop these as every e-mail received from an external sender is seen as a “b new” e-mail without any association with previous e-mails even though they are from the same sender and with the same subject.
This is a common problem faced by Exchange Administrators and many organizations chose to simply remove the functionality altogether. To do this, we can block AutoReply e-mails at a global level which would prevent everyone, with no exception, from AutoReplying e-mails to outside the domain:
If you haven’t created any additional remote domain (for a partner company, for example), the Default domain will match everything outside your domain, thus blocking any AutoReply to the outside world.
Most organizations it is not an option to prevent AutoReply e-mails, they can use a script that tries to detect and prevent storms caused by them.
Detecting Storms
As these storms generate multiple identical e-mails per minute, the script basically looks for e-mails sent from a sender to the same recipient with the exact same subject in a specific timeframe. If it sees a large number of these e-mails in a short period of time, it is fair to assume it is a storm.
To start off with, we will set the script to run every 15 minutes and look for users who sent 25 or more e-mails in the last 15 minutes:
Note:
Because every environment is different, and just because the script is 100% accurate so far for me, it doesn’t mean it will be for you! You might have to fine-tune it by playing around with these parameters or even exclude some mailboxes from “inspection”.
Now let’s analyze the Message Tracking Logs. To do this, we search for EventID of SEND and Source of SMTP as we are looking for e-mails sent outside our organization. We only want those users that sent 25 or more e-mails so let us group all e-mails by sender and filter them based on the number of e-mails sent. If there are none, then we simply exit:
Note:
if you have multiple Active Directory sites or are running in a co-existence scenario with multiple Exchange versions, you might want to include -and $_.Recipients -notmatch "your_domain” to make sure you only include e-mails sent to recipients outside your domain. This is because the previous search will capture internal e-mails if your environment matches these conditions. For example, if you are running both Exchange 2007 and 2010, e-mails sent from a user in 2007 to a user in 2010 will be sent over SMTP from Exchange 2007 to 2010. To ignore these, let’s update our search:
We now have in $logEntries a list of all users who sent 25 or more e-mails within the last 15 minutes. However, we need to search the Transport Logs again for each of these users in order to check if they sent multiple e-mails to the same recipient and with the same subject. To achieve this, the easiest way I found was to use a Hash Table, a data structure that maps values known as keys (e.g., a person's name) to their associated values (e.g., their telephone number). We will go through all the e-mails found in our search and add them to the hash table where each key will be a string composed of the sender’s e-mail address, the recipient’s e-mail address and the message subject. When we first process an e-mail, the initial value for each key will be one; if there is more than one e-mail with the exact same subject sent to the same recipient from the same sender, the value will be incremented by one. At the end, the hash table will give us a list in the following format:
In the above picture we can see that nuno@letsexchange.com sent 33 e-mails with 3 different subjects. Out of these 3, we are only interested in the one with the subject of “Trying to Generate Storm” because it represents more than 25 e-mails in 15 minutes which indicates a possible storm.
The 19 e-mails sent from mota@letsexchange.com also indicate a possible storm but because it didn’t cross the 25 threshold, the script will ignore it.
To build this hash table, we need to go through all the e-mails saved into $logEntries so we can group them into a string made of “sender_address, recipient_address, email_subject” and add them to our hash table:
A couple of notes about this code:
To be able to sort/filter a hash table, we need to transform its content into individual objects by using the GetEnumerator method;
If in the hash table we have more than 1 case of storms, individual e-mails will be sent instead of everything in just one e-mail (we will change this in the next part of this article).
If a storm is detected, the e-mail will contain its details which match what we have in the hash table:
To schedule the script to run every 15 minutes and see how we can automatically put an end to a storm once it is detected!
Development of a script to help detect and prevent e-mail storms caused by Outlook AutoReply rules.
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:
We can even explore the rules and see what they exactly do:
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)...
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*:
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:
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:
Script
Below is the script with everything we discussed so far. Alternatively you can download it here. 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.
reference article: Nuno Mota
To detect and prevent e-mail storms caused by Outlook AutoReply rules.
An e-mail storm is normally associated with a big spike in the number of e-mails being sent due to the Reply All functionality on a large Distribution List [DL]. These storms start when multiple members of the DL reply to the entire list around the same time in response to the original e-mail followed by other members also replying asking to be removed from the DL or answering someone’s question. When enough members reply to these messages, a chain reaction of e-mails is triggered, and this dramatically increases the generated traffic and can bring e-mail servers to a halt.
we will focus on storms not created by DLs but by an AutoReply rule in Outlook (not to be confused with Out-Of-Office [OOF] messages).
A Microsoft Outlook AutoReply rule is a feature only available through Outlook and Exchange that allows users to send automated replies to incoming messages. The biggest difference between an AutoReply and an OOF e-mail is that the OOF only replies once per sender when they first e-mail the user, while the AutoReply rule will reply to every single e-mail received by the mailbox.
This rule is often used on generic mailboxes to inform senders that they will receive a response shortly or that the mailbox is no longer in use, for example, and is created using the have server reply using a specific message action
Storm Generating
The problem with an AutoReply rule is when both the sender and the recipient have one enabled. Imagine UserA and UserB:
- UserA sends an e-mail to UserB;
- The AutoReply rule in user’s B mailbox sends an automatic reply to UserA;
- UserA also has an AutoReply rule in place;When Exchange receives this e-mail for UserA, it automatically sends a reply back as UserA also has an AutoReply set
- UserB receives the automatic reply from UserA and sends another automatic reply;
- and it goes on ...
As you can imagine, this can generate a huge number of e-mails per minute and the only way to stop them is for one of the users to temporarily disable the rule... This is known as an AutoReply Storm.
Exchange is smart enough to prevent these storms from happening when both sender and recipient are internal users.
However, if one of them is external, Exchange will not stop these as every e-mail received from an external sender is seen as a “b new” e-mail without any association with previous e-mails even though they are from the same sender and with the same subject.
This is a common problem faced by Exchange Administrators and many organizations chose to simply remove the functionality altogether. To do this, we can block AutoReply e-mails at a global level which would prevent everyone, with no exception, from AutoReplying e-mails to outside the domain:
Set-RemoteDomain Default –AutoReplyEnabled $False
If you haven’t created any additional remote domain (for a partner company, for example), the Default domain will match everything outside your domain, thus blocking any AutoReply to the outside world.
Most organizations it is not an option to prevent AutoReply e-mails, they can use a script that tries to detect and prevent storms caused by them.
Detecting Storms
As these storms generate multiple identical e-mails per minute, the script basically looks for e-mails sent from a sender to the same recipient with the exact same subject in a specific timeframe. If it sees a large number of these e-mails in a short period of time, it is fair to assume it is a storm.
To start off with, we will set the script to run every 15 minutes and look for users who sent 25 or more e-mails in the last 15 minutes:
[Int] $intTime = 15
[Int] $intNumEmails = 25
[DateTime] $dateStartFrom = (Get-Date).AddMinutes(-$intTime
[Int] $intNumEmails = 25
[DateTime] $dateStartFrom = (Get-Date).AddMinutes(-$intTime
Note:
Because every environment is different, and just because the script is 100% accurate so far for me, it doesn’t mean it will be for you! You might have to fine-tune it by playing around with these parameters or even exclude some mailboxes from “inspection”.
Now let’s analyze the Message Tracking Logs. To do this, we search for EventID of SEND and Source of SMTP as we are looking for e-mails sent outside our organization. We only want those users that sent 25 or more e-mails so let us group all e-mails by sender and filter them based on the number of e-mails sent. If there are none, then we simply exit:
$logEntries = Get-TransportServer | Get-MessageTrackingLog -ResultSize Unlimited -Start $dateStartFrom -EventId SEND | ? {$_.Source -eq "SMTP"} | Group Sender | ? {$_.Count -ge $intNumEmails}
If ($logEntries -eq $null) { Exit }
If ($logEntries -eq $null) { Exit }
Note:
if you have multiple Active Directory sites or are running in a co-existence scenario with multiple Exchange versions, you might want to include -and $_.Recipients -notmatch "your_domain” to make sure you only include e-mails sent to recipients outside your domain. This is because the previous search will capture internal e-mails if your environment matches these conditions. For example, if you are running both Exchange 2007 and 2010, e-mails sent from a user in 2007 to a user in 2010 will be sent over SMTP from Exchange 2007 to 2010. To ignore these, let’s update our search:
$logEntries = Get-TransportServer | Get-MessageTrackingLog -ResultSize Unlimited -Start $dateStartFrom -EventId SEND | ? {$_.Source -eq "SMTP" -and $_.Recipients -notmatch "letsexchange.com"} | Group Sender | ? {$_.Count -ge $intNumEmails}
We now have in $logEntries a list of all users who sent 25 or more e-mails within the last 15 minutes. However, we need to search the Transport Logs again for each of these users in order to check if they sent multiple e-mails to the same recipient and with the same subject. To achieve this, the easiest way I found was to use a Hash Table, a data structure that maps values known as keys (e.g., a person's name) to their associated values (e.g., their telephone number). We will go through all the e-mails found in our search and add them to the hash table where each key will be a string composed of the sender’s e-mail address, the recipient’s e-mail address and the message subject. When we first process an e-mail, the initial value for each key will be one; if there is more than one e-mail with the exact same subject sent to the same recipient from the same sender, the value will be incremented by one. At the end, the hash table will give us a list in the following format:
In the above picture we can see that nuno@letsexchange.com sent 33 e-mails with 3 different subjects. Out of these 3, we are only interested in the one with the subject of “Trying to Generate Storm” because it represents more than 25 e-mails in 15 minutes which indicates a possible storm.
The 19 e-mails sent from mota@letsexchange.com also indicate a possible storm but because it didn’t cross the 25 threshold, the script will ignore it.
To build this hash table, we need to go through all the e-mails saved into $logEntries so we can group them into a string made of “sender_address, recipient_address, email_subject” and add them to our hash table:
# Initialize HashTable
$hashEmails = @{}
# For each sender, check all the e-mails they sent
ForEach ($logEntry in $logEntries)
{
ForEach ($email in (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
}
}
This will ensure that $hashEmails will contain data in the format that we want (Figure 2). However, please note that because an e-mail can be sent to more than one recipient at a time, the key in the hash table might be in the format of “sender_address, recipient_address, recipient2_address, recipient3_address, …, email_subject” but because when a storm happens it will only be between one sender and one recipient, we don’t need to worry about these situations.
Now that we have our hash table populated, we need to check if any of the entries have a value of 25 or higher. If yes, then we send an e-mail to the administrator. Because all the information we want is in big strings (the keys), we will need to split them in order to compose the e-mail. This can easily be done with the Split method:
$arrDetails = [Regex]::Split($storm.Name, ", ")
In this case we are splitting the string by commas, and saving it to the $arrDetails array. The problem with this is that sometimes the subjects of e-mails contain commas, so the best option is to use a character that we know users will not use. I will be using the ô character because it is not part of the English alphabet but you can use any character you want.
(...)
$hashEmails["$($email.Sender) ô $($email.Recipients) ô $($email.MessageSubject)"] += 1
(...)
ForEach ($storm in ($hashEmails.GetEnumerator() | ? {$_.Value -ge $intNumEmails} | Sort Name))
{
$arrDetails = [Regex]::Split($storm.Name, " ô ")
$strEmailBody += "`n`nSender: ", $arrDetails[0]
$strEmailBody += "`nRecipient: ", $arrDetails[1]
$strEmailBody += "`nSubject: ", $arrDetails[2]
$strEmailBody += "`n# e-mails: ", $storm.Value
Send-MailMessage -From "Administrator@letsexchange.com" -To "AdminNuno@letsexchange.com" -Subject "Storm Detected!" -Body $strEmailBody -SMTPserver "smtp.letsexchange.com" -DeliveryNotificationOption onFailure
}
$hashEmails = @{}
# For each sender, check all the e-mails they sent
ForEach ($logEntry in $logEntries)
{
ForEach ($email in (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
}
}
This will ensure that $hashEmails will contain data in the format that we want (Figure 2). However, please note that because an e-mail can be sent to more than one recipient at a time, the key in the hash table might be in the format of “sender_address, recipient_address, recipient2_address, recipient3_address, …, email_subject” but because when a storm happens it will only be between one sender and one recipient, we don’t need to worry about these situations.
Now that we have our hash table populated, we need to check if any of the entries have a value of 25 or higher. If yes, then we send an e-mail to the administrator. Because all the information we want is in big strings (the keys), we will need to split them in order to compose the e-mail. This can easily be done with the Split method:
$arrDetails = [Regex]::Split($storm.Name, ", ")
In this case we are splitting the string by commas, and saving it to the $arrDetails array. The problem with this is that sometimes the subjects of e-mails contain commas, so the best option is to use a character that we know users will not use. I will be using the ô character because it is not part of the English alphabet but you can use any character you want.
(...)
$hashEmails["$($email.Sender) ô $($email.Recipients) ô $($email.MessageSubject)"] += 1
(...)
ForEach ($storm in ($hashEmails.GetEnumerator() | ? {$_.Value -ge $intNumEmails} | Sort Name))
{
$arrDetails = [Regex]::Split($storm.Name, " ô ")
$strEmailBody += "`n`nSender: ", $arrDetails[0]
$strEmailBody += "`nRecipient: ", $arrDetails[1]
$strEmailBody += "`nSubject: ", $arrDetails[2]
$strEmailBody += "`n# e-mails: ", $storm.Value
Send-MailMessage -From "Administrator@letsexchange.com" -To "AdminNuno@letsexchange.com" -Subject "Storm Detected!" -Body $strEmailBody -SMTPserver "smtp.letsexchange.com" -DeliveryNotificationOption onFailure
}
A couple of notes about this code:
To be able to sort/filter a hash table, we need to transform its content into individual objects by using the GetEnumerator method;
If in the hash table we have more than 1 case of storms, individual e-mails will be sent instead of everything in just one e-mail (we will change this in the next part of this article).
If a storm is detected, the e-mail will contain its details which match what we have in the hash table:
To schedule the script to run every 15 minutes and see how we can automatically put an end to a storm once it is detected!
Development of a script to help detect and prevent e-mail storms caused by Outlook AutoReply rules.
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:
We can even explore the rules and see what they exactly do:
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)...
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
$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
Script
Below is the script with everything we discussed so far. Alternatively you can download it here. 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"} | Group Sender | ? {$_.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 ($logEntry in $logEntries)
{
ForEach ($email in (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 ($storm in ($hashEmails.GetEnumerator() | ? {$_.Value -ge $intNumEmails} | Sort Name))
{
$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" -DeliveryNotificationOption onFailure }
# 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"} | Group Sender | ? {$_.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 ($logEntry in $logEntries)
{
ForEach ($email in (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 ($storm in ($hashEmails.GetEnumerator() | ? {$_.Value -ge $intNumEmails} | Sort Name))
{
$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" -DeliveryNotificationOption onFailure }
reference article: Nuno Mota