Recipe 7.8 Resolving a Primary Group ID

7.8.1 Problem

You want to find the name of a user's primary group.

7.8.2 Solution Using a graphical user interface
  1. Open the Active Directory Users and Computers snap-in.

  2. If you need to change domains, right-click on Active Directory Users and Computers in the left pane, select Connect to Domain, enter the domain name, and click OK.

  3. In the left pane, right-click on the domain and select Find.

  4. Type the name of the user and click Find Now.

  5. In the Search Results, double-click on the user.

  6. Click the Member Of tab.

  7. The Primary Group name is shown on the bottom half of the dialog box. Using VBScript
' This code prints the group name of a user's primary group
strNTDomain = "<DomainName>" ' NetBios Name of the AD domain, e.g. RALLENCORP
strUser     = "<UserName>"   ' e.g. Administrator
' ------ END CONFIGURATION ---------

' Iterate over the user's groups and create a search filter
' that contains each group
set objUser  = GetObject("WinNT://" & strNTDomain & "/" & strUser & ",user")
strFilter = ""
for each objGroup in objUser.Groups
   strFilter = strFilter & "(samAccountName=" & objGroup.Name & ")"
strFilter = "(|" & strFilter & ")"

' Now need to perform a search to retrieve each group 
' and their primaryGroupToken
strBase = "<LDAP://" & strNTDomain & ">;"
strFilter = "(&(objectcategory=group)" & strFilter & ");"
strAttrs = "name,primaryGroupToken,cn;"
strScope = "subtree;"
set objConn = CreateObject("ADODB.Connection")
objConn.Provider = "ADsDSOObject"
objConn.Open "Active Directory Provider"
set objComm = CreateObject("ADODB.Command")
set objComm.ActiveConnection = objConn
objComm.CommandText = strBase & strFilter & strAttrs & strScope
' Be sure to enable paging in case number of groups > 1000
objComm.Properties("Page Size") = 1000
set objRS = objComm.Execute

' Iterate over each group again and stop after a match with the user's
' primaryGroupID has been made
strPrimaryGroup = ""
while ( (not objRS.EOF) and (strPrimaryGroup = "") )
  if (objUser.PrimaryGroupID = objRS.Fields("primaryGroupToken").value) then
     strPrimaryGroup = objRS.Fields("name").Value
  end if

WScript.Echo "Primary Group for " & strUser & " is " & strPrimaryGroup & _
             " (" & objUser.PrimaryGroupID & ")"

7.8.3 Discussion

When trying to determine a user's group membership, you have to look at both user's memberOf attribute, which contains a list of DNs for each group the user is a member of, and the user's primary group. By default, all users are assigned Domain Users as their primary group. Therefore, by default all users in a domain are implicitly members of the Domain Users group. Unfortunately, a user's primary group will not show up in the memberOf attribute unless explicitly added.

Services for Macintosh and POSIX-based applications are the main users of primary groups. If you don't use either of those, you don't need to worry about changing a user's primary group.

The primary group is stored in the primaryGroupID attribute on user objects. Unfortunately, the RID of the group is stored in that attribute, not the DN or even sAMAccountName as you might expect. group objects have a primaryGroupToken attribute, which contains the same value, but is a constructed attribute. Because Active Directory dynamically constructs it, you cannot utilize it in search filters. So even if you have the primaryGroupID of a user, e.g., 513, you cannot do a simple query to find out which group it is associated with.

You can find the name of a user's primary group relatively easily using the Active Directory Users and Computers snap-in as I described in the GUI solution. Finding it via a script, on the other hand, is considerably more complicated. There are a few different ways to go about determining a group given a primary group ID and they are covered pretty well in MS KB 321360 and 297951. For the API solution, I use the approach I feel is the most efficient.

I first used the WinNT: provider to retrieve a user's groups. The difference between using the WinNT: provider and using the LDAP: provider is that the WinNT: provider returns the primary group as part of the IADsGroup collection whereas the LDAP: provider does not. Unfortunately, there is no indication which of the groups is the primary group. So I needed to iterate over each group and build an LDAP filter that will be used later to retrieve each group using ADO. After I execute the ADO query, I then iterate over each group and check the primaryGroupToken attribute of that group to see if it matches the user's primaryGroupID attribute. If it does, I've found the user's primary group.

7.8.4 See Also

MS KB 297951 (HOWTO: Use the PrimaryGroupID Attribute to Find the Primary Group for a User) and MS KB 321360 (How to Use Native ADSI Components to Find the Primary Group)

    Chapter 3. Domain Controllers, Global Catalogs, and FSMOs
    Chapter 6. Users
    Appendix A. Tool List