Let's now take a look at simple manipulation of Active Directory objects using ADSI. We are using Active Directory as the primary target for these scripts, but the underlying concepts are the same for any supported ADSI namespace and automation language. All the scripts use GetObject to instantiate objects, assuming you are logged in already with an account that has administrator privileges; if you aren't, you need to use IADsOpenDSObject::OpenDSObject as shown earlier in the chapter.
The easiest way to show how to manipulate objects with ADSI is through a series of real-world examples, the sort of simple tasks that form the building blocks of everyday scripting. To that end, imagine that you want to perform the following tasks on the mycorp.com Active Directory forest:
Create an Organizational Unit called Sales.
Create two users in the Sales OU.
Iterate through the Sales OU and delete each user.
Delete the Organizational Unit.
This list of tasks is a great introduction to how ADSI works because we will reference some of the major interfaces using these examples.
The creation process for the Sales Organizational Unit is the same as for any object. First you need to get a pointer to the container in which you want to create the object. You do that using the following code:
Set objContainer = GetObject("LDAP://dc=mycorp,dc=com")
Since we are creating a container of other objects, rather than a leaf object, you can use the IADsContainer interface methods and properties. The IADsContainer::Create method is used to create a container object, as shown in the following code:
Set objSalesOU = objContainer.Create("organizationalUnit","ou=Sales")
Here we pass two arguments to IADsContainer::Create: the objectclass of the class of object you wish to create and the Relative Distinguished Name (RDN) of the object itself. We use the ou= prefix because the type of object is an Organizational Unit. Most other objects use the cn= prefix for the RDN.
The IADsContainer interface enables you to create, delete, and manage other Active Directory objects directly from a container. Think of it as the interface that allows you to manage the directory hierarchy. A second interface called IADs goes hand in hand with IADsContainer, but while IADsContainer works only on containers, IADs will work on any object.
To commit the object creation to Active Directory, we now have to call IADs::SetInfo:
ADSI implements a caching mechanism in which object creation and modification are first written to an area of memory called the property cache on the client executing the script. Each object has its own property cache, and each cache has to be explicitly written out to Active Directory using IADs::SetInfo for any creations or modifications to be physically written to Active Directory. This may sound counterintuitive but in fact makes sense for a number of reasons, mostly involved with reducing network traffic. The property cache is discussed in more detail in Chapter 19.
Each object has a number of properties, some mandatory and some optional. Mandatory properties have to be defined during the creation of an object. They serve to uniquely identify the object from its other class members and are necessary to make the object usable in Active Directory. If you need to create an object with a large number of mandatory properties, it makes sense to write them all into a cache first and then commit them to Active Directory in one operation, rather than perform a sequence of SetInfo operations.
While the Organizational Unit example has no other mandatory properties, other objects do. User objects, for example, require sAMAccountName to be set before they can be written out successfully. In addition, you can also choose to set any of the optional properties before you use IADs::SetInfo.
Putting it all together, we have our first simple script that creates an OU:
Set objContainer = GetObject("LDAP://dc=mycorp,dc=com") Set objSalesOU = objContainer.Create("organizationalUnit", "ou=Sales") objSalesOU.SetInfo
We now will move to the second task of creating a couple user objects. Creating user objects is not much different from creating an OU in the previous task. We use the same IADsContainer::Create method again as in the following:
Set objUser1 = objSalesOU.Create("user", "cn=Sue Peace") objUser1.Put "sAMAccountName", "SueP" objUser1.SetInfo Set objUser2 = objSalesOU.Create("user", "cn=Keith Cooper") objUser2.Put "sAMAccountName", "KeithC" objUser2.SetInfo
The IADs::Put method is used here to set the SAM Account Name, a mandatory attribute that has no default value. The SAM Account Name is the name of the user as it would have appeared in previous versions of NT and is used to communicate with down-level NT domains and clients. It is still required because Active Directory supports accessing resources in down-level Windows NT domains, which use the SAM Account Name.
It is also worth pointing out that the IADs::SetInfo calls can be put at the end of the script if you want to. As long as they go in the right order (i.e., the OU must exist before the user objects within that OU exist), the following works:
Set objContainer = GetObject("LDAP://dc=mycorp,dc=com") Set objSalesOU = objContainer.Create("organizationalUnit", "ou=Sales") Set objUser1 = objSalesOU.Create("user", "cn=Sue Peace") objUser1.Put "sAMAccountName", "SueP" Set objUser2 = objSalesOU.Create("user", "cn=Keith Cooper") objUser2.Put "sAMAccountName", "KeithC" objSalesOU.SetInfo objUser1.SetInfo objUser2.SetInfo
This works because the property cache is the only thing being updated until the SetInfo call is issued. Since ADSI works against the property cache and not Active Directory directly, you could put off the SetInfo calls until the end of your scripts. There is no special benefit to doing scripts this way, and it can lead to confusion if you believe incorrectly that properties exist in the underlying service during later portions of the script. In addition, if you bunch up cache writes, and the server crashes, none of your writes will have gone through, which I suppose you could see as a good thing. However, we will not be using this method; we prefer to flush the cache as soon as feasible. Bunching caches to write at the end of a script encourages developers to neglect proper error checking and progress logging to a file from within scripts.
As you've seen, creating objects is a breeze with ADSI. Deleting objects is also very straightforward. Let's iterate through the Sales OU and deleting the two users we just created:
for each objUser in objSalesOU objUser.DeleteObject(0) Next
We used a For Each loop to enumerate over the objects in objSalesOU. The objUser variable will get set to a reference of each child object in the Sales OU. We then use IADsDeleteOps::DeleteObject method to delete the object. The value 0 must be passed in to DeleteObject, but it does not hold any special significance (it is reserved for later use).
The final step is to delete the Sales OU using the same method (IADsDeleteOps::DeleteObject) that we used to delete users:
objSalesOU.DeleteObject(0) Set objSalesOU = Nothing
The IADsDeleteOps::DeleteObject method can delete all the objects within a container, so it wasn't really necessary for us to delete each user object individually. We could have instead used DeleteObject on the Sales OU to delete the OU and all child objects within the OU. This method should be used with care since a lot of objects can be wiped out by using DeleteObject on the wrong container.