[ Team LiB ] |
18.3 ADSIBefore you can start writing scripts that use ADSI, you first need to understand the basic COM concept of interfaces and ADSI's concepts of namespaces, programmatic identifiers (ProgIDs), and ADsPaths. 18.3.1 Objects and InterfacesA COM interface defines the properties associated with an item, how to access those properties, and how to access specific functionality of the item, more commonly referred to as an object. For example, WSH has a number of objects that represent files, shortcuts, network access, and so on. ADSI provides a specification for interfaces that each directory service provider must implement to maintain uniformity. Each ADSI interface normally supports methods that can be called to perform a specific action, and properties (or property methods) to retrieve information about the object. A method is a procedure or function that is defined on an object and interacts with the object. So an interface to access Active Directory group objects would have Add and Remove methods, so that members could be added or removed from a group. Methods are normally represented as Interface::MethodName when referenced, and this is the form we adopt in this book. Objects also have properties that are retrieved using the IADs::Get or IADs::GetEx methods and set or replaced using the IADs::Put or IADs::PutEx methods. Each ADSI object supports an IADs interface that provides six basic pieces of information about that object:
If you wanted to retrieve the GUID property of an object, you would use the following: strGUID = objX.Get("GUID") You can see that we are calling the IADs::Get method on the object called objX; the dot (.) indicates the invocation of a property or method. The IADs::Get method takes as its one parameter the property to retrieve, which in this case is the GUID, and passes it out to a variable that we have called strGUID. So that you do not have to use the IADs::Get method for the most common properties, certain interfaces define these common properties with property methods. In these specific cases, you use the dotted method notation to retrieve the property by using the property method of the same name. In the previous GUID example, the GUID property has a property method of the same name (i.e., IADs::GUID). We could therefore retrieve the GUID with: strGUID = objX.GUID We won't go into the interfaces in any more depth here; we just want to give you a feel for the fact that methods and properties can be accessed on an object via ADSI interfaces. Although an object can support more than one interface without a problem, each object supports only the interfaces that are relevant to it. For example, the user object does not support the interface that works for groups. The other interfaces, of which there are around 40, begin with the prefix IADs. Interfaces can relate to many different types of objects, including objects that reside in directory services (e.g., IADsUser and IADsGroup), transient objects that don't exist in a directory service (e.g., IADsPrintJob), and security-related objects (e.g., IADsOpenDSObject and IADsAccessControlList). Note that not all objects have a specific IADs interface that applies its objectclass (e.g., IADsUser), so in those cases you have to use the more generic IADs or IADsContainer interfaces. Because each directory service is slightly different, not every ADSI interface method and property works in every directory service. If you make a method call to a directory service that doesn't support that method, you'll receive an error message specifying that the provider doesn't support that method. According to the ADSI specification, each service provider must reject inappropriate calls with the correct ADSI error message. 18.3.2 Namespaces, ProgIDs, and ADsPathsTo reference different types of servers (e.g., Windows NT 4.0, NetWare, etc.) with ADSI, you must use the namespaces that correspond to the ADSI providers used by that directory service. ADSI uses a unique prefix called a ProgID to distinguish between these namespaces. Each ProgID is synonymous with a particular namespace and directory provider. In a script, you specify the ProgID, which is used behind the scenes to correctly connect and bind to the corresponding directory service. For example, you specify WinNT:// to access individual Windows NT 3.51, 4.0, Windows 2000, and Windows Server 2003 systems; you use LDAP:// to access Active Directory and other LDAP directories. When ADSI encounters the ProgID, ADSI loads an appropriate ADSI-provider DLL to correctly process the bind request and method invocations.
Since each ProgID is synonymous with a particular namespace, the term ProgID usually is dropped. For example, individual systems are accessed using the PRogID WinNT:. However, conventionally, this namespace is referred to as the WinNT namespace rather than the WinNT ProgID. This is the convention adopted in the book. This references JoeB, a user on computer MOOSE in WORKGROUP: WinNT://WORKGROUP/MOOSE/JoeB This references JoeB, a user on computer MOOSE: WinNT://MOOSE/JoeB As these examples show, you can reference each object by using only its name or, more properly, by using its name and type, if two or three identically named objects with different types exist. Each namespace has a unique format for the ADsPath string, so you need to make sure that you're using the correct ADsPath notation. For example, each of these ADsPaths references a unique object. This ADsPath references JoeB, a user in DOMAIN: WinNT://DOMAIN/JoeB, User This next one references JoeB, a user in the Finance Organizational Unit (OU) within the Mycorp organization of the IntraNetWare tree called MyNetWareTree: NDS://MyNetWareTree/O=MYCORP/OU=FINANCE/CN=JoeB This one references JoeB, a NetWare 3.x or 4.x (bindery services) user that exists on server MYSERVER: NWCOMPAT://MYSERVER/JoeB Finally, this one references the WWW service component of IIS running on the local host: IIS://localhost/w3svc/1 In the preceding examples, NDS: refers to IntraNetWare 5.x and 4.x. (Because IntraNetWare 5.x is LDAP-compliant, you also can use LDAP paths with it.) NWCOMPAT: refers to NetWare 4.x, 3.2, 3.12, and 3.11 servers in bindery-emulation mode. IIS: refers to metabase paths on a host running IIS 3.0 or later. One of the most commonly used namespaces is the LDAP namespace. You can use LDAP with ADSI to access a variety of directory services, including Active Directory. Although you can use the WinNT namespace to access Active Directory, you need to use the LDAP namespace to fully utilize all of ADSI's methods and properties. For this reason, our primary focus will be on the LDAP namespace. You can use several formats to refer to LDAP directories. For example, all the following ADsPaths reference the Administrator object within the Users container of the moose directory server in the mycorp.com zone: LDAP://cn=administrator,cn=users,dc=mycorp,dc=com LDAP://moose.mycorp.com/cn=administrator,cn=users,dc=mycorp,dc=com LDAP://moose/cn=administrator,cn=users,dc=mycorp,dc=com LDAP://DC=com/DC=mycorp/CN=Users/CN=Administrator LDAP://moose.mycorp.com/DC=com/DC=mycorp/CN=Users/CN=Administrator In these examples, CN stands for common name, and DC stands for domain component. These examples show that you can specify the LDAP namespace ADsPath going down or up the hierarchical Directory Information Tree (DIT). Most people have adopted the naming style used in the first three examples, where the most specific element of an object is used first. Also note that you can specify a fully qualified Domain Name System (DNS) server name after LDAP://, using a forward slash character (/) to separate the DNS server name from the rest of the path. If a name includes some unusual characters, such as a forward slash or a comma, you can use double quotation marks ("/") or a single backslash (\) to specify that the character should be interpreted as part of the ADsPath itself. For example, if you have a user called AC/DC on the server, this is wrong: LDAP://cn=ac/dc,cn=users,dc=amer,dc=mycorp,dc=com This will interpret the path using cn=ac followed by dc followed by cn=users and so on. As dc on its own is not a valid part of the path, the ADsPath is invalid. Here are the correct paths: LDAP://cn=ac\/dc,cn=users,dc=amer,dc=mycorp,dc=com LDAP://"cn=ac/dc",cn=users,dc=amer,dc=mycorp,dc=com Obviously, as the backslash is a special character, you would need to do the following for an object called cn=hot\cold: LDAP://cn=hot\\cold,cn=users,dc=amer,dc=mycorp,dc=com LDAP://"cn=hot\cold",cn=users,dc=amer,dc=mycorp,dc=com The first specifies that the character following the first backslash is to be interpreted as part of the name, and the latter says to specify that the whole first name is a valid string.[2]
18.3.3 Retrieving ObjectsNow that you know how to use ADsPaths to distinguish between different namespaces, we'll demonstrate how to establish a connection and authenticate to the server containing the directory service you want to access. Authenticating a connection isn't always necessary; some directories, such as Active Directory, can allow anonymous read-only access to certain parts of the directory tree if you configure it that way. In general, allowing anonymous access is not a good practice. It can make things much more difficult to troubleshoot if you discover that one of your domain controllers is being impacted by an overzealous client. When using ADSI, if authentication is not done explicitly, the credentials of the account the script is running under will be used. If the account running the script is not part of the Active Directory you want to query or in a trusted domain, you will not be able to do very much. That's why performing explicit authentication in ADSI scripts is generally the best way to go. If you just want to use the current account's credentials to bind to a directory server to get a reference to an object, use the GetObject function:[3]
Dim strPath 'path to the directory server Dim objMyObject 'root object of the directory strPath = "LDAP://dc=amer,dc=mycorp,dc=com" Set objMyDomain = GetObject(strPath) The code begins by declaring two variables with VBScript Dim statements. The first variable, strPath, is an ADsPath. The prefix str specifies that this ADsPath is a text string; see the sidebar about typical VBScript naming conventions. The second variable, objMyDomain, is a pointer to the object in the directory that the ADsPath represents. The prefix obj specifies that the variable is an object. Next, we assign the strPath variable to the path of the directory server we want to bind to, in this case, LDAP://dc=amer,dc=mycorp,dc=com. You need to enclose this path in quotation marks, because it's a text string. Finally, we use VBScript's Set statement with the GetObject method to create a reference between the variable we declared and the existing object we want to interact with. In this case, we're creating a reference between objMyObject and the existing object that the ADsPath LDAP://dc=amer,dc=mycorp,dc=com represents (i.e., the domain object of the amer.mycorp.com domain). After we've established this reference, we can use other IADs-based interfaces to interact with that object. To explicitly authenticate to a directory server, use the IADsOpenDSObject interface, which contains only one method: OpenDSObject, which takes four arguments:
The following listing shows how to use IADsOpenDSObject::OpenDSObject to authenticate to a directory server. We begin by declaring three string variables (strPath, strUsername, and strPassword) and two object variables (objNamespaceLDAP and objMyObject): Dim strPath 'path to authenticate to in the directory service Dim strUsername 'DN of the username Dim strPassword 'plain text password Dim objNamespaceLDAP 'ADSI namespace object Dim objMyObject 'root object of the directory strPath = "LDAP://dc=amer,dc=mycorp,dc=com" strUsername = "cn=Administrator,cn=Users,dc=amer,dc=mycorp,dc=com" strPassword = "the password goes here in plain text" Set objNamespaceLDAP = GetObject("LDAP:") Set objMyObject = objNamespaceLDAP.OpenDSObject(strPath,strUsername,strPassword,0) We then assign the strPath, strUsername, and strPassword variables the appropriate ADsPath, username, and password strings. The username string, which is also called the Distinguished Name (DN), references the username's exact location in the directory. A User Principal Name (UPN) can also be used in place of a DN. A UPN typically has the format of username@ForestDnsName (e.g., [email protected]). The strPath is used to authenticate to a specific point in Active Directory if you wish. This can be used if the user authenticating does not have permission to work at the root and has to authenticate further down the tree. Next, we use a Set statement with GetObject to create a reference for the variable called objNamespaceLDAP. Notice that we're using "LDAP:" rather than strPath as an argument to GetObject. Using the LDAP namespace might seem unusual, but it is necessary so that in the next line, you can call the IADsOpenDSObject::OpenDSObject method on the LDAP namespace that ADSI returns. The last IADsOpenDSObject::OpenDSObject argument is to specify any security settings that should be applied to the connection. When set to 0 or left blank, no security is enabled for the connection. That is typically not the optimal choice, considering that all traffic between client and server will be sent in plain text over the network. The following two constants are important to use if at all possible:
You use multiple constants by adding them together—i.e., (ADS_SECURE_AUTHENTICATION + ADS_USE_ENCRYPTION) as they represent integer values. While these are defined constants, they cannot be used by name from VBScript. The entire set of values from the ADS_AUTHENTICATION_ENUM enumerated type can be found under the MSDN Library (http://msdn.microsoft.com/library/), by following this path: Networking and Directory Services Active Directory, ADSI and Directory Services SDK Documentation Directory Services Active Directory Service Interfaces Active Directory Service Interfaces Reference ADSI Enumerations ADS_AUTHENTICATION_ENUM. The following code is slightly modified from the previous example to show how to enable ADS_SECURE_AUTHENTICATION and ADS_USE_ENCRYPTION for a connection: Const ADS_SECURE_AUTHENTICATION = 1 Const ADS_USE_ENCRYPTION = 2 Dim strPath 'path to authenticate to in the directory service Dim strUsername 'DN of the username Dim strPassword 'plain text password Dim objNamespaceLDAP 'ADSI namespace object Dim objMyObject 'root object of the directory strPath = "LDAP://dc=amer,dc=mycorp,dc=com" strUsername = "cn=Administrator,cn=Users,dc=amer,dc=mycorp,dc=com" strPassword = "the password goes here in plain text" Set objNamespaceLDAP = GetObject("LDAP:") Set objMyObject = objNamespaceLDAP.OpenDSObject(strPath, _ strUsername, strPassword, _ ADS_USE_ENCRYPTION + ADS_SECURE_AUTHENTICATION) While securing the connection to the domain controller is an important precaution to take, including an administrator's password in a script can obviously be pretty insecure. If you don't want to include plain-text passwords, you have several options. The first option is to assign a value to strPassword from the VBScript InputBox function. The following listing shows this: Const ADS_SECURE_AUTHENTICATION = 1 Const ADS_USE_ENCRYPTION = 2 Dim strPath 'path to authenticate to in the directory service Dim strUsername 'DN of the username Dim strPassword 'plain-text password Dim objNamespaceLDAP 'ADSI namespace object Dim objMyObject 'root object of the directory strPath = "LDAP://dc=amer,dc=mycorp,dc=com" strUsername = "cn=Administrator,cn=Users,dc=amer,dc=mycorp,dc=com" strPassword = InputBox("Enter the Administrator password","Password entry box") Set objNamespaceLDAP = GetObject("LDAP:") Set objMyObject = objNamespaceLDAP.OpenDSObject(strPath, _ strUsername, strPassword, _ ADS_USE_ENCRYPTION + ADS_SECURE_AUTHENTICATION) When you run the script, the InputBox prompts you to enter the administrator's password. However, the InputBox echoes the password in plain text while you type it into the password entry box, so this approach isn't terribly secure itself. Three other options are secure. However, because VBScript doesn't natively support password retrieval boxes, you can't use these solutions without some work:
If you want to authenticate a connection but have already logged on to the directory, you can use the default credentials for your existing connection. You simply use the VBScript vbNullString constant in both the username and password fields, as the following listing shows: Dim strPath 'path to authenticate to in the directory service Dim objNamespaceLDAP 'ADSI namespace object Dim objMyObject 'root object of the directory strPath = "LDAP://dc=amer,dc=mycorp,dc=com" Set objNamespaceLDAP = GetObject("LDAP:") Set objMyObject = objNamespaceLDAP.OpenDSObject(strPath, vbNullString, _ vbNullString,0)
From now on, most of the scripts will use GetObject for simplicity, but if you need to, you can just as easily use IADsOpenDSObject::OpenDSObject without modifying any of the other code. |
[ Team LiB ] |