Lost Password? No account yet? Register
Home arrow Knowledge Center arrow Servers & Data Center arrow Active Directory arrow Don't Let Your AD Scripts Hang on You
The Most $100k+ Jobs. Over 30,000 new open positions monthly. Sign up now with TheLadders.com. Find 70,000 Jobs that pay over $100,000 � Search now at TheLadders.com
Don't Let Your AD Scripts Hang on You PDF Print E-mail
Written by David Noel-Davies   
Tuesday, 14 August 2007

Getting a script to process all the machines in Microsoft Active Directory domains can be a challenging task for several reasons, including having different Windows operating systems and old computer accounts in domains. ScriptTemplate.vbs can help you successfully automate a task on all the servers in your Active Directory domain running in a Windows Server 2003, Windows 2000 Server, or mixed environment. ScriptTemplate.vbs uses Windows Management Instrumentation, Active Directory Service Interfaces, Windows Script Host, and ActiveX Data Objects.

Most systems administrators know that automating tasks is an important skill in Active Directory (AD) environments, especially in large enterprises. However, getting a script to process all the machines in an AD domain is a challenging task, especially when the AD domain includes 500 or more machines. Some machines are online and appear to be functioning properly, but when a script attempts to access them to perform a task, these machines cause the script to hang. As a result, the script won't process the rest of the machines on the list. This problem occurs because properly configuring a network is difficult. The difficulties include:

  • The domain contains machines running different Windows OSs.
  • The domain contains old computer accounts, which leads to an additional burden for the script.
  • The network might not be properly configured for the script to run from a central location. For example, there might be blocked firewall ports across the WAN.

I created a template, ScriptTemplate.vbs, that can give you a better chance of successfully automating a task on all the servers in your AD domain. ScriptTemplate.vbs first obtains a list of the servers in a Windows Server 2003/Windows 2000 Server AD domain. Alternatively, you can use an input file to provide the list of servers. ScriptTemplate.vbs then tests to see whether the target servers are online and whether a Windows Management Instrumentation (WMI) connection can be established. If the servers are online and their WMI service is working, the script performs a specified task (e.g., obtain data, make a configuration change) on those servers. If a server is offline or its WMI service isn't working, the script won't hang. Instead, it records the failure in a log file, then continues to the next server.

ScriptTemplate.vbs is currently set up to produce four output log files so that you know when problems occur and why. One log file reports on the servers that were offline when the script ran (aka the Offline log). Another log file reports on servers that were online but unable to establish a WMI connection (aka the Error log). There's also a log file that reports on the servers whose computer accounts are no longer found in DNS but still remain in the AD database (aka the Unknown log). The last log file (aka the Main Report log file) reports on the success or failure of the specified task (e.g., a server query) that the script is supposed to perform. You can easily adapt the script to produce other output log files. For example, you can adapt the script to produce a Secondary Report log file if the script performs several tasks (e.g., a server query and a server configuration change).

All the log files are tab-delimited text files that have an .xls extension. Although I could have used Microsoft Excel to log the output data, most system administrators like to run scripts on servers, which usually don't have Excel installed. In addition, if a script uses Excel to write to the logs and the script is run on a workstation in which Excel is installed, the script will halt if a user launches Excel to open a worksheet. Therefore, I decided to use a text file format for the log files. By using tabs as delimiters and an .xls extension, you can open the log files with Excel by simply double-clicking the files.

Three subroutines—checkArguments, RuntheScript, and ProcessServers—constitute the heart of ScriptTemplate.vbs. They call on many self-contained functions that you can easily reuse in other scripts. Because a lot of the code in the script is written for reusability, the subroutines and functions declare their own variables and constants. However, there are five global variables:

  • StrScriptName. This variable stores the script's friendly name (e.g., System Audit Script), which is used as the title of the help screen.
  • StrMainReport. This variable stores the name of the Main Report log file.
  • StrSecReport. This variable stores the name of the Secondary Report log file, if applicable.
  • StrProgram. This variable stores the word script, which is used within the description of the help message in the help screen.
  • StrProgramName. This variable stores the basic launch command for the script name (e.g., ScriptTemplate.vbs), which is also used in the help screen.

Let's look at how the checkArguments, RuntheScript, and ProcessServers subroutines use these global variables and how they work in concert with the functions to perform tasks on many servers without the usual problems.

The checkArguments Subroutine
The script uses the checkArguments subroutine in Listing 1 to retrieve and process any arguments you provide on the command line when you launch the script. It is important to note that although the script uses the checkArguments subroutine to obtain the command-line arguments, the arguments are optional. The available optional arguments are:

  • The /?, /h, help, -h, or –help switches. You can specify any one of these switches to obtain help on how to run the script.
  • The /e server,server2 argument, where server1 specifies any server you want to exclude from being processed by the script when the script obtains its server list from an AD query. If you want to specify more than one server, you use a comma as a delimiter.
  • The /f filename argument, where filename is the name of an input file that contains the names of the servers to be processed by the script. In the input file, each computer name should go on a separate line.

As callout A in Listing 1 shows, the checkArguments subroutine stores the command-line arguments in a dynamic array named arrArguments. If you don't supply any arguments, the checkArguments subroutine simply calls the RuntheScript subroutine and passes the strInputFile and arrExServers variables with empty strings as parameters. If you don't supply a proper argument (e.g., a /g argument, which doesn't exist) or the necessary arguments (e.g., the /f without a filename), the script calls the ShowUsage subroutine to display a popup dialog box that explains how to run the script.

If you include the /e argument on the command line, the script stores the specified server names in a dynamic array named arrExServers, as callout B in Listing 1 shows. If you include the /f argument, the script stores the specified filename in the strInputFile variable, as callout C in Listing 1 shows. After processing the /e and /f arguments, the checkArguments subroutine calls the RuntheScript subroutine with the strInputFile and arrExServers variables passed in as parameters, as callout D in Listing 1 shows.

The RuntheScript Subroutine
Listing 2 shows the RuntheScript subroutine. After declaring its variables, the subroutine calls upon three functions to get the data it needs to calculate the script's run time and name the output log files, as callout A in Listing 2 shows.

RuntheScript first calls VBScript's Now function to obtain the current date and time and sets it to the strStartTime variable. The subroutine later uses this variable to calculate how long it took the script to run.

Next, RuntheScript calls the fnDate function in Listing 3 to obtain the current date and put it in the format mm-dd-yyyy. The value returned by the fnDate function is stored in the strFNDate variable, which is used to construct the log files' names.

RuntheScript subroutine then calls the getDNSDomain function to obtain the DNS domain name, which is stored in the strDomain variable. Like strFNDate, strDomain is used to construct the log files' names. As Listing 4 shows, the getDNSDomain function first binds to the RootDSE object (a special object in LDAP 3.0). Using the Get method, the function reads this object's defaultNamingContext attribute to obtain the distinguished name (DN) of the domain in which the script was launched. The function converts this DN to a DNS domain name that follows the subroot domain format (e.g., mydomain.com).

After the RuntheScript subroutine has the data it needs, it constructs the names of the log files, as callout B in Listing 2 shows. The filenames follow the format

Domain_LogPurpose_Servers_Date.xls

where Domain is the DNS domain name in the strDomain variable and LogPurpose is the log descriptor (i.e., the strMainReport variable's value or the word Error, Offline, or Unknown). Following the descriptor is the word Servers. The last two parts of the filename are the current date in the strFNDate variable and the .xls extension.

With the log files ready to go, the RuntheScript subroutine obtains the list of target servers from either the input file or from AD. The code at callout C obtains the server list from an input file, whereas the code at callout D obtains the server list from AD.

Obtaining the server list from an input file. The code at callout C uses VBScript's Len function to determine whether the strInputFile variable holds a value (i.e., the name of the input file that contains the server list). If there is a value, the subroutine calls the PrintMsg2 subroutine to display a screen message, then calls the getServerList function to read the server names in the input file. RuntheScript passes in two parameters to the getServerList function: the strInputFile variable and the strInRec variable (which is empty at this point).

As Listing 5 shows, the getServerList function instantiates the FileSystemObject object. The function uses the object's OpenTextFile method to obtain an instance of a TextStream object to represent the input file so that the file's contents can be read. Using VBScript's IsObject function, getServerList checks to see whether the strInputFile variable stores a valid filename.

When the input filename is valid, IsObject returns a True value. In response, getServerList creates a Dictionary object and assigns it to a variable named dicDataList. The Dictionary object requires key-item pairs. So, in a Do...Loop statement (aka Do loop), the function assigns each server name as an item in the dicDataList array. The Dictionary object's Count property (dicDataList.Count) is used as a placeholder for the key. The count increments by 1 in each iteration through the loop. After all the server names in the file are read in, the count in the Dictionary object’s key is set to the strInRec variable. The getServerList function returns this variable along with the server array in dicDataList to the RuntheScript subroutine, which stores it in the arrServerList variable.

When the input filename isn't valid, IsObject returns a False value. In this case, getServerList doesn't return any server names, and strInRec contains a null value. When the strInRec value is null, the RuntheScript subroutine calls the ShowUsageRec subroutine, which displays a message that states the input file is empty, then ends the script.

Obtaining the server list from AD. If an input file isn't supplied, the script calls the getADServerList function to obtain a server list from AD, passing in the strFNDate variable as a parameter. Listing 6 shows the getADServerList function. Like the getServerList function, the getADServerList function creates a Dictionary object to store the server names and assigns it to the dicData variable. Next, the function creates a RootDSE object and uses its Get method to read the domain's DN from the defaultNamingContext attribute. The DN is converted to a DNS domain name and stored in the strDomain variable. This domain name is used for a screen display and for a log file that's used only for logging the server list from AD. The log file's name follows the format

strDomain_Domain_Servers_Date.xls

where strDomain is the DNS domain name in the strDomain variable, which is followed by the words Domain_Servers, the current date in the strFNDate variable, and the .xls extension. The getADServerList function uses the FileSystemObject and TextStream objects to create and prepare the Domain_Servers log file for writing.

To fill that log file with server names, the getADServerList function uses ActiveX Data Objects (ADO). As callout A in Listing 6 shows, the function uses ADO's Connection object to connect to AD and ADO's Command object to define the query to run against AD. The query uses three AD attributes to obtain the server data: Name, distinguishedName, and operatingSystem. The Name attribute returns the server name. The distinguishedName attribute returns the DN, which the getReverseOU function in Listing 7 converts to the organization unit (OU) where the server resides. The operatingSystem attribute returns the machine’s OS name, which is used to separate out the servers in the AD server list.

The code at callout B in Listing 6 creates a disconnected ADO Recordset object and assigns it to the DataList variable. In a disconnected recordset, you can work with the records after you terminate the connection to the database that generated the recordset. In this case, the getADServerList function iterates through the recordset returned by the query, adding only server records to the disconnected recordset in DataList. The function then sorts the server data by OU name, then server name.

After sorting the data, the getADServerList function uses a Do loop to iterate through DataList. For each server, getADServerList constructs a string that consists of four elements—a count and the server's name, OU, and OS—that are delimited with tabs. The tab-delimited string is set to the strHostData variable. Then, the string in strHostData is assigned as the item and Count property is the placeholder for the key in the dicData array.

Besides constructing the string, the getADServerList function writes the server data to the Domain_Servers log file during the Do loop. The function adds the headers "No.," "Host Name," "OU Container," and "Operating System" to the log file.

After the Do loop completes, the getADServerList function returns the server array in dicData to the RuntheScript subroutine. RuntheScript, in turn, stores the array in the arrServerList variable.

At this point, the arrServerList variable contains the server data, no matter whether the server list came from an input file or AD. The RuntheScript subroutine processes the servers in arrServerList by calling the ProcessServers subroutine. As callout E in Listing 2 shows, seven parameters are passed to the ProcessServers subroutine: the strInputFile variable, the arrServerList variable, the arrExServers variable, and the variables representing the four output log files (i.e., the Main Report, Error, Offline, and Unknown log files). As mentioned previously, the number of the log files can vary, depending on the tasks you want the script to perform on the servers. If the script needs more than one Main Report log file, you can add parameters for the extra log files.

The ProcessServers Subroutine
Listing 8 shows the ProcessServers subroutine. After declaring its variables, this subroutine defines the constant for and creates a FileSystemObject object. ProcessServers then uses the Windows Script Host (WSH) WshNetwork object's ComputerName property to obtain the name of the computer on which the script was launched. This computer name is assigned to the strLComputer variable, which will be used by the WMIConnection function to test for a WMI connection.

Next, the ProcessServers subroutine uses a For Each…Next statement (aka For Each loop) to iterate through the server array in arrServerList. Within this For Each loop, the script performs its tasks.

As callout A in Listing 8 shows, the ProcessServers subroutine uses the Len function to determine whether the strInputFile variable holds a value. If there is a value (which means an input file was provided), the subroutine sets the strComputer variable to each value in the array. If there isn't a value (which means an input file wasn't provided), the subroutine has to take a more roundabout way to obtain each server name because the name is part of a tab-delimited string. To obtain the name, ProcessServers uses VBScript's Split function with a tab delimiter to parse the string back into its four elements (i.e., the count and the server's name, OU, and OS) . The subroutine sets the strComputer variable to the second element and the strOU variable to the third element.

Next, the ProcessServers subroutine calls the excludedServer function to check the server name in the strComputer variable to make sure that it isn't a server that should be excluded. As Listing 9 shows, the excludedServer function accepts two parameters: the strComputer variable, which contains the target server's name, and the arrExServers variable, which contains the exclusion list. The function iterates through each server name in arrExServers and compares it with the target server name. If the names match, the value for the excludedServer function is set to True, which prompts the ProcessServers subroutine to skip that particular server and continue to the next one. If no match is found, the value for the excludedServer function is set to False. ProcessServers, in turn, logs a variable (h) to count the server for a message displayed by the PrintMsg3 subroutine, then calls the getPingIP function to ping the server, as callout B in Listing 8 shows.

The getPingIP function in Listing 10 accepts two parameters: the target server's name (strComputer) and a placeholder variable named strPingStatus, which returns the ping status to ProcessServers. After declaring its variables, getPingIP sets the variables for a WshShell object, Dictionary object, and Regular Expression (RegExp) object. It also sets the RegExp pattern being searched for.

The getPingIP function uses the WshShell object's Exec method to execute ping.exe against the target server. (Note that instead of using ping.exe, you can use WMI's Win32_PingStatus class to ping servers. However, because the Win32_PingStatus class isn't available in Win2K, I had to use ping.exe.)

The Dictionary object stores the target server's response to the ping. In a Do loop, the getPingIP function processes the ping response to obtain the server's IP address and determine the ping status. The function uses the RegExp pattern to search for the IP address. When one is found, the function returns the IP address back to the ProcessServers subroutine, along with the status of On line or Off line in the strPingStatus variable, depending on whether the server was online or offline. When the IP address isn't found (i.e., it no longer exists in DNS), the function returns the value of No IP Address instead of an IP address, along with the status of Unknown host in the strPingStatus variable.

When the strPingStatus value is Off line, the ProcessServers subroutine writes the server name to the Offline log file. When the strPingStatus value is Unknown host, the server name is written to the Unknown log file. When the strPingStatus value is On line, the subroutine calls the WMIConnection function, as callout C in Listing 8 shows.

The WMIConnection function accepts two parameters: the target server's name (strComputer) and the local computer name ( strLComputer). As Listing 11 shows, the function begins by comparing the target server name against the local computer name to see whether they're the same. When the two names are the same, the connection test is skipped and the function ends. This is done so that when the script is launched from a member server, there's no need to test the connection to the server on which the script is launched.

When the two names differ, the function creates an instance of the SWbemLocator object. As callout A in Listing 11 shows, it uses that object's ConnectServer method with the WBEM_FLAG_CONNECT_USE_MAX_WAIT flag to test the WMI connection. (For information about the ConnectServer method and its flags, see the SWbemLocator.ConnectServer Web page at http://msdn2.microsoft.com/en-us/library/aa393720.aspx.) With this flag, the ConnectServer method has a connection timeout of two minutes if the WMI service on the target machine isn't working. When this happens, the WMIConnection function returns an error and the connection to the target server is terminated. Using the timeout flag ensures that the script doesn't hang indefinitely, thereby ensuring that the script will process all the servers on the list.

The WMIConnection function's return value is set to the strConnection variable. When the WMI connection doesn't generate an error, the strConnection value is an empty string. When the WMI connection generates an error, strConnection holds a string that contains an error number and error description. What is written to the Error log file depends on whether the server name was supplied by an input file or by a server list from an AD query. When the server is from an input file, the Error log file includes the server name, IP address, error number, and error description. Otherwise, the Error log file includes the server name, IP address, server OU, error number, and error description.

When the WMIConnection test doesn't return an error, the ProcessServers subroutine continues to process the target server for information about its OS, service pack, domain role, and NetBIOS domain name. If the server name was obtained from the input file, the subroutine also determines the server's OU. To get this information, ProcessServers calls four functions, as callout D in Listing 8 shows. The output from the following functions are written to the Main Report log file:

  • The getOSVersion function in Listing 12. The getOSVersion function accepts one input parameter: the target server's name. It uses WMI's Win32_OperatingSystem class to obtain the target server's OS name and the service pack level. It combines them to form a custom label (e.g., Windows Server 2003 , Datacenter Edition Service Pack 1), which is set to the strOSVersion variable.
  • The getDomainRole function in Listing 13. Like the getOSVersion function, the getDomainRole function accepts one input parameter: the target server's name. The getDomainRole function uses the Win32_ComputerSystem class's DomainRole property to obtain the target server's domain role. The function's return value is set to the strDomainRole variable.
  • The getNetBIOSDomain function in Listing 14. After accepting the target server's name as an input parameter, the getNetBIOSDomain function obtains the NetBIOS domain name for that server. Although you can use the Win32_ComputerSystem class's Domain property to obtain a computer's domain name, the return value can either be a NetBIOS domain name or a DNS domain name, depending on the computer's Windows platform. For example, this WMI class returns a NetBIOS domain name for Win2K computers and a DNS domain name for Windows 2003 and Windows XP computers. To be consistent across all the Windows platforms, I used WMI's StdRegProv class's GetStringValue method to obtain the NetBIOS domain name from the target machine's registry. The registry key that contains the NetBIOS domain name is HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\WinLogon. The entry name is CachePrimaryDomain, which has a value type of REG_SZ. The function's return value is set to the strNTBDomain variable.
  • The getOU function in Listing 15. The getOU function accepts two input parameters: the target server's name and the NetBIOS domain name obtained by the getNetBIOSDomain function. The getOU function uses Active Directory Service Interfaces' (ADSI's) IADsNameTranslate object to obtain the target server's DN. (For information about the IADsNameTranslate object, go to the IADsNameTranslate Web page at http://msdn2.microsoft.com/en-us/library/aa706046.aspx.) The getOU function then calls the getReverseOU function in Listing 7 to convert the DN into an OU name. The getOU function's return value is set to the strOU variable.

If the strOSVersion, strDomainRole, strNTBDomain, or strOU variable is returned to the ProcessServers subroutine empty, the subroutine sets that variable's value to "Unknown."

At this point, the ProcessServers subroutine is ready to perform a specific task in AD, as callout E in Listing 8 shows. I will discuss what you might include in this code in the "Using the Script" section.

After all the servers in the arrServerList array have been processed, the ProcessServers subroutine uses the IsObject function to determine the log files that were generated during the run. It then closes these open log files and cleans up the variables.

The last task that the script performs is to calculate the time (in seconds) it took to run. To perform this calculation, the RuntheScript subroutine uses VBScript's DateDiff function to subtract the current time from the starting time that was stored in the strStartTime variable at the beginning of the script. The result of this calculation is set to the strElapsedTime variable. The script then uses the convertTime function in Listing 16 to convert the elapsed number of seconds into a day hh:mm:ss time format, which is displayed on the screen along with a finish message. The script is also set up to display a finish message and the run time in a pop-up dialog box. Because this dialog box waits until you acknowledge the script has finished, the call to the ShowFinish subroutine that displays the pop-up dialog box is commented out. If you want the pop-up dialog box to appear, you need to remove the Rem statement from the call, which appears at the end of the RuntheScript subroutine.

Using the Script
Before you use ScriptTemplate.vbs, you need to know about several prerequisites and how to customize the script. Here are the prerequisites you need to be aware of:

  • For ScriptTemplate.vbs work in the AD environment, you need to run it on a server or workstation that is a member of the AD domain. You must run the script using an account that has administrative privileges in that AD domain.
  • When a custom server list is provided as an input for the script to run, all the servers in the list must be from the same domain of the computer on which the script is run.
  • For Windows 2003 SP1, the Windows Firewall can't be enabled. (In Windows 2003, Windows Firewall is disabled by default.) Otherwise, the script generates an Access is denied error, which will be written to the Error log file.
  • The network ports required for running the script must not be blocked by the network firewall. Otherwise, the following error message is written to the Error log file: The RPC server is unavailable.
  • On Win2K servers, the script might hang indefinitely when it reaches the WMIConnection function because of a shortcoming in WMI in Win2K. According to the IWbemLocator::ConnectServer Web page at http://msdn2.microsoft.com/en-us/library/aa391769.aspx, the ConnectServer method's WBEM_FLAG_CONNECT_USE_MAX_WAIT flag isn't available for the Win2K platform. (Unfortunately, the SWbemLocator.ConnectServer Web page at http://msdn2.microsoft.com/en-us/library/aa393720.aspx doesn't mention this limitation.) When Win2K servers cause the script to hang in this manner, you can use the script's command-line option to exclude them from being processed by the script. In some instances, restarting the WMI service on the target server allows the script to continue to the next server.

Before you use ScriptTemplate.vbs, you need to make two customizations. First, you need to customize the values for the StrScriptName, StrMainReport, and StrSecReport (if applicable) global variables that I discussed earlier. You'll find these variables at the beginning of the script.

Second, you need to customize the section of code highlight by callout E in Listing 8. In the ProcessServers subroutine, you need to add code that performs a specific task on the target servers. Using the information stored in the strOSVersion, strDomainRole, or strOU variable, you can target servers on a certain Windows platform, in a certain OU, or that play a certain domain role. You can use custom functions and subroutines to perform the tasks that you want the script to perform. You can then use the return values from your custom routines in the Main Report log file.

A Useful Addition to Your Scripting Toolkit
I hope you'll find ScriptTemplate.vbs useful. If you add this script to your scripting toolkit, you’ll always have a template that you can easily adapt to automate tasks in your AD environment. In a future article, I'll demonstrate how to use this template to create a script that audits the system time in Windows servers.







 


Listing 1

' Listing 1: The checkArguments Subroutine  Private Sub checkArguments()   Dim strCmd, objItem, i, j, customServerList   Dim ExcludeServer, objServer, arrExServers(), strInputFile  ' ******* BEGIN CALLOUT A *******   ' Get argument(s) when run under CScript.   With WScript.Arguments     If .Count > 0 Then       For i = 0 To WScript.Arguments.Count - 1         ReDim Preserve arrArguments(i)         arrArguments(i) = WScript.Arguments(i)         If i = 0 Then           If Mid(arrArguments(i),1,1) <> "/" Then             ShowUsage()           End If         End If       Next ' ******* END CALLOUT A *******       i = 0       For Each objItem In arrArguments         i = i + 1         Select Case i           Case 1             strCmd = LCase(objItem)             Select Case strCmd               Case "/","/?","/h","help","-h","-help"                 ShowUsage()               Case "/e"                 ExcludeServer = True               Case "/f"                 customServerList = True               Case Else                 ShowUsage()             End Select           Case 2             strCmd = Trim(objItem) ' ******* BEGIN CALLOUT B *******             If ExcludeServer = True Then               If InStr(strCmd,",") > 0 Then                 For Each objServer In Split(strCmd,",")                   ReDim Preserve arrExServers(j)                   arrExServers(j) = UCase(objServer)                   j = j + 1                 Next               Else                 ReDim Preserve arrExServers(j)                 arrExServers(j) = UCase(strCmd)               End If ' ******* END CALLOUT B ******* ' ******* BEGIN CALLOUT C *******             ElseIf customServerList = True Then               strInputFile = strCmd ' ******* END CALLOUT C *******             End If           Case Else             ShowUsage()         End Select       Next     End If     ' Make sure that the user supplies the necessary arguments.     If ExcludeServer = True Or customServerList = True Then       If i <> 2 Then ShowUsage()     End If   End With ' ******* BEGIN CALLOUT D *******   RuntheScript strInputFile, arrExServers ' ******* END CALLOUT D ******* End Sub 


 



Listing 2

' Listing 2: The RuntheScript Subroutine  Private Sub RuntheScript(strInputFile, arrExServers)   Dim strStartTime, strFNDate, strDomain, outFileName1   Dim outFileName2, outFileName3, outFileName4   Dim arrServerList, strInRec, strElapsedTime, strRunTime  ' ******* BEGIN CALLOUT A *******   ' Get the start time.   strStartTime = Now    ' Get the current date for the output log files' names.   strFNDate = fnDate(Date)    ' Get DNS domain name from the current domain for the output log files.   strDomain = getDNSDomain() ' ******* END CALLOUT A *******  ' ******* BEGIN CALLOUT B *******   ' Set the names of the output log files.   outFileName1 = strDomain & "_Error_Servers_" & strFNDate & ".xls"   outFileName2 = strDomain & "_" & strMainReport & "_Servers_" & _     strFNDate & ".xls"   outFileName3 = strDomain & "_Offline_Servers_" & strFNDate & ".xls"   outFileName4 = strDomain & "_Unknown_Servers_" & strFNDate & ".xls" ' ******* END CALLOUT B *******  ' ******* BEGIN CALLOUT C *******   If Len(strInputFile) > 0 Then     ' Read the server list from the input file if it's provided.     PrintMsg2 "Processing servers from the input file " & strInputFile & "."     ' Read the server list into the array.     arrServerList = getServerList(strInputFile, strInRec)     ' Check to see whether the input file contains any records.     If strInRec < 1 Then       ShowUsageRec(strInputFile)       WScript.Quit     End If ' ******* END CALLOUT C ******* ' ******* BEGIN CALLOUT D *******   Else     ' Get the server list from AD.     arrServerList = getADServerList(strFNDate)     PrintMsg1 "Processing servers from the Active Directory of " & _       strDomain & " domain."   End If ' ******* END CALLOUT D *******  ' ******* BEGIN CALLOUT E *******   ' Process the servers in arrServerList.   ProcessServers strInputFile, arrServerList, arrExServers, _     outFileName1, outFileName2, outFileName3, outFileName4 ' ******* END CALLOUT E *******    ' Calculate the elapsed time and display the finish message.   strElapsedTime = DateDiff("s",strStartTime,Now)   strRunTime = convertTime(strElapsedTime)   PrintMsg4 "******** The " & strProgram & " has finished ********"   PrintMsg4 "The " & strProgram & " run time is: " & strRunTime & "."      ' Below is a subroutine to display a finish message in a pop-up   ' dialog box. Remove the rem character to allow this dialog box.   ' ShowFinish strRunTime    End Sub  


 



Listing 3

' Listing 3: The fnDate Function  Private Function fnDate(tDate)   Dim mDate, dDate, yDate   mDate = Month(tDate)   If Len(mDate) = 1 Then mDate = "0" & mDate   dDate = Day(tDate)   If Len(dDate) = 1 Then dDate = "0" & dDate   yDate = Year(tDate)   fnDate = mDate & "-" & dDate & "-" & yDate   Set mDate = Nothing : Set dDate = Nothing   Set yDate = Nothing End Function  


 



Listing 4

' Listing 4: The getDNSDomain Function  Private Function getDNSDomain()   Dim objRootDSE, strDN, strSubDomain, strRootDomain   ' Determine the DNS domain name from the RootDSE object.   Set objRootDSE = GetObject("LDAP://RootDSE")   strDN = Split(objRootDSE.Get("defaultNamingContext"),",")   strSubDomain = Mid(strDN(0),InStr(strDN(0),"=") + 1)   strRootDomain = Mid(strDN(1),InStr(strDN(1),"=") + 1)   getDNSDomain = strSubDomain & "." & strRootDomain   Set objRootDSE = Nothing End Function 


 



Listing 5

' Listing 5: The getServerList Function  Private Function getServerList(ByVal strInputFile, ByRef strInRec)   Dim objFSO, inFile, dicDataList, strData   Const ForReading = 1   Set objFSO = CreateObject("Scripting.FileSystemObject")   On Error Resume Next   Set inFile = objFSO.OpenTextFile(strInputFile, ForReading, False)   If IsObject(inFile) Then     Set dicDataList = CreateObject("Scripting.Dictionary")     Do Until inFile.AtEndOfStream       strData = Trim(UCase(inFile.ReadLine))       If Len(strData) > 0 Then            dicDataList.Add dicDataList.Count, strData       End If     Loop     getServerList = dicDataList.Items     strInRec = dicDataList.Count   End If   Err.Clear   On Error Goto 0   Set objFSO = Nothing: Set inFile = Nothing   Set dicDataList = Nothing End Function 


 



Listing 6

' Listing 6: The getADServerList Function  Private Function getADServerList(strFNDate)   Dim dicData, objRootDSE, strDNSDomain, strDN, strSubDomain   Dim strRootDomain, strDomain, outFileName, objFSO, outFile   Dim objConnection, objCommand, strQuery, objRecordSet, DataList   Dim strOS, strComputer, strComputerDN strOU, strOSVer, strHostData, i   Const ForWriting = 2    Set dicData = CreateObject("Scripting.Dictionary")    Set objRootDSE = GetObject("LDAP://RootDSE")   strDNSDomain = objRootDSE.Get("defaultNamingContext")   strDN = Split(strDNSDomain,",")   strSubDomain = Mid(strDN(0),InStr(strDN(0),"=") + 1)   strRootDomain = Mid(strDN(1),InStr(strDN(1),"=") + 1)   strDomain = strSubDomain & "." & strRootDomain   outFileName = strDomain & "_Domain_Servers_" & strFNDate & ".xls"    Set objFSO = CreateObject("Scripting.FileSystemObject")   Set outFile = objFSO.OpenTextFile(outFileName, ForWriting, True)   WScript.Echo   WScript.Echo Space(3) & "Querying server list from the " & _     strDomain & " domain. Please wait. . ."   WScript.Echo  ' ******* BEGIN CALLOUT A *******   Const ADS_SCOPE_SUBTREE = 2   Set objConnection = CreateObject("ADODB.Connection")   Set objCommand = CreateObject("ADODB.Command")   objConnection.Provider = "ADsDSOObject"   objConnection.Open "Active Directory Provider"   Set objCommand.ActiveConnection = objConnection   strQuery = "<LDAP://" & strDNSDomain _     & ">;(&(objectCategory=Computer));" _     & "Name,distinguishedName,operatingSystem;Subtree"   objCommand.CommandText = strQuery   objCommand.Properties("Page Size") = 1000   objCommand.Properties("Timeout") = 30   objCommand.Properties("Searchscope") = ADS_SCOPE_SUBTREE   objCommand.Properties("Cache Results") = False   Set objRecordSet = objCommand.Execute ' ******* END CALLOUT A *******  ' ******* BEGIN CALLOUT B *******   Set DataList = CreateObject("ADODB.Connection") ' ******* END CALLOUT B *******   DataList.Fields.Append "strHostName", 200, 255 ' adVarChar   DataList.Fields.Append "strOUName", 200, 255 ' adVarChar   DataList.Fields.Append "strOSVersion", 200, 255 ' adVarChar   DataList.Open    ' Iterate through query's results and add only the server records to   ' DataList.   objRecordSet.MoveFirst   Do Until objRecordSet.EOF     strOS = objRecordSet.Fields("operatingSystem").Value     If InStr(LCase(strOS),"server") > 0 Then       DataList.AddNew       strComputer = UCase(objRecordSet.Fields("Name").Value)       strComputerDN = objRecordSet.Fields("distinguishedName").Value       strOU = getReverseOU(strComputerDN)       strOSVer = objRecordSet.Fields("operatingSystem").Value       DataList("strHostName") = strComputer       DataList("strOUName") = strOU       DataList("strOSVersion") = strOSVer       DataList.Update     End If       objRecordSet.MoveNext   Loop    ' Sort the records by OU name, then server name.   DataList.Sort = "strOUName,strHostName"    ' Fill the dicData array and write the server data to the log file.   DataList.MoveFirst   Do Until DataList.EOF     i = i + 1     strComputer = DataList.Fields.Item("strHostName")     strOU = DataList.Fields.Item("strOUName")     strOSVer = DataList.Fields.Item("strOSVersion")     strHostData = i & vbTab & strComputer & vbTab & strOU & _       vbTab & strOSVer     If i = 1 Then outFile.WriteLine "No." & vbTab & "Host Name" _       & vbTab & "OU Container" & vbTab & "Operating System"     outFile.WriteLine i & vbTab & strComputer & vbTab & strOU _       & vbTab & strOSVer     dicData.Add dicData.Count, strHostData   DataList.MoveNext   Loop   getADServerList = dicData.Items    ' Clean up the variables.   Set dicData = Nothing : Set objRootDSE = Nothing   Set objFSO = Nothing : Set outFile = Nothing   Set objConnection = Nothing: Set objCommand = Nothing   Set objRecordSet = Nothing: Set DataList = Nothing End Function 


 



Listing 7

' Listing 7: The getReverseOU Function  Private Function getReverseOU(strDN)   Dim strOUNames, arrItem, i, strTemp, strResult   strOUNames = Mid(strDN, InStr(strDN, "=") + 1)   strOUNames = Mid(strOUNames, InStr(strOUNames, "=") - 2)   strOUNames = Left(strOUNames, InStr(UCase(strOUNames), "DC=") - 2)   arrItem = Split(strOUNames, ",")   For i = UBound(arrItem) To LBound(arrItem) Step - 1     strTemp = Right(arrItem(i), Len(arrItem(i)) - InStr(arrItem(i), "="))     strResult = strResult & strTemp & "\"   Next   getReverseOU = Left(strResult, Len(strResult) - 1) End Function 


 



Listing 8

' Listing 8: The ProcessServers Subroutine  Private Sub ProcessServers(strInputFile, arrServerList, arrExServers, _    outFileName1, outFileName2, outFileName3, outFileName4)    ' Declare the standard variables for the script template.   Dim objFSO, strLComputer, objServer, strServerData, strComputer   Dim strOU, h, strNLength, SpToAdd, strIPAddress, strPingStatus    Dim strConnection, i, strOSVersion, strDomainRole, strNTBDomain   Dim j, k, l, m, outFile1 outFile2, outFile3, outFile4    ' Declare the custom variables for the script's specific tasks.   Dim strVariable_for_Specific_Task    ' Define the constant for and instantiate the FileSystemObject object.   Const ForWriting = 2   Set objFSO = CreateObject("Scripting.FileSystemObject")    ' Get the local computer name for the WMIconnection function.   strLComputer = CreateObject("WScript.Network").ComputerName      For Each objServer In arrServerList     strServerData = objServer ' ******* BEGIN CALLOUT A *******     If Len(strInputFile) > 0 Then       strComputer = strServerData      Else       strComputer = Split(strServerData,vbTab)(1)       strOU = Split(strServerData,vbTab)(2)     End If ' ******* END CALLOUT A *******     If Not excludedServer(strComputer, arrExServers) Then       ' Count each record for display.       h = h + 1       strNLength = Len(h)       Select Case strNLength         Case 1           SpToAdd = 3         Case 2           SpToAdd = 2         Case 3           SpToAdd = 1         Case 4           SpToAdd = 0       End Select        ' Print the process message.       PrintMsg3 Space(SpToAdd) & h & Space(2) & "Processing server: " _         & strComputer & ". . ."       ' Clear the variables.       strIPAddress = "" : strPingStatus = "" : strConnection = ""       PrintMsg4 "Pinging server " & strComputer & ". . ." ' ******* BEGIN CALLOUT B *******         ' Ping the server.       strIPAddress = getPingIP(strComputer, strPingStatus) ' ******* END CALLOUT B *******       If strPingStatus = "On line" Then         PrintMsg4 "Checking for connection status on " & strComputer & ". . ." ' ******* BEGIN CALLOUT C *******         ' Test the WMI connection.         strConnection = WMIConnection(strComputer, strLComputer) ' ******* END CALLOUT C *******         If strConnection <> "" Then           i = i + 1           If Len(strInputFile) > 0 Then             If i = 1 Then  ' Write the first line as a header.               Set outFile1 = objFSO.OpenTextFile(outFileName1, _                 ForWriting, True)               outFile1.WriteLine "No." & vbTab & "Host Name" & vbTab _                 & "IP Address" & vbTab & "Error Information"             End If             PrintMsg4 "***** Error connecting to " & strComputer & " ***** . . ."             PrintMsg5 strConnection             outFile1.WriteLine i & vbTab & strComputer & vbTab & _               strIPAddress & vbTab & strConnection           Else             If i = 1 Then  ' Write the first line as a header.               Set outFile1 = objFSO.OpenTextFile(outFileName1, _                 ForWriting, True)               outFile1.WriteLine "No." & vbTab & "Host Name" & vbTab & _                 "IP Address" & vbTab & "OU Container" & vbTab & _                 "Error Information"             End If             PrintMsg4 "***** Error connecting to " & strComputer & " ***** . . ."             PrintMsg5 strConnection             outFile1.WriteLine i & vbTab & strComputer & vbTab & _               strIPAddress & vbTab & strOU & vbTab & strConnection           End If         Else           ' Clear the variables.           strOSVersion = "" : strDomainRole = "" : strNTBDomain = "" ' ******* BEGIN CALLOUT D *******           ' Get the OS version and service pack.           strOSVersion = getOSVersion(strComputer)           ' Get the server role.           strDomainRole = getDomainRole(strComputer)           ' Get the NetBIOS domain name.           strNTBDomain = getNetBIOSDomain(strComputer)           ' Get the OU name if the server name is from an input file.           If Len(strInputFile) > 0 Then             strOU = getOU(strComputer, strNTBDomain)           End If            ' If any of the variable names is blank, set it to Unknown.           If strDomainRole = "" Then strDomainRole = "Unknown"           If strOSVersion = "" Then strOSVersion = "Unknown"           If strNTBDomain = "" Then strNTBDomain = "Unknown"           If strOU = "" Then strOU = "Unknown" ' ******* END CALLOUT D *******  ' ******* BEGIN CALLOUT E *******           ' Below is the area that contains the variables that accept the           ' return values from the custom functions to perform queries,           ' configuration changes, or other tasks. These variables are           ' used for writing information to the log file. This area can also           ' contain task-specific subroutines that don't return values.            ' AREA FOR CODE THAT PERFORMS A TASK.            ' Write your code's return value in the main log file. Here's an           ' example of how the code might look:           ' j = j + 1           ' If j = 1 Then  ' Write the first line as a header.           '  Set outFile2 = objFSO.OpenTextFile(outFileName2, _           '    ForWriting, True)           '  outFile2.WriteLine "No." & vbTab & "Host Name" & vbTab & _           '    "IP Address" & vbTab & "OU Container" & vbTab & _           '    "Operating System" & vbTab & "Server Role" & vbTab & _           '    "Domain" & vbTab & "Header for specific task"           ' End If           ' Write the result to the log file.           ' outFile2.WriteLine j & vbTab & strComputer & vbTab & _           ' strIPAddress & vbTab & strOU & vbTab & strOSVersion & _           ' vbTab & strDomainRole & vbTab & strNTBDomain & _           ' vbTab & strVariable_for_Specific_Task ' ******* END CALLOUT E *******          End If       ElseIf strPingStatus = "Off line" Then         ' Log the offline servers.         PrintMsg4 strComputer & " is currently off line."         l = l + 1         If l = 1 Then           Set outFile3 = objFSO.OpenTextFile(outFileName3, ForWriting, True)           outFile3.WriteLine "No." & vbTab & "Host Name" & _             vbTab & "IP Address"         End If         outFile3.WriteLine l & vbTab & strComputer & vbTab & strIPAddress       Else         ' Log the unknown host servers.         PrintMsg4 strComputer & " is an unknown host."         m = m + 1         If m = 1 Then           Set outFile4 = objFSO.OpenTextFile(outFileName4, ForWriting, True)           outFile4.WriteLine "No." & vbTab & "Host Name"         End If         outFile4.WriteLine m & vbTab & strComputer       End If     End If   Next    ' Close log files and clean up the variables.   If IsObject(outFile1) Then     outFile1.Close: Set outFile1 = Nothing   ElseIf IsObject(outFile2) Then     outFile2.Close: Set outFile2 = Nothing   ElseIf IsObject(outFile3) Then     outFile3.Close: Set outFile3 = Nothing   ElseIf IsObject(outFile4) Then     outFile4.Close: Set outFile4 = Nothing   End If   Set objFSO = Nothing End Sub  


 



Listing 9

' Listing 9: The excludedServer Function  Private Function excludedServer(ByVal strComputer, ByVal arrExServers)   Dim objItem   For Each objItem In arrExServers     If LCase(strComputer) = LCase(objItem) Then       excludedServer = True       Exit Function     End If   Next   excludedServer = False End Function 


 



Listing 10

' Listing 10: The getPingIP Function  Private Function getPingIP(ByVal strComputer, ByRef strPingStatus)   Dim objList, objRegEX, objShell, objExecObject, strText, objItem, i   Dim objIp, colItems   Set objList = CreateObject("Scripting.Dictionary")   Set objRegEX = New RegExp   objRegEx.Pattern = "\[((\d+\.){3}\d+)\]"   Set objShell = CreateObject("WScript.Shell")    Set objExecObject = objShell.Exec _     ("%comspec% /c ping -n 3 -w 1000 " & strComputer)    Do Until objExecObject.StdOut.AtEndOfStream     strText = objExecObject.StdOut.ReadLine     If Len(strText) > 2 Then       objList.Add objList.Count, strText       If objList.Count = 2 Then Exit Do       End If   Loop    For Each objItem In objList.Items     i = i + 1     If InStr(objItem,"could not find host") > 0 Then       strPingStatus = "Unknown host"       getPingIP = "No IP Address"       Exit Function     ElseIf InStr(1,objItem,strComputer,1) > 0 Then       If i = 1 Then         Set colItems = objRegEX.Execute(objItem)         For Each objIp In colItems           getPingIP = objIp.SubMatches(0)         Next       End If     ElseIf InStr(objItem,"Reply from") > 0 Then       strPingStatus = "On line"       Exit Function     ElseIf InStr(objItem,"Request timed") > 0 Then       strPingStatus = "Off line"       Exit Function     End If   Next   Set objShell = Nothing: Set objExecObject = Nothing   Set objRegEx = Nothing End Function 


 



Listing 11

' Listing 11: The WMIConnection Function  Private Function WMIConnection(strComputer, strLComputer)   If strComputer = strLComputer Then     Exit Function   Else     Const WBEM_FLAG_CONNECT_USE_MAX_WAIT = &H80     On Error Resume Next     Dim objSWbemLocator, objWMIService     ' ******* BEGIN CALLOUT A *******     Set objSWbemLocator = CreateObject("WbemScripting.SWbemLocator")     Set objWMIService = objSWbemLocator.ConnectServer _       (strComputer,"root\CIMV2","","","","", _       WBEM_FLAG_CONNECT_USE_MAX_WAIT)     ' ******* END CALLOUT A *******     If Err.Number <> 0 Then       WMIConnection = "Error: " & Hex(Err.Number) & _         ". " & Err.Description & "."     End If   End If   Err.Clear   On Error Goto 0   Set objSWbemLocator = Nothing: Set objWMIService = Nothing End Function 


 



Listing 12

' Listing 12: The getOSVersion Function  Private Function getOSVersion(ByVal strComputer)   Dim objWMIService, colItems, objItem, strOSVer, strSP   Set objWMIService = GetObject("winmgmts:\\"& strComputer & _     "\root\cimv2")   Set colItems = _     objWMIService.ExecQuery("Select * from Win32_OperatingSystem")   On Error Resume Next   For Each objItem In colItems     strOSVer = objItem.Caption     strSP = "Service Pack " & objItem.ServicePackMajorVersion     Select Case strOSVer       Case "Microsoft Windows 2000 Server"         getOSVersion = "Windows 2000 Server " & strSP       Case "Microsoft Windows 2000 Advanced Server"         getOSVersion = "Windows 2000 Advanced Server " & strSP       Case "Microsoft(R) Windows(R) Server 2003, Enterprise Edition"         getOSVersion = "Windows 2003 Enterprise Server " & strSP       Case "Microsoft(R) Windows(R) Server 2003, Standard Edition"         getOSVersion = "Windows 2003 Standard Server " & strSP       Case "Microsoft(R) Windows(R) Server 2003, Datacenter Edition"         getOSVersion = "Windows Server 2003, Datacenter Edition " & _           strSP       Case "Microsoft(R) Windows(R) Server 2003, Datacenter " & _         "Edition for 64-Bit Itanium-based Systems"         getOSVersion = "Windows Server 2003, 64-Bit Itanium " & _         "Datacenter Edition " & strSP       Case "Microsoft(R) Windows(R) Server 2003, Enterprise " & _         "Edition for 64-Bit Itanium-based Systems"         getOSVersion = "Windows Server 2003, 64-Bit Itanium " & _         "Enterprise Edition " & strSP       Case "Microsoft(R) Windows(R) Server 2003 Enterprise x64 Edition"         getOSVersion = "Windows Server 2003 Enterprise " & _         "x64 Edition " & strSP       Case "Microsoft Windows XP Professional"         getOSVersion = "Windows XP Professional " & strSP       Case Else         getOSVersion = "Unknown OS Type"     End Select   Next   Err.Clear   On Error Goto 0   Set objWMIService = Nothing: Set colItems = Nothing End Function 


 



Listing 13

' Listing 13: The getDomainRole Function  Private Function getDomainRole(ByVal strComputer)   Dim objWMIService, colItems, objItem, strRole   Set objWMIService = GetObject("winmgmts:\\"& strComputer & "\root\cimv2")   Set colItems = objWMIService.ExecQuery("Select * from Win32_ComputerSystem")   On Error Resume Next   For Each objItem In colItems     Select Case (objItem.DomainRole)       Case 0         strRole = "Standalone Workstation"       Case 1         strRole = "Member Workstation"       Case 2         strRole = "Standalone Server"       Case 3         strRole = "Member Server"       Case 4         strRole = "Backup DC"       Case 5         strRole = "Primary DC"     End Select   Next   getDomainRole = strRole   Err.Clear   On Error Goto 0   Set objWMIService = Nothing: Set colItems = Nothing End Function 


 



Listing 14

' Listing 14: The getNetBIOSDomain Function  Private Function getNetBIOSDomain(ByVal strComputer)   Const HKEY_LOCAL_MACHINE = &H80000002   Dim strKeyPath, strValueName, objRegistry, strValue   strKeyPath = _     "SOFTWARE\Microsoft\Windows NT\CurrentVersion\WinLogon"   strValueName = "CachePrimaryDomain"   On Error Resume Next   Set objRegistry = GetObject("winmgmts:{impersonationLevel=impersonate}//" & _      strComputer & "/root/default:StdRegProv")   objRegistry.GetStringValue HKEY_LOCAL_MACHINE,strKeyPath,strValueName,strValue   If Len(strValue) Then     getNetBIOSDomain = strValue   End If   Err.Clear   On Error Goto 0   Set objRegistry = Nothing End Function 


 



Listing 15

' Listing 15: The getOU Function  Private Function getOU(ByVal strComputer, ByVal strNTBDomain)   Dim objTrans, strDN   Const ADS_NAME_INITTYPE_GC = 3   Const ADS_NAME_TYPE_NT4 = 3   Const ADS_NAME_TYPE_1779 = 1   Set objTrans = CreateObject("NameTranslate")   objTrans.Init ADS_NAME_INITTYPE_GC, ""   objTrans.Set ADS_NAME_TYPE_NT4, strNTBDomain & "\" _     & strComputer & "$"   strDN = objTrans.Get(ADS_NAME_TYPE_1779)   getOU = getReverseOU(strDN)   Set objTrans = Nothing: Set strDN = Nothing End Function 


 



Listing 16

' Listing 16: The convertTime Function  Private Function convertTime(seconds)   Dim ConvSec, ConvMin, ConvHour, ConvDay, strDay   ConvSec = seconds Mod 60   If Len(ConvSec) = 1 Then     ConvSec = "0" & ConvSec   End If   ConvMin = (seconds Mod 3600)\60   If Len(ConvMin) = 1 Then     ConvMin = "0" & ConvMin   End If   ConvHour =  seconds\3600   If Len(ConvHour) = 1 Then     ConvHour = "0" & ConvHour   End If   If ConvHour = 24 Then     ConvHour = 00     convertTime = "1 Day, " & ConvHour & ":" & ConvMin & ":" & ConvSec   ElseIf ConvHour > 24 Then     ConvDay = ConvHour\24     ConvHour = ConvHour Mod 24     If Len(ConvHour) = 1 Then ConvHour = "0" & ConvHour     If ConvDay = 1 Then       strDay = " Day, "     Else       strDay = " Days, "     End If     convertTime = ConvDay & strDay & ConvHour & ":" & ConvMin & ":" & ConvSec   Else     convertTime = ConvHour & ":" & ConvMin & ":" & ConvSec   End If End Function 
Comments
Add New Search
Write comment
Name:
Email:
 
Website:
Title:
UBBCode:
[b] [i] [u] [url] [quote] [code] [img] 
 
 
:angry::0:confused::cheer:B):evil::silly::dry::lol::kiss::D:pinch:
:(:shock::X:side::):P:unsure::woohoo::huh::whistle:;):s
:!::?::idea::arrow:
 
Please input the anti-spam code that you can read in the image.

3.26 Copyright (C) 2008 Compojoom.com / Copyright (C) 2007 Alain Georgette / Copyright (C) 2006 Frantisek Hliva. All rights reserved."

 
< Prev

Related Items