0 votes

Hi,

Group memberships are kept when using "User Copy" function.
Is it possible to do the same thing between two existing users ? (custom commands or else)

Thanks for your response,
Yoann

by (180 points)
0

Yoann,

We have a solution for you. We are now writing some PowerShell scripts for you to use, I'll update the topic as soon as we get these scripts working.

1 Answer

0 votes
by (216k points)

Hello Yoann,

We don't yet have this functionality in Adaxes, but it is in our TODO list.

Nevertheless, there is a workaround that we may suggest for the time being. You need to create two Custom Commands.

One of these Custom Commands, when executed on a user, will copy GUIDs of all the groups that the user is a member of to the CustomAttributeBinary1 property of the user who invokes the Custom Command. CustomAttributeBinary1 is one of the Adaxes virtual properties that can store binary data.

The other Custom Command will read the value of the CustomAttributeBinary1 property of the user who invokes the Custom Command. If this property is not empty, it will set the user, on which the command is performed, to be a member of the groups represented by the GUIDs.

To create a Custom Command that will copy group membership to CustomAttributeBinary1:

  1. Create a new Custom Command

  2. On the 2nd step of the Custom Command creation wizard, select User.

  3. On the 3rd step of the wizard, add the Run a program or PowerShellScript action and paste the following script:

     # Get a list of GUIDs of the groups the selected object is a member of.
     $sourceGroupGuids = $Context.TargetObject.GetEx("adm-DirectMemberOfGuid") 
    
     # Build a single-dimention array that will contain all GUIDs of the groups
     $totalBytes = $sourceGroupGuids.Count * 16
     $result = New-Object 'System.Collections.Generic.List[System.Byte]' $totalBytes
     foreach ($groupGuidBytes in $sourceGroupGuids)
     {
         $result.AddRange($groupGuidBytes)
     }
     # Save the data to the adm-CustomAttributeBinary1 virtual property of the operation initiator.
     $Context.Initiator.UserAdsObject.Put("adm-CustomAttributeBinary1", $result.ToArray())
     $Context.Initiator.UserAdsObject.SetInfo()

To create a Custom Command that will 'paste' group membership from CustomAttributeBinary1:

  1. Create a new Custom Command

  2. On the 2nd step of the Custom Command creation wizard, select User.

  3. On the 3rd step of the wizard, add the Run a program or PowerShellScript action and paste the following script:

     # Get an array of  group GUIDs
     try
     {
         $sourceGroupGuids = $Context.Initiator.UserAdsObject.Get("adm-CustomAttributeBinary1")
     }
     catch
     {
         $Context.Cancel("Failed to get group GUIDs.")
     }
    
     # Calculate the number of GUIDs
     $totalBytes = $sourceGroupGuids.Length
     # Make sure that the total number of  bytes is a divisible of 16
     $remainder = 0
     [System.Math]::DivRem($totalBytes, 16, [ref] $remainder)
     if ($remainder -ne 0)
     {
         $Context.Cancel("Unexpected data length!")
     }
     $groupsToAdd = New-Object "System.Collections.Generic.HashSet[System.Guid]"
    
     for ($i = 0; $i -lt ($totalBytes / 16); $i++)
     {
         $bytes = [System.Guid]::Empty.ToByteArray()
         [System.Array]::Copy($sourceGroupGuids, $i * 16, $bytes, 0, 16)
         $guid = New-Object "System.Guid" (,$bytes)
         $groupsToAdd.Add($guid)
     }
    
     # Get GUIDs of the groups the user is a member of
     $targetGroupGuids = $Context.TargetObject.GetEx("adm-DirectMemberOfGuid")
    
     # Adjust the list of groups to add the user to, and the list of groups to remove the user from
     $groupsToRemove = New-Object "System.Collections.Generic.HashSet[System.Guid]"
     foreach($targetGroupGuidBytes in $targetGroupGuids)
     {
         $guid = New-Object "System.Guid" (,$targetGroupGuidBytes)
         if ($groupsToAdd.Contains($guid))
         {
             $groupsToAdd.Remove($guid) # already a member of the group
         }
         else
         {
             $groupsToRemove.Add($guid)
         }
     }
    
     # Remove from groups that are not in the list of copied groups
     foreach($groupGuid in $groupsToRemove)
     {
         $groupGuid = $groupGuid.ToString("B")
         $groupPath = "Adaxes://<GUID=$groupGuid>"
         $group = $Context.BindToObjectEx($groupPath, $True)
         $group.Remove($Context.TargetObject.AdsPath)
     }
    
     # Add to groups
     foreach($groupGuid in $groupsToAdd)
     {
         $groupGuid = $groupGuid.ToString("B")
         $groupPath = "Adaxes://<GUID=$groupGuid>"
         $group = $Context.BindToObjectEx($groupPath, $True)
         $group.Add($Context.TargetObject.AdsPath)    
     }
0

Hello,

Thanks a lot for those scripts.

I run the 2 Custom commands and I have an error with the second one when the target user has no group before the copy :

Exception lors de l'appel de « .ctor » avec « 1 » argument(s) : « La longueur du tableau d'octets pour le GUID doit être exactement de 16 octets. » Exception lors de l'appel de « .ctor » avec « 1 » argument(s) : « La longueur du tableau d'octets pour le GUID doit être exactement de 16 octets. » Exception lors de l'appel de « .ctor » avec « 1 » argument(s) : « La longueur du tableau d'octets pour le GUID doit être exactement de 16 octets. » Exception lors de l'appel de « .ctor » avec « 1 » argument(s) : « La longueur du tableau d'octets pour le GUID doit être exactement de 16 octets. » Exception lors de l'appel de « .ctor » avec « 1 » argument(s) : « La longueur du tableau d'octets pour le GUID doit être exactement de 16 octets. » Exception lors de l'appel de « .ctor » avec « 1 » argument(s) : « La longueur du tableau d'octets pour le GUID doit être exactement de 16 octets. » Exception lors de l'appel de « .ctor » avec « 1 » argument(s) : « La longueur du tableau d'octets pour le GUID doit être exactement de 16 octets. » <output truncated>

If there is only one group to be copied, it is not copied. If there are several, they are copied except the first one.

Any idea to correct this ?

0

Hello,

I have an error with the second one when the target user has no group before the copy

Are you copying membership of user accounts? Each user must be a member of at least one primary group (Domain Users by default). So, the situation when a users has no groups is impossible.

If there is only one group to be copied, it is not copied. If there are several, they are copied except the first one.

We tested the script once again and it works perfectly. Can you post screenshots of the Member Of tab for the source and target users.

0

You're right, when I said "no group", I wanted to say no group except Domain Users.

(Source)

(Target before copy)

(Target AFTER copy)
Result :

L'opération a réussi.
Détails
Coller les groupes: 1 operation executed
Run PowerShell script 'Coller_groupe' for the user
Exception lors de l'appel de « .ctor » avec « 1 » argument(s) : « La longueur du tableau d'octets pour le GUID doit être exactement de 16 octets. » Exception lors de l'appel de « .ctor » avec « 1 » argument(s) : « La longueur du tableau d'octets pour le GUID doit être exactement de 16 octets. » Exception lors de l'appel de « .ctor » avec « 1 » argument(s) : « La longueur du tableau d'octets pour le GUID doit être exactement de 16 octets. » Exception lors de l'appel de « .ctor » avec « 1 » argument(s) : « La longueur du tableau d'octets pour le GUID doit être exactement de 16 octets. » Exception lors de l'appel de « .ctor » avec « 1 » argument(s) : « La longueur du tableau d'octets pour le GUID doit être exactement de 16 octets. » Exception lors de l'appel de « .ctor » avec « 1 » argument(s) : « La longueur du tableau d'octets pour le GUID doit être exactement de 16 octets. » Exception lors de l'appel de « .ctor » avec « 1 » argument(s) : « La longueur du tableau d'octets pour le GUID doit être exactement de 16 octets. » <output truncated>

I tried with differents source users, who had differents groups, it is the same result.

0

We've managed to find the cause of the problem. Since adm-DirectMemberOfGuid is a multi-valued property, instead of using the Get method, the script must use the GetEx method - $Context.TargetObject.GetEx("adm-DirectMemberOfGuid").

I've modified both scripts above to use GetEx instead of Get.

Please let me know if the scripts work now.

0

It's OK, thanks a lot.

Inspired by your scripts, I created two other custom commands to copy other attributes.

The first one :

# Get organization for a user
$sourceOU = $Context.TargetObject.Get("ou")
$sourceDepartment = $Context.TargetObject.Get("department")
$sourceDivision = $Context.TargetObject.Get("division")
$sourceCity = $Context.TargetObject.Get("l")
$sourceBuilding = $Context.TargetObject.Get("cRPLBuildingName")

If ((-Not $sourceOU) -and (-Not $sourceDepartment) -and (-Not $sourceDivision) -and (-Not $sourceCity) -and (-Not $sourceBuilding))
{
        $Context.Cancel("L'utilisateur sélectionné n'a pas de Direction, Service ou Pôle, ni de site ou bâtiment.")
}

If ($sourceOU -ne $NULL) {
    $ofs = "|"
    $Direction = [string]$sourceOU
    $Context.Initiator.UserAdsObject.Put("adm-CustomAttributeText2", $Direction)
}
Else {
    $Context.Initiator.UserAdsObject.Put("adm-CustomAttributeText2", "Null")
}

If ($sourceDepartment -ne $NULL) {
    $Context.Initiator.UserAdsObject.Put("adm-CustomAttributeText3", $sourceDepartment)
}
Else {
    $Context.Initiator.UserAdsObject.Put("adm-CustomAttributeText3", "Null")
}

If ($sourceDivision -ne $NULL) {
    $Context.Initiator.UserAdsObject.Put("adm-CustomAttributeText4", $sourceDivision)
}
Else {
    $Context.Initiator.UserAdsObject.Put("adm-CustomAttributeText4", "Null")
}

If ($sourceCity -ne $NULL) {
    $Context.Initiator.UserAdsObject.Put("adm-CustomAttributeText5", $sourceCity)
}
Else {
    $Context.Initiator.UserAdsObject.Put("adm-CustomAttributeText5", "Null")
}

If ($sourceBuilding -ne $NULL) {
    $Context.Initiator.UserAdsObject.Put("adm-CustomAttributeText6", $sourceBuilding)
}
Else {
    $Context.Initiator.UserAdsObject.Put("adm-CustomAttributeText6", "Null")
}

$Context.Initiator.UserAdsObject.SetInfo()

And the second one :

#Get strings
$sourceOU = $Context.Initiator.UserAdsObject.Get("adm-CustomAttributeText2")
$sourceDepartment = $Context.Initiator.UserAdsObject.Get("adm-CustomAttributeText3")
$sourceDivision = $Context.Initiator.UserAdsObject.Get("adm-CustomAttributeText4")
$sourceCity = $Context.Initiator.UserAdsObject.Get("adm-CustomAttributeText5")
$sourceBuilding = $Context.Initiator.UserAdsObject.Get("adm-CustomAttributeText6")

If ($sourceOU -ne "Null") {
    $Direction = $sourceOU.split("|")
    $Context.TargetObject.PutEx("ADS_PROPERTY_UPDATE", "ou", $Direction)
}
Else {
    $Context.TargetObject.PutEx("ADS_PROPERTY_CLEAR", "ou", $Null)
}

If ($sourceDepartment -ne "Null") {
    $Context.TargetObject.Put("department", $sourceDepartment)
}
Else {
    $Context.TargetObject.Put("department", $null)
}

If ($sourceDivision -ne "Null") {
    $Context.TargetObject.Put("division", $sourceDivision)
}
Else {
    $Context.TargetObject.Put("division", $null)    
}

If ($sourceCity -ne "Null") {
    $Context.TargetObject.Put("l", $sourceCity)
}
Else {
    $Context.TargetObject.Put("l", $null)    
}

If ($sourceBuilding -ne "Null") {
    $Context.TargetObject.Put("cRPLBuildingName", $sourceBuilding)
}
Else {
    $Context.TargetObject.Put("cRPLBuildingName", $null)    
}

$Context.TargetObject.SetInfo()

It works, but when a source attribute is empty, when I execute the first custom command, I've got an error and I did not find how to mask it.
Could you help me again to do this correctly and nice ? :roll:

0

You need to use the try/catch block:

$sourceOU = $NULL
try
{
    $sourceOU = $Context.TargetObject.Get("ou")
}
catch [System.Runtime.InteropServices.COMException]
{
   # TODO: failed to get the 'ou' property
}
0

Perfect !

Thanks a lot
Yoann

0

Hi Support,

what a great script :D
I'm trying to modify it a Little bit but I'm not able. May be you can help me.
What I'm looking for is:

During creating a User am I able fill out the Field "altRecipient". From this user "altRecipient" should the businessrule "after creating a User" copy all Groupmemberships to the new created User.

Could you help me?

Thanks

0

Hello,

This one should work for you:

# Bind to the Forward To recipient
try
{
    $altRecipientDN = $Context.TargetObject.Get("altRecipient")
}
catch
{
    return
}

$altRecipient = $Context.BindToObjectByDN($altRecipientDN)

# Get object membership
try
{
    $groupGuidsInBytes = $altRecipient.GetEx("adm-DirectMemberOfGuid")
}
catch
{
    $Context.LogMessage($altRecipient.Name + " is not a member of any groups", "Warning")
    return
}

# Get the ID of the user's primary group
$primaryGroupId = $Context.TargetObject.Get("primaryGroupID")

# Add target user to groups
foreach ($groupGuidBytes in $groupGuidsInBytes)
{
    $groupGuid = New-Object "System.Guid" (,$groupGuidBytes)
    $groupGuid = $groupGuid.ToString("B")
    $group = $Context.BindToObject("Adaxes://<GUID=$groupGuid>")

    # Skip the group if it is the primary group for the user
    if ($group.Get("primaryGroupToken") -eq $primaryGroupId)
    {
        continue
    }

    try
    {
        $group.Add($Context.TargetObject.AdsPath)
    }
    catch
    {
        $Context.LogMessage($group.Get("name") + ": " + $_.Exception.Message, "Warning")
    }
}
0

Hello Yoann,
We don't yet have this functionality in Adaxes, but it is in our TODO list.

Was this functionality ever implemented?
I want to be able to copy group membership from one user and give it to another

0

Hello,

The built-in support for this functionality hasn't been implemented yet, but you can use the solution with 2 Custom Commands provided in Copy of groups membership from one user to another. It works well. If you have any qurestions regarding to the solution, do not hesitate to ask.

0

Forgive me for adding to this, but I was wondering if you could add the script path to this script as well based on the user provided for the altRecipient? That would basically automate this our whole process.

0

Are there any security implications for having that adm-CustomAttributeBinary1 attribute filled out on your user profile?

0

Not at all. Thank you.

0

In there a way to edit this script to omit certain groups from being copied.
The Guids are giving me trouble.
I wanted something like this:

$BlackList = "Domain Admins","App\_Test\*","App\_Test2\*"  
 #Foreach ($bgroup in $Blacklist)  
 # {  
 #IF ($groups -notlike $bgroup)  

Then add group to user

0

Hello,

In there a way to edit this script to omit certain groups from being copied.

Yes, sure. You'll need to change only the 1st script that copies groups. The 2nd script that pastes the groups can remain as it is.

To implement what you want, use the following version of the 1st script:

$groupNamesToSkip = @("Domain Admins", "App_Test*", "App_Test2*") # TODO: modfiy me

# Get GUIDS of the groups won't be copied
$domainName = $Context.GetObjectDomain("%distinguishedName%")
$searcher = $Context.BindToObject("Adaxes://$domainName/rootDSE")
$filter = New-Object "System.Text.StringBuilder"
$filter.Append("(&(objectCategory=group)(|") | Out-Null
foreach ($value in $groupNamesToSkip)
{
    $filter.Append("(name=$value)") | Out-Null
}
$filter.Append("))") | Out-Null
$searcher.SearchFilter = $filter.ToString()
$searcher.SearchScope = "ADS_SCOPE_SUBTREE"
$searcher.PageSize = 500
$searcher.SetPropertiesToLoad(@("objectGuid"))

try
{
    $searchResult = $searcher.ExecuteSearch()
    $groups = $searchResult.FetchAll()

    $groupGuidsToSkip = New-Object "System.Collections.Generic.HashSet[System.Guid]"
    foreach ($groupId in $groups)
    {
        $guid = [Guid]$groupId.Properties["objectGuid"].Value
        $groupGuidsToSkip.Add($guid) | Out-Null
    }
}
finally
{
    $searchResult.Dispose()
}

# Get a list of GUIDs of the groups the selected object is a member of.
$sourceGroupGuids = $Context.TargetObject.GetEx("adm-DirectMemberOfGuid")

# Build a single-dimention array that will contain all GUIDs of the groups
$totalBytes = $sourceGroupGuids.Count * 16
$result = New-Object 'System.Collections.Generic.List[System.Byte]' $totalBytes
foreach ($groupGuidBytes in $sourceGroupGuids)
{
    if ($groupGuidsToSkip.Contains([Guid]$groupGuidBytes))
    {
        continue
    }

    $result.AddRange($groupGuidBytes)
}
# Save the data to the adm-CustomAttributeBinary1 virtual property of the operation initiator.
$Context.Initiator.UserAdsObject.Put("adm-CustomAttributeBinary1", $result.ToArray())
$Context.Initiator.UserAdsObject.SetInfo()

In the script, $groupNamesToSkip specifies the names of the groups that will be omitted.

0

Works great
Thanks

Related questions

0 votes
1 answer

Hello Back when we first started using Adaxes you created a couple of great scripts which worked together really well, the first one copied one users group membership and put in ... an addition to what groups the second user is already a member of? Thank you.

asked Aug 4, 2015 by CBurn (700 points)
0 votes
1 answer

goal is to copy groups from one user to another during the crete user process. I created a variable on the create user form to input the UPN of the ... primaryGroupToken") -eq $primaryGroupId) { continue } $group.Remove($Context.TargetObject.AdsPath) } }

asked Nov 30, 2021 by Derek.Axe (480 points)
0 votes
1 answer

We have several contractors that come and go, it would be helpful to have a custom command that will copy only the member of groups from one user to another. We have done this previously with ... ; write-warning "I'm sorry, Jay. I'm afraid I can't do that." }

asked Jan 9, 2017 by willy-wally (3.2k points)
0 votes
1 answer

Is it possible using PowerShell to copy group memberships from an already existing user without copying 2 specific groups named for example test and test 1 ? We are currently ... groups are not included. I can share the PowerShell script if needed. KR, Cas

asked Oct 30, 2023 by Cas (100 points)
0 votes
1 answer

Hi there, i know the multiple ways of copying the user groups - or all of them within the user creation wizard. I want to copy only a couple of groups ... is it possible to create an approval operation out of an powershellscript? Kind regards, Constantin

asked May 27, 2021 by Constey (190 points)
3,326 questions
3,025 answers
7,724 comments
544,675 users