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?
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...
"Joe Richards [MVP]" <humore...@hotmail.com> wrote in message
news:OyDHlA7h...@TK2MSFTNGP12.phx.gbl...
Talk about frustrating.
Stanley
"Joe Richards [MVP]" <humore...@hotmail.com> wrote in message
news:OyDHlA7h...@TK2MSFTNGP12.phx.gbl...
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
p.s. thanks for all the help
"Joe Kaplan (MVP - ADSI)" <joseph....@removethis.accenture.com> wrote
in message news:OG4vXjCi...@TK2MSFTNGP09.phx.gbl...
- 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...
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...
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...