Securing Active Directory Using a Roles-Based Approach

While Windows 2000 and 2003 offered a few new Built-In security groups to use in granting permissions to administrative resources, they both still leave much to be desired when designing security models. With the advent of Sarbanes-Oxley (thanks again Enron), System Administrators are now tasked with not only increasing security, but also auditing changes to the environment. (Note – change management also plays a major role regarding Sarbanes-Oxley; however, that topic is too expansive to be discussed here.)

Let’s take a slight step backwards and define exactly what role-based security is. In a nutshell, role-based security is creating ACE’s (Access Control Entries or users and groups) based upon job function and then using very specific ACL’s(Access Control Lists) to lock them down allowing administrators to have the ability to perform only their job function and nothing more. For example, if we have a junior administrator that performs basic user creation and administration as well as basic workstation management, we probably don’t want this person to have the ability to delete server accounts from AD, or modify Cisco Unity attributes on user accounts, or alter PKI certificate settings in the environment, etc. Using the built-in groups, this is quite cumbersome to do but using a role-based approach, we suddenly discover a new level of granularity that makes such detailed permissions possible.

First Steps

Before we can start creating groups for our different permissions, we must first define our roles. This will more than likely entail some interaction with the management team and HR staff to obtain the company accepted titles and their required job functions. (If you are fortunate enough to have input into this, now is a great time to redefine job responsibilities if they are misaligned to new technologies because they haven’t been updated in a while.)

Once we’ve obtained the roles within the company, we can begin to create the role-based groups in AD. These groups will mimic the roles in title so we know what they represent. For example, if we have a Sr. AD Engineer, AD Engineer II, System Administrator, and Jr. System Administrator we might want to use the following naming schemes for the groups:

* GG_Sr_AD_Eng
* GG_AD_Eng
* GG_SysAdmin
* GG_Jr_SysAdmin

We prepend the “GG” to the name so that we know it’s a Global Group. We will use Global Security Groups to nest within Domain Local Groups explained next. Remember, we can only nest Global Groups with Domain Local Groups, not the other way around. In addition, this allows us the ability to nest the Global Group within Domain Local Groups in other domains if we have a multi-domain forest (must be at least 2000 native mode).

Defining the Access Groups

Now that we have our role-based groups, it is time to create the groups that will allow the actual access to the AD resources. Since domains are simply security boundaries we would create the access groups as Domain Local Groups within each domain in our forest. (Note – we can also apply this same functionality to Forest and Site objects as well. In addition, in Windows2003 we could use Universal Groups as the replication traffic is greatly reduced since only attribute level deltas are replicated instead of the entire object.)

This part is a bit trickier than before as we need to be careful to allow for all necessary permissions for each role to be able to perform their job functions. An easy method to use in visualizing this is a spreadsheet matrix format. Let’s define the job functions of the aforementioned roles as follows (these examples are vastly simplified as there are thousands of possible configurations):

  • Sr_AD_Eng
    • Create, modify, delete users
    • Create, modify, delete workstations and servers
    • Create, modify, delete groups
    • Create, modify, delete OU
    • Create, modify, delete GPO
  • AD_Eng
    • Create, modify, delete users
    • Create, modify, delete workstations and servers
    • Create, modify, delete groups
    • Create, modify OU
    • Create, modify GPO
  • SysAdmin
    • Create, modify users
    • Create, modify workstations and servers
    • Create, modify groups
  • Jr_SysAdmin
    • Modify users
    • Create, modify workstations
    • Modify groups

Remember, each of these are Domain Local Groups that need to be created as listed below.

Our matrix would be as seen in this table.

By using our matrix, we can better visualize who has permissions to do what in the environment. Each cell in the leftmost column represents a Domain Local Group that will be created and the cells in the top row represent the Global Groups we used to define roles. This can be easily be tailored to meet any needs for any AD environment and depending upon the security definition requirements can grow to be quite large depending on how granularly the permissions in the left column are defined.

Implementing the Security Model

The next step is to actually modify the ACL’s on AD resources to meet our security requirements defined in our matrix. To do this we initially use the Delegation Wizard to create the security framework and then the ACL Editor to “tweak” the settings. We will use the DLG_Create_Modify_Delete_Users Domain Local Group in our example.

1. First, open up ADUC and navigate to the Users OU. (It’s always a good idea to move users out of the default Users container into a separate custom OU so that more granular GPO’s can be applied.) Right click on the OU and select Delegate Control. Next, click the Add button and select the DLG_Create_Modify_Delete_Users Domain Local Group created earlier then click Next to get to the Tasks to Delegate window.

2. We will want to select Create a custom task to delegate as the standard common tasks are too broad and will grant more permissions than we want which will require us to go back and make more edits in the ACL Editor later. Click Next.

3. As we are only interested in modifying the ACL’s for User objects in this OU, select the Only the following objects in this folder button. In the object window, select the User objects checkbox. Next, select the Create and Delete checkboxes underneath as we want the Domain Local Group to be able to both create and delete user objects in the Users OU. Click Next.

4. In the Permissions window, select the Property-Specific checkbox in addition to the already checked General checkbox. This brings up many more attribute options for us to work with.

Note – The list of attributes in the Permissions window is NOT a complete list of all the attributes for an object. This list is controlled by the dssec.dat file located in the %SYSTEMROOT%\System32 directory on the Domain Controllers. This file can be edited using Notepad and defines what appears in this list. For example, the City attribute does not appear by default. To modify the file to enable this attribute to appear, open the dssec.dat file in Notepad and navigate towards the bottom to the [user] section. Look for the “l” (lowercase L) attribute and change the number from a 7 to a 0 (zero) and save the file. This setting will make the City attribute appear in the list and be Read/Write enabled. You must close and open the ADUC for the changes to take effect.

5. Select the following settings in the Permissions window to grant the DLG_Create_Modify_Delete_Users Domain Local Group the ability to create, modify, and delete user accounts:

  • Read
  • Write
  • Read all Properties (enables the Read flag on all other attributes)
  • Write all Properties (enables the Write flag on all other attributes)
  • Change Password
  • Reset Password

Review the rest of the permissions and then click Next and then Finish to apply the changes.

6. Follow steps one through four for the other Domain Local Groups. Modify the selections in step five to reflect the correct settings in the matrix we used earlier for each access role.

Review and Edit Permissions using the ACL Editor

1. Right click on the User OU again and select Properties. Next, select the Security tab and then the Advanced button to bring up the Advanced Security Settings window.

2. Highlight the entry for the DLG_Create_Modify_Delete_Users Domain Local Group that has Special underneath the Permissions column and select Edit. In the editor we can view the currently selected Permissions as well as make modifications to the settings.

3. Modify the existing ACL permissions that were created by the Delegation Wizard as needed.

Configure Auditing

In order to track changes made to an object in AD, we must enable Auditing on that object and then specify the ACE (a user or group object) that will be audited as well as what parameters we are going to audit.

1. From the Advanced Security Settings window select the Auditing tab.

2. Select the Add button and add the DLG_Create_Modify_Delete_Users Domain Local Group. Click OK.

3. This brings up the Auditing Entry window in which we will define the level of auditing we want to perform for the DLG_Create_Modify_Delete_Users Domain Local Group on User objects in the Users OU. In the following example, we have selected to audit whenever a member of this group deletes a user object from this OU.

4. Repeat these steps for each additional Domain Local Group on each object that needs to be audited.

Final Steps

To bring it all together we will now nest the Global Groups into the appropriate Domain Local Groups. Using the Jr. SysAdmin role as an example we would place the GG_Jr_SysAdmin Global Group into the DLG_Modify_Users, DLG_Create_Modify_Computers, and DLG_Modify_Groups Domain Local Groups.

Documenting Domain Controller Information

Documentation is definitely one of the more tedious tasks of system administration. Documents relating to domain controller information used to be rather static; however, with the advent of new functionality in Windows 2003 such as the ability to rename domain controllers and domains themselves, even these documents have become a dynamically changing part of document maintenance.

To save time and eliminate possible errors related to documenting this information, I wrote a VBScript that will query domain controllers throughout the forest and enumerate name, IP address, GC functionality, OS version, and SP version. This information is formatted into an Excel spreadsheet and automatically places information related to different domains in the forest into a different worksheet within the workbook. The script queries the forest using RootDSE and dynamically enumerates forest and domain information without changing static variables in the script. In a nutshell; you don’t have to continuously change the code to work within different forests.

Obviously, Excel needs to be installed on the workstation where the script is run and you will need administrative privileges on the DC’s to enumerate all the information as WMI will use the credentials of the currently logged in user. The output file is placed at the root of C:\.

To run the script you can either double click on the script or run it from the command line using cscript. Click here to obtain this script in TXT format (be sure to save it with a VBS extension to use it).

Windows Password Recovery and Reset Tool

It’s your first day on the job and you’re rearing to go. The previous administrator left two weeks ago so the servers have been running on their own with no administrative maintenance. Microsoft decides that today is also the day they are going to release a number of critical update patches to the Windows Server platform. You head into the server room ready to update the servers but realize that you don’t know the administrative password to log on to the machines. To make matters even more interesting, it appears that no one else in the office does either and the previous admin didn’t document them. Thankfully, you are a dedicated reader of the articles on the 2000 Trainers site and have a solution.

Note – The following utility is not supported by Microsoft and does pose the remote possibility of permanently damaging the registry. Use at your own risk and please read all the online material before attempting. In addition, while this utility can be used maliciously, it is meant to be a “save the day” tip for administrators. Please use it responsibly.

The “Offline NT Password and Registry Editor” is located at http://home.eunet.no/~pnordahl/ntpasswd/ and can be used to reset the local administrator password on Windows platforms from Windows 3.51 to Windows 2003. The first thing you want to do is download either the floppy image or the ISO image for a CD-ROM depending on your preference. If you download the floppy image, be sure to grab the SCSI drivers if your boot partition is located on SCSI drives. For this high level walkthrough I used the floppy image.

Once you’ve unzipped the binaries, put a floppy in the drive and run the install.bat file. It will create the floppy image using the included rewrite utility. Place the floppy in the server and restart the server. After the linux kernel loads you will see the following screen:

In our example, we only have a single partition to select so we will choose device number one. The next prompt will be for the location of the registry. Just accept the default and press Enter. Since we want to reset the local administrator password, select option one at the next prompt.

At the next prompt, select option one again as we are editing user data and passwords. Notice how the local administrator account appears as an editable account at the next screen. Select the appropriate option for the administrator.

At the next screen we can change the password to whatever we want or use the asterisk wildcard to blank out the current password. Save your changes and write it back to the registry. Eject the floppy, restart the machine and log on as the administrator using the password you selected when modifying the account.

Extending the Capabilities of Active Directory Users and Computers Using VBScript

Have you ever wished that you could add functionality to the graphical tools for managing user or computer objects, but unfortunately didn’t know C++ to write the code? Even if you did had the necessary skills, you would be lucky if the Schema Admins would let you extend the schema in to utilize the new classes. The good news is that there is another way. You guessed it – VBScript to the rescue!

A while back, I needed a way to allow junior administrators the ability to modify a user attribute called employeeID. The only way they knew how to modify objects was via the Active Directory Users and Computers snap-in, so I did a little research and came across a method that I modified to enhance the tool’s functionality. Essentially, this solution gets Active Directory Users and Computers (ADUC) to call a script that allows these administrators to view or modify this hidden attribute. All these users ultimately have to do is right-click on a user object, select the Employee-ID shortcut, and then set or change its value in the pop-up dialog box that appears.

Creating the Script

The first thing to do is to write a script that handles the necessary functionality as follows:

  1. On Error Resume Next
2. Dim objemployeeID
3. Dim objUser
4. Dim objTemp
5. Set objemployeeID = Wscript.Arguments
6. Set objUser = GetObject(objemployeeID(0))
7. objTemp = InputBox("Current Employee-ID: " & objUser.employeeID & VbCrLf _

& vbCRLF & "If you would like enter a new number or modify the existing number, enter the new number in the textbox below")

8. If objTemp <> "" then objUser.Put "employeeID",objTemp
9. objUser.SetInfo
10. If Err.Number = "-2147024891" Then
11. MsgBox "You current account does not have permission" & VbCrLf _
12. & "to modify the Employee ID attribute. Please" & VbCrLf _
13. & "log on with an account with appropriate permissions.", 16, "Permission Denied"
14. End If
15. Set objUser = Nothing
16. Set objemployeeID = Nothing
17. Set objTemp = Nothing
18. WScript.Quit

(The lines are wrapped so copy and paste the script into your favorite editor for actual viewing)

Let’s briefly break the script down to review functionality:

Lines 1-4: Disable error control and declare variable names.

Line 5: Set the objemployeeID variable name to the value of the Arguments method of the WScriptobject. This variable will be used to store the information from the AD object when the menu item is selected from ADUC (covered later in the article).

Line 6: Bind the objUser variable name to the user object instantiated in Line 5.

Line 7: Use an InputBox constant to hold input and assign it to the objTemp variable name.

Line 8: Check to see if input exists for objTemp and assign that value to the employeeID attribute of the user object if the value is not NULL.

Line 9: Commit the changes to the user object.

Lines 10-14: Error handling in case the admin does not have permissions to modify the employeeID attribute of the user object.

Lines 15-18: Script cleanup and closure.

Next we need save the script (we’ll call it employeeID.vbs) and place it in a location that will be accessible by anyone who launches it when the ADUC menu item is selected. The NETLOGON share is a good location for this since everyone has read access to this share to run logon scripts. You can also create your own share on another server and ACL it accordingly to hold the script should you prefer.

Modifying the Active Directory Users and Computers Shortcut Menu

Now that we have the script created, we need a way to call it from the ADUC GUI. To do this we will need to edit the Properties of the user-Display object. The following steps will facilitate this functionality:

  1. Open ADSI Edit (located in the Support Tools folder of the Windows Server 2003 CD)
  2. Expand the CN=Configuration node and navigate to CN=DisplaySpecifiers, CN=409. Select the 409 node in the left hand pane.
  3. In the right-hand pane, select the CN=user-Display object. Right click and select Properties.
  4. Select the adminContextMenu attribute and click Edit.
  5. We now need to add the value that will be used to create the additional menu item and direct it to the employeeID.vbs script. The syntax is very important. Be sure to include the comma at the beginning and after the menu name (Employee-ID). Add the following syntax to the Value to Add: line:
    ,&Employee-ID,\\servername\sharename\employeeID.vbs
  6. Change the servername and sharename items to reflect your current environment and then click Add.
  7. Click OK to accept the changes and close ADSI Edit.
  8. Allow some time for replication to populate the changes throughout the directory.
  9. Open ADUC and select a user. Right click on the user and notice the new menu item now available.
  10. Select Employee-ID to launch the script from within the ADUC
  11. From here we can either enter a new value for the employeeID attribute for the user or hit Cancel to leave the current value intact. (Note: If no value is present in the field, then the attribute value is empty for that user.)

That’s it; we have extended the schema functionality to expose a hidden attribute for editing via the ADUC interface using VBScript. If the popup is not visible, be sure replication has occurred and double check that the path given in ADSI Edit is valid.

This opens up almost endless possibilities for modifications without knowledge of C++ or advanced coding languages that will enhance functionality of the ADUC snap-in for your administrators. The script code:


On Error Resume Next
Dim objemployeeID
Dim objUser
Dim objTemp
Set objemployeeID = Wscript.Arguments
Set objUser = GetObject(objemployeeID(0))
objTemp = InputBox("Current Employee-ID: " & objUser.employeeID & VbCrLf _
& vbCRLF & "If you would like enter a new number or modify the existing number, enter the new number in the textbox below")
if objTemp <> "" then objUser.Put "employeeID",objTemp
objUser.SetInfo
If Err.Number = "-2147024891" Then
MsgBox "You current account does not have permission" & VbCrLf _
& "to modify the Employee ID attribute. Please" & VbCrLf _
& "log on with an account with appropriate permissions.", 16, "Permission Denied"
End If
Set objUser = Nothing
Set objemployeeID = Nothing
Set objTemp = Nothing
WScript.Quit

Querying Active Directory for Computer Operating System, Service Pack, and Hotfix Details

One of the most painful tasks for administrators is keeping track of the service pack level of their servers and workstations. Thankfully, WMI affords a simple and automated method to retrieve this information from computers. Using ADSI to enumerate all machines within a domain, it is extremely simple to automate the WMI query to obtain this information as the following script demonstrates. Comments have been added for description of each process:

On Error Resume Next
'Instantiate objects
Set objFSO = CreateObject("Scripting.FileSystemObject")
Set outfile = objFSO.OpenTextFile("C:\SP_Output.txt", 2, True)

'Obtain current domain name via RootDSE
Set objRootDSE = GetObject("LDAP://rootDSE")
strADsPath = "'"&"LDAP://" & objRootDSE.Get("defaultNamingContext")&"'"

'Connect to the current domain partition in AD and enumerate all computer
'objects via the filter to enumerate computer objects.
Set objConnection = CreateObject("ADODB.Connection")
objConnection.Open "Provider=ADsDSOObject;"
Set objCommand = CreateObject("ADODB.Command")
objCommand.ActiveConnection = objConnection
objCommand.CommandText = "SELECT Name FROM " & strADsPath & _
" WHERE objectClass='computer'"
objCommand.Properties("Page Size")= 1000
Set objRecordSet = objCommand.Execute
objRecordSet.MoveFirst

'Control Loop to cycle through enumeration results from ADO query.
Do Until objRecordSet.EOF
strCompName = objRecordSet.Fields("Name").Value
outfile.writeline UCase(strCompName)
outfile.writeline "-"
Set objWMIService = GetObject("winmgmts:" _
& "{impersonationLevel=impersonate}!\\" & strCompName & "\root\cimv2")

'Error handling in case the script is unable to connect to the computer.
If Err.Number <> 0 Then
On Error GoTo 0
outfile.writeline Ucase(strCompName ) & " is not responding to the query."
outfile.writeline ""
outfile.writeline "==================="
outfile.writeline ""
On Error Resume Next
Else
Set colOperatingSystems = objWMIService.ExecQuery _
("Select * from Win32_OperatingSystem")
For Each objOperatingSystem in colOperatingSystems
outfile.writeline "Operating System: " &objOperatingSystem.Caption _
& " " & objOperatingSystem.Version
outfile.writeline "Service Pack: " & objOperatingSystem.ServicePackMajorVersion _
& "." & objOperatingSystem.ServicePackMinorVersion
Next
outfile.writeline ""
outfile.writeline "==================="
outfile.writeline ""
End If
objRecordSet.MoveNext
Loop
MsgBox “Script Completed.” & VbCrLf _
& “Results located at c:\SP_Output.txt”
WScript.Quit
'Clean up
Set objFSO = Nothing
Set objConnection = Nothing
Set objCommand = Nothing

In addition to service packs, another enjoyable task is staying current with the deluge of hotfixes as they are released. Thankfully, WMI also offers a class to enumerate installed hotfixes. The above script can be modified to include hotfixes by adding a query to the appropriate class, Win32_QuickFixEngineering. For example:

Set colHotFixes = objWMIService.ExecQuery _
("Select * from Win32_QuickFixEngineering")

After adding this query to the script, add the properties to the script to output the results to the text file. Something similar to this example would work:

For Each objHotFix in colHotFixes
outfile.writeline “Description: “ & objHotFix.Description
outfile.writeline “HotFix ID : “ & objHotFix.HotFixID
outfile.writeline “Install Date: “ & objHotFix.Install.Date
Next

Don’t bother cutting and pasting the text from this file. Instead, get a copy of the completed script by accessing the text version found here. Don’t forget to save it with a VBS extension!

Then, when you’re ready to run the script, just use the cscript scriptname.vbs command.

There are other properties available for this class; however these examples are a good start. The additional properties can be found at http://msdn.microsoft.com/library/default.asp?url=/library/en-us/wmisdk/wmi/win32_quickfixengineering.asp.

When it comes to automation, WMI is an extremely powerful tool that can save literally hours of administrative work as is easily demonstrated in these examples.

VBScript Code Snippets

To follow up after the last few articles on VBScript fundamentals, this article contains several different code snippets that can be used with very little modification in any Windows Active Directory environment. This article will focus on scripts and can be used to administer users and groups with ADSI. The final example is a list of several different code snippets and a logon script written using VBScript instead of batch files.

The scripts will vary a little in format which is intended to demonstrate the different methods possible in writing scripts. Some scripts declare variables while some don’t, some are broken into sections to demonstrate a template style of scripting while some are not, etc. Obviously, when taking pieces of these snippets and incorporating them into a larger script, it is best to declare variables at the beginning, group subroutines and functions together, group the main body of the script together, add comments, and use a template style of scripting that is easy to read by someone other than the script author.

Previous articles that can be used for reference with the code contained herein are:

Windows Scripting – VBScript Fundamentals and

Windows Scripting – VBScript Fundamentals (ADSI)

The following will give high-level explanations of the different examples in addition to the commented details contained within the scripts themselves:

Enable a disabled user account

  1. Assign the value of disabled to the userAccountControl attribute flag. (this value will be reversed using a bitwise operator later in the script).
  2. Bind to the user object with the GetObject method. This script demonstrates two different syntax types for this initial binding; use either LDAP or WinNT, but not both.
  3. Initialize the user object with the userAccountConrol attribute and the associated flag.
  4. Use the AND operator to determine if the flag is set to enabled (meaning the account is enabled)
  5. Use the XOR bitwise operator to reverse the flag on the userAccountControl attribute. Since it was set as a value of 2 (disabled) initially, this changes the flag to enable the account.


'-- LDAP path syntax
'===================
Set objOU = GetObject("LDAP://OU=MIS,dc=mydomain,dc=com")
Set objUser = objOU.Create("User", "cn=jsmith")
objUser.Put "sAMAccountName", "jsmith"
objUser.SetInfo

‘– Domain path syntax
‘=====================
Set objDomain = GetObject(“WinNT://Mydomain”)
Set objUser = objDomain.Create(“user”, “jsmith”)
objUser.SetInfo

‘– Enabling the user account after it has been
‘– created using the flag in the userAccountControl
‘– attribute and the XOR bit.
‘===================================================
Const ADS_UF_ACCOUNTDISABLE = 2
Set objUserACL = GetObject _
(“LDAP://cn=” & objUser & “,ou=MIS,dc=mydomain,dc=com”)
intUAC = objUserACL.Get(“userAccountControl”)
If intUAC And ADS_UF_ACCOUNTDISABLE Then
objUserACL.Put _
& “userAccountControl”, intUAC Xor ADS_UF_ACCOUNTDISABLE
objUserACL.SetInfo
End If
Next

‘– A simpler way to enable a user account
‘=========================================
Set objUser = GetObject _
(“LDAP://cn=jsmith,ou=MIS,dc=mydomain,dc=com”)
objUser.AccountDisabled = FALSE
objUser.SetInfo

Deletes a user account from Active Directory

  1. Bind to the Active Directory container that contains the user object to be deleted.
  2. Use the delete method to remove the account.
  3. The additional example uses an input box for user input when the script is run.


'-- Delete a user object from Active Directory
'=============================================

Set objOU = GetObject(“LDAP://ou=MIS,dc=mycompanydc=com”)
objOU.Delete “User”, “cn=Joe Smith”

‘– Script could also be changed to request input
‘– via a pop-up screen as follows:
‘================================================

Set objOU = GetObject(“LDAP://ou=MIS,dc=mycompanydc=com”)
Set oName = InputBox(“Enter the name of the user to be deleted”)
objOU.Delete “User”, “cn=” & oName

Add a user to a local group

  1. Bind to the local Administrators group.
  2. Bind to the user object to be added to the local group.
  3. Add the user to the local group with the Add method and the IADs path.


'Adds user object to local group by using IADs
'Properties to return AdsPath.
'=============================================
strComputer = "computer_name"
Set objGroup = GetObject _
("WinNT://" & strComputer & "/Administrators,group")
Set objUser = GetObject _
("WinNT://" & strComputer & "/jsmith,user")
objGroup.Add(objUser.ADsPath)

‘Adds user object to local group by specifying
‘object path within domain context.
‘=============================================
DomainString = “mydomain”
UserString = “jsmith”
GroupString = “Administrators”
Set objGroup = GetObject _
(“WinNT://” & DomainString & “/” & GroupString)
objGroup.Add (“WinNT://” & DomainString & “/” & UserString)

Set a user password and force user to change password at next logon.

Set a password
1. Bind to user the object
2. Set the password as specified in script

Change password at next logon
1. Bind to the user object
2. Enable the pwdLastSet attribute on the user account
3. Set the information within AD for the user account.


'-- Set password for a user object
'=================================
Set objUser = GetObject _
("LDAP://cn=jsmith,ou=MIS,dc=mydomain,dc=com")
objUser.SetPassword "P@$$w0rD"

‘– Force user to change password at next logon
‘==============================================
Set objUser = GetObject _
(“LDAP://cn=jsmith,ou=MIS,dc=mydomain,dc=com”)
objUser.Put “pwdLastSet”, 0 ‘change to 1 to disable
objUser.SetInfo

Modify user profile and home directory properties

  1. Bind to the user object
  2. Set the properties for the attributes
  3. Set the information within AD for the user object.


'-- Modify user profile and home directory
'-- properties.
'=========================================
Set objUser = GetObject _
("LDAP://cn=jsmith,ou=MIS,dc=mydomain,dc=com")
objUser.Put "profilePath", "\\ServerA\Profiles\jsmith"
objUser.Put "scriptPath", "logonscript.vbs"
objUser.Put "homeDirectory", "\\ServerA\HomeDirs\jsmith"
objUser.Put "homeDrive", "H:"
objUser.SetInfo

Create 1,000 user accounts for testing purposes

  1. Bind to the RootDSE of the directory (binds to a DC without specifying the LDAP path)
  2. Build the ADsPath to the current domain with defaultNamingContext.
  3. Using a For…Next control loop, a counter is used and a each time a user is created, the counter is incremented by one until it reaches the limit of 1000.


'-- Create 1000 User accounts for testing
'========================================
Set objRootDSE = GetObject("LDAP://rootDSE")
Set objContainer = GetObject("LDAP://cn=Users," & _
objRootDSE.Get("defaultNamingContext"))
For i = 1 To 1000
Set objLeaf = objContainer.Create("User", "cn=User" & i)
objLeaf.Put "sAMAccountName", "User" & i
objLeaf.SetInfo
Next
WScript.Echo "1000 Users created."

Adding users and global groups to a local group using a text file list as input into an array and calling a command line from within the script.

  1. Declare the variables used in the script
  2. Bind to the Scripting.FileSystemObject and use the TextFile method to create a text file for output as well as specify the text file to be used as input.
  3. Bind to the WScript.Shell object to be used in calling the command line executables.
  4. Initialize the array by using the ReadAll method on the input text file.
  5. Using a control loop…
  1. Assign the path to the executable to be called from the command line with the appropriate switches to the commandline variable.
  2. Use the Run method of the WScript.Shell object to run the command from within the script.
  3. Bind to the machine accounts in the array to determine local group memberships within the specified group and print the output in the text file.

'-- Examples of adding groups and users to
'-- the local Administrators group.
'=========================================

'-- Declare variables
'====================
Dim fso
Dim outfile
Dim infile
Dim Shell
Dim str
Dim SvrArray
Dim Group
Dim Domain
Dim oDomain

On Error Resume Next

'-- Assign values to variables
'=============================
Set fso = CreateObject("Scripting.FileSystemObject")
Set outfile = fso.OpenTextFile _
("C:\Scripting\AddDomainUsers.txt", 2, True)
Set infile = fso.OpenTextFile _
("C:\Scripting\ComputerNames.txt")
Set Shell = CreateObject("Wscript.Shell")

outfile.writeline " "
outfile.writeline "Addition of MYCOMPANY accounts" _
& "account to Local Administrators "
outfile.writeline "group started at " & Now()
outfile.writeline " "

'-- Initialize and populate array
'================================
str = infile.ReadAll()
SvrArray = Split(str, vbCrLf)

oDomain = "MY_DOMAIN"
Set Domain = GetObject("WinNT://" & oDomain)

'-- Control Loop
'===============
For Each Server In SvrArray
commandline = "C:\WINNT\System32\NET LOCALGROUP " _
& "Administrators MY_DOMAIN\Domain Users /delete"
Shell.Run commandline, 1, True
commandline = "C:\WINNT\System32\NET LOCALGROUP " _
& "Administrators MY_DOMAIN\Desktop Admins /add"
Shell.Run commandline, 1, True
commandline = "C:\WINNT\System32\NET LOCALGROUP " _
& "Administrators MY_DOMAIN\Domain Admins /add"
Shell.Run commandline, 1, True

Set User = GetObject _
("WinNT://" & Server.Name & "/Administrators")
If User.IsMember("WinNT://MY_DOMAIN/Domain Admins") Then
outfile.writeline " MY_DOMAIN\Domain Admins already exists on " _
& Server & " or computer is not reachable."
ElseIf
User.IsMember("WinNT://MY_DOMAIN/Desktop Admins") Then
outfile.writeline " MY_DOMAIN\Desktop Admins already exists on " _
& Server & " or computer is not reachable."
Else
outfile.writeline " Adding accounts to Local Adminstrators on " & Server
User.Add("WinNT://MY_DOMAIN/username")
End If
outfile.writeline " "
Next

outfile.writeline "Script completed at " & Now()
MsgBox "Add_DomainUsers script has completed."

Lists the group memberships of all users within a specified container

This script is best run using cscript your_script.vbs from the command line as it will generate large amounts of data. Another option is to incorporate the TextFile method used in the last example to re-direct the output to a text file.

  1. Set the value of the constant to the number of the error that will be returned by ADSI if the user is not a member of any groups.
  2. Bind to the users container in Active Directory.
  3. Initialize the dynamic array to contain the users within the specified container.
  4. Using a control loop…
    1. Obtain the primary group ID
    2. Initialize another array to contain the group memberships pertaining to current user in the first array. This array uses the GetEX method as users can be members of more than one group making the memberOf attribute of the user a multivalued attribute. (Basically a user can belong to more than one group and since memberOf is the attribute specifying group membership, it may have more than one value requiring the use of GetEX instead of simply Get which is used for single values.
    3. Check to see if ADSI raised the error mentioned earlier specifying no group membership. If no error is raised, display the memberships of the user.


'-- Lists group membership of all users within the
'-- specified container. Note - This script will generate
'-- A LOT of data and is best run using cscript.
'=========================================================
'=========================================================

'On Error Resume Next
'-- Uncomment the previous line to suppress ADSI run-time
'-- errors as they occur.

'-- Number value of the error return by ADSI if the
'-- memberOf attribute cannot be found.
'==================================================
Const E_ADS_PROPERTY_NOT_FOUND = &h8000500D

'-- Bind to the Users container
'==============================
Set objOU = GetObject _
("LDAP://cn=Users,dc=mydomain,dc=com")

'-- Initialize the array for user accounts.
'==========================================
ObjOU.Filter= Array("user")

'-- Control Loop
'===============
For Each objUser in objOU
Wscript.Echo objUser.cn & " is a member of: "
Wscript.Echo vbTab & "Primary Group ID: " & _
objUser.Get("primaryGroupID")

'-- Use the GetEX method to intialize the array for group
'-- membership. Get method cannot be used as it does not
'-- multivalued attributes (user can be member of many groups.)
'==============================================================
arrMemberOf = objUser.GetEx("memberOf")

'-- If the error is not raised from ADSI, then list the
'-- groups that are entries within the arrMemberOf array.
'-- If error is raised, display notification on screen.
'========================================================
If Err.Number <> E_ADS_PROPERTY_NOT_FOUND Then
For Each Group in arrMemberOf
Wscript.Echo vbTab & Group
Next
Else
Wscript.Echo vbTab & objUser.cn & " is not a member of any groups."
Err.Clear
End If
Next

Determine when a user password was last set.

  1. Bind to the user object in Active Directory
  2. Read the passwordLastChanged attribute and display results.


'-- Determine when a user password was last set
'==============================================

oUser = "John Smith"
Set objUser = GetObject _
("LDAP://cn=" & oUser & ",cn=Users,dc=mycompany,dc=com")
oPassVal = objUser.PasswordLastChanged
Wscript.Echo "Password was last set: " & oPassVal

Setting multiple values for multiple user accounts within a single container.

This example might be used if a company was acquired requiring a company name change and email address change.
  1. Bind to the container containing the user accounts to be modified.
  2. Initialize the dynamic array.
  3. Using a control loop…
    1. Change the company attribute of each user in the array to the name of the new company; in this example… NewCompany.
    2. Change the email address of each user in the array by concatenating (bringing together) the mailNickName attribute of the user and appending @NewCompany.com to the end.
    3. Set the information in Active Directory.


'-- Set multiple values for multiple accounts within
'-- a common Active Directory container.
'===================================================

'-- Change this path to reflect appropriate information.
'=======================================================
Set objOU = GetObject _
("LDAP://cn=Users,dc=mycompany,dc=com")

objOU.Filter = Array("user")

'-- Modify NewCompany and @NewCompany.com to reflect
'-- the appropriate information.
'===================================================
For Each objUser In objOU
WScript.Echo "Modified " & objUser.Name
objUser.Put "company", "NewCompany"
objUser.Put "mail", mailNickname & "@NewCompany.com"
objUser.SetInfo
WScript.Echo "The new company name is: " & _
objUser.Get("company")
WScript.Echo "The new email address is: " & _
objUser.Get("mail")
Next

Additional Code Snippets

This page is simply several different snippets of code that can be used in administering users and groups as well as an example of how VBScript can be used in logon scripting in place of batch files. The code snippets are small, easy to understand, and can be combined to create a larger script performing multiple administrative functions.


'-- Set new value for Maximum Bad Passwords Allowed
'==================================================
DomainName = "Your_Domain"
Set Domain = GetObject("WinNT://" & DomainName)
NewValue = 3 'Change this
Domain.MaxBadPasswordsAllowed = NEW_VALUE
Domain.SetInfo

'-- Set a new value for Maximum Password Age
'===========================================
DomainName = "Your_Domain"
Set Domain = GetObject("WinNT://" & DomainName)
NewValue = 2592000 'Change this
Domain.MaxPasswordAge = NewValue
Domain.SetInfo

'-- Set new value for Minimum Password Age
'=========================================
DomainName = "Your_Domain"
Set Domain = GetObject("WinNT://" & DomainName)
NewValue = 3 'Change this
Domain.MinPasswordAge = NewValue
Domain.SetInfo

'-- Set new value for Minimum Password Length
'============================================
DomainName = "Your_Domain"
Set Domain = GetObject("WinNT://" & DomainName)
NewValue = 6 'Change this
Domain.MinPasswordLength = NewValue
Domain.SetInfo

'-- Set new value for Password History
'=====================================
DomainName = "Your_Domain"
Set Domain = GetObject("WinNT://" & DomainName)
NewValue = 5 'Change this
Domain.PasswordHistoryLength = NewValue
Domain.SetInfo

'-- Enumerate computer accounts within a container
'=================================================
ContainerName = "LDAP_path_to_container"
Set Container = GetObject("LDAP://" & ContainerName)
Container.Filter = Array("Computer")
For Each Computer in Container
WScript.Echo Computer.Name
Next

'-- Enumerate user accounts within a container
'=============================================
ContainerName = "LDAP_path_to_container"
Set Container = GetObject("LDAP://" & ContainerName)
Container.Filter = Array("User")
For Each User in Container
WScript.Echo User.Name
Next

'-- Enumerate groups within a container
'======================================
ContainerName = "LDAP_path_to_container"
Set Container = GetObject("LDAP://" & ContainerName)
Container.Filter = Array("Group")
For Each Group in Container
WScript.Echo Group.Name
Next

'-- Rename a computer account
'============================
oldPC = "Current_name_of_PC"
newPC = "New_name_of_PC"
PCPath = "LDAP_path_container_PC_resides_within"
Set objNewOU = GetObject("LDAP://" & PCPath)
Set objMoveComputer = objNewOU.MoveHere _
("LDAP://CN=" & oldPC & ", OU=MIS,DC=mycompany,DC=com", _
& newPC)

'-- Create a new group
'=====================
NewGroup = "Enter_new_group_name"
Set objOU = GetObject("LDAP://ou=MIS,dc=mycompany,dc=com")
Set objGroup = objOU.Create("group", "cn=" & NewGroup)
objGroup.Put "sAMAccountName", NewGroup
objGroup.SetInfo

'-- Enumerating all disabled user accounts in a container
'-- and list results in a text file.
'========================================================
Const ADS_UF_ACCOUNTDISABLE = 2
Set FSO = CreateObject("Scripting.FileSystemObject")
Set outfile = FSO.OpenTextFile("C:\Scripts\AcctDisabled.txt", 2, True)
ContainerName = "LDAP_path_to_container"
Set Container = GetObject("LDAP://" & ContainerName)
Container.Filter = Array("User")
For Each User in Container
intUAC = User.Get("userAccountControl")
If intUAC AND ADS_UF_ACCOUNTDISABLE Then
outfile.writeline User.Name & " is disabled"
End If
Next

'-- Unlocking a user account
'===========================
UserPath = "LDAP_path_to_container"
UserName = "User_CN_name" 'i.e. John Smith
Set User = GetObject("LDAP://"& UserName & UserPath ,user")
If User.IsAccountLocked = True Then
User.IsAccountLocked = False
User.SetInfo
End If

'-- Enumerate user account expiration information
'================================================
On Error Resume Next
UserPath = "LDAP_path_to_container"
UserName = "User_CN_name" 'i.e. John Smith
Set objUser = GetObject("LDAP://" & UserName & UserPath)
dtmAccountExpiration = objUser.AccountExpirationDate

'-- Number and date is recognized standard for expired accounts.
If err.number = -2147467259 Or dtmAccountExpiration = "1/1/1970" Then
WScript.echo "Account is set to never expire."
Else
WScript.echo "Account expiration: " & objUser.AccountExpirationDate
End If

'-- Forcing Logoff and Computer Shutdown
'=======================================
'0 - Logoff
'1 - Shutdown
'2 - Reboot
'4 - Forced Logoff
'5 - Forced Shutdown
'6 - Forced Reboot
'8 - Power Off
'12 - Forced Power Off

Const SHUTDOWN = 1 'Change to reflect method
strComputer = "." 'Change to relect computer
Set objWMIService = GetObject("winmgmts: {(Shutdown)}" _
& "{impersonationLevel=impersonate}!\\" & strComputer & "\root\cimv2")
Set colOperatingSystems = objWMIService.ExecQuery _
("SELECT * FROM Win32_OperatingSystem")
For Each objOperatingSystem in colOperatingSystems
ObjOperatingSystem.Win32Shutdown(SHUTDOWN)
Next

'-- Example Logon Script
'===============================================
'-- This script maps drives, checks for the existence of SMS and
'-- launches the SMS client installation if it does not exist as
'-- well as logging the computer name to a network text file, automates
'-- the installation of anti-virus via network installation (in this
'-- example, Trend Micro), checks for a RAS connection and bypasses
'-- A/V installation if present, and checks certain computer names
'-- to bypass installations on servers that should be skipped (i.e.
'-- Terminal Servers).
'
'-- Obviously, this would require a bit of modification to tailor to
'-- different environments, but it does illustrate how VBScript can
'-- be used in Logon Scripting instead of batch files.
'===============================================
On Error Resume Next

'-- Set variables --
Set objFSO = CreateObject("Scripting.FileSystemObject")
Set objShell = CreateObject("WScript.Shell")
Set objNetwork = Wscript.CreateObject("WScript.Network")
Set colWinDir = objShell.Environment("System")
Set outfile = objFSO.OpenTextFile("\\ServerA\public\sms\" _
& objNetwork.ComputerName & ".txt", 8, True)

' === START SUBROUTINES ===

' -- SMSSub SubRoutine --
Sub SMSSub
objNetwork.MapNetworkDrive "X:", "\\ServerA\public"
WinTemp = colWinDir("WINDIR")
objBin = WinTemp & "\ms\sms\core\bin\"
objBinA = WinTemp & "\ms\sms\clicomp\apa\bin\"
objBinB = WinTemp & "\ms\sms\clicomp\hinv\"
objBinC = WinTemp & "\ms\sms\clicomp\sinv\"
objBinD = WinTemp & "\ms\sms\clicomp\swdist32\bin\"

If objFSo.FileExists(objBin & "clicore.exe") Then
WScript.Sleep 100
Else SMSCompLog
End If

If objFSo.FileExists(objBinA & "smsapm32.exe") Then
WScript.Sleep 100
Else SMSCompLog
End If

If objFSo.FileExists(objBinB & "hinv32.exe") Then
WScript.Sleep 100
Else SMSCompLog
End If

If objFSo.FileExists(objBinC & "sinv32.exe") Then
WScript.Sleep 100
Else SMSCompLog
End If

If objFSo.FileExists(objBinD & "smsmon32.exe.exe") Then
WScript.Sleep 100
Else SMSCompLog
End If

WScript.Quit
End Sub

' -- SMSCompLog SubRoutine --
Sub SMSCompLog
outfile.writeline "SMS components not installed properly on " _
& objNetwork.ComputerName & vbCrLf
objShell.Run "\\DC-01\netlogon\smsls.bat",,True
End Sub

' === END SUBROUTINES ===

' === BEGIN MAIN SCRIPT ===

'-- Remove Current mapped drives --
objNetwork.RemoveNetworkDrive "F:"
objNetwork.RemoveNetworkDrive "G:"
objNetwork.RemoveNetworkDrive "P:"
objNetwork.RemoveNetworkDrive "V:"

'-- Map Network Drives --
objNetwork.MapNetworkDrive "F:", "\\FS-01\public"
objNetwork.MapNetworkDrive "G:", "\\FS-01\group"
objNetwork.MapNetworkDrive "P:", "\\FS-01\apps"
objNetwork.MapNetworkDrive "V:", "\\FS-01\data"

'-- Check for Terminal Servers and call Subroutine --
If objNetwork.ComputerName = "TERM-SVR-01" Then
WScript.Quit
ElseIf objNetwork.ComputerName = "TERM-SVR-02" Then
WScript.Quit
ElseIf objNetwork.ComputerName = "TERM-SVR-03" Then
WScript.Quit
Else WScript.Sleep 100
End If

'-- Check for RAS using the CheckRAS utility from --
'-- the Resource Kit. --
objShell.Run "\\DC-01\netlogon\checkras.exe",,True

If Err.Number = 1 Then
SMSSub
Else objShell.Run "\\AV-SVR-01\ofcscan\autopcc"

End If

Err.Clear

SMSSub

WScript.Quit

VBScript Fundamentals for Windows Scripting – WMI

Before we begin, be forewarned that WMI encompasses A LOT of material and this will be a long article as is required to cover the material at even a high level. I will also preface the article with the fact that many of the diagrams and references included herein were derived from “The Windows Scripting Guide” by Microsoft Press which, in my opinion, is about the best book on Windows Scripting available at this time. Microsoft has done a great job in diagramming the many layers of the architecture that were used for modeling some of the diagrams seen later in this article. In addition, this article requires an intermediate to advanced knowledge of Widows Scripting and COM (Component Object Model) fundamentals. If you are not comfortable with the foundations of VBScript, please review these first two articles in this series:

VBScript Fundamentals for Windows Scripting – The Basics

VBScript Fundamentals for Windows Scripting – ADSI

In regards to managing resources on the network, you can’t find a more convenient tool than scripting with Windows Management Instrumentation, or WMI. WMI facilitates queries, monitoring, and changing settings on network resources including software, hardware, services, accounts, and many other objects. What used to require GUI interfaces to obtain information, such as how much physical memory was installed on a computer using System Properties, can now be done with one script launched from one machine against several remote machines.

The scripting syntax used with WMI is not quite as similar to that used in previous articles. It is still VBScript; however, the syntax used to connect to classes within WMI is more involved and looks more complicated. Don’t let that confuse you as you read. The majority of the code used in scripting WMI works as a template in that you can simply cut and paste the “meat and potatoes” of the code into almost every WMI script you write with minor editing to achieve the desired results. In fact Microsoft released a very cool tool called SCRIPTOMATIC that reveals the WMI classes on your machine as well as displays informational code pertaining to the WMI classes which you can modify directly or cut and paste into your own code.

Now, let’s dive into Windows Management Instrumentation beginning with a look at the WMI architecture itself.

Intro to WMI Architecture

The key to working with WMI is not actually writing the code, but finding out what classes are available to use within the script to access the required resources. For example, the Win32_Service class enables the collection of information about services, but how do find information on what other resource WMI can manage? Understanding the WMI architecture is the key to knowing what classes WMI can manage as well as what methods and properties can be used in each class. (if you need a refresher in properties and methods, please see my previous article here.)

WMI is made up of three main layers:

  1. Managed Resources
  2. WMI Infrastructure
  3. Consumers

These layers and their associated WMI components are displayed in the following diagram to demonstrate interoperability:

Figure

To obtain a better understanding, we will briefly look at the layers in reverse, beginning at the bottom as this is where the actual managed objects reside.

Managed Resources

This layer of the WMI model is quite easy to understand. It is simply the layer where items that you want to query or manage reside such as: disk drives, printers, registry, network interface cards, services, file system structures, hardware devices, etc. You can start to get an idea of how powerful WMI is by simply reading the incomplete list of resources WMI can manage in the previous sentence.

Let’s look at a simple WMI code to display the description of the loaded network adapters:

strComputer = "."
Set objWMIService = GetObject("winmgmts:\\" & strComputer)
Set colItems = objWMIService.InstancesOf("Win32_NetworkAdapterConfiguration")
For Each objItem in colItems
Wscript.Echo objItem.Description
Next

Depending on how many NIC’s you have installed and what types of settings are used in the network, you should receive an output similar to the following:

Figure

Note – To demonstrate the re-usability of the code, if we were to change Win32_NetworkAdapterConfiguration to read Win32_LogicalDisk and change the word Description to read DeviceID the output would list all the available drive letters on the machine.

WMI Providers

The Providers are one the main components of the core WMI architecture. Basically, the providers facilitate communication between the Consumer (the script itself) and the Managed Resources. The script example above uses the Win_32 Provider to obtain information about the network adapter as a managed resource. It is the Providers that allow the scripts to access the Win_32 API (Application Programming Interfaces) via scripting instead of needing a more robust programming language such as C++ or Visual Basic.

CIMOM

An acronym for Common Information Model Object Manager, the CIMOM facilitates interaction between Providers and Consumers. All requests sent from the WMI script to the Managed Resource pass through the CIMOM layer of the WMI architecture. It is the CIMOM layer that directs requests from the script to the appropriate Provider which then returns the requested information or performs the requested task. Let’s view this using a scripting example:

strComputer = "."
Set objWMIService = GetObject("winmgmts:\\" & strComputer & "\root\cimv2")
Set colItems = objWMIService.InstancesOf("Win32_Process")
For Each objItem In colItems
Wscript.Echo objItem.Description
Next

In this script, we are returning a list of all running services on the local machine. CIMOM does not actually return the information, but rather forward the request to the Win32_Process Provider which actually does the legwork and then returns the information to the CIMOM layer of the architecture. CIMOM then forwards the returned information into the output you see as the results.

Note – You may notice that this script differs a little from the previous one in that the objWMIService variable has “\root\cimv2” appended to the end of the statement. This simply specifies the namespace that the query should be run against in the WMI Provider model. We will cover namespaces a little later. In this sample, you may remove the “\root\cimv2” and still obtain the same results providing you have not changed the default WMI namespace paths after installation.

CIM Repository

This is the object repository, or the schema, which defines all the data that is exposed by WMI. Think of the schema as a map that defines the objects stored in the repository. Using Active Directory as an example, First Name is an attribute of the User object. It is the schema that defines not only what type of information may be assigned to the First Name attribute, but also what the relationship is between the First Name attribute and the User object.

It is important to note here that while objects in the Active Directory are static for the most part, objects referenced in WMI are not. The state of the computer is dynamic and as such, a true repository, or snapshot, of the system is not possible. It is this design that can cause WMI scripts to perform slowly as they must retrieve new values each time they are run by sending requests to the CIM Repository. Querying or changing large amounts of information from several machines can take a while to complete using WMI due to the need to constantly obtain refreshed information.

The easiest way to learn about the classes, properties, and methods in the CIM repository and their relationships is to browse the repository itself. There are two tools that allow you to open the repository and visualize the interaction of these items. The WMI Tester is available as part of the default WMI install and can be located by searching for Wbemtest.exe on your machine. The second tool is called CIM Studio and may be obtained by downloading and installing the WMI Administration Tools here. Both are beneficial in examining the repository, but the CIM Studio utility offers a little more information and is a little easier to read.

Consumers

This is the highest layer in the Infrastructure model and the easiest to understand. In a nutshell, the Consumer is the script you write or the application you run that launches WMI code in the background.

CIM (Common Information Model)

This is the WMI schema that is used to store the class definitions that are used in modeling managed resources. It is the CIM definitions that allow us to make minor modifications to scripts and obtain completely different output as shown in the following two scripts:

strComputer = "."
Set objWMIService = GetObject("winmgmts:\\" & strComputer)
Set colItems = objWMIService.InstancesOf("Win32_OperatingSystem")
For Each objItem in colItems
Wscript.Echo "Name: " & objItem.Name
Wscript.Echo "BuildNumber: " & objItem.BuildNumber
Wscript.Echo "Manufacturer: " & objItem.Manufacturer
Next

The above script returns information regarding the Operating System currently installed on the local machine. Making some slight modifications to the script as shown below will return information about shares on the local machine.

strComputer = "."
Set objWMIService = GetObject("winmgmts:\\" & strComputer)
Set colItems = objWMIService.InstancesOf("Win32_Share")
For Each objItem in colItems
Wscript.Echo "Description: " & objItem.Description
Wscript.Echo "Name: " & objItem.Name
Wscript.Echo "Path: " & objItem.Path
Next

The only changes made were to the class name (changing Win32_OperatingSystem to Win32_Share) and to the property values to return the appropriate information related to the different classes. The CIM allows us to obtain completely different results from a script without re-writing the entire script.

Note – If you are wondering how to figure out what properties are associated with what classes, it is here that the SCRIPTOMATIC tool comes into play. This tool enumerates every property for all available classes on the machines by simply selecting the appropriate class.

Namespaces

Namespaces are used to logically contain similar classes within the CIM. These are similar to folder path structures on a logical drive and follow the same rules in that while you cannot have two files with the same name in the same folder path, it is not possible to have two classes with the same name in a shared namespace. For a detailed view of the WMI Schema namespace, click here.

The default namespace is \root\cimv2 within WMI. This can be changed, but unless necessary for advanced scripting, it will usually be found within that context. You can find out what the current default namespace is on a machine by running the following script:

strComputer = "."

Set objWMIService = GetObject("winmgmts:\\" & strComputer & "\root\cimv2")
Set colWMISettings = objWMIService.InstancesOf("Win32_WMISetting")

For Each objWMISetting in colWMISettings
Wscript.Echo "Default namespace for scripting: " & _
objWMISetting.ASPScriptDefaultNamespace
Next

Similarly, you can retrieve a list of all available namespaces by running the following script:

strComputer = "."
Call EnumNameSpaces("root")

Sub EnumNameSpaces(strNameSpace)
Wscript.Echo strNameSpace
Set objSWbemServices = _
GetObject("winmgmts:\\" & strComputer & "\" & strNameSpace)
Set colNameSpaces = objSWbemServices.InstancesOf("__NAMESPACE")
For Each objNameSpace In colNameSpaces
Call EnumNameSpaces(strNameSpace & "\" & objNameSpace.Name)
Next
End Sub

Running this using cscript from the command line returns the following output:

Figure

This script recursively searches through all available namespaces on the target computer (in this example, the local machine since we defined the strComputer variable as “.”) using a subroutine. When called, the subroutine uses the GetObject function to connect to the initial namespace identified as “root”, establishes a connection to the WMI provider, and then returns all instances of namespace beneath the current namespace using the For…Next control loop.

WMI Class Components

The CIM Repository contains several class categories. While the details of the categories are beyond the scope of this article, understanding the differences in the classes and the class types can be useful in determining classes to use in your scripting. More information on class categories and class types can be found here.

The WMI classes represent the templates mentioned earlier that define the manageability of the resource instances within WMI. Take for example the Win32_Process class. All processes in this class (which are the processes on the target machine) share the same properties which can be queried or managed using WMI. Classes represent tangible components on a computer which can be both hardware and/or software related (e.g. fixed disks, PCI cards, services, printers, network shares, etc.).

WMI Class Components – Properties

Properties are used to further describe the managed resources. For example, a NIC card has a description name, an OS installation has a build number, a monitor has a refresh rate, etc. In the following code snippet, the properties of the Win32_Process class are in bold:

strComputer = "."
Set objWMIService = GetObject("winmgmts:\\" & strComputer & "\root\cimv2")
Set colItems = objWMIService.InstancesOf("Win32_Process")
For Each objItem In colItems
Wscript.Echo "Description: " & objItem.Description
Wscript.Echo "ExecutablePath: " & objItem.ExecutablePath
Wscript.Echo "Name: " & objItem.Name
Next

WMI Class Components – Methods

Methods are actions that can be taken directly upon a managed resource. Services present the best examples to demonstrate methods because of the actions that can be performed on them including stop, start, pause, install, delete, etc. In the following example we will SWbemObject in conjunction with a method to stop the Alerter service:

strComputer = "."
Set objWMIService = GetObject("winmgmts:\\" & strComputer & _ \root\cimv2:Win32_Service.Name='Alerter'")
objWMIService.StopService

For this example the SWbemObject is actually a reference to objWMIService and the method used on the managed resource (the Alerter service) is StopService. Objects, such as the SWbemObject, declared in WMI are resources managed using the WMI Scripting Library object model which we’ll cover a little later.

WMI Class Components – Qualifiers

Qualifiers offer additional information about properties, classes, and methods. They define the operations of the called method, class or property. Using the example in the Properties section earlier, it is the qualifier that defines the properties used in the fact that the information retrieved is to be stored in an array within the control loop.

SWbemObject Concept Explained

To better understand SWbemObject, it helps to understand what an object path is. An object path is the logical path that defines an object’s location in the WMI Repository. The object path can be represented by the following:

[\\Computer Name] [\Namespace] [:Class Name] [.Property=Value]

Used in code, it would look like this:

… GetObject(“winmgmts:\\ServerA\root\cimv2:Win32_Service.Name=’Alerter’”)

The underlined portion in bold represents the object path defines as follows:

[\\Computer Name] = \\ServerA
[\Namespace] = \root\cimv2
[:Class Name] = :Win32_Service
[.Property=Value] = .Name=’Alerter’

To put it all together, consider the following script:

1. strComputer = "."

2. strNameSpace = "root\cimv2"

3. strClass = "Win32_Service"

4.

5. Set objClass = GetObject("winmgmts:\\" & strComputer & _

"\" & strNameSpace & ":" & strClass)

6.

7. For Each objClassProperty In objClass.Properties_

8.     Wscript.Echo objClassProperty.Name

9. Next

In the above script, lines 1-3 are initializing variables and assigning values, line 5 uses the GetObject function to connect to the WMI service and return the SWbemObject reference to the target class (in this example, the Win32_Service class), lines 7-9 used a For… Next control loop to display the properties collection for the Win32_Service class property. The output would resemble the following:

AcceptPause
AcceptStop
Caption
CheckPoint
CreationClassName
Description
DesktopInteract
DisplayName
ErrorControl
ExitCode
InstallDate
Name
PathName
ProcessId
ServiceSpecificExitCode
ServiceType
Started

Each of the above properties describe the Win32_Service class in that every service has a Description, a DisplayName, a Name, a value of Started (or stopped), etc…

To again illustrate the versatility and ease of use of the WMI code structure, if we were to use the same code as previously executed and simply change the three instances of the word “Properties” to the word “Methods” as seen here:

strComputer = "."

strNameSpace = "root\cimv2"

strClass = "Win32_Service"

Set objClass = GetObject("winmgmts:\\" & strComputer & _

"\" & strNameSpace & ":" & strClass)

For Each objClassMethod In objClass.Methods_

Wscript.Echo objClassMethod.Name

Next

We would return the following results using cscript from a command prompt:

Figure

As explained in the Methods section earlier, these are the actions that may be performed upon a managed resource within the Win32_Service class. In this instance, a managed resource might be the Alerter service, the Spooler service, the DHCP Server service, etc.
WMI Object Model

Now that we’ve touched on some of the WMI architecture, it is a good time to introduce the object model. Again, Microsoft has a nice flowchart of the library object model for WMI which can be found here. This will greatly aid in visualizing the process flow between the objects and instances mentioned so far. Take a few minutes to view the diagram and understand the processes occurring between objects in the library object model when different methods are called. This article will not go into details surrounding the object model; however, the following script will demonstrate the object calls made within the model.

strComputer = "ServerA"
Set objSWbemLocator = CreateObject("WbemScripting.SWbemLocator")
Set objSWbemServices = objSWbemLocator.ConnectServer _
(strComputer, "root\cimv2", "alan", "p@ssw0rd")
Set colSWbemObjectSet = objSWbemServices.InstancesOf("Win32_Process")
For Each objSWbemObject In colSWbemObjectSet
Wscript.Echo "Name: " & objSWbemObject.Name
Next

In the first line, we define the variable strComputer, next we create a reference to the SWbemLocator which resides at the top of the object model library (use the diagram for reference) using the CreateObject function. In the third line, we do two things: first we use the ConnectServer method of the SWbemLocator object to connect to the \root\cimv2 WMI namespace and second, pass user credentials to authenticate against the namespace on the remote server. This is only necessary if you are running the script in the context of a user account without administrative privileges on the remote computer. Now that we have connected to the WMI service, we again use the reference to connect to the Win32_Process class in line 4 (line 3 wraps around). Finally, we use the For… Next control loop in lines 5-7 to display the name property of each instance of the Win32_Process class.

For general WMI scripts used for tasks such as finding all the services on a machine, it is not completely necessary to understand every aspect of the WMI object model. However, a solid knowledge of the Automation objects (SWbem*) within the model is the beginning to writing more advanced WMI scripts.

Specifying Return Parameters in Queries

When using WMI to obtain information, it is not always desired to obtain all information regarding every instance of the object class being queried. Take the Event Logs for example. If you were to run the following script:

strComputer = "."
Set objWMIService = GetObject("winmgmts:\\" & strComputer & _ "\root\cimv2")
Set colItems = objWMIService.InstancesOf("Win32_NTLogEvent")
For Each objItem in colItems
Wscript.Echo "EventCode: " & objItem.EventCode
Wscript.Echo "User: " & objItem.User
Wscript.Echo "Logfile: " & objItem.Logfile
Wscript.Echo "Message: " & objItem.Message
Next

You would receive information on every Event ID from every Event Log on the local system. While that is a lot of information, let’s take it a step further. Let’s say that every machine in an organization has a log file size limit of 30MB and that we use an array to query 50 different machines in one script. Since WMI scripts store their data in memory as they run, guess what’s going to occur. That’s right… system crash when we run out of available memory with the script process still hung running.

To narrow down the returned information in our script, we can use the WHERE clause in our variable statements. Up to now, we have been using the InstancesOf method in our script. To specify tighter query parameters, we will now use a new method called ExecQuery which replaces the InstancesOf method in the syntax. Compare the method below using our previous script with some modifications:

strComputer = "."
Set objWMIService = GetObject("winmgmts:\\" & strComputer & _
"\root\cimv2")
Set colItems = objWMIService.ExecQuery("SELECT * FROM " & _
"Win32_NTLogEvent WHERE Logfile = 'System'")
For Each objItem in colItems
Wscript.Echo "EventCode: " & objItem.EventCode
Wscript.Echo "User: " & objItem.User
Wscript.Echo "Logfile: " & objItem.Logfile
Wscript.Echo "Message: " & objItem.Message
Next

This would return specified information from the System Log only. Let’s examine the script more closely to identify the processes associated with the new ExecQuery method:

The first line sets the strComputer variable to the local machine. The second line (wrapped) uses the GetObject method to attach to the \root\cimv2 namespace on the local machine using the winmgmts:// WMI moniker (think of moniker as another word for name) and assign this value to the objWMIService variable. The third line (wrapped) uses the ExecQuery method of the SWbemObject (objWMIService) to specify the information query parameters. If you were reading the statement in plain English, it would read as follows, “Select all (*) from the Win32NTLogEvent class where the Logfile property equals System.” So basically, only information in the System log will be returned.

Finally we use the For… Next control loop to return specified property information on the objects return from the query.

We can even drill down further for more detailed returns with another slight modification to the ExecQuery clause as demonstrated below in the same script using bold text:

strComputer = "."
Set objWMIService = GetObject("winmgmts:\\" & strComputer & _
"\root\cimv2")
Set colItems = objWMIService.ExecQuery("SELECT * FROM " & _
"Win32_NTLogEvent WHERE Logfile = 'System' AND " & _
"EventCode = ‘6005’")
For Each objItem in colItems
Wscript.Echo "EventCode: " & objItem.EventCode
Wscript.Echo "User: " & objItem.User
Wscript.Echo "Logfile: " & objItem.Logfile
Wscript.Echo "Message: " & objItem.Message
Next

This is the exact same script as before; however, using the AND clause we are now filtering the query for not only information specific to the System Event Log, but also only System Event Log information with the Event ID of 6005.

As you can see, using clauses can dramatically narrow your results and thereby limit the amount of data you would have to filter through to find the desired results. You may also notice that the query clauses resemble Structured Query Language (SQL). While the syntax is very similar, it is actually WMI Query Language (WQL) and not really SQL. Basically it doesn’t have the SQL functionality to UPDATE or INSERT or modify database tables as SQL does and is used for SELECT query statements.

Conclusion

You can definitely see how powerful WMI can be in Windows Scripting from the many previous examples. With a robust object model, access to an extensive amount of classes and methods, the ability to make very minor modifications to scripts to obtain completely different results, and the ability to easily specify finite target information in WMI queries it becomes apparent why more and more applications are leveraging WMI via scripting to retrieve information about the current state computing environment.

If you have experience with WMI, then hopefully this article clarified some behind the scenes processes that you may have had taken for granted when running your scripts. If you are new to WMI, don’t get discouraged. WMI is more in-depth than the other subjects we have covered in scripting so far and takes a little more effort to learn. Don’t let any confusion you may have now dissuade you from continuing to learn WMI as it can make your administration tasks immensely easier not to mention being an invaluable addition to your skill set. Take advantages of the many resources on WMI listed in this article to help and post any questions to the forums as you test your new WMI scripts.

The following are simply a few script examples that can be used as templates demonstrating a few common tasks that may be performed using WMI Scripting with minor editing changes. Once you grasp the WMI concepts, you will find you only need to write a few scripts to have a whole arsenal of powerful tools for administration.

Stopping Services

strComputer = "." ‘Edit this for remote machines
strNamespace = "\root\cimv2" ‘Edit to change namespace
strServiceName = "Alerter" ‘Edit to reflect service to be stopped

Set objWMIService = GetObject("winmgmts:" _
& "{impersonationLevel=impersonate}!\\" & strComputer & strNamespace)
Set colServices = objWMIService.ExecQuery _
("SELECT * FROM Win32_Service WHERE Name = '" & strServiceName & "'")
For Each objService in colServices
errReturnCode = objService.StopService() ‘StartService may also be used
Next

Forcing User Logoff

Const x = 4 'This can be changed to one of the following:
'0 = Logoff
'1 = Shutdown
'2 = Reboot
'4 = Forced Logoff
'5 = Forced Shutdown
'6 = Forced Reboot
'8 = Power Off Computer
'12 = Forced Power Off

strComputer = "." 'This can be changed for remote machines
strNamespace = "\root\cimv2" 'This can be changed for namespaces

Set objWMIService = GetObject("winmgmts:{impersonationLevel=impersonate}!\\" _
& strComputer & strNamespace)
Set colOperatingSystems = objWMIService.ExecQuery _
("SELECT * FROM Win32_OperatingSystem")
For Each objOperatingSystem in colOperatingSystems
ObjOperatingSystem.Win32Shutdown(x)
Next

Account Logon Failures

strComputer = "." ‘This can be changed for remote machines
strNamespace = “\root\cimv2\” ‘This can be changed for namespaces
strEventCode = “681” ‘Edit to reflect error code

Set objWMIService = GetObject("winmgmts:\\" & strComputer & _
"\root\cimv2")
Set colItems = objWMIService.ExecQuery("SELECT * FROM " & _
"Win32_NTLogEvent WHERE Logfile = 'Security'AND " & _
"EventCode = ‘" & strEventCode & “’”)
For Each objItem in colItems
Wscript.Echo "EventCode: " & objItem.EventCode
Wscript.Echo "User: " & objItem.User
Wscript.Echo "Logfile: " & objItem.Logfile
Wscript.Echo "Message: " & objItem.Message
Next

VBScript Fundamentals for Windows Scripting – ADSI

The previous article introduced the fundamental concepts of VBScript. If you are new to scripting and have not yet read this article, you should take a few moments to go over the information contained therein as it explains important fundamentals. This article will take that initial foundation and build on it to illustrate how VBScript can be applied in Windows Scripting in an applicable manner. Although a thorough knowledge of COM (Component Object Model) is not required, it is beneficial to be familiar with the ADSI object model, components, and provider architecture to create more efficient scripts. The next several sections will introduce the ADSI architecture to build a foundation that will aid in ADSI scripting.

Interfaces

Active Directory Service Interfaces, or ADSI, is the collection of DLL interfaces that allows the manipulation of different directory objects. This collection is used not only in scripting, but also with the snap-in MMC’s such as the AD Users & Computers snap-in. The interface is what provides information about an object such as its path, name, path to the parent object, etc. The most common interface is called IADs which is used by ADSI to interact with objects.

Figure

In the above example we used the LDAP provider (explained a little later) to instantiate (or create) the object we called oUser. The LDAP provider is able to determine that oUser is a User object in Active Directory so it creates the appropriate COM object, which we named oUser, along with the appropriate interfaces for that object which are listed to the left of the object. The IADs interface is what allows us to use the Get method to read the information about our User object, JoeUser. IADsUser is simply another common interface that might be used with this object and IUnknown is a base for other interfaces to be attached to the object which in turn are used to provide additional information about the object. We’ll look at an example shortly after discussing providers in more detail.

Namespaces

Everything in the directory has a specific place. The directory service maintains this information and makes it available to user queries, application calls, etc. But, how does Active Directory know where to go when you double click on JoeUser in the Users and Computers snap-in? It looks up where the JoeUser object is located within the AD namespace. Think of a namespace as a location in the directory to which an object name can be resolved. DNS is the best example to use for namespace. It is a hierarchical method of maintaining domains in a tree-like structure and is scalable and efficient. Active Directory uses not only DNS namespacing, but also LDAP (Lightweight Directory Access Protocol) to keep object organized into OU’s and containers. In the example above, we used the LDAP moniker (LDAP://) to point to where the JoeUser object resides in the AD namespace.

Providers

ADSI uses providers to connect to and interact with the directory in scripts and GUI’s. More specifically, the providers allow ADSI to interact with the directory namespaces. To bind to an object (if you remember from the VBScript Fundamentals – Part 1 Article, binding is how we connect and interact with an object using scripting as well as declaring a variable name to be used with that object), ADSI uses a specific syntax that is part of the ADsPath. The ADsPath is simply the name provider combined with the path to the object as illustrated in the following examples:

LDAP://dc=lab,dc=mycompany,dc=com
LDAP://server01/dc=lab,dc=mycompany,dc=com

The LDAP provider facilitates access to LDAP based directories. The first example demonstrates the syntax for serverless binding. This method simply binds to the path in the directory and does specify a specific server. This is the ideal method to use when binding as it does not require an additional operation to first bind to a specific domain controller before binding to the object.

WinNT://MYCOMPANY
WinNT://MYCOMPANY/dc/JoeUser

The WinNT provider facilitates access to a domain controller path in the MYCOMPANY domain. The first example simply binds a domain controller within whatever domain the script is run in, while the second method access the JoeUser account on the dc domain controller in the MYCOMPANY domain. This method is preferred for use in NT domains. It can be used with AD; however, the script’s performance is much better when the LDAP provider is used.

GC://dc=lab,dc=mycompany,dc=com
GC://server01/dc=lab,dc=mycompany,dc=com

The GC provider facilitates access to Global Catalog server in the directory path. The first example is one that uses the serverless binding mentioned earlier while the second binds to a specific server in the specified path.

NDS://NetTree/o=org01/dc=com,dc=mycompany,dc=lab,cn=JoeUser

The NDS provider facilitates directory path access within Novell NetWare directory services. Here we are referencing JoeUser who’s user object resides in the org01 organizational unit of the intraNetWare tree named NetTree. Since intraNetWare is also LDAP compliant, you can use the LDAP provider paths as well to facilitate access.

NWCOMPAT://SERVER01/JoeUser

NWCOMPAT provider facilitates access to the Novell NetWare bindery. In this example we are binding to the JoeUser object that exists on SERVER01 in the bindery.

IIS://server01.mycompany.com
IIS://localhost/w3svc/1

The IIS provider facilitates access to IIS service paths. The first example binds to the server server01 in the mycompany.com domain, while the second example references the w3svc on the local server.  The IIS:// provider references information in the IIS metabase.

NOTE – The ADSI provider names listed above are case-sensitive. All must be specified in uppercase with the exception of the WinNT provider.

Now let’s take a look at how binding to an object in the directory using a provider would appear in an actual script.

Set objOU = GetObject("LDAP://ou=Finance,dc=mycompany,dc=com")

objOU.Filter = Array("user")

For Each objUser In objOU

Wscript.Echo objUser.Name

Next

In the first line, we are binding to the Finance organizational unit in the mycompany.com domain using the Get method and assigning the operation to the variable name objOU. Remember, it is the LDAP provider that creates the COM object (called objOU) and the appropriate IADs interfaces (allowing us to use the Get method to read directory information) to facilitate interaction with the newly created object based upon the object type.

The next line uses the Filter method of our newly created (instantiated) object to put the user objects within the Finance OU into a dynamic array. We then use the For…Next control loop to display each user’s name within the array.

Consider the following graphic to better visualize the different ADSI layers and the logical architecture flow:

Figure

We have already discussed the Directory Namespace layer which resides at the server level (on the domain controllers in the AD partitions) as well as the provider layer which allows access to the namespace layer via the COM interfaces created by the ADSI DLL’s also mentioned earlier. The top three layers are easier to explain as they are the layers that process all the information created by the script.

Router

The router does most of the work when it receives the scripted request. Take for example, the GetObject method in the above example. The router is the layer that receives the request, recognizes the provider used in the request (LDAP://), looks up the provider information in the registry, and then loads the provider into memory. Next, the router layer creates the object and sends the object information back to the provider layer for use in the script. Once the router has created the object (the binding process), items processed with the object in the script are performed at the Provider layer. There may also be information passed directly between the Application and the Provider layer when applications such as the MMC are used for information searches or user/group creation.

Property Cache

The cache is simply a place in memory on the machine from which the script was run that is used to hold the object after the binding operation in the script. The object is not downloaded to the machine, but rather a virtual copy of the object is maintained locally for use in the script. When we use the Get method (i.e. GetObject), it is here that the script looks first for the information. If it cannot locate it in the local memory, or property cache, then a GetInfo call is initiated to AD to read the property information into cache.

Applications

This is the top layer of the architecture where all interaction with AD starts. A good example of interaction within this layer is the Users and Computers MMC snap-in. The MMC initiates at this layer and then interacts with the rest of the ADSI layers depending on what you do within the MMC.

Interfaces

Every object in Active Directory is defined explicitly by attributes. To administer these objects and attributes, we use a set of methods contained within the ADSI interfaces. There are 6 core properties common to all objects in ADSI called IADs. IADs is the most basic interface in ADSI and the 6 core properties are as follows:

IADs::GUID – Represents the Globally Unique Identifier of an object returned as a string.
IADs::Class – Represents the schema class of the object returned as a string.
IADs::ADsPath – Represents the full path of the object within the current namespace directory. The path is returned as a string and identifies the objects location in the directory.
IADs::Name – Represents the object’s relative name. Name is returned as a string.
IADs::Parent – Represents the path to the parent object of the current object in the directory returned as a string.
IADs::Schema – Represents the path to the object and its schema class in the directory returned as a string.

As each of the above IADs methods are fairly vague by themselves, so let’s illustrate this in a small script:

Set objDomain = GetObject("LDAP://dc=internal,dc=testdom,dc=com")
WScript.Echo "ADsPath:" & objDomain.ADsPath
WScript.Echo "Class:" & objDomain.Class
WScript.Echo "GUID:" & objDomain.GUID
WScript.Echo "Name:" & objDomain.Name
WScript.Echo "Parent:" & objDomain.Parent
WScript.Echo "Schema:" & objDomain.Schema

The first line binds to the internal.testdom.com domain using the LDAP provider and the Get method to read information from the object as discussed at the beginning of this article. Each line thereafter simply displays the results in a pop-up window on the screen. Cut and paste the script into notepad, edit the domain information accordingly, and run the script. You will get the following results tailored for your domain:

ADsPath:LDAP://dc=internal,dc=testdom,dc=com
Class:domainDNS
GUID:718b9aa6e578ab49b954f4afa230b9d2
Name:dc=internal
Parent:LDAP://dc=testdom,dc=com
Schema:LDAP://schema/domainDNS

You may be wondering, “With all this discussion surrounding IADs, I didn’t see it listed anywhere in the script”. This is because IADs is not an actual property or method, it’s an interface. So referring back to the script above, the objDomain variable becomes our IADs container after the binding process. For further visual illustration, you could view the above script as:

Set objDomain = GetObject("LDAP://dc=internal,dc=testdom,dc=com")
WScript.Echo "ADsPath:" & IADsContainer.ADsPath
WScript.Echo "Class:" & IADsContainer.Class
WScript.Echo "GUID:" & IADsContainer.GUID
WScript.Echo "Name:" & IADsContainer.Name
WScript.Echo "Parent:" & IADsContainer.Parent
WScript.Echo "Schema:" & IADsContainer.Schema

NOTE – This is NOT proper syntax and will not work. It is only for illustration of the topic.

Viewing the above, you can see that once the binding process has completed, the objDomain object essentially becomes our IADs container. I have substituted this in the second example for better visual representation of the concept.

IADsContainer Methods

We just learned the concept of IADsContainers and how in ADSI, once we bind to an object, it then becomes the IADsContainer. Let’s go even one step further to look at the methods used with IADsContainers to obtain results and functionality within the script.

IADsContainer::Filter (reading values)

This one is most commonly used methods when returning objects from an enumeration into an array. For example:

Set objOU = GetObject("LDAP://OU=MIS,dc=internal,dc=testdom,dc=net")
ObjOU.Filter = Array("user")
For each strObject in objOU
Wscript.Echo strObject.cn
Next

The first line is the binding process with the Get method. The second line is where we utilize the Filter method on the IADsContainer (objOU) to read the information into a dynamic array. The next three lines use a For…Next loop to display items in the array returned by the Filter method.

IADsContainer::Create

The Create method, as the name so intuitively implies, is used to create objects in the directory. Consider the following example to create a user object in the MIS Organization Unit:

Set objOU = GetObject("LDAP://ou=MIS,dc=internal,dc=testdom,dc=net")

Set objUser = objOU.Create("user", "cn=UserA")

objUser.Put "sAMAccountName", "UserA"

objOU.SetInfo

As you are getting used to by now, the first line is the binding process. The second line uses the Create method on the IADsContainer (objOU) to create a user object and assign it the name “UserA”. Since the sAMAccountName is a mandatory attribute for a user object (every user must have a logon account name), we set that attribute value in the third line. The fourth line commits the account to the Active Directory.

NOTE – Be careful when using SetInfo as this writes the results of the script directly into the Active Directory.

IADsContainer::Delete

The Delete method is just the opposite of the Create method in that it deletes objects from the directory. The next example will delete the user object we just created:

Set objOU = GetObject("LDAP://ou=MIS,dc=internal,dc=testdom,dc=net")

Set objUser = objOU.Delete "user", "cn=UserA"

IADsContainer::Filter (writing values)

Another method used with Filter is writing values to objects in the directory as demonstrated here:

Set objUser = GetObject("LDAP://cn=UserA,ou=MIS,dc=internal,dc=testdom,dc=net")

objUser.Put “description”, “MIS Dude”
objUser.SetInfo

In this example we initiate the binding process, add a description to the user object UserA using  Put, and then use SetInfo to commit to Active Directory.

IADsContainer::MoveHere and IADsContainer::GetObject

These are two more methods which may be utilized in IADsContainer. The GetObject method should be quite familiar now as this is the method we have used continuously to bind to directory objects in a single step. The MoveHere method can be extremely involved and usually entails using ADO (ActiveX Data Objects) to connect to the directory and retrieve records for better performance. This method is beyond the scope of this article; however, I wanted to list it as it is a method of the IADsContainer.

Conclusion

ADSI is a very powerful tool in scripting and this article only touches the surface of the possibilities of the versatility of the interfaces. You can find additional information regarding ADSI online at http://www.microsoft.com/technet/treeview/default.asp?url=/technet/scriptcenter/scrguide/sas_ads_overview.asp. The more you work with ADSI and become familiar with it, the more functional and robust scripts you will be able to write. Remember, robust doesn’t mean long and drawn out. Only a few paragraphs earlier, we deleted a user object with only two lines of code.

My next article will cover WMI (Windows Management Instrumentation) use in VBScript.

VBScript Fundamentals for Windows Scripting – The Basics

This article explains the general basics behind VBScript. Its purpose is to de-mystify VBScript for System Administrators, System Engineers, and anyone else who is interested in writing Windows Script but does not have a background in VB coding.

For many administrators, the thought of writing code throws up an immediate red flag. “That’s not the sort of thing we do, that’s for the developers who write all that gibberish”. The simple fact is that scripting can be as hard or as easy as you choose to make it. Can you write a long and involved script to map drives automatically? Sure. Do you have to? No, it can be as simple as two lines:

Set oDrives = CreateObject(“WScript.Network”)
oDrives.MapNetworkDrive “Z:”, \\SVR01\Home

We’ll go into more detail surrounding the above a little later in the article; the purpose here is to demonstrate that scripting can automate repetitive tasks. How many times have you mapped drives from the command prompt or written them in batch files? Write some script, save some time.

A little VBScript History

When VBScript first came out, it was part of the Microsoft Windows Script Technologies release. VBScript and Jscript (Java Script) were both released to facilitate client-side scripting in web pages. The initial target audience was obviously web developers so system administrators saw no need to get involved since it was used in Web Development. In addition, most of the information surrounding VBScript’s applications in technologies such as WMI (Windows Management Instrumentation) and ADSI (Active Directory Services Interface) was only available to developers in the SDK’s (Software Development Kit) and other targeted information. Lastly, when administrators were able to find some information on the technologies, it was written in pure coding jargon which was not discernable unless you had a developer background.

Today; however, the information on VBScript and scripting technologies is widely available and administrators are noticing the usefulness of the scripts to make their jobs easier.

VBScript Fundamentals

VBScript works with objects (specifically Automation objects) which are simply a smaller set of COM (Component Object Model). COM is what facilitates the functionality of items like .dll’s and .exe’s. Take our earlier example:

Set oDrives = CreateObject(“WScript.Network”)
oDrives.MapNetworkDrive “Z:”, \\SVR01\Home

In this example, we are using the CreateObject method to bind to the element (or object), WScript.Network, and give it the name of “oDrives” for use in the next line. Binding is the method used to make a connection to an Automation object (WScript.Network) and create a new instance of that object (oDrives). We now call the “MapNetworkDrive” method on our oDrives object followed by the arguments directing the method to map the Home share on SVR01 to the Z:\ drive.

Note – When I first learned this stuff, it was right about here that I thought, “You