Click here to Skip to main content
11,928,786 members (54,240 online)
Click here to Skip to main content
Add your own
alternative version


12 bookmarked


, 21 Mar 2007 CPOL
Rate this:
Please Sign up or sign in to vote.
Enumerating and searching groups in Active Directory

Screenshot - RPGroupMembers.jpg


Problem: The Customer calls you to check / to document which users can access his server share. Normally, I take a simple template LDAP query but this is error-prone and not very handy.

This little tool helps you to determine all members of a domain group in Active Directory; inclusive to members of nested groups. In addition, the tool offers a data export function through Excel-Automation. Make sure your computer is a member of a domain.

Using the code

The source code of GroupMembers shows you how you can enumerate and search the entire Forest for objects in Active Directory. The application is quite simple (without extra threads, etc.).

GroupMembers does two main steps, described below.

Part 1: Searching objects

To find objects in an Active Directory domain tree you need to bind to a global catalog server. Therefore, we use the function ADsOpenObject with an LDAP path like GC:\\ After this we can use the IDirectorySearch interface to search our objects.

HRESULT FindMultipleADObjects(LPWSTR pszSearchBase, 
    LPWSTR pszFilter, CPtrArray* arIADsList)
    HRESULT               hr                        = S_OK;

    wchar_t               pszADsPath[MAX_PATH];
    IDirectorySearch*     pDSSearch                 = NULL;
    IADs*            ppObj                     = NULL;
    ADS_SEARCHPREF_INFO arSearchPrefs[3];
    ADS_SEARCH_COLUMN     col;ADS_SEARCH_HANDLE     hSearch          = NULL;
    LPWSTR           pszAttribute[1]           = {L"ADsPath" };
    // we need only one attribute
    if (NULL == pszSearchBase || NULL == pszFilter ||
        NULL == arIADsList)
        return (E_INVALIDARG);
    // binds to our base ADSI object
    hr = ADsOpenObject(pszSearchBase, NULL, NULL,
            (void**) &pDSSearch);
    if (FAILED(hr))
        return hr;
    // set/use the page size -> don't stress the server
    arSearchPrefs[0].dwSearchPref      = ADS_SEARCHPREF_PAGESIZE;
    arSearchPrefs[0].vValue.dwType     =
    arSearchPrefs[0].vValue.Integer = 100;
    // we will search also child trees
    arSearchPrefs[1].dwSearchPref  =
    arSearchPrefs[1].vValue.dwType = ADSTYPE_INTEGER;
    arSearchPrefs[1].vValue.Integer         =
    //set a time limit for big domains
    arSearchPrefs[2].dwSearchPref      =
    arSearchPrefs[2].vValue.dwType     =
        ADSTYPE_INTEGER;arSearchPrefs[2].vValue.Integer = 120;
    // set the search
    configurationhr = pDSSearch->SetSearchPreference(arSearchPrefs,3);
    if (FAILED(hr))
        if (pDSSearch)pDSSearch->Release();
        return (hr);
    // now start the search
    hr = pDSSearch->ExecuteSearch(pszFilter,pszAttribute, (UINT) 1,
    if (SUCCEEDED(hr))
        while (SUCCEEDED(hr =pDSSearch->GetNextRow(hSearch)))
            if (S_OK == hr)
                // extract our one attribute we need
                hr = pDSSearch->GetColumn(hSearch, pszAttribute[0], 
                if (SUCCEEDED(hr))
                    wcsncpy(pszADsPath, col.pADsValues->CaseIgnoreString,
                    pszADsPath[MAX_PATH - 1] = 0;
                    // open the found object
                    hr = ADsOpenObject(pszADsPath, NULL, NULL,

                    if (SUCCEEDED(hr))
                    // and add it to our ptr array
                    // !!! the caller must release the object !!!
        // free the search handle
    if (pDSSearch)
    // release the search object
    return (hr);

This function takes 3 parameters: pszSearchBase defines the LDAP path like GC:\\, pszFilter defines the search filter like (objectClass=user), and the last parameter (arIADsList) is a pointer array that stores the IADs objects from our search results. Important note: the returned IADs objects must be released by the caller.

Part 2: Enumerating group members

To query object details or enumerating members we need to rebind to ADSI. Why? The global catalog server we have bound to does not have all the information we need. This information is only available on normal domain controllers.

Therefore, I use a little helper class (CADGroupList). This class stores the different LDAP path names of the IADs object for us to rebind to ADSI. Now, after we have found our group name, we can enumerate the group members. We call the function below recursively.

The first parameter is an IADsMembers interface which we have extracted from the group itself. This interface is needed for enumerating the members. The second parameter is again a little helper class (CADGroupMemberList) which stores the group members with a few member details, including the group name to which the member belongs.

HRESULT EnumGroupMembers(IADsMembers* pGrpMembers,
    CADGroupMemberList* pMemberList, CString csGroupName)
    HRESULT hr = E_FAIL;
    IADs* pADs = NULL;
    IUnknown* pUnk = NULL;
    IEnumVARIANT* pEnum = NULL;
    IADsGroup* pGroup = NULL;
    IADsMembers* pMembers = NULL;
    VARIANT var;
    ULONG lFetch = 0;
    BSTR bstr;

    hr = pGrpMembers->get__NewEnum(&pUnk);
    if (FAILED(hr))

    hr = pUnk->QueryInterface(IID_IEnumVARIANT, (void**) &pEnum);

    if (FAILED(hr))

    hr = pEnum->Next(1, &var, &lFetch);
    if (hr == S_FALSE)
    while (hr == S_OK)
        if (lFetch == 1)
            hr = V_DISPATCH(&var)->QueryInterface(IID_IADs, 
                (void**) &pADs);
            if (FAILED(hr))
            if (ADsIsGroup(pADs))
                hr = pADs->QueryInterface(IID_IADsGroup, 
                    (void**) &pGroup);
                if (FAILED(hr))
                pGroup->get_Name(&bstr); // get the group name

                // recursive enum call -> get nested members

                hr = EnumGroupMembers(pMembers, pMemberList, bstr);


                if (FAILED(hr))

                if (pMembers) { pMembers->Release(); pMembers = NULL; }

                if (pGroup)
                    pGroup = NULL;


                if (ADsIsUser(pADs)) // this is a user object

                    // add to the member list
                    pADs->get_Name(&bstr); // inclusive group name
                    CADGroupMemberProperty GP;
                    GP.m_ADGroupMemberName = bstr;
                    GP.m_ADGroupName = csGroupName;

                if (pADs)
                    pADs = NULL;


        hr = pEnum->Next(1, &var, &lFetch);



    if (pADs) pADs->Release();

    if (pUnk) pUnk->Release();

    if (pEnum) pEnum->Release();

    if (pGroup) pGroup->Release();

    if (pMembers) pMembers->Release();

    return hr;


That's it. OK, it's a minimalist approach. But I hope you find this information / Tool helpful. You are free to use this code in our own projects. Comments are welcome!


  • Version 0.5

Initial release on Code Project


This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


About the Author

Systems Engineer
Germany Germany
Ralph started programming in Turbo Pascal, later in Delphi. After this, he began learning C++, which is his favorite language up to now.

He is interested in almost everything that has to do with computing, his special interests are security and networking.

You may also be interested in...

Comments and Discussions

GeneralDon't find Groups that are Members of the group (WinNT:\\) Pin
StanBurton12-Apr-11 7:25
memberStanBurton12-Apr-11 7:25 
GeneralRe: Don't find Groups that are Members of the group (WinNT:\\) Pin
perle113-Apr-11 12:54
memberperle113-Apr-11 12:54 
GeneralRe: Don't find Groups that are Members of the group (WinNT:\\) Pin
StanBurton13-Apr-11 14:24
memberStanBurton13-Apr-11 14:24 
GeneralMy vote of 5 Pin
kiany18-Feb-11 21:00
memberkiany18-Feb-11 21:00 
GeneralNo code found Pin
laurayjp20-May-08 3:13
memberlaurayjp20-May-08 3:13 
GeneralExcellent example Pin
robert.nack10-Aug-07 6:26
memberrobert.nack10-Aug-07 6:26 
QuestionIADsADSystemInfo Object Pin
Emad Wahib14-Jun-07 6:32
memberEmad Wahib14-Jun-07 6:32 
AnswerRe: IADsADSystemInfo Object Pin
perle114-Jun-07 11:02
memberperle114-Jun-07 11:02 
GeneralRe: IADsADSystemInfo Object Pin
Emad Wahib14-Jun-07 12:38
memberEmad Wahib14-Jun-07 12:38 
GeneralGet Email. Pin
Senthu16-Apr-07 13:10
memberSenthu16-Apr-07 13:10 
GeneralRe: Get Email. Pin
perle116-Apr-07 23:15
memberperle116-Apr-07 23:15 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web01 | 2.8.151126.1 | Last Updated 21 Mar 2007
Article Copyright 2007 by perle1
Everything else Copyright © CodeProject, 1999-2015
Layout: fixed | fluid