On a hectic day in ABC Company's support center, the vice president of manufacturing calls in because he can't review an important Request for Proposal (RFP) before the deadline. He dials into the network to find his V drive missing. Infections of the latest virus are rampant; apparently, about 25 percent of the company's computers haven't run the virus software update that the logon script launches. The Windows XP project team needs a current hardware inventory for planning purposes, but the inventory database is out-of-date.
Logon scripts perform many tasks, such as mapping network drives, connecting printers, and scanning for viruses. Logon scripts also let you centrally manage which resources computers should connect to after they authenticate to the network. For many organizations, logon scripts are the most reliable way to run management tasks on thousands of computers.
Windows logon scripts are challenging because they're triggered reliably only when the computer that authenticates the user account is hard-wired to the network. Logging on over VPN and RAS connections usually fails to trigger logon scripts properly. Faulty triggering occurs in part because the user has already authenticated to local cached credentials and the network logon is a secondary authentication. On XP and Windows 2000 machines, logon scripts also aren't triggered properly because both OSs can use the NIC to connect to the network dynamically after the computer is booted and fully operational. Regardless of why a system startup doesn't trigger a logon script, its failure to do so can keep users from accomplishing their tasks and administrators from effectively managing mobile systems.
The script I present uses Windows Management Instrumentation (WMI) event monitoring to solve this problem. The script monitors for connections to the corporate network, then triggers logon-script execution. Although the script requires that WMI and Windows Script Host (WSH) be installed, you can deploy these services even with Windows 95. To ensure backward compatibility, I tested the script on a Win95 OEM Service Release 2 (OSR2) machine with WMI and WSH installed—and it worked well. Windows versions before Win2K don't include WMI and require that you install the runtimes. You can obtain the runtimes at http://msdn.microsoft.com/downloads/default.asp?url=/downloads/sample.asp?url=/msdn-files/027/001/576/msdncompositedoc.xml. (Any machines that are Microsoft Systems Management Server—SMS—clients will have the runtimes, and you shouldn't update them with the downloadable runtimes.) Now, let me introduce the script goals, take you through the script, discuss how it's executed, and describe how to implement it.
WMI Event Monitoring
I won't review the fundamentals of WMI; they've been well covered in print and on the Web. However, I briefly discuss WMI's relevant event-monitoring abilities to show you how WMI lets this script accomplish its task. WMI's built-in event-monitoring system tracks events in any of the WMI classes. Any script can subscribe to these events and perform actions based on the occurrence of monitored events. Event-based systems generally reduce the CPU cycles used and the delays associated with polling a system element for changes in state. If designed correctly, a WMI event-monitoring script doesn't need to spin in a loop to monitor events; therefore, the script's CPU overhead is virtually unnoticeable. Internally, WMI performs some polling operations for event monitoring. However, the operations take place within the efficient WMI namespace. Your script doesn't need to start additional processes, reactivate idle processes, connect to external data sources, or wait for disk latency.
WMI event monitoring can enable event monitoring even where that capability wasn't designed into the original API. When a WMI provider is built for the API, the provider will automatically notify WMI when instances of its class are created, updated, and deleted. Even if you could employ a crack Windows system programmer, he or she might not find a combination of base APIs that allows reliable hooking and monitoring of VPN, RAS, and dynamic connection events across all versions of Windows (XP through Win95). Such a complex undertaking is in stark contrast to writing a short VBScript to monitor these events and take appropriate actions.
The Script Design Goals
Typically, when I create a script to be used in many companies, I make as few assumptions about the run environment as possible. In keeping with this approach, I adhered to the following design goals for the logon monitor script. The script should
- offer a reliable solution to a complex problem
- provide maximum compatibility for multiple versions of Windows
- have minimal performance impact on client computers and on user experience
- support simple tailoring that would let it run in other environments
- provide for easy implementation (e.g., no Administrator rights required)
A set of design goals that defines the nature of the scripting effort helps you create flexible scripts that you can adapt to additional environments and uses.
The Logon Monitor Script
The first two lines of the script, which Listing 1 shows, define two variables, which I discuss later. At callout A in Listing 1, the GetObject function establishes a connection to WMI and sets up an event-notification query. The SELECT string specifies that the script should receive the name (TargetInstance.Name) of any new or changed instance (FROM __InstanceOperationEvent) of the WMI class that represents network connections (TargetInstance ISA 'Win32_NetworkAdapterConfiguration'). Note that Win32_NetworkAdapterConfiguration covers all network connections whether or not a physical network adapter is involved. In the case of a RAS connection, a VPN, and other connection types, the RAS or VPN API creates a "virtual adapter" that subsequently appears in this WMI class. The class __InstanceOperationEvent notifies the script when it detects a new RAS or VPN connection and when a NIC card is plugged into the network after user logon. (Although __InstanceOperationEvent also notifies the script when a connection is deleted, this script discards that type of notification.)
WMI notifies the script about any events that match the criteria in the query. The WITHIN 4 portion of the query is the amount of time in seconds that WMI should use internally to poll the class for events. As I mentioned before, this internal polling is very efficient. (To attempt to detect the processor overhead introduced by this event query, use Performance Monitor to monitor the WMI service. Performance Monitor will reveal that the script doesn't generate any CPU overhead.) Most versions of Windows through Win2K call the WMI service winmgmt.exe. In XP, the WMI service is an instance of svchost.exe. To accurately determine the processor overhead, compare performance counter values for processor utilization—per process—before the script is running and while it's running.
The Do loop. The script's Do loop isn't an infinite polling loop—it allows continuous monitoring for connection events. Without this loop, the script would execute only once. The script must stay in this loop to handle the cases in which users who connect to the network by using VPN, RAS, or Dynamic NIC connections keep their laptops on continuously (using standby or hibernate).
The line at callout B prevents the need to poll a configuration event (as a traditional monitoring script would do). The script waits at this point until WMI notifies it about an event that matches the criteria specified. When WMI delivers a matching event to the script, execution continues, beginning with the next line.
The next line ensures that the connection object returns a string containing the Ipaddress property. This approach automatically screens out several unwanted events—most notably, a connection deletion, which does not return an array of strings for the IP address variable. In addition, some connection types (e.g., VPNs) have multiple modifications of their Win32_NetworkAdapterConfiguration instance before they acquire a valid IP address. The VarType check helps ensure that the monitoring script ignores these interim events.
The SubnetMatch function. The Logon Monitor script uses a basic method to identify the target network, which is important for a couple of reasons. In addition to reducing the number of "false matches" against other networks, this approach prevents the script from attempting to run the logon script when there are changes to other connection types that the Win32_NetworkAdapterConfiguration class tracks. Certain types of wrapper protocols as well as infrared (IR) ports also create and modify the list of instances in this class. Subnet matching helps screen these out so that users don't experience logon scripts running at odd times.
The SubnetMatch function provides the matching capability. Although a line-by-line explanation of how SubnetMatch works is beyond the scope of this article, you need to know what services it provides. This SubnetMatch function uses the computer's IP address and searches for matches in a list of subnets that you supply. The function returns a Boolean (true or false) value and places the list of matches in an array.
The SubnetMatch routine supports classless subnetting, so it generates accurate results with networks that use any type of subnetting scheme. Because the function takes a list of subnets, you can supply individual subnets that make up some type of meaningful collection such as Building A, London Campus, or Finance Division.
You can use supernet addresses for accurate matches to a segment of contiguous subnets. The most common use of supernets would be to match for any connection to the entire company network. If your company uses a single class-B address, you can use that address instead of a long list of subnets. Using supernets simplifies the script and improves the performance of the matching function for complex networks. When you use supernets, the script will be more likely to find matches on networks other than the target corporate network. You can modify the script to identify networks with an extremely high degree of accuracy by having it retrieve and compare other TCP/IP and connection attribute values from the event the script analyzes from the TargetInstance object. The DNS suffix, WINS Servers, and DNS Servers are good examples of unique identifying information that can help ensure that the connection is to the entire network. The later discussion about graceful failover explains how the script handles false matches.
The code at callout C makes a call to the SubnetMatch function. The function takes four parameters, the first of which is the list of subnets (aSubnetList) that you define. This array is a list of IP address and subnet mask pairs separated by a forward slash (/). Because the list is processed sequentially, you should list the subnets that match the largest number of computers first (e.g., all supernets, then subnets with the most mobile clients, then other subnets).
The second parameter is the IP address of the connection that WMI just returned to the script. The script references the IP address as part of the ConnectEvent object with the name Connect-Event.TargetInstance.Ipaddress(0). You can use other property names of the Win32_NetworkAdapterConfiguration class to retrieve many other attributes of the connection.
The third parameter is bAllMatches. If this parameter is set to True, SubnetMatch finds all matches. If this parameter is set to False, SubnetMatch returns after finding the first match. The False setting performs better if the script must check many subnets.
The fourth parameter is the name of the array that SubnetMatch will populate with the list of matches. (This array will contain more than one value only if you set bAllMatches to True. Retrieving all matches can help you debug large lists of subnets.) Because this script doesn't need to know which subnets match (this generic subnet match routine is designed to handle many cases), it doesn't use the returned list.
The SubnetMask function performs some calculations, determines which subnets in aSubnetList match the IP address of the computer, and returns a Boolean value (True or False) that indicates whether a match was found. The line at callout D simply checks whether SubnetMatch found a matching subnet before continuing.
Executing the Logon Script
The lines at callout E create a shell object, map a network drive, and use the shell object's run method to execute the logon script. Because they require a default directory that Universal Naming Convention (UNC)—based execution doesn't provide, .bat and .cmd logon scripts require a drive letter. If your logon script is a .vbs, .exe, or other script that doesn't require a drive letter, you can remove all the code for mapping the drive (callout F) and retain only the code to create the shell object and execute the script directly from a UNC. Listing 2 shows the modified segment.
Graceful failover. The logon monitor script uses graceful failover to handle scenarios in which a subnet address appears to match the defined corporate network but the logon script is not available at the location the script assumes. Graceful failover means that if the logon monitor script can't execute the logon script, all error messages are suppressed and execution continues. The On Error Resume Next and Err.clear statements at callout E support the graceful failover that lets the script continue execution without errors when the logon script can't be found.
One graceful-failover scenario occurs when the network connection is severed. WMI sends this event to the logon monitor script because __InstanceOperationEvent traps deletions. The object returned contains the attributes of the connection that was just deleted.
Another scenario occurs when the computer running the logon monitor script connects to a network whose IP address matches the target network but which isn't really the correct network (e.g., an Internet ISP, another corporation's network). In this scenario, the script will attempt to run the logon script but won't find it.
Finally, additional scenarios involve instances of matches that include devices that don't connect to the corporate network. For example, certain types of IR connections use IP addresses that might overlap with the defined corporate subnets.
You must change the Listing 1 and Listing 2 lines that contain references to the share name and logon script name to the correct names for your implementation. You might want to comment out the On Error Resume Next line during testing so that you can detect mistakes made in these values.
Implementing the Logon Script
The logon monitor script must be installed on each computer's local hard drive in one of the many places that Windows looks for programs to initiate during user logon. The script should be configured to run in the user's context to ensure that connections and other logon script functions are made with the user's credentials. Because users sometimes meddle with items configured in their Startup Folder, you should initiate the script by using the HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Run registry key.
Because this hooking script must reside on each computer, you should decide ahead of time how you will update it when that becomes necessary. With a little creative coding, you might even make the script self-updating.
WMI event monitoring can help you solve a problem that many Windows administrators face. I hope that applying WMI to the logon script—triggering problem demonstrates some of the creative possibilities that WMI brings to scripting.