This article describes an efficient method of finding the primary group of a user.
BackgroundBefore the technique described in this article was developed, the only way to determine a user's primary group was to perform a Lightweight Directory Access Protocol (LDAP) dialect ActiveX Data Objects (ADO) query against the Active Directory to request all of the group objects in the domain, and then browse through the returned recordset to search for the group with a PrimaryGroupToken that matched the PrimaryGroupID of the user. This query can be very time consuming, depending on the number of groups in a given domain.
The PrimaryGroupToken of a group object is a constructed attribute. This means that the attribute is not stored in the Active Directory but is constructed on the client by the Active Directory Services Interface (ADSI) provider. Because the attribute is constructed, it cannot be used in a search criteria in an LDAP query.
It is possible to build the security identifier (SID) for the primaryGroup by considering the following:
- The SID for a group/user consists of the domain's relative identifier (RID) plus the RID of the group/user. For example if the SID for this user is S-1-111-222-333-efg, the RID for this user is "efg" and the domain's RID is S-1-111-222-333.
- The PrimaryGroupID attribute on a user/group object holds the RID of the primary group.
- A user's primary group must be a group that exists in the user's primary domain.
- The SID for the primary group can be built by concatenating the domain RID with the primaryGroupID attribute of the user object. For example, the domain RID could be S-1-111-222-333 and the value of the primarygroupID could be abc. The SID of the primary group would then be S-1-111-222-333-abc.
In order for this method to be used on a Windows NT 4.0-based system, the raw SID must be converted into its string counterpart through the use of some type of COM wrapper DLL. An example of how to build the SDDL form of an SID on Widows NT 4.0 is provided in the following Microsoft Knowledge Base article:
286182 How To Use Microsoft Visual Basic to Convert a Raw SID into a String SID
Steps to Build the SID for the Primary Group of a User
- Bind to the user object.
- Retrieve the ObjectSID property of the user object.
- Use the IADsSID object to convert the user's SID from its binary form into is SDDL form.
- Strip off the user's RID from the SDDL form of the SID.
- Retrieve the user's PrimaryGroupID property and convert it into an unsigned long string.
- Append the string representation of the PrimaryGroupID to the modified user SID.
- Use IADsSID to convert the SDDL form of the SID into its Windows NT or LDAP ADsPath.
- Bind to the ADsPath to retrieve whatever information about the primary group that you want.
Visual Basic Script to Locate the Primary Group for a Given ADsPath
' The following VBS code illustrates how to determine the primary group
' given an ADsPath as a single argument. The script determines if the
' string passed is a WinNT or LDAP path and then uses the appropriate
' method for retrieving the Primary Group path.
' ADsSecurity Constants
const ADS_SID_RAW = 0
const ADS_SID_HEXSTRING = 1
const ADS_SID_SAM = 2
const ADS_SID_UPN = 3
const ADS_SID_SDDL = 4
const ADS_SID_WINNT_PATH = 5
const ADS_SID_ACTIVE_DIRECTORY_PATH = 6
const ADS_SID_SID_BINDING = 7
' Function StrRID returns and unsigned long of
' the given RID value
' If the most significant bit is set in a VB Long
' then VB will interpret the value as a negative number
' and CStr will convert the unsigned long into a string with a leading
' "-" sign.
' This function checks to see if the most significant bit
' is set, then tricks the CStr function into outputting
' and unsigned long value by using a double float value
' to store the RID value, then uses the CStr function to get the
' string version.
function StrRID( inVal )
if( (inVal and &H80000000) <> 0 ) then
dLocal = CDbl((inval and &H7FFFFFFF))
dLocal = dLocal + 2^31
StrRID = cstr(dLocal)
StrRID = Cstr(inVal)
' Main Script
' Assumes that the first argument is a WinNT or
' LDAP user path
set args = WScript.Arguments
WScript.Echo "Start: "& Now
set ADsSid = CreateObject("ADsSID")
' Determine if we are using the LDAP or WinNT providers
userAdsPath = args(0)
if( InStr(userAdsPath,"LDAP") <> 0 ) then
' LDAP ADS Path, need to work with the an Active Directory Path
ADS_SID_Constant = ADS_SID_ACTIVE_DIRECTORY_PATH
' WinNT Path, working with the WinNT provider
ADS_SID_Constant = ADS_SID_WINNT_PATH
' Initialize the IADsSID object and retrieve
' the SDDL form of the SID
ADsSID.SetAs ADS_SID_Constant, CStr(userADsPath)
DomainSID = ADsSID.GetAs(ADS_SID_SDDL)
' We have the SDDL form of the user's SID.
' Remove the user's RID ( the last sub authority)
' up to the "-"
DomainSID = mid(DomainSID,1,(InStrREV(DomainSID,"-")))
' Bind to the user object to retrieve the PrimaryGroupID.
' Build the SID of the Primary group
' from the domainSID and the Primary Group RID in
' the PrimaryGroupID.
set obj = GetObject(userADsPath)
lngGroupID = obj.Get("primaryGroupID")
strGroupRID = StrRID( lngGroupID )
DomainSID = DomainSID & strGroupRID
' Use ADsSID to retrieve a WinNT path or
' a SID Bind string to locate the LDAP path
ADsSID.SetAs ADS_SID_SDDL, CStr(DomainSID)
if( ADS_SID_Constant = ADS_SID_ACTIVE_DIRECTORY_PATH ) then
' With the LDAP provider, build a SID bind string and
' retrieve the Group object via this bind string
SIDBindStr = ADsSID.GetAs(ADS_SID_HEXSTRING)
SIDBindStr = "LDAP://<SID=" & SIDBindStr & ">"
set oGrp = GetObject(SIDBindStr)
strPrimaryGroupADsPath = oGrp.Get("DistinguishedName")
set oGrp = Nothing
' Its a WinNT path, retrieve the ADsPath for the WinNT object
strPrimaryGroupADsPath = ADsSID.GetAs( ADS_SID_Constant )
WScript.Echo "Primary group ADS Path for user : " & userADsPath
WScript.Echo "Is: " & strPrimaryGroupADsPath
WScript.Echo "Finished: " & Now