Mar 26, 2016

How to Develop a Script to Generate Exchange Distribution Group Creation Report

In this article, we will develop a script to generate a report of Distribution Groups created by end users. 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:

 

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.

Image
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:

Image

Figure 2

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

Image 
Figure 3

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

Image 
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:

Image 
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;
                                    font-size: 10pt;
                                    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:

Image
Figure 6

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

Image
 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:

Image
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;
                                    font-size: 10pt;
                                    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.


Post a Comment

 
TECH SUPPORT © 2012 - Designed by INFOSBIRD