Forum Discussion

Matthew Davis's avatar
Matthew Davis
Copper Contributor
Apr 23, 2024

Script to balance AD group membership.

The Security team is using Windows Event Forwarding (WEF) to collect logs from Windows workstations.  Using Windows Event Collectors (WEC) pointed to AD groups or computers objects.  But there is a maximum number of computers that each WEC can handle.  So computers have been manually divided between several AD groups.

 

This is a huge maintenance headache for both the help desk who adds them initially, and the security team that is ultimately responsible for it.  Especially during the constant workstation replacement projects.

 

They have requested a script that they can run (Probably nightly with Task Scheduler) to manage this, but I've been having a hard time finding the logic required to pull it off.  Basically it needs to take an inventory of several groups, and then distribute the members across those groups evenly (as much as possible).

 

The most relevant script sample I could find was of all things ,this script that Google "AI search" spit out. It was a complete mess of context errors but it gave me somewhere to start. It now works up to the point of the "$MembersToAdd" line. Meaning it appears to hash the members of the groups, remove the right amount from the larger groups, but then it "Cannot validate argument on parameter 'Members'. The argument is null or empty." when trying to add the excessive computers to the other groups.

 

Anyone tried anything like this before, or have an idea of what I can do to fix the end of this script?

 

 

 

 

 

 

 

 

# Import the Active Directory module
Import-Module ActiveDirectory

# Get all of the AD groups
$groups = Get-ADGroup -Filter { Name -like "TEST-ADGroupMembershipBalance*" } -Properties *

# Create a hashtable to store the group membership counts
$groupMembershipCounts = @{}

# Iterate through each group and get the membership count
foreach ($group in $groups) {
    $groupMembershipCounts[$group.Name] = $group.Members.Count
}

# Get the average group membership count
$averageGroupMembershipCount = $groupMembershipCounts.Values | Measure-Object -Average | Select-Object -ExpandProperty Average

# Iterate through each group and add or remove members to balance the group membership counts
foreach ($group in $groups) {
    Write-Host $group $group.Members.Count
    if ($groupMembershipCounts[$group.Name] -gt $averageGroupMembershipCount) {
        # Remove members from the group
        $membersToRemove = $group.Members | Sort-Object -Descending | Select-Object -First ($groupMembershipCounts[$group.Name] - $averageGroupMembershipCount)
        Remove-ADGroupMember -Identity $group -Members $membersToRemove
    } elseif ($groupMembershipCounts[$group.Name] -lt $averageGroupMembershipCount) {
        # Add members to the group
        $membersToAdd = Get-ADComputer -Filter * -SearchBase $group.DistinguishedName | Sort-Object {Get-Random} | Select-Object -First ($averageGroupMembershipCount - $groupMembershipCounts[$group.Name])
        Add-ADGroupMember -Identity $group -Members $membersToAdd
    }
}

 

 

 

 

 

 

 

  

  • kyazaferr's avatar
    kyazaferr
    Iron Contributor

    # Define the AD groups
    $groupNames = @(
        "Group1",  # Replace with actual group names
        "Group2",
        "Group3"
        # Add more groups as needed
    )

    # Function to get members of each group
    function Get-GroupMembers {
        param (
            [string]$groupName
        )
        return Get-ADGroupMember -Identity $groupName | Where-Object { $_.ObjectClass -eq "user" }
    }

    # Function to distribute members
    function Distribute-Members {
        param (
            [array]$groupNames
        )

        # Create an array to store members and group sizes
        $groupMembers = @{}
        $totalMembers = 0

        # Get the current members of each group
        foreach ($groupName in $groupNames) {
            $members = Get-GroupMembers -groupName $groupName
            $groupMembers[$groupName] = $members
            $totalMembers += $members.Count
        }

        # Calculate the target number of members per group
        $numGroups = $groupNames.Count
        $targetMembersPerGroup = [math]::Ceiling($totalMembers / $numGroups)

        # Create an array to store the excess members to redistribute
        $excessMembers = @()

        # Loop through each group and identify excess members
        foreach ($groupName in $groupNames) {
            $currentGroupSize = $groupMembers[$groupName].Count
            if ($currentGroupSize > $targetMembersPerGroup) {
                $excessMembers += $groupMembers[$groupName] | Select-Object -First ($currentGroupSize - $targetMembersPerGroup)
                $groupMembers[$groupName] = $groupMembers[$groupName] | Select-Object -Skip ($currentGroupSize - $targetMembersPerGroup)
            }
        }

        # Now redistribute the excess members to the groups with fewer members
        foreach ($groupName in $groupNames) {
            $currentGroupSize = $groupMembers[$groupName].Count
            while ($currentGroupSize < $targetMembersPerGroup -and $excessMembers.Count -gt 0) {
                $groupMembers[$groupName] += $excessMembers[0]
                $excessMembers = $excessMembers[1..($excessMembers.Count - 1)]
                $currentGroupSize++
            }
        }

        # Add the redistributed members back to the AD groups
        foreach ($groupName in $groupNames) {
            $membersToAdd = $groupMembers[$groupName]
            $existingMembers = Get-GroupMembers -groupName $groupName

            # Filter out existing members to avoid duplicates
            $membersToAdd = $membersToAdd | Where-Object { $_.DistinguishedName -notin $existingMembers.DistinguishedName }

            if ($membersToAdd.Count -gt 0) {
                # Add the members to the group
                Add-ADGroupMember -Identity $groupName -Members $membersToAdd
                Write-Host "Added $($membersToAdd.Count) members to $groupName"
            }
        }
    }

    # Execute the function to redistribute the members
    Distribute-Members -groupNames $groupNames

Resources