Powershell script to report on total send/received e-mails in Exchange v2.0

An updated and improved version of my old script from here.

This script has been tested against Exchange 2016 CU4. I do not know if it will work against older versions of Exchange.

The script can be configured to run as a scheduled task and it generates a e-mail report of users who have sent more than ‘x’ e-mails so far today or the previous day.

You can now exclude specific e-mails from the report by placing them in the ‘exceptionList.txt’ file which is created after the scripts first run

We use this script to find compromised accounts that are blasting out spam.

# Users who sent more than 'x' e-mails so far today or yesterday
# The output of this report is then e-mailed
#
# This script must be called using the argument '-TimeFrame "yesterday"' or '-TimeFrame "today"'
#
# On first run a file called 'exceptionList.txt' in the same directory as itself if the file doesn't exist. This means the script will need
# read-write access to the location on the first run and read-only access from then on. Once created you can add e-mail addresses into the
# text file (one per line) that you want excluded from the report.
#
# Note: This script has been specifically tested against Exchange 2016 CU4 and may not work against previous versions of Exchange
#
# Requirements: The account that runs this script needs at least the "View-Only Organization Management" and the "Records Management" role in Exchange
#
# Created by: Eric Schewe
# Created on: 2017-08-15
#

# This command must be run with '-timeFrame today' or '-timeFrame yesterday' specified
Param(
    [Parameter(Mandatory=$False)]
    [string]$timeFrame
)

# ------------------------- (START) Customize this section per your deployment -------------------------
# Set the minimum sent e-mails count you care about. Anyone who sends more than this number will appear in the report
$minimumEmails = 500

# Location of script, no trailing slash
$scriptLocation = ""

# E-mail stuff
# Multiple e-mail addresses should be in this format "<[email protected]>, <[email protected]>"
$to = ""
$from = "[email protected]"
$smtpServer = "smtp.mydomain.com"

# Exchange server you want to run this PowerShell against
$exchangePowerShellServer = "exchange01.mydomain.com"
# ------------------------- (END) Customize this section per your deployment -------------------------

if ($timeFrame -match "today") {
    # How we define today
    $startTime = (get-date -Hour 00 -Minute 00 -Second 00)
    $endTime = (get-date -Hour 23 -Minute 59 -Second 59)
    $subject = "Staff who sent over $minimumEmails e-mails for $startTime to now"

}
elseif ($timeFrame -match "yesterday") {

    # How we define yesterday
    $startTime = (get-date -Hour 00 -Minute 00 -Second 00).AddDays(-1)
    $endTime = (get-date -Hour 23 -Minute 59 -Second 59).AddDays(-1)
    $subject = "Staff who sent over $minimumEmails e-mails for $startTime to $endTime"

}
else {

    # Output parameter information if it isn't specified
    Write-Host "Please specify '-timeFrame today' or '-timeFrame yesterday' when invoking this script" -ForeGroundColor "Red"
    Exit

}

# Debugging - This is useful if you're executiong the script under your account and need to test with different credentials
#$UserCredential = Get-Credential
#$Session = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri http://brockton-03.it.int.viu.ca/PowerShell/ -Authentication Kerberos -Credential $UserCredential

# Production - This assumes you're running this as a scheduled task under a user account with the proper credentials so we don't prompt for credentials
$Session = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri http://$exchangePowerShellServer/PowerShell/ -Authentication Kerberos

# Assuming we've gotten here in the script start a Exchange session
Import-PSSession $Session -AllowClobber

# Check if the exception list exists, create it if it doesn't and then read the exception list into the script skipping blank lines and comments
if (!(Test-Path "$($scriptLocation)\exceptionList.txt")) {
    New-Item -path $($scriptLocation) -name exceptionList.txt -type "file" -value "# One entry per line, E-mail addresses are NOT case sensitive" | Out-Null
}
$exceptionList = Get-Content "$($scriptLocation)\exceptionList.txt" | Where-Object { $_ -notmatch "#" -or $_ -notmatch "" }

# Get a list of transport servers in case there is more than one
$transportServers = Get-TransportService

# Initialize the variable so we can append to it. Initial stats array is cast differently because we can't use "Group-Object" on an ArrayList and we can't use .Remove on a Array
$mailStats = @()
[System.Collections.ArrayList]$mailStatsToDelete = @()
[System.Collections.ArrayList]$mailStatsArrayList = @()

# Search the message tracking log on each transport server
foreach ($server in $transportServers) {

    # Search the message tracking log within a time frame on each transport server only looking for the 'SENDEXTERNAL' EventID
    $mailStats += Get-MessageTrackingLog -Server $server.name -Start $startTime -End $endTime -EventID "SENDEXTERNAL" -ResultSize Unlimited

}

# Filter out anyone that didn't send enough e-mails
$mailStats = $mailStats |Group-Object -Property Sender | Where-Object {$_.Group.Recipients.Count -gt $minimumEmails}

# Convert the Array to an ArrayList so we can use the .Remove method and so we can sort things later
foreach ($stat in $mailStats) {

    $line = "" | Select-Object Email,Total
    $line.Email = $stat.Name
    $line.Total = $stat.Group.Recipients.Count
    $mailStatsArrayList += $line

}

# If there are entries in the exception list grab all of the matches and store them in a seperate array
if ($exceptionList.Count -ne 0) {
    # Go through the list of exceptions and find any matches and store them
    foreach ($exception in $exceptionList) {

        foreach ($stat in $mailStatsArrayList) {

            if ($stat.Email -like $exception) {

                $mailStatsToDelete += $stat

            }

        }

    }

    # Remove the matched exceptions from the final results
    foreach ($stat in $mailStatsToDelete) {

        $mailStatsArrayList.Remove($stat)

    }
}

# Check and see if there are any results to report
if ($mailStatsArrayList.Count -ne 0) {

    # Sort and format the output into a table for the e-mail
    $results = $mailStatsArrayList | Sort-Object -Property Total -Descending | Format-Table -Property @{Expression={$_.Total};Label="Count";Width=15; Alignment="left"},@{Expression={$_.Email};Label="Sender"; Width=250; Alignment="left"} |Out-String -width 300

    # Send the e-mail
    $smtp = new-object Net.Mail.SmtpClient($smtpServer)
    $smtp.Send($from, $to, $subject, $results)

}

# Clean-up our session
Remove-PSSession $Session

2 thoughts on “Powershell script to report on total send/received e-mails in Exchange v2.0”

  1. We recently implemented MFA and noticed the script we had working on stopped working.
    Any ideas what we need to do to get this working again?

    Reply
    • I’d guess the account running the script is now trying to prompt for MFA. I’d try running it on an account with out MFA enabled and see how that goes.

      Reply

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.