Forum Discussion
Remove computers from multiple domains from one AD group
Thank you so much Lain! I really appreciate your input. I need a lot more training and practice, but I'll keep at it of course. In this scenario, and to narrow it down, I have to remove computers from potentially 6 groups (3 in each domain) and move them to another group. All 3 groups are the same in each domain. The list of servers in question are split between 2 different domains so that complicates things. To your point about the search string, the computer names are pretty scattered in terms of naming conventions. Is there a way to provide a csv or txt file to plug in to remove them from possibly 2 groups? I only ask because I have over 200 servers to go through and trying to find the distinguished name to all of them would be counter productive. I did test your server with a couple of servers. If I run the script in each domain, it works, so thank you!!!! It would be great to just use an input file and somehow scan all 6 groups and remove accordingly. Still, it's huge that this works so I'm very grateful :-)
Hi jmaraviglia,
You have a few moving parts in this reply. Of note, we have:
- Multiple groups;
- Multiple computers in each group;
- Multiple domains.
I'm going to stick to the first two points in the interest of keeping things (including error handling) simple. You can always run the script manually in the second domain.
The changes requires to cope with multiple groups are minimal.
Firstly, you could store both the group and computer name regex patterns in separate files, however, you only have six groups so I've simply used a variable to hold the group names while electing to use a text file to hold the computer name regex patterns.
Text file
All I've done is take the values from the first script's $Computers variable and drop them into a text file. Note, you should remember these are regex patterns and not simple text values, so don't forget to include the "=" prefix and "," suffix or else you could match other computers you didn't intend to remove.
For example, a regex match for "Computer1" will match all of the following:
- Computer1
- Computer11
- Computer111
And so on. While a pattern of "=Computer1," will only match the intended "Computer1" - noting that technically, "Computer1" could exist in multiple domains (you can combine the second and third example patterns to deal with that scenario).
Next, here's the updated script.
Invoke-ADGroupCleanup.ps1
# Import the Active Directory module
Import-Module ActiveDirectory;
# List of group names in the current domain to search for.
$GroupNames = @(
"Group1"
, "Group2"
, "Print Servers"
);
# Read the list of computer name RegEx values into a variable.
$ComputerNames = Get-Content -Path "D:\Data\Temp\Forum\forum.txt";
$GroupNames | ForEach-Object {
# Common name of the group you want to clean up.
$GroupName = $_;
# Get the Active Directory group object and specifically ask for the member property, which holds the current members in distinguishedName form (they're actually just strings, not AD objects).
$Group = Get-ADObject -Filter { (objectClass -eq "group") -and (name -eq $GroupName) } -Properties member -ErrorAction:Stop;
if ($null -eq $Group)
{
Write-Warning -Message "$GroupName not found.";
}
else
{
# Create an empty list that will hold all the matching distinguishedNames (string) values that match any of the RegEx patterns held in the $Computers variable from above.
$RemovalList = [System.Collections.Generic.List[string]]::new();
# Build the list of members to remove from the group.
$Group.member | ForEach-Object {
foreach ($RegExPattern in $ComputerNames) {
if ($_ -match $RegExPattern)
{
$RemovalList.Add($_);
break; # Break simply terminates the current iteration of a foreach() loop, which we want to do since there's nothing to be gained from performing anymore pattern matches.
}
}
}
if (0 -eq $RemovalList.Count)
{
Write-Warning -Message "$GroupName contained no matching computers.";
}
else
{
# Now that we have our removal list, call Active Directory to perform the actual removal.
$Group | Remove-ADGroupMember -Members ($RemovalList.ToArray()) -ErrorAction:Stop -Confirm:$false;
# If the above Remove-ADGroupMember commandlet didn't error out, send the current group name and the list of members removed to the pipeline.
$RemovalList | ForEach-Object {
[PSCustomObject] @{
Group = $GroupName;
Member = $_;
}
};
}
}
}
The main difference from the first version is we have another nested loop:
- New: Outer-most loop which iterates through each group;
- No change: Inner loop that iterates through the group's membership list;
- No change: Inner-most loop that iterates through the computer name regex patterns looking for a match.
Otherwise, things are pretty much the same.
Some quality-of-life additions are:
- Lines 21 to 24: A check for if no matching group was found;
- Lines 41 to 44: A check for if the group has no matching computers, meaning we shouldn't bother calling Remove-ADGroupMember on line 48;
- Lines 51 to 56: Now that we're dealing with multiple groups, I've adding the current group name to the data the script is outputting to the default output stream.
You'll notice for these checks I'm sending the output to the "warning" stream rather than the default output stream. That's because in PowerShell, you should not limit your thinking to writing output to the screen, but assume that the output from the script could be sent anywhere - even to another script that does something extra with the data.
By sending the notifications to the warning channel, we avoid putting the real data and these informational messages in the same bucket, keeping our dataset clean and easily consumable.
Cheers,
Lain
- jmaravigliaApr 14, 2025Copper Contributor
Hi Lain,
So I'm clear, for the txt file, I should continue to use the format of
^cn=machine,.$
^cn=machine2,.$ and so on? I have to prepare a file of 200 server names :-)
Jeff
- LainRobertsonApr 14, 2025Silver Contributor
Hi jmaraviglia,
Either format will do.
Technically, "^cn=machine2," is just a stricter version of "=machine2," since it's using the "^cn=" to say "this computer's full path must begin with 'cn=machine2,' where "=machine2," is more relaxed and says "this computer's path simply needs to have '=machine2,' anywhere in it".
You can refer back to my first post which has the small table I added as well as a link to Microsoft's RegEx pattern documentation for a more in-depth explainer on those patterns.
Cheers,
Lain
- jmaravigliaApr 15, 2025Copper Contributor
Thank you for all of your help, Lain. I was able to finally complete this task. I will continue to train and practice with Powershell. I can tell I have a very long way to go. :-)
Jeff
- jmaravigliaApr 14, 2025Copper Contributor
Thanks for responding Lain. One question, in regards to the text file, would I just type...
=Computer1,
=Computer2, and so on or use the original ^cn= formatting?