Callout B in Listing 2 highlights the code that adds an icon to the TSA. First, the code tells the TSA to allocate a location for an icon by calling the CreateIcon() subroutine. Next, the code updates the TSA with an icon (in this case, the smiley face icon), then calls the UpdateNormalStatus() subroutine to add a tool tip. The last line in callout B traps the script's interrupt signal ($SIG{'INT'}). Without this line, if the script were to terminate before it could tell the TSA to remove the icon, the icon would continue to show as if the script were still running. Only by moving your mouse over the orphaned icon would Windows discover that the icon needs to be removed. To prevent this situation, the script traps the interrupt signal. That way, if you use Ctrl+C to stop the script, the TerminateScript() subroutine will remove the TSA icon. However, if the script terminates abruptly (e.g., it's killed by the Task Manager or the die command), the icon will remain in the TSA until you manually click it with a mouse.
Callout C shows the last block of setup code that runs before the script starts monitoring machines. This code creates a global Win32::PingICMP object ($PingObject). The script will use this one object to ping all the machines. This script makes use of Win32::PingICMP instead of the more common Net::Ping module because Net::Ping works only on Win32 machines if the script is run under an administrator account.
With the preparatory work finished, the script can finally start pinging machines. The script repeatedly calls various subroutines to ping machines and check whether the pings are successful. The first of these subroutines is PingHosts(), which Listing 3 shows. This simple subroutine enumerates each machine's name. These names are exposed as keys of the passed-in $HostList hash reference. For each machine, PingHosts() calls the global $PingObject's ping method, which callout A in Listing 3 shows. If the first ping is unsuccessful, the subroutine decrements the machine's ping count by 1 and records the date and time. The script later uses the date and time information to determine how long a machine has been unavailable. If a subsequent ping is unsuccessful, the subroutine again decrements the machine's ping count by 1 but doesn't record the date or time. When the ping is successful, the subroutine resets the ping count.
After the PingHosts() subroutine runs, the script calls the CheckHosts() subroutine. CheckHosts() first checks to see whether each machine's ping count has reached 0. When the ping count is 0, the subroutine updates the $AlertMessage and $Message strings with details about the unreachable machine. It also increments the $iDownCount variable's value by 1.
The $iDownCount variable tracks how many machines are currently unavailable. When $iDownCount isn't 0, there's at least one unavailable machine. In response, CheckHosts() raises an alert by calling the Alert() subroutine. When $iDownCount is 0, all hosts are available and the subroutine checks to see whether an alert is still shown. If so, it clears the alert. Otherwise, CheckHosts() calls the UpdateNormalStatus() subroutine to update the TSA icon's tool tip.
As I just mentioned, the Alert() subroutine raises an alert. The TSA has limitations to how much data can be displayed, so Alert() first truncates the alert title, the alert message, and the tool-tip text. Next, the subroutine sets a flag so that the script knows that it's currently displaying an alert. Then, the subroutine updates the %IconData hash with information. Finally, Alert() calls the UpdateTSAIcon() subroutine.
Similarly, the ClearAlert(), ChangeIcon(), and ChangeText() subroutines update the %IconData hash with information, then call the UpdateTSAIcon() subroutine. Note that each call to UpdateTSAIcon() passes in a flag. This flag indicates what information from the %IconData hash will be used.
Listing 4 shows UpdateTSAIcon(). This subroutine is where all the TSA magic takes place. Anything that the script does regarding the TSA goes through this subroutine. UpdateTSAIcon() begins by creating a packed $pNotifyIconData scalar variable. This variable uses the NOTIFYICONDATA constant as the packing template, which creates a NOTIFYICONDATA data structure just like a C or C++ program would. This data structure contains information that the system needs to process TSA messages.
A NOTIFYICONDATA structure contains specific members. The first member is the size (in bytes) of the data structure. (You can learn about the other members at http://msdn.microsoft.com/library/enus/shellcc/platform/shell/reference/structures/notifyicondata.asp.) To determine how big the structure will be, UpdateTSAIcon() packs the data structure with nothing but 0s, as callout A in Listing 4 shows. After the subroutine determines the size, it fills the data structure with actual values from the %IconData hash, as callout B in Listing 4 shows.
After $pNotifyIconData is packed with actual values, UpdateTSAIcon() passes it to Windows' Shell_NotifyIcon function. This function creates, modifies, and deletes icons from the TSA. Details about this function are available at http://msdn.microsoft.com/library/enus/shellcc/platform/shell/reference/functions/shell_notifyicon.asp.
The final subroutine worth noting is the TerminateScript() subroutine. HostMonitor calls this subroutine when you use Ctrl+C to terminate the script. TerminateScript() calls the RemoveIcon() subroutine, which in turn calls UpdateTSAIcon(). UpdateTSAIcon() then removes the script's icon from the TSA.
The TSA Is a Scripter's Friend
Scripters underutilize the TSA. Considering the complexity, there's no doubt as to why. But as HostMonitor shows, using TSA is not only possible, but also probably easier than you might have thought. By using this script as a template, you can add a TSA icon to any script.