This is a PowerShell script to ensure that all users meeting specified conditions are members of a corresponding dynamic group. It also makes sure users not meeting the conditions are not members of the group. The script can be run periodically to maintain the group membership. A Fine Grained Password Policy can be applied to the group.

Dynamic groups include all users that meet specified conditions, but no other users. The conditions are specified by Active Directory attribute values of the users. For example, it could be all users in the Accounting department, with the department attribute equal to "Accounting". Another example could be users with the company attribute equal to "Contoso" and the title attribute equal to "Manager". Any conditions that can be expressed as an LDAP syntax filter involving Active Directory attributes of the users can be used.

Fine Grained Password Policies (FGPP) can only be applied to users and groups. A dynamic group is the best way to apply a FGPP to all users meeting specified conditions. There is no automatic mechanism in Active Directory to keep a dynamic group membership up to date. A script must be run periodically to update the membership. The script must remove any members of the group that no longer meet the conditions, and add any users that are not in the group but meet the conditions. This PowerShell script is designed to accomplish this task.

This script has the following features.

The script exits with an error message under the following conditions.

To run the script in your environment for your purpose, you must assign values to the following variables in the configuration section of the script.

Some example LDAP syntax filters that could be used in the script follow. None of the filters are case sensitive.

PowerShell
Edit|Remove
# A filter to include all users in the domain with the title attribute equal to "Accountant". 
$Filter = "(title=Accountant)" 
 
# A filter to include all Engineers in the domain. The "*" character is the wildcard character. 
# This filter would include titles "Electrical Engineer""Mechanical Engineer", etc. 
$Filter = "(title=*Engineer)" 
 
# A filter to include all Engineers, Accountants, and Managers. The "|" character is the Or operator. 
$Filter = "(|(title=*Engineer)(title=Accountant)(title=Manager))" 
 
# A filter to include users that have company "Contoso" and title "Manager". 
# The "&" character is the And operator. 
$Filter = "(&(company=Contoso)(title=Manager))" 
 
# A filter to include all Engineers and Accountants in the Contoso company. This would be users 
# with company "Contoso" and either a title ending with "Engineer" or with title "Accountant". 
$Filter = "(&(company=Contoso)(|(title=*Engineer)(title=Accountant)))"
 
Documentation of LDAP syntax filters is here:

Active Directory: LDAP Syntax Filters

The PowerShell script follows below, or it can be downloaded from the link above.
PowerShell
Edit|Remove
# DynamicGroup.ps1 
# PowerShell script to ensure all users meeting specified conditions are 
# members of a corresponding dynamic group. Also makes sure users not meeting 
# the specified conditions are not members of the group. 
# This script can be used to maintain a dynamic group of users. 
# A Fine Grained Password Policy can be applied to the dynamic group and it 
# will apply to all users in the group. 
 
# Author: Richard L. Mueller 
# Version 1.0 - March 112019 
 
Write-Host "Please Standby..." 
 
###### Start of Configuration Section ###### 
# The values of the variables in this section should be customized for 
# your specific situation. 
 
# Specify the DNS name of a DC. All additions to and removals from the dynamic 
# group should be done on the same Domain Controller to avoid replication 
# problems. This must be a DC that supports the Active Directory module cmdlets. 
$Server = "dc0321.mydomain.com" 
 
# Specify log file. 
$LogFile = "c:\PowerShell\Dynamic\DynamicGroup.log" 
 
# If $Update is $False, the script only logs what it would do, without actually 
# updating the dynamic group. If $Update is $True, the script will update the group. 
$Update = $False 
 
# If $EnabledOnly is $True only enabled users are to be in the group. 
# If $False, all users meeting the specified conditions will be in the group. 
$EnabledOnly = $True 
 
# Specify the LDAP syntax filter to retrieve users to be members of the dynamic group. 
$Filter = "(|(title=*Engineer)(title=Accountant)(title=Manager))" 
 
# Specify the distinguished name of the corresponding dynamic group. 
# The group can be empty, but it must exist. 
$GroupDN = "cn=SalesDynamic,ou=Sales,ou=West,dc=MyDomain,dc=com" 
 
###### End of Configuration Section ###### 
 
# Script version and date. 
$Version = "Version 1.0 - March 11, 2019" 
 
# Modify the LDAP filter to find users to add to the dynamic group. 
If ($EnabledOnly) 
{ 
    # Add users that are not in the group, are not disabled, and meet the specified conditions. 
    $AddFilter = "(&(!(memberOf=$GroupDN))(!(userAccountControl:1.2.840.113556.1.4.803:=2))$Filter)" 
} 
Else 
{ 
    # Add users not in the group that meet the specified conditions. 
    $AddFilter = "(&(!(memberOf=$GroupDN))$Filter)" 
} 
 
# Modify the LDAP filter to find users to remove from the dynamic group. 
If ($EnabledOnly) 
{ 
    # Remove users in the group if they are disabled or do not meet the specified conditions. 
    $RemoveFilter = "(&(memberOf=$GroupDN)(|(userAccountControl:1.2.840.113556.1.4.803:=2)(!$Filter)))" 
} 
Else 
{ 
    # Remove users in the group if they do not meet the specified conditions. 
    $RemoveFilter = "(&(memberOf=$GroupDN)(!$Filter))" 
} 
 
Try {Import-Module ActiveDirectory -ErrorAction Stop -WarningAction Stop} 
Catch 
{ 
    Write-Host "ActiveDirectory module (or DC with ADWS) not found!!" ` 
        -ForegroundColor Red -BackgroundColor Black 
    Write-Host "Script Aborted." -ForegroundColor Red -BackgroundColor Black 
    # Abort the script. 
    Break 
} 
 
# Check if DN of dynamic group is valid. 
$Y = [ADSI]"LDAP://$GroupDN" 
If ($Y.Name) 
{ 
    If ($Y.objectCategory -NotLike "CN=Group,*") 
    { 
        Write-Host "Error: GroupDN $GroupDN not a group, script aborted." ` 
            -foregroundcolor red -backgroundcolor black 
        Break 
    } 
} 
Else 
{ 
    Write-Host "Error: GroupDN $GroupDN invalid, script aborted." ` 
        -foregroundcolor red -backgroundcolor black 
    Break 
} 
 
# Ensure that distinguished name is properly formated. 
# This will correct situations where the DN provided included 
# spaces after any commas, such as "CN=My Group, OU=East, dc=mydomain, dc=com". 
$GroupDN = $($Y.distinguishedName) 
 
# Check if the designated computer can be contacted. 
$Ping = Test-Connection -ComputerName $Server -Count 1 -Quiet 
If ($Ping -eq $False) 
{ 
    Write-Host "Error: Unable to connect to $Server, script aborted." ` 
        -foregroundcolor red -backgroundcolor black 
    Break 
} 
 
# Check if the computer is a DC that supports the AD modules. 
# Retrieve users to be removed from the dynamic group. 
Try 
{ 
    $Members = Get-ADUser -LDAPFilter $RemoveFilter ` 
        -Server $Server | Select distinguishedName 
} 
Catch 
{ 
    Write-Host "Error: $Server does not support the AD modules, script aborted." ` 
        -foregroundcolor red -backgroundcolor black 
    Break 
} 
 
# The text written to the log depends on $Update. 
If ($Update -eq $True) 
{ 
    $AddText = "added" 
    $RemText = "removed" 
} 
Else 
{ 
    $AddText = "would be added" 
    $RemText = "would be removed" 
} 
 
# Add information to the log file. 
Try 
{ 
    Add-Content -Path $LogFile ` 
        -Value "------------------------------------------------" -ErrorAction Stop 
} 
Catch 
{ 
    Write-Host "Error: Logfile $LogFile invalid or protected, script aborted." ` 
        -foregroundcolor red -backgroundcolor black 
    Break 
} 
Add-Content -Path $LogFile -Value "DynamicGroup.ps1 ($Version)" 
Add-Content -Path $LogFile -Value $("Started: " + (Get-Date).ToString()) 
Add-Content -Path $LogFile -Value "Log file: $LogFile" 
 
Add-Content -Path $LogFile -Value "LDAP syntax filter: $Filter" 
Add-Content -Path $LogFile -Value "Filter to remove users from the dynamic group:" 
Add-Content -Path $LogFile -Value "   $RemoveFilter" 
Add-Content -Path $LogFile -Value "Filter to add users to the dynamic group:" 
Add-Content -Path $LogFile -Value "   $AddFilter" 
 
Add-Content -Path $LogFile -Value "DN of the dynamic group: $GroupDN" 
Add-Content -Path $LogFile -Value "DC used for updates: $Server" 
Add-Content -Path $LogFile -Value "Only enabled users: $EnabledOnly" 
 
Add-Content -Path $LogFile -Value "Update the dynamic group: $Update" 
Add-Content -Path $LogFile -Value "------------------------------------------------" 
 
# Initialize counters. 
$Removed = 0 
$Added = 0 
 
# Flags if too many users removed from or added to the dynamic group. 
# A maximum of 4000 users should be removed or added at a time 
# to avoid excessive network traffic and long running transactions. 
$TooManyRemoved = $False 
$TooManyAdded = $False 
 
# Array of users to be added to the dynamic group. 
$UsersToAdd = @() 
 
# Array of users to be removed from the dynamic group. 
$UsersToRemove = @() 
 
# Enumerate users to be removed from the dynamic group. 
# $Members was retrieved above to test if the DC supports AD modules. 
ForEach ($Member In $Members) 
{ 
    $DN = $Member.distinguishedName 
    # Add this user to the array of users 
    # to be removed from the dynamic group. 
    $UsersToRemove = $UsersToRemove + $DN 
    $Removed = $Removed + 1 
    Add-Content -Path $LogFile ` 
        -Value "$RemText from group: $DN" 
    If ($Removed -gt 3999) 
    { 
        $TooManyRemoved = $True 
        # Break out of the ForEach loop, but not out of the script. 
        Break 
    } 
} 
 
# Remove the users from the dynamic group. 
If (($Update -eq $True) -and ($Removed -gt 0)) 
{ 
    Remove-ADGroupMember -Identity $GroupDN -Members $UsersToRemove ` 
        -Server $Server -Confirm:$False 
    # Short pause. 
    Start-Sleep -Seconds 10 
} 
 
# Retrieve all users to be added to the dynamic group. 
$Members = Get-ADUser -LDAPFilter $AddFilter ` 
    -Server $Server | Select distinguishedName 
 
ForEach ($Member In $Members) 
{ 
    $DN = $Member.distinguishedName 
    # Add this user to the array of users to be added to the dynamic group. 
    $UsersToAdd = $UsersToAdd + $DN 
    $Added = $Added + 1 
    Add-Content -Path $LogFile -Value "$AddText to group: $DN" 
    If ($Added -gt 3999) 
    { 
        $TooManyAdded = $True 
        # Break out of the ForEach loop, but not out of the script. 
        Break 
    } 
} 
 
# Add the missing users to the dynamic group. 
If (($Update -eq $True) -and ($Added -gt 0)) 
{ 
    Add-ADGroupMember -Identity $GroupDN -Members $UsersToAdd -Server $Server 
} 
 
# Update the log file. 
Add-Content -Path $LogFile -Value "------------------------------------------------" 
Add-Content -Path $LogFile -Value $("Finished: " + (Get-Date).ToString()) 
Add-Content -Path $LogFile ` 
    -Value "Number of users $RemText from the group: $('{0:n0}' -f $Removed)" 
Add-Content -Path $LogFile ` 
    -Value "Number of users $AddText to the group: $('{0:n0}' -f $Added)" 
 
If ($TooManyRemoved -eq $True) 
{ 
    Add-Content -Path $LogFile -Value "Caution: 4000 users $RemText from the group." 
    Add-Content -Path $LogFile -Value "This is the maximum allowed." 
    Add-Content -Path $LogFile -Value "Run the script again to process more." 
} 
If ($TooManyAdded -eq $True) 
{ 
    Add-Content -Path $LogFile -Value "Caution: 4000 users $AddText to the group." 
    Add-Content -Path $LogFile -Value "This is the maximum allowed." 
    Add-Content -Path $LogFile -Value "Run the script again to process more." 
} 
 
Write-Host "Done. See log file: $LogFile" 
If ($TooManyRemoved -eq $True) 
{ 
    Write-Host "Caution: 4000 users $RemText from the group." ` 
        -foregroundcolor yellow -backgroundcolor black 
    Write-Host "         Run the script again to process more." ` 
        -foregroundcolor yellow -backgroundcolor black 
} 
Else {Write-Host "Users $RemText from the group: $Removed"} 
 
If ($TooManyAdded -eq $True) 
{ 
    Write-Host "Caution: 4000 users $AddedText to the group." ` 
        -foregroundcolor yellow -backgroundcolor black 
    Write-Host "         Run the script again to process more." ` 
        -foregroundcolor yellow -backgroundcolor black 
} 
Else {Write-Host "Users $AddText to the group: $Added"}