This article describes an efficient method of finding the primary group of a user.
Before 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.
The sample code provided in this article illustrates how to build the SID for the primary group of a user by removing the user's RID from its SID and then replacing it with the primarygroupID (primary group RID). The code takes advantage of the IADsSID object implemented in the ADsSecurity.dll file. The ADsSecurity.dll file is part of Active Directory Service Interfaces (ADSI) software development kit (SDK) 2.5. To download Active Directory Service Interfaces SDK 2.5, visit the following Microsoft Web site:
The IADsSID interface provides a scriptable method for converting a raw SID into its Security Descriptor Definition Language (SDDL) form by using the ConvertSidToStringSid API. This API is available on Windows 2000 and Windows Server 2003 only.
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:
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 = 0const ADS_SID_HEXSTRING = 1const ADS_SID_SAM = 2const ADS_SID_UPN = 3const ADS_SID_SDDL = 4const ADS_SID_WINNT_PATH = 5const ADS_SID_ACTIVE_DIRECTORY_PATH = 6const 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 ) dim dLocal if( (inVal and &H80000000) <> 0 ) then dLocal = CDbl((inval and &H7FFFFFFF)) dLocal = dLocal + 2^31 StrRID = cstr(dLocal) else StrRID = Cstr(inVal) end ifend function'=================================================' Main Script'' Assumes that the first argument is a WinNT or' LDAP user path'set args = WScript.ArgumentsWScript.Echo "Start: "& Nowset 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_PATHelse ' ' WinNT Path, working with the WinNT provider ' ADS_SID_Constant = ADS_SID_WINNT_PATHend if' ' 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 = Nothingelse ' ' Its a WinNT path, retrieve the ADsPath for the WinNT object ' strPrimaryGroupADsPath = ADsSID.GetAs( ADS_SID_Constant )end ifWScript.Echo "Primary group ADS Path for user : " & userADsPathWScript.Echo "Is: " & strPrimaryGroupADsPathWScript.Echo "Finished: " & Now