If you decide you need a GUI-based application instead of a web-based application, it is time to start thinking about coding in a different language. VB is an easy language with the capabilities of great complexity. The VB language itself is very similar to VBScript, so you can port code quickly from your existing scripts. However, there is so much that you can do with VB that the bewildering array of interfaces and methods can easily get confusing. The simplest solution to this is to get a book on VB. There are many dozens of books that already exist on the complexities of writing in VB, and we do not intend to provide an introduction here. If you are seriously considering writing in VB, your best option is to pick up a book on it.
This section covers what you need to do to write ADSI code with VB after having written ADSI code in VBScript. This includes a brief look at the major differences between VBScript and VB, the options that need to be set, and the Platform SDK, which you will need to compile your code. We also briefly cover a series of examples that are available from the O'Reilly web site. The notes that we present in this section are with respect to Microsoft Visual Basic Professional Version 6.0. However, these examples should also work with future versions of VB as well.
To access the ADSI interfaces and libraries, you need to be able to reference the appropriate component of the Microsoft Platform Software Development Kit (SDK) in your code. You can either download the appropriate component or obtain the full SDK, which includes all components.
The full SDK provides developers with a single, easy-to-use location from which to download current and emerging Microsoft technologies; it includes tools, headers, libraries, and sample code. The Platform SDK is the successor to the individual SDKs, such as the Win32, BackOffice, ActiveX/Internet Client, WMI, ADSI, and DirectX SDKs.
You can get the full SDK build environment or just the ADSI component in a number of ways:
If you purchase an MSDN Professional-level subscription, you will be shipped all of the SDKs that you require.
If you purchase an MSDN Enterprise-level subscription, you will be shipped all of the SDKs and all of the Visual Studio products, which includes Microsoft Visual Basic Enterprise Edition as well.
If you purchase Visual Basic 6.0 Enterprise Edition, you receive the full MSDN set of CDs and the SDK build environment.
You can download the parts of the platform SDK by following going to http://www.microsoft.com/msdownload/platformsdk/sdkupdate/.
Once the SDK has been downloaded and installed, start VB and in any new project that you write make sure that you go to Project References and check items according to Table 25-1.
Active DS Type Library
Microsoft ActiveX Data Objects 2.5 Library
You can see the References window in Figure 25-2, with both items checked.
There are many differences between VBScript and VB, but the three major ones that you will come into contact with when porting your scripts can be quickly explained.
The code will not be executing under the WSH any more so Wscript.Echo is not appropriate. While MsgBox still works, these lines should be replaced either by Debug.Print or by directly passing results into TextBox controls.
In VBScript, every variable is of the type Variant and does not have to be declared. In VB, every variable must be declared at the beginning, just as if you were using Option Explicit in VBScript. In addition, each variable must be declared to be of a particular type. Here are some examples for VB:
'VB code Dim objUser as IADsUser Dim objRoot as IADsContainer Dim objMember as IADsMember
In addition, CreateObject is not needed. Instead, you use the New keyword and declare the object created prior to the main code. The following VBScript code:
'VBScript code Set objConn = CreateObject("ADODB.Connection")
is replaced with the following code in VB:
'VB code Dim objConn as New ADODB.Connection
For another important point, look at these declarations:
'VB code Dim objUser as IADsUser Dim objUser2 as IADs
If we want to use the IADsUser methods and properties, we have to use the variable objUser. If we want to use the IADs methods and properties, we have to use objUser2. This is how it works:
'VB code Dim objUser as IADsUser Dim objUser2 as IADs Set objUser = GetObject("LDAP://cn=Administrator,cn=Users,dc=mycorp,dc=com") Set objUser2 = objUser Debug.Print objUser.Description Debug.Print objUser2.Class
The first Debug::Print statement prints the IADsUser::Description property, and the second prints the IADs::Class property. We have to include the second Set command to make sure that objUser2 points to the same object as objUser.
The syntax for loops changes slightly. Here, for example, are two loops in VBScipt:
'VBScript code While (condition) 'Do something Wend For Each objMember In objGroup.Members WScript.Echo objMember.Name & vbCrLf & objMember.ADsPath Next
Here they are again in VB:
'VB code While (condition) Do 'Do something Wend For Each objMember In objGroup.Members Debug.Print objMember.Name & vbCrLf & objMember.ADsPath Next objMember
We now can move on to some proper VB coding.
When you begin to code in VB, the interface tries to help you code by providing you with the appropriate methods and properties for the object you are manipulating.
For example, if we started declaring a variable in VB, then as soon as we had stated something like this:
'Declare use variable Dim objUser As
the interface would pop up a box displaying all the variable types so we could pick one. We'll say that we chose IADsUser from the list at this point. Now in my code we wish to use a method on the object, so we start typing:
'Declare use variable Dim objUser As IADsUser 'Use IADsUser method objUser.
As soon as we have typed the dot, VB knows we wish to use a method, so it pops up all the possible methods that we could use at this point. This is a great help, so that you do not have to remember the names of the methods and properties all the time.
You also can use View Object Browser (or use the F2 key), which shows you all the possible methods and properties available in any SDKs that are currently included as references to your project.
This is a variation on the password changer we introduced earlier. This changer is for use by a help desk to set a user's password and automatically unlock the account if it is locked. All the users are presumed to be in the Users container for this simple project, which makes use of one form, shown in Figure 25-3.
The form (the window) contains the following controls (objects that sit on the window):
One PictureBox control, the O'Reilly logo
Three Label controls, the text fields that cannot be edited
Three TextBox controls (txtUsername, txtPass1, and txtPass2), the three data entry fields
One CommandButton control (cmdChangePassword), the Change Password button
Some of the properties for fields have been set as follows:
To make sure that using the Tab key cycles properly through the three TextBox controls and the CommandButton control, the TabIndex property is set for each control in the order that the Tab key is to cycle through, e.g., txtUsername=1, txtPass1=2, txtPass2=3, cmdChangePassword=4.
The two password boxes have the PasswordChar property set to "*" so that the password is not displayed in plain text on the form.
The ToolTipText property specifies the text that will appear when the cursor hovers over each TextBox and CommandButton. The text for the second password field is displayed in Figure 25-3.
The command button needs some code to tell it what to do when the button is clicked. This is known as an event procedure, as it is triggered when an event (clicking the button) occurs. No code is attached to anything other than the command button. The code that sits behind the command button looks like this:
Private Sub cmdChangePassword_Click( ) Dim objUser As IADsUser If txtUsername.Text <> "" Then If txtPass1.Text = txtPass2.Text Then Set objUser = GetObject("LDAP://cn=" + txtUsername.Text _ + ",cn=Users,dc=mycorp,dc=com") objUser.SetPassword txtPass1.Text objUser.pwdLastSet = 0 If objUser.IsAccountLocked Then objUser.IsAccountLocked = False objUser.SetInfo 'Reset everything txtUsername.Text = "" txtPass1.Text = "" txtPass2.Text = "" MsgBox "Password changed!" Else MsgBox "Passwords are not the same, please try again!" End If Else MsgBox "A username must be specified!" End If
You can see that we are using the text typed into the TextBox::Text property for each TextBox control as necessary. We don't declare these controls as we do with variables, as the very fact that they're on the form is enough to declare them.
There is a procedure that is attached to the CommandButton called cmdChangePassword, and it is executed when a single-click event occurs on that button. When that button is clicked, we check that the txtUsername field has had a username typed in, and if it has, then we check that the two passwords are the same. If they are, we concatenate the username with the domain string and get a handle to the user object. We then use the IADsUser::SetPassword method with one of the two passwords as the parameter and also set the pwdLastSet property to 0 to indicate that the password is expired. This means the user has to change it when he next logs on. We then unlock the account if it was locked, because otherwise the user will be unable to make use of the new password. We then write out the property cache. You can also see that we are not checking that the password was set properly or later that the other properties were set. It would be simple to put in if desired.
Let's take one more example by extending the previous one to modify a variety of user details. Take a look at Figure 25-4.
Figure 25-4 is another simple user querying and modification tool. This one has a number of different features. To start with, the username is typed into the top TextBox. When the user clicks on the Find User! command button, an ADO search function retrieves the full ADsPath of the user. This ADsPath then is used to bind to the user and to retrieve the full name, the expiry date, the group memberships, the last logon and last logoff times, and whether the account is disabled or locked. The group membership's TextBox automatically displays vertical scrollbars if the results cannot be displayed in the space available.
The administrator then can use the Set Password! button to set the password. This time, no confirmation is requested; the password is just accepted as is. The Change Date! button can set the expiration date. The two account status checkboxes in the bottom right can enable/disable the account or unlock it if it gets locked.
Actually, the unlock checkbox should never give the option to lock an account; instead, it should be grayed out (disabled) by default. Then it can be enabled only when an account is locked. Clicking the checkbox on a locked account would unlock the account and then disable the checkbox again immediately. Obviously this means that a user could never change his mind and relock an account, which is fairly nonsensical, but in that case it can simply be disabled instead.
While the code is not particularly complex, it is quite long, and for that reason, we've made it available for download from the companion O'Reilly web site for this book.