Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

PrimaryGroup

59 views
Skip to first unread message

Stanley

unread,
Sep 30, 2003, 3:18:23 PM9/30/03
to
I have a method that queries the Active Directory (AD) and pulls back
information on a user. However, when pulling in the "MemberOf" field it
leaves one important piece out. That piece being the "Primary Group". So if
a user belongs to

Group1 (this is their primary group)

Groupb

GroupOmega

When pulling in the data you would only see:

Groupb

GroupOmega

The only field I can seem to find that is close to the "Primary Group" is
"primaryGroupID" which just gives back a number. I am not sure why the
Primary Group was left out of the MemberOf field but it was. Does anyone
know a way around this?

Joe Richards [MVP]

unread,
Sep 30, 2003, 8:08:04 PM9/30/03
to
primaryGroupID is the RID of the group that is the user's primary group.

memberof does not contain the membership because memberof is a backlink into the group tables and due to how AD was
initially implemented in Windows 2000, a group couldn't have more than approximately 5000 users in it. Obviously there
existed preexisting NT4 domains that had well more than 5k users in them with all users usually set to domain users for
primary group. So MS had to come up with a different strategy and what they did was store primary group membership in
the primaryGroupID attribute.

You can also get it by enumerating the SIDs in the tokenGroups attribute.

--
Joe Richards
www.joeware.net

--

"Stanley" <sgl...@jsahealthcare.com> wrote in message news:%23d8G1e4...@tk2msftngp13.phx.gbl...

Stanley

unread,
Oct 1, 2003, 7:57:22 AM10/1/03
to
Joe do you know of any .NET code that can do what your talking about? I
cannot find any examples on how to get the tokenGroups.


"Joe Richards [MVP]" <humore...@hotmail.com> wrote in message
news:OyDHlA7h...@TK2MSFTNGP12.phx.gbl...

Stanley

unread,
Oct 1, 2003, 9:14:15 AM10/1/03
to
Ok I found this article
(http://support.microsoft.com/default.aspx?scid=KB;EN-US;326340) about
getting groups in .NET but it is a waste because it does not return ALL of
the groups. It is only a slight difference in code from what I am doing to
get the groups. I can't believe that this is so difficult to find something
to get ALL groups. It does not really make sense that the users Primary
Group is not listed in the MemberOf property because the user IS a MemberOf
that group. Does anyone have any code examples on how to get the Primary
Group? My entire project is bust if I cannot do this soon.

Talk about frustrating.

Stanley

"Joe Richards [MVP]" <humore...@hotmail.com> wrote in message
news:OyDHlA7h...@TK2MSFTNGP12.phx.gbl...

Joe Kaplan (MVP - ADSI)

unread,
Oct 1, 2003, 10:31:57 AM10/1/03
to
In .NET, just bind to the user using whatever syntax you would use:

Dim entry as New DirectoryEntry("LDAP://<your dn here") 'you may need
additional arguments here depending on context, etc.
Dim groupSid as Object
dim sid() as byte

entry.RefreshCache(New String() {"tokenGroups"}) 'this line is sometimes
necessary to get tokenGroups in the property cache...
For Each groupSid in entry.Properties("tokenGroups")
sid = DirectCast(groupSid, Byte())
Next

Then you will have the sids of each security group in the user's token for
that domain. What you do next depends on what you are trying to accomplish.

Joe K.

"Stanley" <sgl...@jsahealthcare.com> wrote in message

news:%237FiI4B...@TK2MSFTNGP12.phx.gbl...

Stanley

unread,
Oct 1, 2003, 10:52:49 AM10/1/03
to
Ok that works wonders but how to I take the Byte data and get a group name
for the primary group of the user?

Stanley
p.s. thanks for all the help

"Joe Kaplan (MVP - ADSI)" <joseph....@removethis.accenture.com> wrote
in message news:OG4vXjCi...@TK2MSFTNGP09.phx.gbl...

Joe Kaplan (MVP - ADSI)

unread,
Oct 1, 2003, 11:12:49 AM10/1/03
to
There are two ways that I know of:

- Do a P/Invoke to the LookupAccountSid Win32 API function, or..
- Convert the binary sid into an octet string and create a SID binding
string

Since you are already using S.DS, you probably want to do the latter. The
format of a SID binding string is:

<SID=xxxx>

where xxxx is the octet string representation of the binary data. Octet
strings are just pairs of hex string characters representing each byte in
the byte array in order. You can get this in .NET by using the BitConverter
class and stripping out the - characters it puts between each byte with a
string replace method, or you can roll your own function (which is what I
do):

dim groupEntry as New DirectoryEntry(String.Format("LDAP://<SID={0}>",
ConvertToOctetString(sid)))

'(the first two overloads are just convenience versions of the more verbose
method that allows more control for creating search filter syntax octet
strings):

Public Overloads Shared Function ConvertToOctetString(ByVal values
As Byte()) As String
Return ConvertToOctetString(values, False, False)
End Function

Public Overloads Shared Function ConvertToOctetString(ByVal values
As Byte(), ByVal isAddBackslash As Boolean) As String
Return ConvertToOctetString(values, isAddBackslash, False)
End Function

Public Overloads Shared Function ConvertToOctetString(ByVal values
As Byte(), ByVal isAddBackslash As Boolean, ByVal isUpperCase As Boolean) As
String
Dim iterator As Integer
Dim builder As StringBuilder

Dim slash As String
If isAddBackslash Then
slash = "\"
Else
slash = String.Empty
End If

Dim formatCode As String
If isUpperCase Then
formatCode = "X2"
Else
formatCode = "x2"
End If
builder = New StringBuilder(values.Length * 2)
For iterator = 0 To values.Length - 1
builder.Append(slash)
builder.Append(values(iterator).ToString(formatCode))
Next

Return builder.ToString()

End Function

If you wanted to, you could also do a search for the object by SID, but the
bind is probably faster.

HTH,

Joe K.

"Stanley" <sgl...@jsahealthcare.com> wrote in message

news:%23fj0IvC...@TK2MSFTNGP11.phx.gbl...

Stanley

unread,
Oct 1, 2003, 11:49:29 AM10/1/03
to
Ok so here is what I have so far:
Dim entry As New DirectoryEntry("")

Dim mySearcher As New System.DirectoryServices.DirectorySearcher(entry)

Dim result As System.DirectoryServices.SearchResult

Dim keys As New ArrayList

'sets the filter for all groups

mySearcher.Filter = "(&(objectCategory=user)(sAMAccountName=" & txtName.Text
& "))"

'loop through the results and print out the name and displayname

Dim i As Integer = 0

Dim row As DataRow

result = mySearcher.FindOne()

Dim de As DirectoryEntry = result.GetDirectoryEntry

Dim groupSid As Object

Dim sid() As Byte

ListBox1.Items.Clear()

de.RefreshCache(New String() {"tokenGroups"}) 'this line is sometimes


necessary to get tokenGroups in the property cache...

For Each groupSid In de.Properties("tokenGroups")

sid = DirectCast(groupSid, Byte())

Dim groupEntry As New DirectoryEntry(String.Format("LDAP://<SID={0}>",
ConvertToOctetString(sid)))

Dim propcoll As PropertyCollection = groupEntry.Properties

Dim key As String

Dim values As Object

'loop through all of the properties for this record

For Each key In propcoll.PropertyNames

'loop through all the values associated with our key

For Each values In propcoll(key)

'If LCase(key) = "distinguishedname" Then

ListBox1.Items.Add(key.ToString & "=" & values.ToString)

'End If

Next

Next

Next

This basically lists all properties of that users groups. My only issue now
is how to figure out which is the PrimaryGroup as this code goes past just
the first level of group. So if Group2 is a member of Group1 they are both
listed. Is there some to add to the directory entry to pull back only the
first level?

Stanley

"Joe Kaplan (MVP - ADSI)" <joseph....@removethis.accenture.com> wrote

in message news:uvl856Ci...@TK2MSFTNGP12.phx.gbl...

Joe Kaplan (MVP - ADSI)

unread,
Oct 1, 2003, 2:56:53 PM10/1/03
to
The primary group in the array of SIDs will be the one that matches the SID
of the domain with an additional RID added on equal to the value of the
primaryGroupID on the user object. The correct way to do this would be to
compose a SID using the Win32 API SID functions, but you could hack it up
pretty easily by just appending the 4 bytes from the primary group to the
array of bytes for the domain SID.

The even more hackish way to do this would be to grab the last 4 bytes off
of each SID, convert that to an Int32 with BitConverter and compare that
directly to the primaryGroupID attribute value. However, that assumes that
the rest of the SID is equal to the domain's prefix.

However, if all you wanted was to find the name of the primary group in the
first place, you can just create a SID as byte array by combining the SID
from the domain root with the primaryGroupID from the user and binding to
that like you were doing before.

Here is a complete code sample (using the function I gave you before) that
gets the primary group name given a user. If this were being called often,
it could be optimized to cache the domain SID and/or put the group names in
a hash table for faster subsequent lookups:

Public Shared Function GetPrimaryGroupName(ByVal userSamAccountName As
String) As String
Dim domainSid() As Byte
Dim primaryGroupSid() As Byte
Dim primaryGroupId As Integer
Dim primaryGroupOctet As String
Dim primaryGroupName As String
Dim rootDse As DirectoryEntry
Dim domainRoot As DirectoryEntry
Dim primaryGroup As DirectoryEntry
Dim searcher As DirectorySearcher
Dim results As SearchResultCollection
Dim result As SearchResult
Dim enumerator As IEnumerator

rootDse = New DirectoryEntry("LDAP://rootDSE")
domainRoot = New DirectoryEntry("LDAP://" +
DirectCast(rootDse.Properties("defaultNamingContext").Value, String))

domainSid = DirectCast(domainRoot.Properties("objectSID").Value,
Byte())
searcher = New DirectorySearcher(domainRoot)
searcher.SearchScope = SearchScope.Subtree
searcher.CacheResults = False
searcher.PropertiesToLoad.AddRange(New String() {"primaryGroupID"})
searcher.Filter =
String.Format("(&(objectCategory=user)(sAMAccountName={0}))",
userSamAccountName)

results = searcher.FindAll() 'I don't use FindOne because it leaks
memory if the search fails in 1.1 or lower...
enumerator = results.GetEnumerator
If enumerator.MoveNext Then
result = DirectCast(enumerator.Current, SearchResult)
primaryGroupId =
DirectCast(result.Properties("primaryGroupId")(0), Integer)
ReDim primaryGroupSid(domainSid.Length + 3)
Array.Copy(domainSid, primaryGroupSid, domainSid.Length)
Array.Copy(BitConverter.GetBytes(primaryGroupId), 0,
primaryGroupSid, domainSid.Length, 4)

'this part is really hackish! We have to adjust the SID to have
the proper number
'of sub authorities in the second byte. This will likely change
from 4 for the domain sid
'to 5 for the group sid.
'Win32 APIs should really be used for this, but it works...
primaryGroupSid(1) = Convert.ToByte((primaryGroupSid.Length - 8)
\ 4)

primaryGroupOctet = ConvertToOctetString(primaryGroupSid)

primaryGroup = New
DirectoryEntry(String.Format("LDAP://<SID={0}>", primaryGroupOctet))
primaryGroupName =
DirectCast(primaryGroup.Properties("samAccountName").Value, String)

primaryGroup.Dispose()
Else
primaryGroupName = String.Empty
End If

results.Dispose()
searcher.Dispose()
domainRoot.Dispose()
rootDse.Dispose()

Return primaryGroupName

End Function

HTH,

Joe K.


"Stanley" <sgl...@jsahealthcare.com> wrote in message

news:uldA3ODi...@TK2MSFTNGP11.phx.gbl...

0 new messages