Using the Active Directory Object Selection Dialog

Environment: VC6 SP5/VC7, Windows 2000/XP

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 simply 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.” Figure 1 shows you 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.

Figure 1: The “Select Users or Groups” dialog in action.

Okay, now you know how to bring up the dialog 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 Figure 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 50,000 users… We used the NetGroupGetUsers() function and other functions in that group to do the job. But what we really wanted is the dialog seen in Figure 2:

Figure 2: The NT Select Users Dialog.

Oleg Kagan wrote a nice demo program (refer to Figure 2) on how to solve this problem on NT—the source code is available here: NT User Browser. 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 Figure 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 two 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: Don’t 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 doing 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 DLLs 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 four COM exports, so the DLL had to be used through COM. Looking through the Registry, I was able to determine the GUID for the co-class of the our object selector object.

At this point, I thought about using a disassembler, but maybe there was 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 IDs 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 or object picker. I looked at 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 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.

Figure 3: Advanced Properties for the Object Picker Dialog.

I will comment on this dialog a bit more because 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.

Initialization

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 initializes.

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 structure 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.

Figure 4: Setting DSOP Scope Flags.

Figure 5: Setting the DSOP Filter flags

Figure 6: Setting the down-level filter flags.

Figure 7: The main application, after having picked users

The following code shows how to bring up the dialog box is listed.

// 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

// init the DSOP_SCOPE_INIT_INFO
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 =
              m_dlgAdvanced.m_dlgBoth.m_flags;
aScopeInit[0].FilterFlags.Uplevel.flMixedModeOnly =
              m_dlgAdvanced.m_dlgMixed.m_flags;
aScopeInit[0].FilterFlags.Uplevel.flNativeModeOnly =
              m_dlgAdvanced.m_dlgNative.m_flags;
aScopeInit[0].FilterFlags.flDownlevel =
              m_dlgAdvanced.m_dlgDownLevel.m_flags;

// 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);
return;
}

// 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);
return;
}

// 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..."), MB_OK);
return;
}

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

}

In conclusion, note that the object selection dialog is pretty powerful because 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 and 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.

Downloads

Download source and executable ObjectSelection.zip – 46 Kb

More by Author

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Must Read