Forum Discussion
UC_451435
Mar 26, 2025Copper Contributor
Adding AD users to a specific security group
Hi Everyone,
Sorry if this question has already been asked as I couldn't find an answer.
I’m trying to write a PowerShell script that runs as a scheduled task to add AD users to a specific AD security group. The goal is for this to run daily. The script will first check the users' OU to determine if they are already members of the security group. If they are, it will skip them; if they are not members, it will add them to the group.
I have created the following script, but I’m unsure if it's the best approach. Additionally, can this script be executed on a server that doesn’t have Active Directory installed? If AD must be installed, would it be ideal to run it on a Domain Controller?
# Check if Active Directory module is already imported, import only if necessary
if (-not (Get-Module -Name ActiveDirectory)) {
Import-Module ActiveDirectory
}
# Define the base OU and security group
$BaseOU = "OU=W11_USERS,DC=W11,DC=NET"
$SecurityGroup = "HR"
# Get all users from W11_USERS and its sub-OUs
$Users = Get-ADUser -SearchBase $BaseOU -SearchScope Subtree -Filter *
# Loop through each user and check group membership before adding
foreach ($User in $Users) {
$UserDN = $User.DistinguishedName
# Check if user is already a member of HR
$IsMember = Get-ADGroupMember -Identity $SecurityGroup | Where-Object { $_.DistinguishedName -eq $UserDN }
if (-not $IsMember) {
Try {
Add-ADGroupMember -Identity $SecurityGroup -Members $User -ErrorAction Stop
Write-Host "Added $($User.SamAccountName) to $SecurityGroup" -ForegroundColor Green
} Catch {
Write-Host "Failed to add $($User.SamAccountName): $_" -ForegroundColor Red
}
} else {
Write-Host "$($User.SamAccountName) is already a member of $SecurityGroup" -ForegroundColor Yellow
}
}
Write-Host "User addition process completed."
1 Reply
Sort By
- LainRobertsonSilver Contributor
Hi UC_451435,
Here's a more efficient script that will scale much better in large environments.
The primary reason it's more efficient is that it's minimising the number of calls to Active Directory, which are costly in nature. It's also using hash matching rather than full string comparisons on the distinguishedName, though this won't matter in smaller environments.
The basic approach is:
- Pull down the eligible members into one list (only storing the hash and the distinguishedName, since storing the whole object is wasteful and will harm performance in larger environments);
- Pull down the current list of direct and indirect members;
- Remove those no longer eligible;
- Add those who are eligible and not yet in the group.
This example is very basic and doesn't support nested group memberships in the target group, but I figured keeping it simple to start with is best.
As an aside, you don't need to check for whether or not the ActiveDirectory module is already loaded. If it is already loaded then it won't load again, while if it's not loaded, PowerShell will automatically load it on the first call to any commandlet contained within the module.
There's no harm in your current check, but there's also no benefit.
It's always been best practice to not run anything at all on domain controllers - not even (or especially) other Windows roles or services. You're better off running the script on a member server where the account under which the scheduled task runs has the minimum permissions necessary to maintain the group.
If the member server doesn't have the RSAT role feature installed, just install that one component. Don't accidentally install the full Active Directory Domain Services role, for example.
$OrganisationalUnit = "OU=Users,OU=Staff,OU=RobertsonPayne,DC=robertsonpayne,DC=com"; $Group = Get-ADObject -Identity "CN=Friendly Name,OU=Groups,OU=Staff,OU=RobertsonPayne,DC=robertsonpayne,DC=com"; # Get the list of enabled users from within and below the nominated organisational unit. We're going to store a hash code to allow for significantly faster lookups, which will scale better in larger environments. $SourceList = [System.Collections.Generic.Dictionary[[int64], [string]]]::new(); Get-ADObject -Filter { (objectCategory -eq "person") -and (objectClass -eq "user") -and -not (userAccountControl -band 2) } -SearchBase $OrganisationalUnit | ForEach-Object { $SourceList.Add($_.distinguishedName.GetHashCode(), $_.distinguishedName); }; # Get the list of direct and indirect members of the nominated group, which requires a base search to access the msds-memberTransitive attribute. $TargetList = [System.Collections.Generic.Dictionary[[int64], [string]]]::new(); (Get-ADObject -Filter { cn -like "*" } -SearchBase ($Group.distinguishedName) -SearchScope Base -Properties "msds-memberTransitive")."msds-memberTransitive" | ForEach-Object { $TargetList.Add($_.GetHashCode(), $_); }; # Remove non-existent or disabled entries from the group. $Members = $TargetList.GetEnumerator() | ForEach-Object { if (-not $SourceList.ContainsKey($_.Key)) { $_.value; } }; if (0 -lt $Members.Count) { Remove-ADGroupMember -Members $Members; } # Add new entries not currently in the group (directly or indirectly). $Members = $SourceList.GetEnumerator() | ForEach-Object { if (-not $TargetList.ContainsKey($_.Key)) { $_.value; } }; if (0 -lt $Members.Count) { Add-ADGroupMember -Identity $Group.ObjectGUID -Members $Members; }
Cheers,
Lain