This post describes the implementation of rule based Active Directory groups (RBAG’s), maintained by a custom PowerShell script. The need for such rule based groups can vary. For example maintaining an Active Directory group that holds all members of a specific department can be challenging when no identity management system is available in the company. Hence the creation of this PowerShell script. The script allows for updating Active Directory groups based on a LDAP filter configured on specific Active Directory Groups.

 The script follows the following process:

-          Enumerate all Active Directory groups identified or tagged as RBAG. This script will identify these Active Directory groups based on the description field of the Active Directory group

-          Retrieve all current members of the Active Directory Group

-          Retrieve the LDAP Filter defined on the Active Directory Group. The LDAP Filter is stored in the Notes field of the Active Directory group.

-          Execute a query against Active Directory to find all objects that match the LDAP Filter

-          Compare the results of the query with the current member list and generate a list of members to add and a list of members to remove from the Active Directory Group

-          Finally, update the Active Directory group with the members to add and/or remove and update the description field with the date the last update was done by the script

To use the PowerShell, copy the script to a domain controller running at least Windows Server 2008 R2 and have it run using a scheduled task on a regular basis (daily, weekly, etc.)

It is recommended to create a separate batch file to be used as the target for the Scheduled Task.

The batch file needs to contain the following command:

powershell %~dp0RBAG.ps1 -LogFile C:\Scripts\rbag.log

To configure Active Directory groups to be maintained by the script, update the Active Directory group with the following information:

- Description : enter "RBAG" in the description field

- Notes : specify the LDAP filter to be used by the script

  E.g. (&(objectClass=person)(department=Dept1)(!userAccountControl:1.2.840.113556.1.4.803:=2))

  The filter above will result in finding all enabled user accounts for which the department field in AD is set to Dept1

 

Actual Script content:

PowerShell
Edit|Remove
Param($LogFile#Define Variables 
 
[int]$GLOBAL:TotalGroupsChecked = 0 
[int]$GLOBAL:TotalGroupsUpdated = 0 
[bool]$transcript_enabled = $false 
[bool]$debug = $false 
[string]$usage = "Usage: Set-RuleBasedGroups.ps1 -LogFile <file name>"  
 
function RuleBasedADGroups() 
{ 
  Import-Module ActiveDirectory 
 
  $RBAGGroups = Get-ADGroup -filter {description -like "RBAG*"-properties info,description 
  if ($RBAGGroups -eq $null) 
  { 
     write-host "No RBAG groups found" 
     if ($transcript_enabled -eq $true)  
     {  
        Stop-Transcript  
        #clean up the transcript file (start-transcript doesn't format linebreaks) 
        [string]::Join("`r`n",(Get-Content $LogFile)) | Out-File $LogFile       
         exit    
      } 
  } 
   
  foreach ($group in $RBAGGroups) 
  { 
    if ($group.info -ne $null)  
    { 
      $GLOBAL:TotalGroupsChecked = $GLOBAL:TotalGroupsChecked + 1 
      write-host("") 
      write-host("Group Name        = "+$group.Name) 
      write-host("Group DN          = "+$group.distinguishedName) 
      write-host("Group Description = "+$group.description) 
      write-host("Group Filter      = "+$group.info) 
       
      $ExistingGroupMembers = Get-ADGroupMember $group | Sort-Object distinguishedName 
      $ExistingGroupMembersCount = 0 
      foreach ($member in $ExistingGroupmembers) 
      { 
        if ($member -ne $null) { $ExistingGroupMembersCount += 1 } 
    if ($debug) { write-host($member) } 
      } 
      write-host $ExistingGroupMembersCount " existing group members"     
      $filter = $group.info 
      #write-host($filter) 
       
      try 
      { 
         $groupUpdated = $false 
         $NewGroupMembers  = Get-ADObject -LDAPfilter $filter | Sort-Object distinguishedName 
         $NewGroupMembersCount = 0 
     foreach ($member in $NewGroupMembers) 
         { 
            if ($debug) { write-host($member) } 
        if ($member -ne $null) { $NewGroupMembersCount += 1 } 
         } 
     write-host $NewGroupMembersCount " objects returned from query"     
         if ($ExistingGroupmembersCount -eq 0 -and $NewGroupMembersCount -gt 0) 
         {  
            $MembersToAdd = $NewGroupMembers 
            $MembersToRemove = $null             
     } 
         elseif ($NewGroupMembersCount -eq 0 -and $ExistingGroupMembersCount -gt 0) 
         { 
            $MembersToAdd = $null 
            $MembersToRemove = $ExistingGroupMembers 
         } 
         elseif ($NewGroupMembersCount -eq 0 -and $ExistingGroupMembersCount -eq 0) 
         { 
            $MembersToAdd = $null 
            $MembersToRemove = $null 
         } 
         else 
         { 
            write-host "Comparing existing members with query results" 
            $MembersToAdd = Compare-Object –referenceobject $ExistingGroupmembers –differenceobject $NewGroupMembers -Property distinguishedName | where{$_.SideIndicator -eq "=>"} 
            $MembersToRemove = Compare-Object  –referenceobject $ExistingGroupmembers –differenceobject $NewGroupMembers -Property distinguishedName | where{$_.SideIndicator -eq "<="} 
         } 
         ForEach ($member in $MembersToRemove)  
         { 
           if ($member -ne $null) 
           {   
             $memberDN = $member.distinguishedName 
             write-host("`tMember to Remove: $memberDN") 
             try 
             { 
                 write-host("Command: Remove-ADGroupMember $group -Members $memberDN") 
                 Remove-ADGroupMember $group -Member $memberDN -Confirm:$false 
                 $groupUpdated = $true 
             } 
             catch 
             { 
                $errText = $error[0].Exception.Message 
        write-host("Warning: A problem occurred whilst adding member to group. Reason: $errText")                   
             } 
           }   
         }  
 
         foreach ($member in $MembersToAdd) 
         { 
           if ($member -ne $null) 
           {   
             $memberDN = $member.distinguishedName 
             write-host("`tMember to Add: $memberDN") 
             try 
             { 
                 write-host "Command: Add-ADGroupMember $group -Member $memberDN"  
                 Add-ADGroupMember $group -Member $memberDN -Confirm:$false 
                 $groupUpdated = $true 
             } 
             catch 
             { 
                $errText = $error[0].Exception.Message 
        write-host("Warning: A problem occurred whilst adding member to group. Reason: $errText")                 
             } 
           }              
         } 
 
         if ($groupUpdated) 
         { 
             $GLOBAL:TotalGroupsUpdated = $GLOBAL:TotalGroupsUpdated + 1 
         $now = Get-Date -Format g 
             $description = "RBAG - Last Update:" + $now 
         Set-ADGroup $group -Description $description 
         } 
      } 
      catch 
      {     
        $errText = $error[0].Exception.Message 
        write-host("Warning: A problem occurred whilst querying AD. Reason: $errText") 
      } 
     } 
     else 
     {  
       write-host("Notes field empty on RBAG group. Verify Notes field in Active Directory") 
     } 
  } 
} 
 
function StartProcess() 
{ 
    # Create the stopwatch 
    [System.Diagnostics.Stopwatch] $sw; 
    $sw = New-Object System.Diagnostics.StopWatch 
    $sw.Start() 
     
    RuleBasedADGroups 
 
    $sw.Stop() 
    # Write the compact output to the screen 
    write-host "" 
        write-host $GLOBAL:TotalGroupsChecked " Groups checked." 
        write-host $GLOBAL:TotalGroupsUpdated " Groups updated."  
        write-host "Total Execution Time: "$sw.Elapsed.ToString() 
} 
 
if ($LogFile -ne $null) 
{ 
    $transcript_enabled = $true 
} 
 
cls 
 
if($transcript_enabled -eq $true) 
{ 
    Start-Transcript -Path $LogFile -Append -noclobber 
} 
 
StartProcess 
 
if($transcript_enabled -eq $true) 
{ 
    Stop-Transcript 
    #clean up the transcript file (start-transcript doesn't format linebreaks) 
    [string]::Join("`r`n",(Get-Content $LogFile)) | Out-File $LogFile