bizarre behavior with add-member

building some reports to compare ad users group memberships
whats strange is on random objects returned post add-member, the initial value is passed as {}

then when i populate the object later in the script it winds up as {X} instead of just an X

in testing, if i put any letter value in the value for add-member, for the entries that this is happening on, i see just {} as being put into the value

snippet:

	foreach ($user in $users)
	{
		if ($user)
		{
			Write-Host $user
			$groupnames = Get-ADUser $user -Properties memberof | Select-Object -ExpandProperty memberof
			$usergroups = Get-ADPrincipalGroupMembership $user
			$adgroups += $usergroups
			$adgroups | Add-Member -MemberType NoteProperty -Name "$user" -Value "" -Force
		}
	}

any help here would be greatly appreciated, i’m just at the end of my rope, i’ve passed every one of the ojects through GM and they all appear to be strings i’m just completely lost trying to trouble-shoot this

Curly brackets indicate a collection of objects, such as groups. There is one user and a user is part of many groups. If you build an object manually, you’ll see the same behavior you are referring to:

$obj = @()

$obj += [pscustomobject]@{
    UserName = "User1"
    Groups = "Group1", "Group2"
}

$obj

Output:

UserName Groups          
-------- ------          
User1    {Group1, Group2}

The real question is what are you trying to capture? A user with group memberships? What is the expected result you’re looking for?

well, that’s not what the code does,
the final phase of the data is a table to compare users group memberships.

when this executes:$adgroups | Add-Member -MemberType NoteProperty -Name “$user” -Value “” -Force
I do get the new field added to the array and 95% of the objects have it appropriately, network id with a blank value.
the other 5% randomly give a {} value.

if I modify my value to validate, I tried “U”
95% of the objects have “U” as the value before I load.
but a random 5% of the objects have just “{}” as the stored value.

to make it clear

	foreach ($user in $users)
	{
		if ($user)
		{
			Write-Host $user
			$usergroups = Get-ADPrincipalGroupMembership $user
			$adgroups += $usergroups
			$adgroups | Add-Member -MemberType NoteProperty -Name "$user" -Value "" -Force
		}
	}

i get mostly USERID:U
on random entries however i get USERID:{}

Add-Member isn’t being used properly and it’s not necessary, there are much easier methods to build an object. You are using a variable for the Name of the property. This would make have properties like Groups, Jim, Sally. Add-Member can add a property to an entire object

$obj = @()

$obj += [pscustomobject]@{
    UserName = "User1"
    Groups = "Group1", "Group2"
}

$obj += [pscustomobject]@{
    UserName = "User2"
    Groups = "Group3", "Group1"
}

$obj | Add-Member -MemberType NoteProperty -Name LikesCats -Value $false
$obj

Output:

UserName Groups           LikesCats
-------- ------           ---------
User1    {Group1, Group2}     False
User2    {Group3, Group1}     False

Add-Member is an antiquated way to build objects. The first, simplest approach is just using Select-Object, which generates a PSObject. Try this with a couple of users to validate it contains the data you’re looking for.

$users = "user1", "user2", "user3"

$results = foreach ($user in $users) {
    Get-ADUser -Filter {SamAccountName -eq $user} -Properties MemberOf |
    Select Name, 
           SamAccountName,
           @{Name="Groups";Expression={$_.MemberOf | foreach{$_.Split(",")[0].Replace("CN=","")}}}
}

$results

well, let’s start with the root request I have, to build a report that I can feed multiple ids into, to generate a table containing all of the ad groups the users are a member of, with separate columns for each group to indicate if that user is in the group.
so i “have” to dynamically name values in my object, which is what I am using the add-member for.

so my process is to get-adprincipalgroupmembership of the networkid provided to the script.

I then loop through all of the objects in the get-adprincipalgroupmembership results, and I add to the object a property that is named as the userid so i can populate the membership status for “that” user later in the script.

this works fine. the objects are all given an additional property for each network id i feed in.

the issue is that most times, this attribute is added properly.
at random however, some attributes are added as {}

the script itself works, it does build the objects with the additional property that I require, the only issue is the random attributes being wrapped in the {}

here is a more complete set of the code (id’s obfuscated)

$ids = "USER1,USER2"
$users = $ids.Split(",") | ForEach-Object { $_.trim() }
	foreach ($user in $users)
	{
		if ($user)
		{
			$usergroups = Get-ADPrincipalGroupMembership $user
			$adgroups += $usergroups
			$adgroups | Add-Member -MemberType NoteProperty -Name "$user" -Value "" -Force
		}
	}
	$checkedgroups = @()
    $unique = $adgroups | Sort-Object -Unique
    $unique|out-gridview
	foreach ($group in $unique)
	{
		$groupmembers = Get-ADGroup $group.samaccountname -Properties members | Select-Object -ExpandProperty Members
		if ($groupmembers)
		{
			foreach ($user in $users)
			{
				if ($user)
				{
					if ($groupmembers -like "*$user*")
					{
						$group.$user = 'X'						
					}
					else
					{
						$group.$user = 'N'						
					}
				}
            }
            $checkedgroups += $group
		}
	}
	$checkedgroups|out-gridview

Don’t you want to apply add-member to $usergroups, not to the whole list $adgroups, every time?

good catch JS, that does speed up the information collection there.
unfortunately, it does not resolve the root issue where invalid values are passed into the added property

the values for the 2 added properties in the unique ad group check (prior to adding the indicator for group membership) looks like this
U U
{}
U U

when I get to checked groups, the values for these same entries look like:
N X
{X} X
X X

it technically indicates properly, ie if the user is in the group an X is input, and when a user is not in the group, a N is input.
its just the random entries wrapped in {} that have me confused

i mean if add-member isn’t really appropriate, what should we be using to add a property to an object?
and how else could i have a dynamically named property added?

What you are trying to do is complex because it’s not how Powershell is meant to display data. That’s why I was asking more about what you are trying to do versus what issues you are having with Add-Member. With that said, you can do what you are asking, but I would only do this for a handful of groups, otherwise you will have like a ton of columns. This should be very close to what you are looking for.

$users = "User1"

$results = foreach ($user in $users) {
    Get-ADUser -Filter {SamAccountName -eq $user} -Properties MemberOf |
    Select Name, 
           SamAccountName,
           @{Name="Groups";Expression={$_.MemberOf | foreach{$_.Split(",")[0].Replace("CN=","")}}}
}


#Go thru all users and put groups in an array
$allGroups = foreach($user in $results) {
    $user.Groups
}

#Create a blank hash table for properties
$props = @{}
#Add User property
$props.Add("User", $null)
#Loop thru all Unique groups and create a blank property
foreach ($grp in $allGroups | Select -Unique) {
    $props.Add($grp,$null)
}

#Create a template object with the properties
$object = New-Object -TypeName PSObject -Property $props

#Go back through the results
$final = foreach ($user in $results) {
    #Copy the template
    $objCopy = $object.PSObject.Copy()
    #Set the user property
    $objCopy.User = $user.SamAccountName
    #Loop through all properties except User (assume they are groups)
    foreach($objProp in $objCopy.PSObject.Properties | Where{$_.Name -ne "User"}) {
        $key = $objProp.Name
        #Set the property and look to see if the user's groups contain the current group
        $objCopy."$Key" = $($user.Groups -contains $key)
    }

    $objCopy
}


#Create custom sort order to start with user and then groups alphabetically
$sortProps = @()
$sortProps += "User"
$sortProps += ($allGroups | Sort)


#Pipe to format table to make it pretty.  If you are exporting this somewhere, you need to use Select -Property $sortProps.
$final | Format-Table -Property $sortProps -AutoSize

Output:

User      Group1                      Group2                    Group3
----      --------------------------- ------------------------- --------------------
User1                        True                      True                 True

thanks for the idea Rob, you are closer, just a different orientation from what i’m trying to achieve.
end-stage the actual intended results would look more like the following
user1 is a member of group 1 and 3
user 2 is a member of group 2 and group 3

USER1 User2 Groupname Groupdesc
X N group1 group1desc
N X group2 group2desc
X X group3 group3desc

hence why i am trying to add a single column to my group objects for every user piped in, and the need for the dynamically named variables.

so does nobody know why I’m seeing this behavior with add-member?

or, does anybody else have a reasonable approach to build up this data for a report in the way described?

the code does work and provides an object as I need, my only issue is the random values going in as collections vs strings.

Does this work? I don’t know what $groupnames is for.

$adgroups = @()

foreach ($user in $users)
{
  if ($user)
  {
    Write-Host $user
    $groupnames = Get-ADUser $user -Properties memberof | Select-Object -ExpandProperty memberof
    $usergroups = Get-ADPrincipalGroupMembership $user
    $usergroups | Add-Member -MemberType NoteProperty -Name user -Value $user -Force
    $adgroups += $usergroups
  }
}

followed up with some of my .net dev’s in-house.
we still don’t have a root cause for the add-member issue as i used it, other than we think it came down to the duplicate ad group entries.

when i use add-member on unique ad groups returned from the array of users, the value does not populate as a collection, i get just the raw strings.

$ids = "USERID1,USERID2"
$users = $ids.Split(",") | ForEach-Object { $_.trim() }

    foreach ($user in $users)
    {
        if ($user)
        {
            Write-Host "Getting groups for ${user}"
            $usergroups = Get-ADPrincipalGroupMembership $user
            $adgroups += $usergroups
        }
    }

    $unique =  $adgroups | Sort-Object -Unique

    foreach ($group in $unique)
    {
        foreach ($user in $users)
        {
            $group | Add-Member -MemberType NoteProperty -Name "$user" -Value "" -Force
        }
    }

    $checkedgroups = @()
       
    $unique|out-gridview

    foreach ($group in $unique)
    {
        $groupmembers = Get-ADGroup $group.samaccountname -Properties members | Select-Object -ExpandProperty Members

        if ($groupmembers)
        {
            foreach ($user in $users)
            {
                if ($user)
                {
                    if ($groupmembers -like "*$user*")
                    {
                        $group.$user = 'X'                      
                    }
                    else
                    {
                        $group.$user = 'N'                      
                    }
                }
            }
            $checkedgroups += $group
        }
    }

    $checkedgroups|out-gridview