## p4sync-group ## aaron bockelie <# .SYNOPSIS Synchronize a group with Active Directory. If the group exists in AD but not in perforce, create the group. If no group is specified, all groups for a given depot are synchronized from AD. #> Function p4sync-group {param([Parameter(Mandatory = $true)]$groupname) $servercheck = p4get-server if ($servercheck.pingsuccess.equals($true)) { $removemember = New-Object System.Collections.ArrayList #reporting array of membership removals $deletemember = New-Object System.Collections.ArrayList #reporting array user deletions. $addmember = New-Object System.Collections.ArrayList #reporting array of created users/memberships $nochangemember = New-Object System.Collections.ArrayList #reporting array of users that did not change. $nochangesubgroupmember = New-Object System.Collections.ArrayList #reporting array of subgroups that did not change. $addsubgroupmember = New-Object System.Collections.ArrayList #report array of subgroups to be added. $removesubgroupmember = New-Object System.Collections.ArrayList #report array of subgroups to be removed. $result = @() #parent array for the reporting. $userchanged = $false #always false at first $groupcreate = $false #always false at first $groupdestroy = $false #always false at first $isauthgroup = $false #always false at first. $p4parentauthgroup = "p4" + $servercheck.server #get the primary auth group $p4childauthgroups = p4get-authgroups #get all authentication groups. $userdiff = $null #clear user comparisons $subgroupdiff = $null #clear subgroup comparisons $maxsync = 400 #maximum size of group to sync. #start with getting a diff of the group object. it is null if the object doesn't exist in p4 or ad. #note that subgroup sync only happens on a single tree iteration. This means that we only care about the immediate children of this group. Attempting to descend the tree is to descend into madness. #circular subgroup references should not happen, because active directory will prevent us from creating circular references. And since we trust AD as authoritive, this makes our life a lot easier. try { write-progress -id 10 -activity ("Synchronising group " + $groupname) -status "Performing differential comparison of P4 vs. AD groups." $userdiff = p4diff-groupmembers $groupname #fetch the user difference object for the group between ad and p4 write-progress -id 10 -activity ("Synchronising group " + $groupname) -status "Performing differential comparison of P4 vs. AD subgroups." $subgroupdiff = p4diff-subgroupmembers $groupname #fetch the subgroup difference object for the group between ad and p4 } catch { $message = "Error retrieving group differential for group `'" + $groupname + "`'. Check your authentication and environment and try again." write-error $message -category ObjectNotFound #just in case something strange happens when trying to get the diff } if ($userdiff.count -ge $maxsync) { $message = "Too many user objects to sync." throw $message } if (!($userdiff)) #if no objects returned, we have to determine if the group only has subgroups before we can determine that the group does not exist. Groups CAN exist with no users and only subgroups attached. { if (!($subgroupdiff)) #if there are also no subgroups after falling through this case, we can safely assume this group does not exist. { $message = "No group named `'" + $groupname + "`' exists to sync. Group may be empty." #no work to be done, group object wasn't even returned. Probably an invalid group name. write-warning $message } } #get a bunch of group change data from the diff. write-progress -id 10 -activity ("Synchronising group " + $groupname) -status "Calculating group differential data." $userdiff | ?{$_.sideindicator -eq "=="} | %{[void]$nochangemember.add($_.inputobject)} #users that stay the same, inserted into system.collections.arraylist. $userdiff | ?{$_.sideindicator -eq "=>"} | %{[void]$addmember.add($_.inputobject)} #users to be added, inserted into system.collections.arraylist. $userdiff | ?{$_.sideindicator -eq "<="} | %{[void]$removemember.add($_.inputobject)} #users to be removed, inserted into system.collections.arraylist. $subgroupdiff | ?{$_.sideindicator -eq "=="} | %{[void]$nochangesubgroupmember.add($_.inputobject)} #groups that stay the same, inserted into system.collections.arraylist. $subgroupdiff | ?{$_.sideindicator -eq "=>"} | %{[void]$addsubgroupmember.add($_.inputobject)} #subgroups to be added, inserted into system.collections.arraylist. $subgroupdiff | ?{$_.sideindicator -eq "<="} | %{[void]$removesubgroupmember.add($_.inputobject)} #subgroups to be removed, inserted into system.collections.arraylist. if ($userdiff -or $subgroupdiff) #if a diff object has results, let's do something with it. { #check if group is an authgroup. if yes, set $isauthgroup to true. foreach ($group in $p4childauthgroups) { if ($group.name -eq $groupname) { $isauthgroup = $true } } if ($isauthgroup -eq $true) # if the group we are syncing happens to be the auth group, we want to do special things. Call p4add-newuser which adds to the auth group and also creates a user object for user. # We also want to NEVER delete an auth group. Note this case has no function to delete the group. # if you want to delete an auth group from the p4 server, you must (in active directory) remove it as a child of the parent auth group, and remove all the users and subgroups from it. that's the only way. { $groupstatus = p4get-group $groupname #get the group from the perforce server. Note that this always returns a group object, so a simple if exist doesn't work. we have to check specific criteria, as seen below in the if statement. if (!($groupstatus.users) -and !($groupstatus.subgroups) -and !($groupstatus.owners)) #if users and subgroups are empty, this group does not exist on the perforce server, therefore we will attempt to create it. { $groupcreate = $true #flag that group was created $userchanged = $true #flag that users/group memberships were modified write-progress -id 10 -activity ("Synchronising group " + $groupname) -status "Creating new group in perforce server." p4add-group -group $groupname -subgroups $addsubgroupmember -users $addmember -silent } else { if ($addmember) #case for adding members to the group. we assume that if this is an auth group, they're going to need to have their user created on the perforce server. { write-progress -id 10 -activity ("Synchronising group " + $groupname) -status "Adding user(s) to group." $userchanged = $true p4add-newuser -userobject $addmember -primarygroupoverride $groupname -silent #call p4add-newuser with an array of users to be created. Each user that is created will get assigned an auth group. #depending on how roles (child auth groups) are used, the user (which will exist in AD) will get created with a child auth group (role) as it's primary, or it will get injected into the parent auth group (standalone). } if ($removemember) #case for removing members from the group. { write-progress -id 10 -activity ("Synchronising group " + $groupname) -status "Removing user(s) from group." $userchanged = $true foreach ($member in $removemember) #we need to check each user against the auth groups. If there is more than one auth group, that means we just remove membership. if there are no more auth groups, then we delete. { $groupmembership = @() #initialize this for each member. $authgroupmemberships = @() #initialize this for each member. foreach ($group in $p4childauthgroups) #iterate through each group in p4childauthgroups { if (get-qadgroupmember $groupname | ?{$member -eq $_.samaccountname}) #if the user exists within the group, return the user and do something. { $authgroupmemberships += $group.name #in this case, add them to an array. } } if ($authgroupmemberships -le 1) #if there are one (or zero) auth groups that the user is a member of, mark for deletion. { [void]$deletemember.remove($member) #remove from the remove member list (the list that just removes membership) [void]$removemember.add($member) #add to the delete member list (the list that actually deletes users) } } if ($deletemember) #check the state of this array before using it. sometimes it can be emptied depending on the previous conditional statement where we check the count of $authgroupmemberships { p4remove-user -users $deletemember -tryrevert -silent #delete user(s) from p4 } if ($removemember) #check the state of this array before using it. sometimes it can be emptied depending on the previous conditional statement where we check the count of $authgroupmemberships { p4remove-groupmember -users $removemember -group $groupstatus.group -silent #remove the member from the group. There is an assumption here, that we always have the auth groups. We never destroy auth groups. } } if ($addsubgroupmember) #case for adding subgroups to this group. { write-progress -id 10 -activity ("Synchronising group " + $groupname) -status "Adding user(s) to subgroup." p4add-groupmember -users $addsubgroupmember -group $groupstatus.group -silent } if ($removesubgroupmember) #case for removing subgroups to this group. { write-progress -id 10 -activity ("Synchronising group " + $groupname) -status "Removing user(s) from subgroup." p4remove-groupmember -users $removesubgroupmember -group $groupstatus.group -silent } } } else #else, if the group is NOT an auth group for the depot { write-progress -id 10 -activity ("Synchronising group " + $groupname) -status "Performing secondary group differential check." $groupstatus = p4get-group $groupname #grab the current group status from perforce. Compared against the state of differential that was performed at the beginning of the function, we can determine what actions to take. if (!($groupstatus.users) -and !($groupstatus.subgroups) -and !($groupstatus.owners)) #if users and subgroups are empty, this group does not exist on the perforce server, therefore we will attempt to create it. { #we would not reach this state if there was not a corresponding active directory group. write-progress -id 10 -activity ("Synchronising group " + $groupname) -status "Creating new group in perforce server." p4add-group -group $groupname -users $addmember -subgroups $addsubgroupmember -silent $groupcreate = $true #flag that group was created $userchanged = $true #flag that users/group memberships were modified } else { if ($groupstatus.users -or $groupstatus.subgroups) #if one of these exist, then a valid group object was returned from perforce. If we end up removing all the users (according to the diff) the group will get deleted. { #if one or more criteria exist, then we will not change groupcreate or groupdestroy, which flags for sync, instead. if ($addmember -or $addsubgroupmember) #if a user or subgroup needs to be added. { $addsubgroupmember | %{[void]$addmember.add($_)} #combine the users and subgroups into a single list. write-progress -id 10 -activity ("Synchronising group " + $groupname) -status "Adding user(s) to group." p4add-groupmember -users $addmember -group $groupname -silent $userchanged = $true #flag that users/group memberships were modified. } if ($removemember -or $removesubgroupmember) { $removesubgroupmember | %{[void]$removemember.add($_)} #combine the users and subgroups into a single list. write-progress -id 10 -activity ("Synchronising group " + $groupname) -status "Removing user(s) from group." p4remove-groupmember -users $removemember -group $groupname -cleaninvalidgroup -silent $userchanged = $true #flag that users/group memberships were modified. } write-progress -id 10 -activity ("Synchronising group " + $groupname) -status "Retrieving new status of group for reporting status." $groupstatus = p4get-group $groupname #re-check the status after changing. If the group disappeared, we need to record the deletion. if (!$groupstatus.users -and !$groupstatus.subgroups) #check the state of the group. if it's not returned now, then we've deleted it and flag appropriately. { $groupdestroy = $true #flag that group was removed $userchanged = $true #flag that users/group memberships were modified. } } } } $result = new-object psobject -property @{"group" = $groupname;"nochange" = $nochangemember;"added" = $addmember;"removed" = $removemember;"accountschanged" = $userchanged;"groupcreate" = $groupcreate;"groupdestroy" = $groupdestroy;"server" = $servercheck.server;"isauthgroup" = $isauthgroup} } else { #if the group object returned } } write-progress -id 10 -completed -activity "Complete." -status "Complete." return $result }