A commonly asked question about Microsoft Active Directory Service Interfaces (ADSI) is, "How can I programmatically manipulate profile paths for Windows 2000 Server Terminal Services or Windows NT Server 4.0, Terminal Server Edition (WTS)?" As I discussed last month, you can't use ADSI. However, you can create a COM object that wraps the WTSAPI32 functions. This month, I show you how to use Microsoft Visual Basic (VB) to create that object. This object contains the functions to manipulate three WTSAPI32 attributes: Terminal Server Profile Path, Terminal Server Home Directory Drive Mapping, and Terminal Server Home Directory attributes. After you've compiled this object, you can use it with any scripting language that supports automation.
Learning how to create COM objects in VB is essential if you want to take scripting to a new level. However, if you don't want to embark into the world of COM in VB or you don't have access to the professional or enterprise edition of VB, you can download a precompiled version of the COM object from the Code Library on the Windows Scripting Solutions Web site (http://www.winscriptingsolutions.com). Before I show you how to create, register, instantiate, and use this COM object, let me give you an overview of COM objects in VB.
COM Objects in VB
In VB, the creation of a COM object uses a type of project called ActiveX DLL. As such, the terms ActiveX DLL and COM object (in reference to VB-compiled objects) are interchangeable.
In an ActiveX DLL in VB, the functions you declare as Public become the compiled COM object's methods. The variables outside the functions become the compiled COM object's properties. To explore these concepts further, you can open a new ActiveX DLL project in VB and use the VB Class Builder utility to see how VB defines properties, methods, and events in an ActiveX DLL.
Unlike a traditional Standard EXE project, an ActiveX DLL has meaningful project and class module names. Consider that when you use VBScript's CreateObject function to instantiate the FileSystemObject object, you use the name Scripting.FileSystemObject to specify the type of object to create. In VB-compiled COM objects, the project name equates to Scripting and the class module name equates to FileSystemObject. In this case, the project and class module names are TerminalServer.UserConfig. Together, the project and class module create a COM object that I simply refer to as the TerminalServer object.
If you're new to creating COM objects in VB, go to http://www.microsoft.com/technet/network/making.asp and check out "Chapter 2: Making the Transition: Multi-Tier Development for System Administrators" from Windows NT/2000 ADSI Scripting for System Administration. This sample chapter is a tutorial about how to use VB to create and debug a COM object.
Creating the Object
To set the three WTSAPI32 attributes from a VBScript Windows Script Host (WSH) file, you need to create a COM object to encapsulate the API calls. Because the focus of this column isn't about API development, you'll find all the function calls and declaration statements you need in the ClassModuleCode.txt file in the Code Library on the Windows Scripting Solutions Web site. This file defines three functions:
- ModifyTSProfilePath (modifies the Terminal Server Profile Path attribute)
- ModifyTSHomeDirectoryDrive (modifies the Terminal Server Home Directory Drive Mapping attribute)
- ModifyTSHomeDirectoryPath (modifies the Terminal Server Home Directory attribute)
To create the TerminalServer object, launch VB. Start a new ActiveX DLL project by selecting ActiveX DLL on the New tab in the New Project Dialog box, then click Open. Paste the contents of ClassModuleCode.txt into the class module that appears. In the Properties window, change Project1 to TerminalServer, and change Class1 to UserConfig.
To compile your object, click Make TerminalServer.dll. Select the location in which you want to place the compiled DLL, and click OK. By default, VB doesn't automatically save your project upon compiling, so you might want to take this opportunity to save your ActiveX DLL project's source code.
Registering the Object
When you compile a COM object in VB, VB automatically registers the COM object on the machine on which you compile it. However, this registration probably won't be of any help because the TerminalServer object needs the WTSAPI32 library to run; this library exists only on Terminal Services or WTS platforms. Thus, you'll likely have to move the terminalserver.dll to an appropriate machine and use Regsvr32 to register the DLL on that machine by typing
on the command line. Because you already compiled the object, you don't need to move the source code in UserConfig.cls or TerminalServer.vbp to the server.
Using the Object
After you've registered the TerminalServer object, you're ready to instantiate and use it. Listing 1 contains VBScript code that you can use.
The code at callout A in Listing 1 creates an instance of the TerminalServer object. With instantiation complete, you can call any of the three functions (i.e., ModifyTSProfilePath, ModifyTSHomeDirectoryDrive, and ModifyTSHome-DirectoryPath) of the TerminalServer object. The target user object must exist in the namespace to call the functions. Let's take a look at each function.
ModifyTSProfilePath. To change the Terminal Server Profile Path attribute for a user, you call ModifyTSProfilePath. As callout B in Listing 1 shows, you need to specify three arguments to use this function. In the first argument "TargetMachine", you specify the name of the user domain on which the account you want to modify exists. You can use a domain name or the name of a local machine SAM in which the account resides. In the second argument "TestUser1", you specify the relative name of the user object (i.e., relative username) you want to modify. Don't include the domain name in this argument—you've already specified this information in the first argument. In the last argument "\\Server_01\Share\Directory", you specify the new profile path you want to propagate to the specified User object.
ModifyTSHomeDirectoryDrive. To change the Terminal Server Home Directory Drive Mapping attribute, you call ModifyTSHomeDirectoryDrive. As callout C in Listing 1 shows, you need to specify three arguments. In the first two arguments, you specify the user domain and relative username, respectively, as you did for the ModifyTSProfilePath function. In the third argument "Q:", you specify the drive you want to map for the user's home directory. You must include the colon in this argument.
ModifyTSHomeDirectoryPath. To change the Terminal Server Home Directory attribute, you call ModifyTSHomeDirectoryPath. Again, you specify three arguments, as callout D in Listing 1 shows. Like the other two functions, ModifyTSHomeDirectoryPath takes the user domain and relative username as the first two arguments, respectively. In the last argument "\\Server_01\Share\Directory", you specify the new path you want to propagate to the specified User object.
Each function returns a Boolean value that indicates the success or failure of the operation. An If...Then...Else statement then tests this value. If the value is equal to True, the statement displays a message stating that attribute was modified. Otherwise, the statement displays a message stating that the attribute wasn't modified.
Expanding the Excel Tool
In "Practical Usage of ADSI: Managing User Accounts in Win2K and NT," March 2001, I presented a Microsoft Excel 2000 spreadsheet that creates user accounts and manages home directories. I've added the TerminalServer object functions to this spreadsheet.
You can download this tool from the Code Library on the Windows Scripting Solutions Web site. To use this spreadsheet, you must register the TerminalServer object on either your Win2K client that's running Terminal Services or your WTS machine that's running Service Pack 4 (SP4) or later.
Encapsulate Complex Code
This example of how to create a COM object is by no means a complete primer on COM development in VB. However, you can use the techniques I've shown you to create the Terminal-Server object. You can then use this object to manipulate the WTSAPI32 attributes programmatically. Best of all, you can manipulate these attributes with the ease you've grown accustomed to with ADSI.