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 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:
'
' 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 )
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 if
end function
'=================================================
' 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
else
'
' WinNT Path, working with the WinNT provider
'
ADS_SID_Constant = ADS_SID_WINNT_PATH
end 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 = Nothing
else
'
' Its a WinNT path, retrieve the ADsPath for the WinNT object
'
strPrimaryGroupADsPath = ADsSID.GetAs( ADS_SID_Constant )
end if
WScript.Echo "Primary group ADS Path for user : " & userADsPath
WScript.Echo "Is: " & strPrimaryGroupADsPath
WScript.Echo "Finished: " & Now