Exchange Distribution Group Creation Report

Introduction

For some organizations, allowing end users to create and manage their own Distribution Groups is a standard practice. It usually alleviates work from ServiceDesk or second/third line support teams and gives users more responsibility and freedom to perform their role.

Although this is not enabled by default in Exchange 2016, it is easy to enable it. Since Exchange 2010, that the MyDistributionGroups management role enables individual users to create, modify and view distribution groups, and to modify, view, remove and add members to distribution groups they own:


Figure 1

While this is indeed a great feature for some organizations, it is always important to have a good naming convention in place and ensure that users adhere to it. But no matter how much we tell users how they should be creating a distribution group, we all know there will be situations where the group is not created as it should have been.

In one hand, for IT to check every day all the groups users created would cause some overhead. On the other hand, leave them for too long and then it might be difficult to rectify a wrongly-created distribution group. So why not automatically generate a report when new groups are created for IT to look at? That way they do not need to keep constantly checking and it is quick and easy to make sure the newly-created groups are OK. So let’s get to it.

Distribution Groups I Own

Using Outlook on the Web (or Outlook Web App or simply OWA as it is most commonly known), users can create and manage their own distribution groups:


Figure 2

For this article, I will create a few test groups pretending to be an end user:


Figure 3

Once I am done, I can see all the groups I created:


Figure 4

Now that we have a few groups, let us start with the script.

Script

Using the Get-DistributionGroup cmdlet and the WhenCreated property we can easily check recently created groups. For example, if we want to get a list of all the groups created in the last 24 hours, all we have to do is run the following cmdlet:

Get-DistributionGroup | Where {$_.WhenCreated -ge (Get-Date).AddDays(-1)}

But what about when users create a group and then delete it? Do we want to know about it? Personally, I would want to know in case users are creating loads of test groups and then deleting them, as I have seen happening. To cater for these cases, we will use the Administrator Audit Logging which tells us everything that changes in Exchange, including the creation of groups. As such, we need to make sure this feature is enabled (which it is by default), and that is capturing at least the New-DistributionGroup cmdlet. We do this by using the Get-AdminAuditLogConfig cmdlet and the Set-AdminAuditLogConfig if necessary:


Figure 5

Once we are certain the admin audit logs are capturing the information we need, this is what it will look like when we search for created groups using the Search-AdminAuditLog -Cmdlets New-DistributionGroup cmdlet (the following output has been truncated to only include relevant information):

ObjectModified     : nunomota.pt/Users/Dept - Information Technology
CmdletName         : New-DistributionGroup
CmdletParameters   : {ManagedBy, Name, CopyOwnerToMember, Members, Alias, MemberJoinRestriction}
ModifiedProperties : {}
Caller             : nunomota.pt/Users/Mota
Succeeded          : True
Error              :
RunDate            : 21/01/2016 13:54:25

Using this information, we can see who created which distribution group, even if they deleted it straight after!

To start with, we define a couple or parameters. The LogFile parameter is used to specify a log file (and location if we want to) to record whenever a report gets sent , while the NoLog switch is used to specify that the script should not log this information.

[CmdletBinding()]

Param (

[Parameter(Position = 0, Mandatory = $False, HelpMessage = "The log file where to write the script's actions.")]

[ValidateNotNullOrEmpty()]

[String] $LogFile = "Send_DG_Report.txt",

[Parameter(Position = 1, Mandatory = $False, HelpMessage = "If used, no log file will be created.")]

[Switch] $NoLog = $False

)

For this example, our script will be running every hour and will search the logs for any distribution group created in the last hour. Obviously we can change it to run every 5 minutes, every 24h, once a week, etc.

Write-Verbose "Searching Admin Audit Logs"

$strStartFrom = (Get-Date).AddMinutes(-60)

Now we start searching the Admin Audit logs, but first we create an array to hold objects for every distribution group we find containing the information we want to include in the report:

[Array] $DGs = @()

Search-AdminAuditLog -StartDate $strStartFrom -Cmdlets New-DistributionGroup | Sort RunDate | % {

Once we find a group, we will use the ObjectModified property of the log entry which contains the group name in the following format:

<domain>/<OrganizationalUnit>/<Name>

Such as:

nunomota.pt/Users/Dept - Information Technology

As we only want the name, we need to split this string by “/” and get the string after the last “/”:

$DG = $_.ObjectModified.Split("/")

$DG = $DG[$DG.Count - 1]

To find out who created the group, we use the Caller property of the log entry, which is in the same format so we use the same approach as above. However, Caller shows the account name of the user but I am also interested in its DisplayName so we use the Get-Mailbox cmdlet as well:

$user = $_.Caller.Split("/")

$user = $user[$user.Count - 1]

$userDN = (Get-Mailbox $user).DisplayName

At this stage, we have found at least one distribution group created in the last hour, but how do we know if it still exists? We could search the Admin Audit Logs again for any entry for Remove-DistributionGroup, but the approach I took is to use Get-DistributionGroup. If the $group variable ends up empty, we know that group has been removed:

$group = Get-DistributionGroup $DG -ErrorAction SilentlyContinue

We now have almost all the information we want, so we can create an object to store it. First, we check to see if the group was created by an administrator, in which case we are not interested in it being included in the report (remember to replace “admin” with the name of the administrator you want to exclude, and to add more checks if you want to exclude multiple administrators).

We then create the object, include when the group was created, the user alias and/or display name (in my case I am only including the alias, the group’s display name, the email address and how many members it currently has). Using If ($group) we will be able to tell if the group still exists or not. If not, we just write “DG Deleted” in the report.

After the object is created, we add it to our array:

If ($user -ne "admin") {

$DG = New-Object PSObject -Property @{

Date        = $_.RunDate

UserAlias   = $user

#UserDN      = $userDN

DisplayName = If ($group) {$group.DisplayName} Else {$DG}

Email       = If ($group) {$group.PrimarySmtpAddress} Else {"DG Deleted"}

Members     = If ($group) {(Get-DistributionGroupMember $group).Count} Else {"DG Deleted"}

}

$DGs += $DG

}

}

Now we either have an array with one or more objects representing distribution groups, or none were created in the last hour, so we either terminate the script here or we send a report:

If (!$DGs) {

Write-Verbose "No Distribution Groups were created in the last hour."

} Else {

Write-Verbose "Found $($DGs.count) DG(s)."

SendReport $DGs

}

Creating the report is mainly HTML code, which is beyond the scope of this article:

Function SendReport ($DGs) {

Write-Verbose "Building HTML report"

# HTML colours

$HTMLtableHeader1 = "#002C54" # Dark Blue

$HTMLtableHeader2 = "#325777" # Lighter Blue

$HTMLfontBlack = "#000000"

$HTMLfontWhite = "#FFFFFF"

$reportBody =     "<!DOCTYPE html>

<HTML>

<head>

<title>Distribution Groups Email Notification</title>

<style>

body {

font-family:Verdana,Arial,sans-serif;

background-color: white;

color: #000000;

}

table {

border: 0px;

border-collapse: separate;

padding: 3px;

}

tr, td, th { padding: 3px; }

th {

color: #FFFFFF;

font-weight: bold;

text-align: center;

}

h1,h2,h3,h4,h5 { color: $HTMLtableHeader1; }

</style></head>"

$reportBody +=    "<BODY>

<h2 align=""center"">Distribution Groups Report</h2>

<h4 align=""center"">$((Get-Date).ToString())</h4>

</font> <br>"

$reportBody += "<table>

<tr bgcolor=""$HTMLtableHeader2""><th>Created On</th><th>Created By</th><th>DG Display Name</th><th>DG Email Address</th><th># Members</th>"

$greyRow = $False

ForEach ($DG in $DGs) {

$reportBody += "<tr"

If ($greyRow) {$reportBody += " style=""background-color:#dddddd"""; $greyRow = $False} Else { $greyRow = $True }

$reportBody += "><td>$($DG.Date)</td><td>$($DG.UserAlias)</td><td>$($DG.DisplayName)</td><td>$($DG.Email)</td><td>$($DG.Members)</td>"

}

$reportBody += "</table></BODY></HTML>"

Write-Verbose "Sending Email"

Send-MailMessage -From "ExchangeAdmin@nunomota.pt" -To "nuno@nunomota.pt" -Subject "Distribution Group Report" -Body $reportBody -BodyAsHTML -SMTPserver mail.nunomota.pt

Just before we exit the Function, we check if we need to log anything. In this case, we just save the date the report was sent and how many distribution groups it included:

If (!$NoLog) {"$((Get-Date).ToShortDateString()), $((Get-Date).ToShortTimeString()), Sent report regarding $($DGs.count) DG(s)." >> $LogFile}

}

Testing the Script

Time for testing! After creating some distribution groups, we run the script using the –Verbose parameter and we can see what the script is doing because we used Write-Verbose throughout the script:


Figure 6

So we know the script detected 6 newly-created groups which, hopefully, will be listed in the report. Which they are:


Figure 7

We can easily tell if any group was created and subsequently deleted. With some more HTML code, we could even highlight the entire row for example:


Figure 8

If we want, we can easily add other information, such as the members of each group, the SamAccountName of the user who created the group, and much, much more.

Final Script

# Script:     Send_DG_Report.ps1

# Purpose:    Send HTML report of newly created Distribution Groups

# Author:     Nuno Mota

# Date:       Nov 2015

# Version:    0.1

[CmdletBinding()]

Param (

[Parameter(Position = 0, Mandatory = $False, HelpMessage = "The log file where to write the script's actions.")]

[ValidateNotNullOrEmpty()]

[String] $LogFile = "Send_DG_Report.txt",

[Parameter(Position = 1, Mandatory = $False, HelpMessage = "If used, no log file will be created.")]

[Switch] $NoLog = $False

)

Function SendReport ($DGs) {

Write-Verbose "Building HTML report"

# HTML colours

$HTMLtableHeader1 = "#002C54" # Dark Blue

$HTMLtableHeader2 = "#325777" # Lighter Blue

$HTMLfontBlack = "#000000"

$HTMLfontWhite = "#FFFFFF"

$reportBody =     "<!DOCTYPE html>

<HTML>

<head>

<title>Distribution Groups Email Notification</title>

<style>

body {

font-family:Verdana,Arial,sans-serif;

background-color: white;

color: #000000;

}

table {

border: 0px;

border-collapse: separate;

padding: 3px;

}

tr, td, th { padding: 3px; }

th {

color: #FFFFFF;

font-weight: bold;

text-align: center;

}

h1,h2,h3,h4,h5 { color: $HTMLtableHeader1; }

</style></head>"

$reportBody +=    "<BODY>

<h2 align=""center"">Distribution Groups Report</h2>

<h4 align=""center"">$((Get-Date).ToString())</h4>

</font> <br>"

$reportBody += "<table>

<tr bgcolor=""$HTMLtableHeader2""><th>Created On</th><th>Created By</th><th>DG Display Name</th><th>DG Email Address</th><th># Members</th>"

$greyRow = $False

ForEach ($DG in $DGs) {

$reportBody += "<tr"

If ($greyRow) {$reportBody += " style=""background-color:#dddddd"""; $greyRow = $False} Else { $greyRow = $True }

$reportBody += "><td>$($DG.Date)</td><td>$($DG.UserAlias)</td><td>$($DG.DisplayName)</td><td>$($DG.Email)</td><td>$($DG.Members)</td>"

}

$reportBody += "</table></BODY></HTML>"

Write-Verbose "Sending Email"

Send-MailMessage -From "ExchangeAdmin@nunomota.pt" -To "nuno@nunomota.pt" -Subject "Distribution Group Report" -Body $reportBody -BodyAsHTML -SMTPserver mail.nunomota.pt

If (!$NoLog) {"$((Get-Date).ToShortDateString()), $((Get-Date).ToShortTimeString()), Sent report regarding $($DGs.count) DG(s)." >> $LogFile}

}

# Get the date and time for 60 minutes ago. This will be the starting point to search the message tracking logs

Write-Verbose "Searching Admin Audit Logs"

$strStartFrom = (Get-Date).AddMinutes(-60)

[Array] $DGs = @()

Search-AdminAuditLog -StartDate $strStartFrom -Cmdlets New-DistributionGroup | Sort RunDate | % {

$DG = $_.ObjectModified.Split("/")

$DG = $DG[$DG.Count - 1]

$user = $_.Caller.Split("/")

$user = $user[$user.Count - 1]

$userDN = (Get-Mailbox $user).DisplayName

$group = Get-DistributionGroup $DG -ErrorAction SilentlyContinue

If ($user -ne "admin") {

$DG = New-Object PSObject -Property @{

Date        = $_.RunDate

UserAlias   = $user

#UserDN      = $userDN

DisplayName = If ($group) {$group.DisplayName} Else {$DG}

Email       = If ($group) {$group.PrimarySmtpAddress} Else {"DG Deleted"}

Members     = If ($group) {(Get-DistributionGroupMember $group).Count} Else {"DG Deleted"}

}

$DGs += $DG

}

}

If (!$DGs) {

Write-Verbose "No Distribution Groups were created in the last hour."

} Else {

Write-Verbose "Found $($DGs.count) DG(s)."

SendReport $DGs

}

Conclusion

In this article, we have developed a script to generate a report of Distribution Groups created by end users.

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

8 risks that cybersecurity insurance can manage or mitigate

As cyberattacks become more common and more expensive to recover from, companies are considering cybersecurity insurance as part of their…

2 days ago

Chips are down: PokerTracker.com hit by Magecart hackers

Users of the popular PokerTracker.com site are getting way more than they bet on thanks to a vulnerability that opened…

2 days ago

Biggest 2019 website outages and what caused them

The major website outages that occurred so far this year due to systemic flaws and poor infrastructure are certainly a…

2 days ago

Time’s up: Why you should change your password expiration policy

Forced password expirations are a relic from days gone by and may actually weaken security. Is it time to alter…

3 days ago

IBM updates cloud-native software with Red Hat OpenShift

IBM’s purchase of Red Hat is paying dividends for users optimizing their technology for the cloud era. Here’s more on…

3 days ago

Ease the frustration of managing Office 365 in your enterprise

Office 365 has brought many efficiencies to businesses, but administering and managing it can often be frustrating. Fortunately, CoreView has…

3 days ago