One of the downsides of old environments is the scope (and necessity) for clean-up. It often happens that there are more groups in an environment than users. And it is certainly not uncommon. In today’s post, we look at a small distribution group clean-up project in Microsoft Exchange.

I once embarked on such a project in an environment that had over three thousand groups, but only two thousand users. They needed an extensive clean-up operation done in their environment. However, things are not as easy as they sound. There are a few challenges in distribution group clean-up projects in Exchange, apart from distribution groups being critical to internal communication:

  • Distribution groups are often tied to external providers, such as your insurance provider.
  • Distribution groups provide members with access to Exchange resources, such as public folders.
  • Distribution groups are tied to applications within the environment that do not necessarily integrate with Exchange, and hence, use smtp.

Good care needs to be taken in order to avoid issues in email delivery. Nobody wants to miss that specific change-in-policy communication from their insurance providers. Nobody wants to miss out on important application notifications. Nobody wants to lose access to public folders. (I will address identifying groups providing access to public folders in a different post.)

How to go about the project

Understand that every environment is different. Some environments have strict email policies, strict filters, and strict conventions, to track Exchange objects. Most environments, though, are pretty lax about this. In some environments, you will find emails being filtered by, say, Exchange Online Protection. In these situations, it might help to track emails flowing in and out of eop. There are several aspects to consider from the business point of view as well. Proceed only after you have done your homework.

In some cases, it may be wise to just let everything be.

Safe clean-up

In many cases, you may get lucky. There may be environments that have empty distribution groups. How? Consider this. There was a distribution group that had an X number of mailboxes in it. These mailboxes were part of this group to receive a certain kind of communication. In due course, everybody that these mailboxes belonged to, left the organisation, and the positions were never back-filled. During the account termination process, the employees' group membership was cleared.

Now, you are left with a group that has no mailboxes.

The next step would be to check the internal message routing logs to see if emails were sent to this distribution group. But wait. How do you get a list of empty distribution groups? Use this query to get the list:

# Connect to your Exchange Server and load the Exchange snap-in

Get-DistributionGroup -ResultSize Unlimited |
  Where-Object { (!(Get-DistributionGroupMember $PSItem)) }

It is a rather simple query. You first query all the distribution groups in your environment. Then, you run Get-DistributionGroupMember on each of these groups to find their members. The -not (or !) operator, combined with the empty output, gives a TRUE, thereby picking the empty groups.

Although, you might want to run this query during off-business hours, considering your environment. Export the output to refer at a later point. You may want to check the message routing logs and clean up these groups first. Once the groups are removed, proceed with the next step.

Address DL redundancy

There are some groups that contain single members. These single members may be single users, or single distribution groups. Either case, ideally, these distribution groups should not exist. One of the reasons is the increase in the Active Directory token size of the members. The other—obvious—reason is that you don’t need a DL to send an email to an individual, or another group which is a DL (or a mail-enabled security group) anyway.

Run the following query to look for single-member distribution groups. This function returns a PSObject, with details such as group name, group type, group alias, group manager, whether the group is hidden from gal, the email address, recipient type, name of the member, whether a user-type member is active, and so on.

Just like the previous case, the next step would be to scan the message routing logs to see if emails are being sent to these groups. Now that you have the smtp addresses, this should be simple. You can even use a script to do the email routing scan task for you.

Here is the function that you can use to get single-member distribution group details (also available on GitHub).

function Get-SingleMemberDistributionGroup {
    begin {
        # Connect to your Exchange Server and load the Exchange snap-in
        # Load the AD module

        $GroupDetailTable = @()
    }

    process {
        $Groups = Get-DistributionGroup -ResultSize Unlimited | Where-Object { (Get-DistributionGroupMember $_).Count -eq 1 }

        foreach ($Group in $Groups) {
            $GroupMembership = Get-DistributionGroupMember $Group
            if ($GroupMembership.RecipientType -eq 'User') {
                $UserDetails = Get-ADUser $GroupMembership.SamAccountName
            }
            else {
                $UserDetails = @{}
            }

            $GroupDetailObject = [ordered]@{
                Name                 = $Group.Name
                GroupType            = $Group.GroupType
                SamAccountName       = $Group.SamAccountName
                ManagedBy            = $Group.ManagedBy -join ';'
                EmailAddresses       = $Group.EmailAddresses -join ';'
                HiddenFromGAL        = $Group.HiddenFromAddressListsEnabled
                PrimarySmtpAddress   = $Group.PrimarySmtpAddress
                RecipientType        = $Group.RecipientType
                RecipientTypeDetails = $Group.RecipientTypeDetails
                Member               = $GroupMembership.Name
                MemberType           = $GroupMembership.RecipientType
                MemberSam            = $GroupMembership.SamAccountName
                UserEnabled          = $UserDetails.Enabled
            }

            $GroupDetailTable += New-Object -TypeName PsObject -Property $GroupDetailObject
        }
        $GroupDetailTable
    }
}

Again, think of this as a wrapper function. First, you query the list of all groups that have only one member. You do this in a fashion similar to the previous case, only here, you call the Count property from the output of Get-DistributionGroupMember. Why this works is because Get-DistributionGroupMember returns an array. Every array in PowerShell has the Count property.

You loop through the output of the following:

Get-DistributionGroup -ResultSize Unlimited | Where-Object { (Get-DistributionGroupMember $_).Count -eq 1 }

Each of these members would have the RecipientTypeDetails property. You check if the recipient type is User. If it is, you fetch the user’s details from AD, including whether the user is active or not.

You now have three objects:

  • The output of Get-DistributionGroup.
  • The output of Get-DistributionGroupMember; you need the recipient type and the username of the members from this.
  • The output of Get-ADUser in case of a user recipient. (That else part of checking whether the recipient type is a user is important.)

Next, you combine these three objects into a single PSObject. To do this, you initialise the output object, $GroupDetailTable, at the very beginning of the function, and then, create an ordered hash table of property names and values, and add this hash table to the $GroupDetailTable object for each of the single-member groups.

Further clean-up

The next point to consider would be groups that have all disabled accounts. This could be quite a challenge. And in many cases, the effort may not be worth it (using a pre-made function would be a good idea; you’re welcome).

This script is also available on GitHub.

function Get-DistributionListDisabledAccounts {
    begin {
        # Connect to your Exchange Server and load the Exchange snap-in
        # Load the AD module

        $GroupDetailTable = @()
    }

    process {
        $Groups = Get-DistributionGroup -ResultSize Unlimited | Where-Object { (Get-DistributionGroupMember $_).Count -gt 1 }

        foreach ($Group in $Groups) {
            $GroupMembership = Get-DistributionGroupMember $Group

            $TotalMembers = $GroupMembership.Count
            $UsersInGroup = $GroupMembership | Where-Object RecipientType -eq 'User'
            $UserCount = $UsersInGroup.Count

            if ($UserCount -eq $TotalMembers) {
                $UserTable = @()

                foreach ($User in $UsersInGroup) {
                    $UserTable += New-Object PSObject -Property @{
                        SamAccountName = $User.SamAccountName
                        Enabled = (Get-ADUser $User.SamAccountName).Enabled
                    }
                }

                $DisabledUsers = $UserTable | Where-Object Enabled -eq $false

                if ($DisabledUsers.Count -eq $TotalMembers) {
                    $GroupDetailObject = [ordered]@{
                        Name                 = $Group.Name
                        GroupType            = $Group.GroupType
                        SamAccountName       = $Group.SamAccountName
                        ManagedBy            = $Group.ManagedBy -join ';'
                        EmailAddresses       = $Group.EmailAddresses -join ';'
                        HiddenFromGAL        = $Group.HiddenFromAddressListsEnabled
                        PrimarySmtpAddress   = $Group.PrimarySmtpAddress
                        RecipientType        = $Group.RecipientType
                        RecipientTypeDetails = $Group.RecipientTypeDetails
                    }

                    $GroupDetailTable += New-Object -TypeName PsObject -Property $GroupDetailObject
                }
            }
        }
        $GroupDetailTable
    }
}

Honestly, I would be surprised even if this function output 0.25% of the total number of distribution groups in your environment. ([Let me know](https://twitter.com/{{ site.twitter }}) if your number crosses that.)

Here is the logic I used:

  • List out all the groups in your environment that have more-than-one users as members.
  • Get the membership information for each of these groups.
  • If the total number of members is equal to the total number of users in the group, proceed with the next step.
  • Find the total number of disabled user accounts in the group.
  • If the total number of members in the group matches the total number of disabled user accounts in the group, proceed with the next step.
  • Output all the necessary information about this group.

As usual, you would check the routing logs to see if there are any emails being sent to these groups, then, contact the group owner (hopefully, [s]he’s an active user), and then, proceed with the clean-up.

One point that I did not mention so far is the distribution groups hidden from gal. The reason is that it is unlikely you find such distribution groups, and even if you do, just because the group is hidden from gal, it doesn’t mean it cannot be used with its smtp address. You would, again, need to check the routing logs to identify opportunities for clean-up.

Summing up

In this post, we looked at cleaning up distribution groups from an Exchange environment. We looked at the things to consider when going ahead with an Exchange distribution group clean-up project, about the different scenarios and classes of distribution groups that can be cleaned up, and how to identify groups such as empty distribution groups, distribution groups with one member, and distribution groups with (mailboxes of) disabled user objects. The necessary functions to perform this identification have also been included.

This brings us to the end of the post. I hope it was helpful. Share your thoughts with [me on Twitter](https://twitter.com/{{ site.twitter }}), and more importantly, share this post with your fellow-administrators.