Someone once asked me an intriguing question: How can you hide the DOS box in which Perl runs? The person wanted to hide a logon script so that users couldn't try to stop the script—a simple idea for which Perl doesn't provide support.
Fascinated with the notion, I started looking for a way to hide a running Perl script. Then I stumbled on Aldo Calpini's Win32::GUI extension. This code provides Win32 Perl GUI capabilities that rival Tcl/Tk. The extension included a script that minimized the console window in which the Perl script runs, effectively hiding the window. Inspired by this script, I dug into my copies of MSDN Magazine and discovered some nifty, seemingly magical tricks.
You can perform many of the tricks I cover this month with various extensions, such as Win32::GUI and Win32::Console (another one of Calpini's Perl modules). Such extensions are useful for creating windows and managing them. However, with my home-brewed version of the Win32::API extension—the Win32::API::Prototype extension—you can manage existing windows. Win32::API is one of the most powerful and useful Win32 Perl extensions because it lets you call directly into DLL functions. However, some aspects of this extension are confusing, such as having to pass in an anonymous array that specifies a function's parameters. So, I adapted Win32::API to hide those confusing aspects.
This column assumes you're familiar with the Win32 API and the Win32::API extension. In addition, to use the code and scripts in this column, you need to download and install the Win32::API::Prototype extension. To learn where you can download the various extensions I discuss in this column and where you can obtain information about the Win32 API and Win32::API extension, see the sidebar "Obtaining the Extensions and More Information."
With the exception of the script in Listing 4, page 14, the code and scripts in the listings work on a standard installation of ActivePerl's Win32 Perl on Windows 2000 but not Windows NT because they use a Win32 API function called GetConsoleWindow() that Win2K introduced. However, the Perl versions of the listings in the Code Library on the Windows Scripting Solutions Web site (http://www.winscriptingsolutions.com) include code that lets you use them on NT machines.
Each graphical object on a Win32 machine has a handle associated with it. Thus, every window, button, toolbar, and icon has such a handle. This handle is similar to a file handle in that it represents a data structure. However, in this case, the data structure represents a graphical object instead of a file object.
You can use a window's handle to identify and learn about that window. For example, the script in Listing 1 identifies and prints out the coordinates of the current console window (i.e., the window in which the Perl script is running). This script uses the Win32::API::Prototype extension's GetConsoleWindow() function to obtain the console window's handle. After you have this handle, you can call all sorts of cool window functions, including functions to minimize, maximize, hide, and restore windows.
Minimizing and Maximizing Console Windows
Because the console window is typically no different from any other window, you can treat it as a regular window. For example, you can minimize and maximize it with the ShowWindow() function. This function has the syntax
BOOL ShowWindow( HWND hWnd, int iCommand );
The first parameter, HWND hWnd, is the window handle you want to manipulate. The second parameter, int iCommand, is an integer whose value specifies what you want to do with the window. Table 1 lists the different commands that you can specify.
You can use this function when you need to control how a Perl console window is displayed. For example, suppose that a Perl script needs to process a lot of data, but you don't want the script's console window to bother users while the script chugs away. As the script in Listing 2 shows, you can use the ShowWindow() function to minimize the Perl console window during data processing, maximize the window to display the script's results after the script finishes executing, then restore the window to its original size and position.
Minimizing a Perl console window is quite useful when you want to temporarily move it out of the way. If users want to view the window, they can select the window to restore or maximize it.
Hiding Console Windows
Many systems administrators create user logon scripts that perform many tasks, including cleaning up Temp directories and connecting users to various shared drives and printers across the network. However, some users cancel the logon script if it takes too long or for some other reason. Although you could minimize the logon script's console window, users might discover that the window is only minimized, then proceed to restore the window and terminate the logon script. In this situation, hiding Perl's console window comes in handy.
Listing 3, page 14, contains a script that hides the console window. As callout A in Listing 3 shows, you hide the console window by using the ShowWindow() function with two parameters: the GetConsoleWindow() command (represented by $hWnd), which obtains the console window's handle, and the SW_HIDE (0x00) constant, which hides the console window. By the time the next line of code runs, the console window is hidden and its icon is no longer displayed in the task bar. Interestingly, Win2K displays the window's icon when you press Alt+Tab, but selecting that icon doesn't restore the window.
Callout B in Listing 3 highlights one of the most important parts of this script. This line restores the console window. Restoring the window is exceedingly important because otherwise the window will remain hidden, even after the script ends. If the script terminates without restoring the window, you'll have an open console window that you can't close because it's hidden. The only way to solve this problem is to terminate the script with the task manager or a kill command.
Minimizing Different Types of Windows
Thus far, I've shown you how to manipulate a console window. Although helpful, having a script that manipulates any type of window would be more useful. Minimize.pl in Listing 4 is an example of a script that does just that. You can use this script to minimize any window, as long as the window has a title.
As callout A in Listing 4 shows, Minimize.pl uses a Win32 API function called FindWindow(). If you provide FindWindow() with the title of a window (i.e., the text in the window's title bar), the function compares that title against the titles of any open windows. If the function finds a match, it returns that window's handle. FindWindow() performs case-insensitive comparisons. However, the title you specify must be the complete string. If you specify a partial string, the function returns the value of 0, which means it can't find a match.
The FindWindow() function's first parameter is the window's class name. For most Perl users, class names are unimportant. All you need to do is specify a value of 0.
The window's title is the function's second parameter. Minimize.pl is set up to accept the window's title through the command line. For example, if you have the MSDN Library - July 2000 window open, you type
perl minimize.pl "MSDN Library - July 2000"
at the command line. This command prompts Minimize.pl to run, which, in turn, minimizes that window.
Enumerating Open Windows
Although Minimize.pl can minimize any open window, it has one drawback: You need to know and manually enter the complete title of each window you want to minimize. An even better solution would be a script that automatically discovers the title of each open window and determines whether to minimize that window based on the criteria you specify. Such a script would prove useful when you need to minimize multiple windows, such as all instances of Notepad.
Listing 5 contains an example of such a script. Minimize_all.pl enumerates each open window and determines whether to minimize that window based on multiple parameters you specify at the command line. These parameters are the strings the script needs to look for in the titles of the open windows. For example, if you type
perl minimize_all.pl notepad dos
at the command line, the script minimizes all windows that have either notepad or dos in their titles. The script uses a case-insensitive regular expression to compare the specified strings against each window's title. As a result, you don't have to worry about case and can use regular expression wildcards.
Minimize_all.pl performs its magic by applying several new Win32 API functions: GetTopWindow(), GetWindow(), GetWindowTextLength(), and GetWindowText(). If you specify null (i.e., the value of 0) as the GetTopWindow() function's argument, the function returns the handle of the topmost window. After you have the topmost window's handle, you can use it in a call to the GetWindow() function. In this case, you pass in the command value of 0x02, which tells GetWindow() to return the window handle of the next window. By repeatedly calling GetWindow(), the script ends up cycling through all the windows.
The GetWindowTextLength() function returns the number of characters (not bytes—in case the extension was compiled using Unicode) of the specified window's title. The GetWindowText() function lets you pass in a window handle, a string buffer, and the length of the buffer. GetWindowText() populates the buffer with the title of the window. This code is pretty straightforward, with one exception: The string must be a C string, not a Perl string.
Unlike a C string, the size of a Perl string increases as needed. A C string, however, is a block of memory that must be allocated before being used. So, before you call GetWindowText(), you need to first allocate a block of memory, which is the purpose of the code at callout A in Listing 5. To determine how much memory to allocate, the code uses GetWindowTextLength() to determine the string's length. The code multiplies the number of characters to allocate by $CharSize to accommodate Unicode strings in case the Win32::API function was compiled with Unicode defined. Finally, the code allocates the memory by creating a string of the specified length out of null characters ("\x0") because C strings are null-terminated strings (i.e., ASCII strings terminated by the null character). The code at callout B in Listing 5 removes any remaining null characters, effectively cleaning up empty characters and Unicode strings.
As these examples show, you can coax Perl into working magic with windows. Minimizing, maximizing, hiding, and restoring console and other windows isn't difficult. You just need a bit of thought and the Win32::API or Win32::API::Prototype extension. Until next month, happy Perling—and don't forget to restore your hidden windows.