The Admin’s First Steps: Empty Groups

The Admin’s First Steps: Empty Groups

  • Comments 4
  • Likes

Summary: Richard Siddaway talks about using Windows PowerShell to discover Active Directory groups that have no members.

Hey, Scripting Guy! Question Hey, Scripting Guy! I’ve just starting using Windows PowerShell to administer my systems, and I’ve been told I need to audit our Active Directory to discover any empty groups. How can I do that?

—TL

Hey, Scripting Guy! Answer Hello TL,

Honorary Scripting Guy, Richard Siddaway here today—filling in for my good friend, The Scripting Guy. You’re in luck because today I’ve got a solution to that issue as part of my series of posts about how an administrator can start making productive use of Windows PowerShell.

I’m a big fan of doing regular audits of your Active Directory. It keeps it clean and reduces problems in the long run. I don’t want to count the number of hours I’ve spent cleaning up Active Directory environments where there’s been no housekeeping for years. There are a number of things you should consider when auditing Active Directory, including:

  • Old user accounts
  • Old computer accounts
  • Empty groups, or groups with very few users
  • Membership of sensitive groups such as Enterprise Admins or Domain Admins

In this post, we’ll look at group membership.

You have four ways to work with Active Directory from Windows PowerShell:

  1. Windows PowerShell cmdlets (introduced in Windows Server 2008 R2)
  2. .NET classes in the System.DirectoryServices namespace
  3. Active Directory provider that comes with the Windows PowerShell cmdlets
  4. Dell (Quest) cmdlets

My preference these days is to use the Windows PowerShell cmdlets or scripting with the .NET classes. You can do most things through the Active Directory provider, but it’s a more cumbersome and difficult that way. The Quest cmdlets are good, but with Windows PowerShell cmdlets becoming more widely available as organizations move to Windows Server 2008 R2 or later for their domain controllers, I prefer to use the native tools.

The Windows PowerShell cmdlets are available in the Active Directory module. This is available on any computer running Windows Server 2008 R2 or later that is a domain controller or has the Active Directory RSAT tools installed. If you are running Windows PowerShell 4.0 or Windows PowerShell 3.0, this module is automatically loaded for you. Otherwise, you need to import it as follows:

Import-Module ActiveDirectory

The first step is to find the groups in your environment. Assuming that you want to test the groups in the whole domain, use:

Get-ADGroup -Filter * | Select Name, DistinguishedName

If you want to restrict this process to a specific OU tree, you can be more granular in your approach:

Get-ADGroup -Filter * -SearchBase "OU=All Groups,DC=Manticore,DC=org" -SearchScope Subtree | Select Name, DistinguishedName

If you haven’t performed a cleanup for a long time, a more granular approach may be best unless you keep all of your groups in one OU.

Now that you know the groups you want to work with, you need to find the members of those groups. The Get-ADGroupMember cmdlet performs this task. Unfortunately, it doesn’t work with the group name. You need to use one of the following:

  • SamAccountName
  • Distinguished name
  • GUI
  • SID

As an extra awkwardness, the Get-ADGroupMember cmdlet doesn’t support using a Windows PowerShell or LDAP filter. Still, there’s always a way when you have a Windows PowerShell pipeline to play with:

Get-ADGroup -Filter {Name -eq 'English Scientists'} | Get-ADGroupMember

For this process, you don’t care who is a member of the group because you are looking for empty groups or groups with a very low number of members. All you need is a count of the membership:

(Get-ADGroup -Filter {Name -eq 'English Scientists'} | Get-ADGroupMember).Count

-Or-

Get-ADGroup -Filter {Name -eq 'English Scientists'} | Get-ADGroupMember |

Measure-Object | select -ExpandProperty Count

The first option is less typing; but unfortunately, the techniques breaks if there is only one group member, so this is better:

Import-Module ActiveDirectory

 

Get-ADGroup -Filter * |

foreach {

 New-Object -TypeName psobject -Property @{

 GroupName = $_.Name

 MemberCount = Get-ADGroupMember -Identity "$($_.samAccountName)" | Measure-Object | select -ExpandProperty Count

}

} | sort MemberCount 

You actually do know the SamAccountName of the group because you are using Get-ADGroup to find the groups, which simplifies the script. One drawback to using New-Object is that the properties don’t come out in the order you specify. With Windows PowerShell 4.0 or Windows PowerShell 3.0, you can use an ordered hash table to resolve this issue:

Import-Module ActiveDirectory

 

Get-ADGroup -Filter * |

foreach {

 $props = [ordered] @{

 GroupName = $_.Name

 MemberCount = Get-ADGroupMember -Identity "$($_.samAccountName)" | Measure-Object | select -ExpandProperty Count

 }

 New-Object -TypeName psobject -Property $props

} | sort MemberCount 

I would recommend changing the last line from:

 | sort MemberCount

to:

| sort MemberCount  | Export-Csv  -Path membercount.csv  -NoTypeInformation   

The data is available for future analysis, and you can always rerun the script to compare against your last listing.

Note  One important point to remember is that some of the default groups that Active Directory creates, such as Cryptographic Operators, may well be empty. Don’t delete those groups!

This is all very nice for those admins who are able to use the Windows PowerShell cmdlets.  What about those people still using older versions of Windows Server on their domain controllers?

Life gets a little bit more involved if you can’t use the cmdlets. You have to fall back on scripting against the ADSI scripting interface in Active Directory. This involves using two .NET classes:

  • System.DirectoryServices.DirectoryEntry
  • System.DirectoryServices. DirectorySearcher

That can be a lot to type, so the Windows PowerShell team very kindly gave us a couple of shortcuts.  Instead of typing [System.DirectoryServices.DirectoryEntry], you just need to use [adsi], and instead of [System.DirectoryServices.DirectorySearcher], you can use [adsisearcher].

If you are still using Windows PowerShell 1.0, [adsisearcher] is not available, so you’ll need to use the full class name:

$root = [adsi]""

$search = [adsisearcher]$root

$search.Filter = "(objectclass=group)"

$search.SizeLimit = 3000

$search.FindAll() |

foreach {

 $group = $_.GetDirectoryEntry() 

 $count = ($group.Member).Count

 if ($count -eq $null){$count = 0}

 

 $props = [ordered] @{

 GroupName = $($group.Name)

 MemberCount = $count

 }

 

 New-Object -TypeName PSObject -Property $props

} | sort MemberCount 

The script starts by getting a pointer to the root of the Active Directory domain. A DirectorySearcher object is created that uses the root as the base of its search. The search filter is set to find everything that matches "(objectclass=group)"—that is all groups. A limit of 3000 is set for the number of objects to return. You can modify this to match your environment (the default is 1000).

The FindAll() method is used to pull all of the groups in the domain. The objects that are returned from the search are not full Active Directory objects, so you need to iterate through them and use the GetDirectoryEntry() method to retrieve the group object.

You can then get a count of the members. In this technique, an empty group returns a NULL result. Convert that to zero—it is much easier to read and work with. The same previous hash table structure is used to create the output object, and a final sort displays the data with the empty groups at the top of the list.

If you compare the results, you will see some groups, such as Domain Users, in the output from the cmdlets that don’t appear in the output from the script. This is because they are groups that are automatically maintained by Active Directory, and the ADSI interface ignores them.

If you want to extend the use of this script, you could:

  • Add the distinguished name to the output.
  • Create a script to compare the current list with a historical list to track changes.

TL, that’s how you use Windows PowerShell to audit your Active Directory for empty groups. Next time, I’ll have another idea for you to try as you bring more automation into your environment.

If you would like to read more in this series, check out these posts:

Bye for now.

~Richard

Richard Siddaway is based out of the UK, and he spends his time automating anything and everything for Kelway, Ltd. A Windows PowerShell MVP since 2007, Richard is a prolific blogger, mainly about Windows PowerShell (see Richard Siddaway's Blog: Of PowerShell and Other Things), and a frequent speaker at user groups and Windows PowerShell conferences. He has written a number of Windows PowerShell books: PowerShell in Practice, PowerShell and WMI, PowerShell in Depth (co-author); and PowerShell Dive (co-editor). He is currently finishing Learn Active Directory Management in a Month of Lunches, which features a lot of Windows PowerShell. All of the books are available from Manning Publications Co.

Thanks, Richard.

I invite you to follow me on Twitter and Facebook. If you have any questions, send email to me at scripter@microsoft.com, or post your questions on the Official Scripting Guys Forum. See you tomorrow. Until then, peace.

Ed Wilson, Microsoft Scripting Guy

Your comment has been posted.   Close
Thank you, your comment requires moderation so it may take a while to appear.   Close
Leave a Comment
  • (Get-ADGroup -Filter * -Properties Members | where {$_.Members.count -eq 0}).Name

    Seems a bit easier than piping to get-adgroupmember to me.

  • If you only want to identify empty groups (and don't care how many members are in groups that do contain members), you'll probably see better performance by doing that with an LDAP filter:

    Get-ADGroup -Filter 'member -notlike "*"'

    or:

    Get-ADGroup -LdapFilter '(!member=*)'

  • The idea reasons a large amount regarding strife in a very family's lifetime Männergruppen

  • thanks