In this webinar, our experts showcase a variety of demo use cases of how different components of the...
PowerShell is a powerful scripting language that works well with Azure DevOps build/release pipelines and general AD/Dynamics 365 user management. When querying data from Active Directory, Enterprise customers may find that common scripts take substantially longer than is acceptable and PowerShell is considered at fault. Below we’ll walk through a common way to pull data from Active Directory, and then another way to do it at scale.
A common way to get this information is to use Get-ADGroupMember:
$UsersWithoutEmails = Get-ADGroupMember -Identity "Tiny_AD_Group"
But a problem arises when querying a group larger than 5,000 members:
$usersInGroup = Get-ADGroupMember -Identity "Small_AD_Group" Get-ADGroupMember : The size limit for this request was exceeded At line:1 char:6 + $usersInGroup = Get-ADGroupMember -Identity "Small_AD_Group" + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + CategoryInfo : NotSpecified: (Small_AD_Group:ADGroup) [Get-ADGroupMember], ADException + FullyQualifiedErrorId : ActiveDirectoryServer:8227,Microsoft.ActiveDirectory.Management.Commands.GetADGroupMember
Often, the workaround for this is to query the AD Group and then loop through the members to get additional properties:
$userGroup = Get-ADGroup -filter { name -eq "Small_AD_Group" } -properties members $usersWithEmails = $userGroup.members | ForEach { Get-ADUser -Identity $_ -properties mail };
This does work, but there are some caveats. For one thing, it’s slow. For each AD user in the AD Group, it must do a single query to get additional properties.
Get-ADGroup -filter { name -eq "Small_AD_Group" } -properties members | Select -ExpandProperty Members | Measure; Count : 9269 Measure-Command -Expression { $userGroup = Get-ADGroup -filter { name -eq "Small_AD_Group" } -properties members $usersWithEmails = $userGroup.members | ForEach { Get-ADUser -Identity $_ -properties mail }; } TotalSeconds : 44.0692102 TotalSeconds : 44.7685904 TotalSeconds : 43.0848431
At 9,269 members, it takes 44 seconds to query all members of this group with their email addresses. This continues to scale upwards… with 16,000+ members, we’re at 70+ seconds:
Get-ADGroup -filter { name -eq "Medium_AD_Group" } -Properties Members | Select -ExpandProperty Members | Measure; Count : 16096 Measure-Command -Expression { $userGroup = Get-ADGroup -filter { name -eq "Medium_AD_Group" } -properties members $usersWithEmails = $userGroup.members | ForEach { Get-ADUser -Identity $_ -properties mail }; } TotalSeconds : 71.993939 TotalSeconds : 78.6801553 TotalSeconds : 75.251051
And again! With nearly 80,000 members, we’re at 360+ seconds (6+ minutes):
Get-ADGroup -filter { name -eq "Large_AD_Group" } -Properties Members | Select -ExpandProperty Members | Measure; Count : 79620 Measure-Command -Expression { $userGroup = Get-ADGroup -filter { name -eq "Large_AD_Group" } -properties members $usersWithEmails = $userGroup.members | ForEach { Get-ADUser -Identity $_ -properties mail }; } TotalSeconds : 386.8179244 TotalSeconds : 367.0947574 TotalSeconds : 363.0887966
If we were to plot these results based on averages, a representative graph would look like below. You can see how this can quickly become unfeasible, as your script may have dozens of AD Groups to query and manipulate. With an 80,000-member group taking 6 minutes, ten different groups could easily take an hour to query!
Fear not! There is an alternative method. Instead, an LDAP query can be completed as shown below:
Measure-Command -Expression { $usersWithEmails = Get-ADUser -LDAPFilter "(memberOf=CN=Small_AD_Group,OU=Groups,OU=Best Buy,OU=Users and Groups,DC=na,DC=Sample_Domain,DC=com)" -Properties mail; } TotalSeconds : 11.8649441 TotalSeconds : 12.0721195 TotalSeconds : 11.9379321
This method brought our 9,269-member group from 44 seconds down to 12! Very impressive, but does it scale?
Measure-Command -Expression { $usersWithEmails = Get-ADUser -LDAPFilter "(memberOf=CN=Large_AD_Group,OU=Groups,OU=Best Buy,OU=Users and Groups,DC=na,DC=Sample_Domain,DC=com)" -Properties mail; } TotalSeconds : 104.4181624 TotalSeconds : 102.8956929 TotalSeconds : 104.1290567
Yes, it does! Our group of 80,000 dropped from 6 minutes to about 100 seconds. If we plot these results based on averages, it looks quite a bit better:
Querying a group with about 210,000 members using an LDAP filter is done in about the same amount of time required to query just 90,000 individually.
Bonus Tip #1: If you have multiple groups, you can construct a single LDAP query that represents all the groups. This would save even more time!
Bonus Tip #2: If your AD tree has multiple branches, consider setting a basePath in the Get-ADUser command. It identifies where in AD to start searching.
Bonus Tip #3: If you’re going to query that result set multiple times, convert it to a hash using a PowerShell Filter. Using Where-Object on the array is much too slow.
Bonus Tip #4: If you want to get a distinct or unique set of users from multiple AD Groups, use Sort -Unique and not Select -Unique. It’s a magnitude faster due to how it sorts.
Be sure to subscribe to our blog for more tips and tricks!
Happy PowerShell’ing!