The scripts copy group membership from the user specified to the target user.
Using a custom command parameter
The script copies group membership from the user specified in an AD object picker parameter of a custom command to the target user.
Parameters:
- $sourceUserDNParamName - Specifies the name of the parameter used to select the source user with the param- prefix.
- $replaceGroups - If set to $True, the script will replace all the target user group membership with that of the source user. If set to $False, the target user will retain its group membership and will be added to the groups the source user is a member of.
- $onlyEntraGroups - If set to $True, the script will only add the target user to the Microsoft Entra groups the source user is a member of. If set to $False, the target user will be added to all the groups the source user is a member of.
- $groupNamesToSkip - Specifies sAMAccountNames of the groups that should be skipped by the script.
PowerShell
$sourceUserDNParamName = "param-User" # TODO: modify me
$replaceGroups = $False # TODO: modify me
$onlyEntraGroups = $False # TODO: modify me
$groupNamesToSkip = @("Group1", "Group2", "Group3*") # TODO: modify me
function SkipGroupByName($patterns, $group)
{
if ($NULL -eq $patterns)
{
return $False
}
if ($group.DirectoryType -eq 1)
{
$groupName = $group.Get("sAMAccountName")
}
else
{
$groupName = $group.Get("name")
}
foreach ($pattern in $patterns)
{
if ($groupName -like $pattern)
{
return $True
}
}
return $False
}
function SkipGroupByType($group, $skipPrimaryGroup, $primaryGroupId, $onlyEntraGroups)
{
if ($onlyEntraGroups -and $group.DirectoryType -ne 2)
{
continue
}
if (($group.DirectoryType -eq 1) -and $skipPrimaryGroup)
{
# Check if the group is primary
if ($group.Get("primaryGroupToken") -eq $primaryGroupId)
{
return $True
}
}
# Check if the group is dynamic
try
{
$dynamicMembership = $group.Get("adm-AzureDynamicMembership")
}
catch
{
$dynamicMembership = $False
}
# Check if the group is Rule based
$isRuleBasedGroup = $group.MembershipType -eq "ADM_GROUPMEMBERSHIPTYPE_RULEBASED"
return $dynamicMembership -or $isRuleBasedGroup
}
# Bind to the source user
$sourceUserDN = $Context.GetParameterValue($sourceUserDNParamName)
$sourceUser = $Context.BindToObjectByDN($sourceUserDN)
# Get groups to add
$groupGuidsToAdd = New-Object "System.Collections.Generic.HashSet[System.Guid]"
$sourceUser.GetEx("adm-DirectMemberOfGuid") | %%{[void]$groupGuidsToAdd.Add([Guid]$_)}
# Get current groups
$currentGroupGuids = New-Object "System.Collections.Generic.HashSet[System.Guid]"
$Context.TargetObject.GetEx("adm-DirectMemberOfGuid") | %%{[void]$currentGroupGuids.Add([Guid]$_)}
# Update groups
foreach ($guidBytes in $groupGuidsToAdd)
{
$guid = [Guid]$guidBytes
if ($currentGroupGuids.Remove($guid))
{
continue
}
# Skip special groups
$group = $Context.BindToObjectEx("Adaxes://<GUID=$guid>", $True)
if ((SkipGroupByName $groupNamesToSkip $group) -or (SkipGroupByType $group $False $NULL $onlyEntraGroups))
{
continue
}
$group.Add($Context.TargetObject.AdsPath)
}
if ($replaceGroups)
{
$primaryGroupId = $NULL
if ($Context.TargetObject.DirectoryType -eq 1)
{
# Get the primary group ID
$primaryGroupId = $Context.TargetObject.Get("primaryGroupID")
}
foreach ($guidBytes in $currentGroupGuids)
{
$guid = [Guid]$guidBytes
$group = $Context.BindToObjectEx("Adaxes://<GUID=$guid>", $True)
# Skip the group if it is the user's Primary Group
if (SkipGroupByType $group $True $primaryGroupId $onlyEntraGroups)
{
continue
}
$group.Remove($Context.TargetObject.AdsPath)
}
}
Using a DN syntax property
The script copies group membership from the user specified in a DN syntax property (e.g. Assistant) of the target user.
Parameters:
- $sourceUserDNPropertyName - Specifies the LDAP name of the DN syntax property storing the user to copy membership from.
- $replaceGroups - If set to $True, the script will replace all the target user group membership with that of the source user. If set to $False, the target user will retain its group membership and will be added to the groups the source user is a member of.
- $onlyEntraGroups - If set to $True, the script will only add the target user to the Microsoft Entra groups the source user is a member of. If set to $False, the target user will be added to all the groups the source user is a member of.
- $groupNamesToSkip - Specifies sAMAccountNames of the groups that should be skipped by the script.
PowerShell
$sourceUserDNPropertyName = "assistant" # TODO: modify me
$replaceGroups = $False # TODO: modify me
$onlyEntraGroups = $False # TODO: modify me
$groupNamesToSkip = @("Group1", "Group2", "Group3*") # TODO: modify me
function SkipGroupByName($patterns, $group)
{
if ($NULL -eq $patterns)
{
return $False
}
if ($group.DirectoryType -eq 1)
{
$groupName = $group.Get("sAMAccountName")
}
else
{
$groupName = $group.Get("name")
}
foreach ($pattern in $patterns)
{
if ($groupName -like $pattern)
{
return $True
}
}
return $False
}
function SkipGroupByType($group, $skipPrimaryGroup, $primaryGroupId, $onlyEntraGroups)
{
if ($onlyEntraGroups -and $group.DirectoryType -ne 2)
{
continue
}
if (($group.DirectoryType -eq 1) -and $skipPrimaryGroup)
{
# Check if the group is primary
if ($group.Get("primaryGroupToken") -eq $primaryGroupId)
{
return $True
}
}
# Check if the group is dynamic
try
{
$dynamicMembership = $group.Get("adm-AzureDynamicMembership")
}
catch
{
$dynamicMembership = $False
}
# Check if the group is Rule based
$isRuleBasedGroup = $group.MembershipType -eq "ADM_GROUPMEMBERSHIPTYPE_RULEBASED"
return $dynamicMembership -or $isRuleBasedGroup
}
# Bind to the source user
$sourceUserDN = $Context.TargetObject.Get($sourceUserDNPropertyName)
$sourceUser = $Context.BindToObjectByDN($sourceUserDN)
# Get groups to add
$groupGuidsToAdd = New-Object "System.Collections.Generic.HashSet[System.Guid]"
$sourceUser.GetEx("adm-DirectMemberOfGuid") | %%{[void]$groupGuidsToAdd.Add([Guid]$_)}
# Get current groups
$currentGroupGuids = New-Object "System.Collections.Generic.HashSet[System.Guid]"
$Context.TargetObject.GetEx("adm-DirectMemberOfGuid") | %%{[void]$currentGroupGuids.Add([Guid]$_)}
# Update groups
foreach ($guidBytes in $groupGuidsToAdd)
{
$guid = [Guid]$guidBytes
if ($currentGroupGuids.Remove($guid))
{
continue
}
# Skip special groups
$group = $Context.BindToObjectEx("Adaxes://<GUID=$guid>", $True)
if ((SkipGroupByName $groupNamesToSkip $group) -or (SkipGroupByType $group $False $NULL $onlyEntraGroups))
{
continue
}
$group.Add($Context.TargetObject.AdsPath)
}
if ($replaceGroups)
{
$primaryGroupId = $NULL
if ($Context.TargetObject.DirectoryType -eq 1)
{
# Get the primary group ID
$primaryGroupId = $Context.TargetObject.Get("primaryGroupID")
}
foreach ($guidBytes in $currentGroupGuids)
{
$guid = [Guid]$guidBytes
$group = $Context.BindToObjectEx("Adaxes://<GUID=$guid>", $True)
# Skip the group if it is the user's Primary Group
if (SkipGroupByType $group $True $primaryGroupId $onlyEntraGroups)
{
continue
}
$group.Remove($Context.TargetObject.AdsPath)
}
}
Please, specify which of the two scripts you need updated.
We use the "Using a custom command parameter" version of the script.
Copying group membership goes well, but we get a lot of errors due the script trying to add or remove the user to or from dynamic user groups.
The log shows:
Add [user] to [groupname] ([domainname].onmicrosoft.com\Groups)
Insufficient privileges to complete the operation.
I would like to be able to exclude dynamic user groups from the script. Unfortunately our naming have not been the best, so doing it on name will not work. I would like to exclude on a group property.
Such a thing can happen in case a group is dynamic in Microsoft Entra or rule-based in Adaxes. We updated the scripts to skip such groups.