Synchronize/Mirror AD Security Group Membership Script

I need to synchronize group membership for several AD security groups.  I was able to put together a script that will add any new group members from the source security group to the destination security group, however, I also need to be able to have the script remove members from the destination security group, when they are removed from the source.  How can I modify this script to achieve this?

$source = Get-ADGroupMember -Identity SourceADGroup
foreach ($user in $source) { 
    Add-ADGroupMember -Identity DestADGroup -Members $user.distinguishedname 
}

Open in new window


Thank you in advance.
LVL 1
fireguy1125Asked:
Who is Participating?
I wear a lot of hats...

"The solutions and answers provided on Experts Exchange have been extremely helpful to me over the last few years. I wear a lot of hats - Developer, Database Administrator, Help Desk, etc., so I know a lot of things but not a lot about one thing. Experts Exchange gives me answers from people who do know a lot about one thing, in a easy to use platform." -Todd S.

oBdACommented:
Copy-ADGroupMember.ps1; -WhatIf is supported.
Will by default only copy; use -Mirror to remove members from destination that aren't in the source anymore.
Use -Confirm:$False to suppress the confirmation prompt (or change ConfirmImpact to 'None' if you never want to be asked)
[CmdletBinding(SupportsShouldProcess=$True, ConfirmImpact='High')]
Param(
	[PSObject]$Identity,
	[PSObject]$Destination,
	[Switch]$Mirror
)
$SourceMembers = Get-ADGroupMember -Identity $Identity | Select-Object -ExpandProperty distinguishedName
$TargetMembers = Get-ADGroupMember -Identity $Destination | Select-Object -ExpandProperty distinguishedName
$DifferenceMembers = Compare-Object -ReferenceObject $SourceMembers -DifferenceObject $TargetMembers
$ProcessMembers = $DifferenceMembers | Where-Object {$_.SideIndicator -eq '<='} | Select-Object -ExpandProperty InputObject
If ($PSCmdlet.ShouldProcess($Destination, "Add objects:`r`n`t$($ProcessMembers -join "`r`n`t")`r`n")) {
	Add-ADGroupMember -Identity $Destination -Members $ProcessMembers
}
If ($Mirror) {
	$ProcessMembers = $DifferenceMembers | Where-Object {$_.SideIndicator -eq '=>'} | Select-Object -ExpandProperty InputObject
	If ($PSCmdlet.ShouldProcess($Destination, "Remove objects:`r`n`t$($ProcessMembers -join "`r`n`t")`r`n")) {
		Remove-ADGroupMember -Identity $Destination -Members $ProcessMembers
	}
}

Open in new window

0
fireguy1125Author Commented:
Thank you, where would I specify the source and destination group? Am I just replacing [PSObject] in lines 3 and 4?
0
oBdACommented:
No. It's a complete script accepting arguments.
.\Copy-ADGroupMember.ps1 -Identity SourceADGroup -Destination DestADGroup -Mirror -WhatIf

If you really want to hard code that, make a function out of it and call the function:
Function Copy-ADGroupMember {
[CmdletBinding(SupportsShouldProcess=$True, ConfirmImpact='High')]
Param(
	[Parameter(ValueFromPipeline=$True, Position=0)]
	[PSObject]$Identity,
	[PSObject]$Destination,
	[Switch]$Mirror
)
	$SourceMembers = Get-ADGroupMember -Identity $Identity | Select-Object -ExpandProperty distinguishedName
	$TargetMembers = Get-ADGroupMember -Identity $Destination | Select-Object -ExpandProperty distinguishedName
	$DifferenceMembers = Compare-Object -ReferenceObject $SourceMembers -DifferenceObject $TargetMembers
	$ProcessMembers = $DifferenceMembers | Where-Object {$_.SideIndicator -eq '<='} | Select-Object -ExpandProperty InputObject
	If ($PSCmdlet.ShouldProcess($Destination, "Add objects:`r`n`t$($ProcessMembers -join "`r`n`t")`r`n")) {
		Add-ADGroupMember -Identity $Destination -Members $ProcessMembers
	}
	If ($Mirror) {
		$ProcessMembers = $DifferenceMembers | Where-Object {$_.SideIndicator -eq '=>'} | Select-Object -ExpandProperty InputObject
		If ($PSCmdlet.ShouldProcess($Destination, "Remove objects:`r`n`t$($ProcessMembers -join "`r`n`t")`r`n")) {
			Remove-ADGroupMember -Identity $Destination -Members $ProcessMembers
		}
	}
}

.\Copy-ADGroupMember.ps1 -Identity SourceADGroup -Destination DestADGroup -Mirror -WhatIf

Open in new window

0
Ultimate Tool Kit for Technology Solution Provider

Broken down into practical pointers and step-by-step instructions, the IT Service Excellence Tool Kit delivers expert advice for technology solution providers. Get your free copy now.

fireguy1125Author Commented:
Thank you. The script seems to work, but throws errors.  Can these be suppressed?

PS C:\scripts> .\Copy-ADGroupMember.ps1 -Identity SourceADGroup -Destination DestDL -Mirror -Confirm:$False
Remove-ADGroupMember : Cannot validate argument on parameter 'Members'. The argument is null or empty. Provide an
argument that is not null or empty, and then try the command again.
At C:\scripts\Copy-ADGroupMember.ps1:17 char:56
+ ...  Remove-ADGroupMember -Identity $Destination -Members $ProcessMembers
+                                                           ~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidData: (:) [Remove-ADGroupMember], ParameterBindingValidationException
    + FullyQualifiedErrorId : ParameterArgumentValidationError,Microsoft.ActiveDirectory.Management.Commands.RemoveADG
   roupMember

PS C:\scripts> .\Copy-ADGroupMember.ps1 -Identity SourceADGroup -Destination DestDL -Mirror -Confirm:$False
Add-ADGroupMember : Cannot validate argument on parameter 'Members'. The argument is null or empty. Provide an
argument that is not null or empty, and then try the command again.
At C:\scripts\Copy-ADGroupMember.ps1:12 char:52
+     Add-ADGroupMember -Identity $Destination -Members $ProcessMembers
+                                                       ~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidData: (:) [Add-ADGroupMember], ParameterBindingValidationException
    + FullyQualifiedErrorId : ParameterArgumentValidationError,Microsoft.ActiveDirectory.Management.Commands.AddADGrou
   pMember

PS C:\scripts>
0
oBdACommented:
[CmdletBinding(SupportsShouldProcess=$True, ConfirmImpact='High')]
Param(
	[PSObject]$Identity,
	[PSObject]$Destination,
	[Switch]$Mirror
)
$SourceMembers = Get-ADGroupMember -Identity $Identity | Select-Object -ExpandProperty distinguishedName
$TargetMembers = Get-ADGroupMember -Identity $Destination | Select-Object -ExpandProperty distinguishedName
$DifferenceMembers = Compare-Object -ReferenceObject $SourceMembers -DifferenceObject $TargetMembers
$ProcessMembers = $DifferenceMembers | Where-Object {$_.SideIndicator -eq '<='} | Select-Object -ExpandProperty InputObject
If ($ProcessMembers -and $PSCmdlet.ShouldProcess($Destination, "Add objects:`r`n`t$($ProcessMembers -join "`r`n`t")`r`n")) {
	Add-ADGroupMember -Identity $Destination -Members $ProcessMembers
}
If ($Mirror) {
	$ProcessMembers = $DifferenceMembers | Where-Object {$_.SideIndicator -eq '=>'} | Select-Object -ExpandProperty InputObject
	If ($ProcessMembers -and $PSCmdlet.ShouldProcess($Destination, "Remove objects:`r`n`t$($ProcessMembers -join "`r`n`t")`r`n")) {
		Remove-ADGroupMember -Identity $Destination -Members $ProcessMembers
	}
}

Open in new window

0
fireguy1125Author Commented:
Thanks, that resolved those errors.  However I just tested the script, and find that when the destination group has no members yet, it fails to populate with members in the source group.  It works when the destination group contains at least one member.  here is the error I get:

PS C:\scripts> .\Copy-ADGroupMember.ps1 -Identity SourceADGroup -Destination DestDL -Mirror -Confirm:$False
Compare-Object : Cannot bind argument to parameter 'DifferenceObject' because it is null.
At C:\scripts\Copy-ADGroupMember.ps1:13 char:87
+ ... ject -ReferenceObject $SourceMembers -DifferenceObject $TargetMembers
+                                                            ~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidData: (:) [Compare-Object], ParameterBindingValidationException
    + FullyQualifiedErrorId : ParameterArgumentValidationErrorNullNotAllowed,Microsoft.PowerShell.Commands.CompareObje
   ctCommand

Open in new window



Please note that the line 13 referenced in the error is
$DifferenceMembers = Compare-Object -ReferenceObject $SourceMembers -DifferenceObject $TargetMembers

Open in new window

(I added 4 commented lines to the top of your script when I ran, so for your original script line error would likely be line 9) Hope this didn't confuse you!
0
oBdACommented:
[CmdletBinding(SupportsShouldProcess=$True, ConfirmImpact='High')]
Param(
	[Parameter(ValueFromPipeline=$True, Position=0)]
	[PSObject]$Identity,
	[PSObject]$Destination,
	[Switch]$Mirror
)
Try {$SourceMembers = @(Get-ADGroupMember -Identity $Identity -ErrorAction Stop | Select-Object -ExpandProperty distinguishedName)} Catch {Throw $_}
Try {$TargetMembers = @(Get-ADGroupMember -Identity $Destination -ErrorAction Stop | Select-Object -ExpandProperty distinguishedName)} Catch {Throw $_}
If ($DifferenceMembers = Compare-Object -ReferenceObject $SourceMembers -DifferenceObject $TargetMembers) {
	$ProcessMembers = $DifferenceMembers | Where-Object {$_.SideIndicator -eq '<='} | Select-Object -ExpandProperty InputObject
	If ($ProcessMembers -and $PSCmdlet.ShouldProcess($Destination, "Add objects:`r`n`t$($ProcessMembers -join "`r`n`t")`r`n")) {
		Add-ADGroupMember -Identity $Destination -Members $ProcessMembers
	}
	If ($Mirror) {
		$ProcessMembers = $DifferenceMembers | Where-Object {$_.SideIndicator -eq '=>'} | Select-Object -ExpandProperty InputObject
		If ($ProcessMembers -and $PSCmdlet.ShouldProcess($Destination, "Remove objects:`r`n`t$($ProcessMembers -join "`r`n`t")`r`n")) {
			Remove-ADGroupMember -Identity $Destination -Members $ProcessMembers
		}
	}
} Else {
	Write-Verbose -Message "'$($Identity)' --> '$($Destination)': No differences found."
}

Open in new window

0

Experts Exchange Solution brought to you by

Your issues matter to us.

Facing a tech roadblock? Get the help and guidance you need from experienced professionals who care. Ask your question anytime, anywhere, with no hassle.

Start your 7-day free trial
fireguy1125Author Commented:
Works beautifully, thank you!
0
It's more than this solution.Get answers and train to solve all your tech problems - anytime, anywhere.Try it for free Edge Out The Competitionfor your dream job with proven skills and certifications.Get started today Stand Outas the employee with proven skills.Start learning today for free Move Your Career Forwardwith certification training in the latest technologies.Start your trial today
Active Directory

From novice to tech pro — start learning today.

Question has a verified solution.

Are you are experiencing a similar issue? Get a personalized answer when you ask a related question.

Have a better answer? Share it in a comment.