Click here to Skip to main content
15,905,144 members
Articles / Programming Languages / C#

Using the Windows 2000/XP Object Selection Dialog

Rate me:
Please Sign up or sign in to vote.
4.92/5 (29 votes)
21 Nov 2005CPOL14 min read 252.3K   4.5K   57   77
This article describes how to use the "Select Users or Groups" system dialog.


This article deals with the programmatic use the Active Directory "Select Users or Groups", or "Find Computers" system dialogs. By programmatic, I mean "How do I write a program to show these dialogs and retrieve the results?" as opposed to manually brining up these dialogs. The article is divided up into three sections; Part 1 is called Object Selection in C++. Since flags passed in to initialize the dialog can radically affect the behaviour (i.e. am I looking for Users, Groups or Computers), these are explained in this section. [This section assumes you know C++ on Windows] Part 2 and Part 3 discuss how to use these dialogs in Visual Basic 6, and C# on the .NET 1.1 platform. While Part 2 focuses on building a COM wrapper object exposing the required functionality [This section assumes you know ATL/COM], Part 3 shows how to use COM wrapper with C# to call up the Object Picker dialogs. [This section should be fairly easy to understand] Full source is included with this article; the demo project only has the executables.

Part 1: Object Selection in C++

Do you have an application where for some reason or other you must pick users from either an old-style NT domain, or a new-fangled Active Directory? I was recently in that situation -- what I wanted was very simple: I wanted to show the system dialog "Select Users or Groups" from the system. To bring up this dialog in any 2000/XP system, you can simple use Explorer to navigate to any file, right-click, use the Properties Menu, go to the security tab (assuming you have an NTFS volume), and click "Add". Below is the dialog that you will see if you are running Windows 2000. Under Windows NT 4 as will be discussed later, the dialog looks a bit different and comes from a completely different area; the dialog below is NOT available under Windows NT 4. I am also including how these dialogs look on XP, as there are significant differences in the look & feel. Since our company LAN is not Active Directory enabled, the dialogs do not show visually that they are in fact also capable of browsing the Active Directory Tree / Forest.

Image 1
Fig. 1 The "Select Users or Groups" dialog in action on Windows 2000

Image 2
Fig 2. Select Users or Groups on Windows XP - a different look & feel

Image 3
Fig 3. After choosing Advanced, and clicking "Find Now" on Windows XP

OK, so now you know how to bring the dialog up manually. The question now becomes: how do I get my program to display the dialog, and retrieve the information the user has picked from it. Here's a bit of history of how I came to be interested in this problem.

In NT 4 times, our application had the same need: pick a user from the list of users from the domain. We had searched high and low for the dialog presented in Fig. 2 A programmer from my group then finally coded our home-grown version of what we needed; the first version unfortunately blew up at a customer's site with 50000 users... We used the NetGroupGetUsers() function and other functions in that group to do the job. But what we really wanted is the dialog below:

Image 4
Fig. 4 The NT4 Select Users Dialog

Oleg Kagan wrote a nice demo program (see picture above) on how to solve this problem on NT -- the Author's web site is unfortunately down, so source code is no longer available (I have it if you're desperate). This user browser uses undocumented features of the Microsoft provided netui2.dll. Why is using a system dialog better than our home-grown solution? The above dialog does not look terribly difficult to replicate, but actually is. The dialog in Fig. 2 actually creates a separate thread responsible for contacting the NT domain (if applicable) and getting the list of users. I have personally listed about 50 000 users in a large firm -- it usually takes at least 2 minutes or so to completely fill the box. Having the separate thread also allows the user to enter known usernames in the "Add Names" box and click OK before the enumeration of all the users is complete. One can add many users in using the DOMAIN\username style; multiple users are separated by semicolons. This is a very useful feature for power users. The moral of the story is - do not write your own dialogs if the system provides a standard way of doing what you need to do.

Back to the problem of bringing up the Windows 2000 / XP "Show Users or Groups" dialog. While on a trip in Europe, I thought "there has got to be a documented way of bringing up this dialog". I scanned my October 2001 MSDN far and wide, but found nothing. I then decided to resort to do a bit of system "hacking" to find out more. First I ran explorer.exe within the VC debugger, bringing up the desired dialog. One of the first DLL's to be dynamically loaded was the objsel.dll, located in the system32 directory. Looking at the dll's resources using VC again showed me I was on the right track. Running DUMPBIN, showed only the standard 4 COM exports, so the DLL had to be used through COM. Looking through the registry, I was able to determine the GUID for the coclass of the our object selector object. At this point, I thought about using a disassembler, but thought maybe there is a better way to find out what's going on. I entered the GUID into GOOGLE, and got exactly 1 hit - I guess that is why these ID´s are called globally unique... Some nice soul was trying to do the same thing (displaying the Select Users or Groups dialog) using .NET and C# and had a lot of info about the required structures. I then went back to the online MSDN web site and looked further, finding a demo project on how to invoke the object selection dialog [Search for InvokeDialog in the MSDN online library] or object picker. I looked a the code, and quickly made up some test code using a simple MFC dialog-only application - it worked right away. However all the constants used to invoke the dialog were hard-coded in the sample. In this article, I present what I would have liked to see as an MSDN sample - to gain a better understanding you should download the code and have a look at the relevant header files and the most recent MSDN documentation.

The following sample lets the user specify the individual parameters to the dialog before invoking the dialog.

Image 5
Fig. 5 Advanced Properties for the Object Picker Dialog

I will comment on this dialog a bit more, since these are the master settings that will control the object picker dialog. I don't understand everything fully but will try to explain the gist of what things mean. Note that some of the settings only really make sense if your company is really running Active Directory. This application should allow testing the possibilities and allow you to pick the flags that you need for your purposes. I will list the excellent introduction from the MSDN object picker example, which describes how to use this dialog.


When the object picker dialog box is initialized, a set of scope types and filters is specified. The specified scope types determine the locations, domains or computers for example, from which a user can select objects. The filters determine the types of objects that a user can select from a given scope type. For more information, see the Scopes and Filters section below.

By default, a user can select a single object in the directory object picker dialog box. To enable multiple selections, set the DSOP_FLAG_MULTISELECT flag in the flOptions member of the DSOP_INIT_INFO structure when the dialog box initialized.

Scopes and Filters

The Look in drop-down list contains the scopes from which a user can select objects. A scope is a domain, computer, workgroup, or global catalog that stores information about and provides access to a set of available objects. The entries in the scope list depend on the scope types and the target computer specified when the IDsObjectPicker::Initialize method was last called to initialize the object picker dialog box.

A scope type is a generic category of scopes, such as all domains in the enterprise to which the target computer belongs, or the global catalog for the target computer's enterprise, or the target computer itself. For each specified scope type, the dialog box uses the context of the target computer to determine the scope list entries.

The IDsObjectPicker::Initialize method takes a pointer to a DSOP_INIT_INFO structure that contains an array of DSOP_SCOPE_INIT_INFO structures. Each entry in the DSOP_SCOPE_INIT_INFO array specifies one or more scope types as well as applicable filters and other attributes. The filters determine the types of objects, such as users, groups, contacts, and computers, that the user can select from a given scope type. When the user selects a scope from the list, the dialog box applies the filters for that scope type to display a list of objects from which the user can select.

Each DSOP_SCOPE_INIT_INFO structure contains a DSOP_FILTER_FLAGS tructure that specifies the filters for that scope type. The DSOP_FILTER_FLAGS structure distinguishes between up-level and down-level scopes.

  • An up-level scope is a global catalog or a Windows 2000 domain that supports the ADSI LDAP provider.
  • A down-level scope includes Windows NT 4.0 domains, workgroups, and all individual computers, whether running Windows 2000 or Windows NT 4.0. The dialog box uses the ADSI WinNT provider to access a down-level scope.

There are two sets of filter flags defined for use in the DSOP_FILTER_FLAGS structure: one for up-level scopes and one for down-level scopes. The Uplevel member of the DSOP_FILTER_FLAGS structure is a DSOP_UPLEVEL_FILTER_FLAGS structure that specifies the filters for up-level scopes. The flDownlevel member of the DSOP_FILTER_FLAGS structure is a set of flags that specify the filters for down-level scopes.

Image 6

Image 7
Fig. 6 Setting DSOP Scope Flags

Image 8
Fig. 7 Setting the DSOP Filter flags

Image 9
Fig. 8 Setting the downlevel filter flags

Below, the code is listed which shows how to bring up the dialog box.

// the headers don't have a definition for the 
// the compiler provided smart pointers,
// so we use the macro to roll our own. 
// I like using smart pointers, because they clean
// up the code considerably, compared to standard COM calls.

_COM_SMARTPTR_TYPEDEF(IDsObjectPicker, IID_IDsObjectPicker);

void CSelectUsersOrGroupsDlg::OnSelusers() 
 IDsObjectPickerPtr ptrObjPick (CLSID_DsObjectPicker);  
  // semi-smart pointer to object
 IDataObjectPtr  pDataObject;   // result data object
 DSOP_INIT_INFO  InitInfo;    // Init Info
 DSOP_SCOPE_INIT_INFO   aScopeInit[1];   // Scope Init Info
 HRESULT   hr;    // standard hresult

 ZeroMemory(aScopeInit, sizeof(aScopeInit));
 aScopeInit[0].cbSize = sizeof(DSOP_SCOPE_INIT_INFO);

 // all the relevant settings are assigned directly from the dialogs
 aScopeInit[0].flType = m_dlgAdvanced.m_dlgScopeType.m_flags;
 aScopeInit[0].flScope = m_dlgAdvanced.m_dlgScopeFlag.m_flags;
 aScopeInit[0].FilterFlags.Uplevel.flBothModes = 
 aScopeInit[0].FilterFlags.Uplevel.flMixedModeOnly =
 aScopeInit[0].FilterFlags.Uplevel.flNativeModeOnly =
 aScopeInit[0].FilterFlags.flDownlevel = 

 // init the struct
 ZeroMemory(&InitInfo, sizeof(DSOP_INIT_INFO));
 InitInfo.cbSize = sizeof(DSOP_INIT_INFO);
 InitInfo.pwzTargetComputer = NULL;
 InitInfo.cDsScopeInfos = sizeof(aScopeInit) / sizeof(DSOP_SCOPE_INIT_INFO);
 InitInfo.aDsScopeInfos = aScopeInit;

 // pick up the optional settings
 InitInfo.flOptions = m_dlgAdvanced.m_flOptions;

 // bail out if we could not do anything
 if (ptrObjPick == NULL)
  AfxMessageBox(_T("Could not create the required COM object in objsel.dll.
    Are you running Win2K or XP?"), MB_OK);

 // make the call to tell the system what kind of dialog it should display
 hr = ptrObjPick->Initialize(&InitInfo);
 if (!SUCCEEDED(hr))
  AfxMessageBox(_T("Something went wrong trying in the call to Initialze(),
   bailing out..."), MB_OK);

 // make the call to show the dialog that we want
 hr = ptrObjPick->InvokeDialog(GetSafeHwnd(), (IDataObject**)&pDataObject);
 if (!SUCCEEDED(hr))
  AfxMessageBox(_T("InvokeDialog returned with a failure, bailing out..."), 

 // decode the results from the dialog
 hr = ProcessResults(pDataObject);
 if (!SUCCEEDED(hr))
  AfxMessageBox(_T("Problem processing the results, bailing out..."),


Note that the object selection dialog is pretty powerful as it can navigate the entire Active Directory. At a site in Switzerland, I was able to browse the company's Asia top level domain, drill down to the Shanghai organizational unit, and list & pick users. Pretty amazing stuff when set up correctly. Replicating this kind of functionality in house is next to impossible - this article attempts to shed some light on how to use the system functionality instead. Visual Basic users will still be out of luck, one would have to create a COM wrapper object using C++ that would allow setting the properties and showing the dialog.

Part 2: Building a COM Wrapper object

One shortcoming of the code presented above is that Visual Basic users and .NET programmers are left out in the cold. So far, I have not been able to find system API's in the .NET library that bring up these dialogs. As a response to a request I received from a friend [and no, I cannot accommodate arbitrary requests], I wrote a wrapper COM Object that can be used both to instantiate the Object Dialog, as well as to retrieve the results of the user, group or computer selection in a convenient way.

The wrapper object, called ObjectPickerHelper, is an ATL 7 attributed COM server. In retrospect, I regret having built the server with attributed COM, as I found it impossible (due to a bug in the tools) to inject enums representing the flags into the IDL and thus into the type library. The attributed code is however more compact and is relatively easy to understand. The project exposes three objects, and I will try to explain them in sequence. The first is called CADObjectPicker and is the main object. This object exposes a number of properties that are flags to initialize the Object Picker dialog. The flags are preset to reasonable defaults, which cause the Select Users or Groups to function. There is one method called InvokeDialog(), which shows the coveted dialog to the user. Finally the main object immediately creates a collection object, and exposes this object as a read-only property. The collection object is called CADObjectColl, and it holds CADInfoObjects. These info objects in turn contain four properties to expose Name, Class, ADPath and UPN Name as strings. To recap how this is intended to work: First you set the flags on the object if you don't like the defaults. [You can use the SelectUsersOrGroups.exe included in the downloads to determine what those flags should be] Then you call Invoke Dialog, which brings up the UI. After the user dismisses the UI, the entries are retrieved from the dialog. For each entry, and ADInfo object is created, populated with the results and added to the collection.

Since this article is about programming, I want to show an interesting technique to implement the collection object. When implemented properly, a collection is very nice to use, as it allows constructs such as "For each object in Object Collection ... Next" to be written to iterate over the items. What methods/properties do you need to implement a collection. You should implement a few things. First, its nice to have a property called Count, which just tells you how many objects are in the collection. Next there is usually a method called Object Item(int Index), taking in an index, and returning the object located at the index position. Here is where it gets tricky. To allow For Each statements to be written, you must implement another method called LPUNKNOWN _NewEnum() with a dispatch ID of -4. The returned LPUNKNOWN must in reality be a pointer to an IEnumVariant interface. This interface in return, provides a Reset() method to reset the enumerator, and a Next() method to retrieve a VARIANT type that contains an IDispatch * to the object in question. Confused Yet? Implementing the methods on the IEnumVariant requires you to first understand how this stuff works in detail, and then to provide a reasonably error free implementation. Lastly, its nice to provide a method called RemoveAll() that will release all the objects in the collection, and free any memory associated with the collection. As you can see, the collection class defines a vector of Dispatch Pointers to ADObjectInfo called m_coll

typedef std::vector <IADObjectInfo*> ADObjectInfoVector; <BR>ADObjectInfoVector m_coll;
STDMETHODIMP CADObjectColl::get_Count(ULONG* pVal)
 if (m_coll.empty())
  *pVal = 0;
  *pVal = (ULONG)m_coll.size();
 return S_OK;

// this is a structure to allow STL to copy
// IADObjectInfo* to a Variant
struct _CopyVariantFromADObjectInfo
 static HRESULT copy(VARIANT* p1, IADObjectInfo** p2)
  p1->vt = VT_DISPATCH;
  p1->pdispVal = *p2;
  return S_OK;
 static void init(VARIANT* p){VariantInit(p);}
 static void destroy(VARIANT* p){VariantClear(p);}

// this part makes the For .. Each work
 typedef CComEnumOnSTL <IEnumVARIANT, 
    ADObjectInfoVector >

 CComObject<CCOMENUMVARIANTONVECTOR>* pe = 0; // our enumerator

 // create it
 HRESULT hr = CComObject<CComEnumVariantOnVector>:CreateInstance(&pe);
 if (SUCCEEDED(hr))

 // copy the data from our vector to it
 hr = pe->Init(this->GetUnknown(), m_coll);

 // and hand to caller
 if (SUCCEEDED(hr))
  hr = pe->QueryInterface(pVal);

 // cleanup

 return S_OK;

STDMETHODIMP CADObjectColl::Item(long index, IADObjectInfo** pVal)
 // sanity check
 if (index < 1 || index > (long)m_coll.size())
  return E_INVALIDARG;

 // vb is 1 based
 *pVal = m_coll[index - 1];

 // handing out a copy, so addref it
 return S_OK;

STDMETHODIMP CADObjectColl::Add(IADObjectInfo** ppObjInfo)
 CComPtr<IADObjectInfo> ptr;   // the object
 IADObjectInfo * pInfo = NULL;  // the interface pointer
 // init
 *ppObjInfo = NULL;

 // create the object
 hr = ptr.CoCreateInstance (CLSID_CADObjectInfo);
 if (!SUCCEEDED(hr))
  return E_FAIL;

 // addref because we're handing out the pointer
 (ptr.p)->QueryInterface(IID_IADObjectInfo, (void**)&pInfo); 

 // addref because we are storing it, released on destructor

 // add to stl vector

 // and return copy to caller
 *ppObjInfo = pInfo;
 return S_OK;

STDMETHODIMP CADObjectColl::RemoveAll(void)
 return S_OK;

Part 3: Using the Wrapper Object in a Sample C# Project

This is the short & sweet part of the article; when the wrapper object is available using the Object Picker Dialogs becomes short and elegant. Below a VB .NET (don't laugh, BASIC was my first programming language) simplistic implementation, a more complete example can be found in the C# source code found in this article. To see the output of code snippet below, you must run it in the debugger.

Private Sub Button1_Click(ByVal sender As System.Object, _
  ByVal e As System.EventArgs) Handles Button1.Click
 Dim objPicker As New ObjectPickerHelper.CADObjectPicker
 Dim entry As ObjectPickerHelper.IADObjectInfo

 For Each entry In objPicker.ADObjectsColl
    Debug.WriteLine(entry.Name + "; " + entry.ADPath + "; " + _
                entry.Class + "; " + entry.UPN)
End Sub

Below, you can see the C# program after having used the Show Computers button:

Image 10

Fig 9. Select Users or Groups in C#

private void btnComputers_Click(object sender, System.EventArgs e)
 // grab the helper object
 ObjectPickerHelper.CADObjectPicker picker = 
    new ObjectPickerHelper.CADObjectPickerClass();
 // mess with the flags
 picker.ScopeFlags = 
 picker.ScopeTypeFlags = 
 picker.UplevelFilterFlags_Both = 
 picker.UplevelFilterFlags_Mixed = 0;
 picker.UplevelFilterFlags_Native = 0 ;
 picker.DownLevelFilterFlags = 
 picker.InitInfo_OptionFlags = (uint)ADFlagConstants.DSOP_FLAG.MULTISELECT;

 // show the dialog
 // somehow C# seems more picky with the types than VB, 
 // so you have to use an intermediate var.
 ObjectPickerHelper.CADObjectColl coll = 

 // and walk the collection
 int i = 0;
 foreach (ObjectPickerHelper.CADObjectInfo InfoObject in coll)
  ListViewItem theItem = new ListViewItem(InfoObject.Name, i);

Using the code

The code should be fairly self-explanatory -- the download for the code does not contain the executables. These are available as a separate download. Note that Visual Studio .NET 2003 was used as a development platform; you must make sure the C++ runtime and other dependencies are satisfied if you plan to deploy any of the code. The code will only work on Windows 2000 or greater and will not run on the Win9X/ME platform. All the code I write is usually UNICODE compliant, and this is no exception - it should be fairly easy to port to MBCS. The C# example will require the .NET Framework version 1.1. Also note that you will have to self-register the ObjectPickerHelper.dll using regsvr32 ObjectPickerHelper.dll before you attempt to add the reference to this DLL back to the project. The C# project will also need the generated ComInterop DLL in the same directory as the executable, which is also included.


  • 2004 March 1 - Initial Posting


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

Written By
Software Developer (Senior) AB SCIEX
Canada Canada
I was born and grew up in Northern Germany grew up in Quebec in a French Language environment. I finished High School in Fergus, Ontario. After a 4 year training as a Pipe Organ Builder in Germany, I returned to Canada to get a B.Sc. in Computer Science. I'm currently working for a company called AB SCIEX working on Mass Spectrometer Software, am married, and have three often wonderful children. What you believe in matters - I am a follower of Jesus Christ - we attend a German-Lutheran congregation in downtown Toronto.

Comments and Discussions

GeneralThanks! Pin
originSH15-May-07 1:35
originSH15-May-07 1:35 
QuestionHow to show all AD object checked.. Pin
Ronakkumar Patel30-Apr-07 10:42
Ronakkumar Patel30-Apr-07 10:42 
GeneralUMDH: Can't open ûp:3224 for reading Pin
Soumyajit_039-Apr-07 4:38
Soumyajit_039-Apr-07 4:38 
GeneralThanks Pin
Member 7873363-Jan-07 6:46
Member 7873363-Jan-07 6:46 
GeneralLicensing Pin
Aambei6-Nov-06 8:52
Aambei6-Nov-06 8:52 
GeneralRe: Licensing Pin
Friedrich Brunzema2-Aug-09 7:43
Friedrich Brunzema2-Aug-09 7:43 
GeneralFound settings as shown could miss some groups Pin
iknowindigo27-Sep-06 11:54
iknowindigo27-Sep-06 11:54 
GeneralRe: Found settings as shown could miss some groups Pin
vtchris-peterson23-Jul-08 8:53
vtchris-peterson23-Jul-08 8:53 
The fix for me was to add:

ObjectPickerHelper2Lib.IADObjectPicker picker = new ObjectPickerHelper2Lib.ADObjectPickerClass();

// This is a useful reference:
// though, these value had to be derived empirically -- DOWN filter seems to affect local, UP seems to affect domain
picker.DownLevelFilterFlags = 0x80002007;

picker.UplevelFilterFlags_Both = 0x000003fe;

This seems to work, though, it was a bit of guess work...
GeneralUnd so funktioniert es auch mit VB6... Pin
Obelix6912-Sep-06 1:28
Obelix6912-Sep-06 1:28 
GeneralRe: Und so funktioniert es auch mit VB6... Pin
Friedrich Brunzema2-Aug-09 7:45
Friedrich Brunzema2-Aug-09 7:45 
QuestionSet the location to default (highlight) domain name Pin
id10t21-Aug-06 5:39
id10t21-Aug-06 5:39 
AnswerRe: Set the location to default (highlight) domain name Pin
Friedrich Brunzema24-Aug-06 2:50
Friedrich Brunzema24-Aug-06 2:50 
GeneralRe: Set the location to default (highlight) domain name Pin
id10t29-Aug-06 3:52
id10t29-Aug-06 3:52 
GeneralRe: Set the location to default (highlight) domain name Pin
aferende11-Sep-06 20:29
aferende11-Sep-06 20:29 
GeneralDidn't work on XP. [modified] Pin
Shawn Chen27-Nov-06 3:21
Shawn Chen27-Nov-06 3:21 
QuestionRe: Didn't work on XP. Pin
id10t22-Dec-06 6:49
id10t22-Dec-06 6:49 
AnswerSomehow figured it out and it now works on XP as well. Pin
Shawn Chen22-Dec-06 7:48
Shawn Chen22-Dec-06 7:48 
GeneralRe: Somehow figured it out and it now works on XP as well. Pin
id10t5-Jan-07 10:47
id10t5-Jan-07 10:47 
AnswerRe: Didn't work on XP. Pin
Michal Blazevic7-Jul-08 8:31
Michal Blazevic7-Jul-08 8:31 
GeneralTranslate to Visual Basic 2005 Pin
fredy66617-Jan-06 1:45
fredy66617-Jan-06 1:45 
GeneralRe: Translate to Visual Basic 2005 Pin
fredy66617-Jan-06 22:08
fredy66617-Jan-06 22:08 
GeneralRe: Translate to Visual Basic 2005 Pin
abhinaw3-Mar-08 18:08
abhinaw3-Mar-08 18:08 
Generalc# code available Pin
FipMajestix10-Jan-06 9:48
FipMajestix10-Jan-06 9:48 
GeneralRe: c# code available Pin
originSH15-May-07 1:34
originSH15-May-07 1:34 
Questionlicense to use ObjectPickerHelper wrapper Pin
boubounas7-Dec-05 5:09
boubounas7-Dec-05 5:09 

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.