| Don't Let Your AD Scripts Hang on You |
|
|
|
| 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:
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:
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
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 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 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:
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 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
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
Powered by !JoomlaComment 3.26
3.26 Copyright (C) 2008 Compojoom.com / Copyright (C) 2007 Alain Georgette / Copyright (C) 2006 Frantisek Hliva. All rights reserved." |
|||||||
| < Prev |
|---|








