Windows NT introduced some network-related dialog boxes (like that in Figure
1) integrated in the standard Windows NT administrative tools. That was
fine for administrators, but there was no API for programmers to include such
dialog boxes in their own application, so many programmers created custom
code to let users perform tasks such as selecting users.
Windows 2000 offers an object picker dialog that can be used to let users select users, contacts, groups, and computers. It can access objects from the Active Directory global catalog, a Windows 2000 domain or computer, a Windows NT v4.0 domain or computer, or a workgroup. This article describes some of the abilities of this dialog and provides a code example.
Active Directory Introduction
Windows 2000 provides a brand new network common repository named Active Directory. Active Directorys power consists of an extensible schema queryable with the standard LDAP protocol. The platform SDK provides large amounts of documentation for Active Directory.
Briefly, Active Directory is a hierarchical database for storing and retrieving arbitrary information. The items in the database are objects, named sets of attributes that describe something (e.g., a user, a printer, an application , etc.). Some aspects of the database are predefined, but you can extend the database and add new categories of information. You can refer to a particular position in this hierarchical database with something called a distinguished name (sometimes abbreviated DN). This is analogous to a path in a hierarchical file system.
Unlike a file system path, distinguished names require you to specify the class of each node in the path. For example, here is a string representation of a fictional distinguished name for an Active Directory object representing a user in company:
cn=John Smith,ou=Sales,dc=microsoft,dc=com
As you can see, the distinguished name format starts the leaf object, and works back towards the root (the opposite of how you normally write a file system path). The funny two-character codes are called prefixes, which I will explain next.
RFC 1779 (www.faqs.org/rfc/rfc1779.html) defined this string syntax for distinguished names. This RFC defined several prefixes for indicating the class of object being referred to, but Active Directory uses only three of the possible prefixes: cn, ou, and dc.
cn stands for common name. It specifies the name of the object searched for. All objects in the database have this attribute, and it must be unique for objects within a given container. Thats roughly the same thing as saying that no two files within a given directory can have the same name.
ou stands for organizational name unit. These are container objects, but they represent a security boundary. For example, if you wanted to give one manager authority to manage a section of the Active Directory representing the machines in just his department, you would probably place those objects underneath an ou object. It would be possible to grant him complete authority to add, delete, and modify objects beneath that organizational name unit, without granting any permissions to objects higher in the tree.
dc stands for domain component, and this prefix is used only for specifying domain roots.
The previous sample distinguished name:
cn=John Smith,ou=Sales,dc=microsoft,dc=com
can now be translated as: user John Smith in the Sales organizational unit of the microsoft.com domain.
The Object Picker
The object picker is a Windows 2000 COM component you can use to allow users
to graphically select user, contact, group, or computer objects from the Active
Directory. This provides dialog box services for some common tasks that were
not available under Windows NT v4.0. I will illustrate how to use this COM
object with the sample program in op.cpp (Listing
1). This months code archive contains the complete source code needed
to build this application.
You will need to include objsel.h to get the appropriate object picker definitions. Depending upon how new your compiler is, you may have to obtain this file from the platform SDK. This is currently downloadable from www.microsoft.com/msdownload/platformsdk/setuplauncher.htm.
The object picker COM object has two main methods of interest: Initialize() and InvokeDialog(). The Initialize() method takes a pointer to a DSOP_INIT_INFO structure specifying:
- the kind of objects that should be selected through standard filters like DSOP_DOWNLEVEL_FILTER_COMPUTERS for computers or DSOP_DOWNLEVEL_FILTER_USERS for users
- if multi-selection should be enabled or not with the DSOP_FLAG_MULTISELECT option
- which domains should be browsed (parent domains, child domains, workgroups, etc.) through the flType member flags.
InvokeDialog() simply displays the standard dialog box using the parameters specified in the last call to Initialize(). It takes the handle of the parent window and returns a data object using the CFSTR_DSOP_DS_SELECTION_LIST clipboard format. This format handles an HGLOBAL containing a DS_SELECTION_LIST structure.
Sample Program
I wrote a demonstration program in op.cpp (Listing
1) showing how to invoke the object picker dialog box. SelectUsersOrComputers()
will show a list of the users or computers depending on whether the bUsers
parameter is TRUE or not. After creating the object picker COM object,
I initialize it to use a scope that will include all the up and down-level
joined domains in the DSOP_SCOPE_INIT_INFO flType member. I also specify
which objects (users or computers) I want to filter, using the FilterFlags
member. Once my scope is fully initialized, I fill in the DSOP_INIT_INFO
main structure by setting a pointer to the previously created scope in aDsScopeInfos
and enabling multi-selection with the DSOP_FLAG_MULTISELECT flag in
flOptions. I then only need to call Initialize() and InvokeDialog()
to get the IDataObject in return. Note that S_FALSE will be
returned if the user clicks on Cancel.
The example also includes a function named PrintSelection() that will iterate through the selected elements. I simply initialize my FORMATETC and STGMEDIUM structures for the CFSTR_DSOP_DS_SELECTION_LIST clipboard format and ask for the HGLOBAL by calling its GetData() method. I then only need to lock the HGLOBAL memory and cast my pDsSelList pointer to a PDS_SELECTION_LIST. This structure will contain a cItems member containing the number of selected items and an array of DS_SELECTION structures in the aDsSelection member specifying for each Active Directory object: the name, Active Directory path, class name, and principal name.
Fetching Attributes
The sample program shows how to select users or computers, but you can go further by fetching attributes to the user-selected objects. As described previously, the Active Directory is composed of objects. Each object belongs to a class having several attributes. You can browse for your Active Directory classes structure using the Active Directory Schema snap-in and see, for example, that the class user has attributes like mail for the email address or company. You can ask the object picker to store in the data object attributes you are interested in. I added to my sample some lines of code to fetch those two attributes by setting cAttributesToFetch to two and apwzAttributeNames to mail and company in the DSOP_INIT_INFO structure. I also added the necessary code to read the attributes in PrintSelection() in the pvarFetchedAttributes member of each DS_SELECTION structure. It points to VARIANTs I convert to VT_BSTR type before printing them.
Conclusion
COM and the object picker provide dialog box services that were not available on Windows NT v4.0 but it goes far beyond that. It allows the developer to select any kind of Active Directory objects including printers, groups, domains, and even to get directly their attributes.
Arnaud Aubert is the R&D manager of IS Decisions and is available for independent Windows Development short-term debugging/consulting. He can be reached at info@arnaudaubert.com.