PREVIOUS  TABLE OF CONTENTS  NEXT 

Win32 Perl

Dave Roth

Regardless of how I feel about Microsoft Windows, they're here to stay and I have to administer a WAN of them. I've gotten used to them in spite of their limitations, but there's one burning issue that I can't get over: the lack of a decent scripting shell. The DOS-like command line isn't helpful for anything more than the most simple of automation. That's where Win32 Perl comes in.

The Win32 port of Perl works on both Windows NT and Windows 95, thanks to the folks from ActiveState (http://www.activestate.com). Dick Hardt and his team have done a remarkable job at developing the port and implementing useful features that make administering Win32 networks a breeze.

Win32 Perl is a Win32 native application that, for the most part, works just like its UNIX brethren and can handle almost anything that you can throw at it (with a few exceptions discussed later). If you subscribe to this magazine, you'll be right at home with it. Well, almost right at home, as you'll see.

Obtaining and Installing Win32 Perl

There are currently two major versions of Win32 Perl: the ActiveState version and the core distribution version. The O'Reilly Perl Conference initiated an ongoing effort to merge them.

The core distribution version. The Official Perl distribution available from the CPAN has rolled in Win32 support. Nick Ing-Simmons and Gurusamy Sarathy have done a great job with this port, which contains both source and binaries. Just make sure that you read the documentation, because you might need to jump through a few hoops if you compile the source code yourself. The Win32 specific extensions found in the ActiveState version are available, but come in a separate package called libwin32, which you can download from http://www.perl.com/CPAN/authors/id/GSAR/. So far all of my scripts originally written for ActiveState have worked perfectly with the core.

The ActiveState version. This is the version most users have because it's been around for a while. ActiveState distributes the source and binaries (for both x86 and Alpha chips). The binaries come with everything you need to get started. You just download it (check out their home page for the latest build), run the self-extracting archive, and everything snaps into place. Or, if you have a C compiler (Microsoft Visual C++ is preferred) then you can download the source code and compile the beast. The ActiveState port comes in three flavors; it's important that you know the differences between them: the regular Perl executable, PerlIS, and PerlScript.

The Perl executable. The ActiveState Perl executable (derived from Perl 5.003) is a Win32 console application. It runs as you would expect Perl to run, on a console-based command line.

PerlIS. PerlIS.DLL is an Internet Server API (ISAPI) extension designed to work with Microsoft's Internet Information Server (IIS). It works just like the Perl executable, but since it's a .DLL file ( A .DLL file is a Dynamic Linked Library, a library of functions loaded during run time and dynamically linked to by the calling application. It is similar in function to the ELF library format in the UNIX world. ) it is loaded only once during the life of the web server, remaining in memory until the server process has completed. You might want to use this for CGI scripts; the DLL links Perl into the web server, so there's no overhead for starting a new Perl process. This is faster than starting a script with perl.exe, but of course there's no speed gain for script execution.

PerlScript. Then there's PerlScript, similar in function to JavaScript and VBScript. It's an interface between Win32 Perl and Microsoft's Active Server Pages (ASP) that allow you to embed Perl code in HTML pages for processing by either web server or browser. [See PerlScript]

For the rest of this article, I'll concentrate on the ActiveState port of the Perl executable since it has the largest user base.

Gotchas & Dilemmas with the Win32 Port

I've used Win32 Perl for CGI scripting, database maintenance, and system administration, and in the process compiled a list of gotchas and dilemmas. Gotchas are little issues that I try to remember when coding; dilemmas are larger problems that I've had to write code to work around.

Gotchas. Most of these are annoyances that can be resolved easily. Novices spend most of their time grappling with these issues when experimenting with code from Perl books.

In Search of the Win32 Guru

Dilemmas. Dilemmas are issues that aren't so easy to work around. These dilemmas pose a problem with porting scripts from other platforms such as UNIX. To ActiveState's credit, most of these dilemmas are either technical restrictions due to the Win32 platform, or exist simply because they haven't had time to address them yet.

What about DOS?

When a new console processes is started, it begins with three open filehandles: STDIN, STDOUT, and STDERR. Each of these Win32 filehandles are mapped to C runtime filehandles with file descriptors of 0, 1, and 2 respectively. Let's say that your parent process opens an anonymous pipe:
pipe(READ, WRITE);
and then redirects STDIN to the READ filehandle:
open(STDIN, "< &READ");
When the child is created it can inherit this pipe as its STDIN. If the parent process then writes data to the pipe, the child can read it. It's also possible to use a socket instead of the pipe. This process works so long as you don't use the CREATE_NEW_CONSOLE flag for the new process, which creates a new console window and resets STDIN, STDOUT, and STDERR. This is obviously a poor replacement for fork() but with a bit of tweaking you can convince Win32 Perl to act as a server spawning children. The following two scripts show this process. The scripts are assumed to be in c:\perl\test, the Perl executable in c:\perl\bin.

Server Script

use Win32::Process; 
%Data = ( 	one => 1, 	two => 2, 	three => 3 ); 
pipe(READ, WRITE); 
select(WRITE); 
$| = 1; 
select(STDOUT); 
open(SAVEIN, "<&STDIN") || die "Can't save STDIN\n"; 
open(STDIN, "<&READ") || die "Can't redirect STDIN\n"; 
select(STDIN); 
$| = 1; 
select(STDOUT); 
Win32::Process::Create($Process, 
                      "	c:\\perl\\bin\\perl.exe", 
                      "c:\\perl\\bin\\perl.exe", 
                      "c:\\perl\\test\\client.pl", 
                      	1, NORMAL_PRIORITY_CLASS,
                      "c:\\perl\\test"); 
open(STDIN, "<&SAVEIN"); 
close(SAVEIN); 
close(READ); 
print "$0: Sending variables to child...\n"; 
foreach $Temp (keys(%Data)){ 
    print "$0:\t$Temp=$Data{$Temp}\n"; 
    	print WRITE "\$Data{$Temp}=$Data{$Temp};\n"; 
} 
print "$0: Finished sending variables.\n"; 
close(WRITE); 
print "$0: About to terminate. Waiting for <RETURN>...\n"; 
<STDIN>; 
print "$0: End.\n";

Client Script

print "$0: Starting.\n"; 
print "$0: Reading in variables...\n"; 
while(<STDIN>) { 
    eval($_); 
    print "$0: \t$_"; 
} 
print "$0: Finished reading variables.\n"; 
print "$0: Dumping variables...\n"; 
foreach $Temp (keys(%Data)){ 
    print "$0:\t$Temp=$Data{$Temp}\n"; 
} 
print "$0: End.\n";

Server Script

use Win32::Pipe;
$Pipe = new Win32::Pipe("My_Pipe"); 
$Pipe->Connect(); 
$Pipe->Write("Hello client, from $0"); 
$Pipe->Close(); 

Client Script

open(CLIENT, "< //./pipe/My_Pipe") || die; 
$Data = <CLIENT>; 
print "$Data\n"; 
close(CLIENT);

Benefits of Win32 Perl

In addition to the rich features of Perl, Win32 Perl has some nifty extensions that make it a great tool for any administrator. These extensions give you the ability to remotely control, configure, and manage a machine from across the net. I routinely manage my customers' mail, web, and FTP servers in real time over the Internet using Win32 Perl and the Internet. I've written CGI scripts that allow users to change their NT domain passwords or modify other account information, all using the extensions bundled with Win32 or available on the Internet.

Extensions. Win32 Perl comes with a set of useful platform specific extensions that provide an interface to some of the Win32 API features. I've stumbled upon a few extensions that were missing segments of code or were miscoded. Some of these bugs have been corrected as new builds have been released. If you're not proficient at extension coding (or just have limited time), you should post to Usenet or send mail to the one of the Win32 Perl listservers; chances are someone else has experienced the same problem.

Installing extensions. If you look around your Perl directory tree you'll find that the extension packages (the .pm files) are in your library's Win32 subdirectory. Actual extensions, however, are in perl\lib\auto\win32 instead. In fact, all extensions must go into a subdirectory of perl\lib\auto. For example, when I installed the GD extension I copied the GD.PLL file to c:\perl\lib\auto\gd\tt>. (The extension itself uses a .PLL file extension even though it's a .DLL file.(When you create a Win32 Perl extension, the binary will be a DLL file. Win32 Perl, however, only recognizes it if it has an extension of .PLL, so renaming is necessary. )) Since Win32-based extensions fall into the Win32 namespace they can be found in subdirectories of perl\lib\auto\win32\; the Win32::ODBC extension can be found in c:\perl\lib\auto\win32\odbc\ odbc.pll on my machine.

Win32.pm. The most basic Win32-specific extension is the Win32 module. This isn't an extension so much as a package - there's no WIN32.PLL file. The methods found in this package are already built into Perl, but need to be accessed via this package. (The actual source code is found in ntxs.cpp.) This package gives you, among other things, access to the current user's name, domain, and computer name:

use Win32; 
$User   = Win32::LoginName(); 
$Domain = Win32::DomainName(); 
$Node   = Win32::NodeName(); 

There's also a handy function that returns an error message from the Win32 API for any API call regardless of what extension called it:

	$Error = Win32::FormatMessage(Win32::GetLastError()); 	

Perl and ActiveX

Win32::NetAdmin. The Win32::NetAdmin extension is an administrator's best friend, allowing you to manipulate user accounts and check for group membership. The following subroutine returns a 1 if the user in the specified domain is a member of the specified group:

sub CheckMembership { 
    	my($Domain, $User, $Group) = @_; 
    return Win32::NetAdmin::GroupIsMember(
                          $Domain, $Group, $User) 
        || Win32::NetAdmin::LocalGroupIsMember(
                          $Domain, $Group, $User); 
}	 

An administrator could use a tab-delimited text file or database to store information about users, so that if he ever had to reload the user database into the NT servers, it would be this simple:

open(FILE, "< users.txt") || die "Couldn't open ($!)"; 
@Users = <FILE>; 
close(FILE); 
foreach $Data (@Users){ 
    	chop $Data;
    	($Domain, $User, $Password, $HomeDir, $Password,
		$Comment) = split("\t", @Users);
		Win32::NetAdmin::UserCreate( 
      		  $Domain, 
      		  $User,			
      		  $Password, 
      		  $PasswordAge, 
      		  USER_PRIV_USER,	                   # Privileges 
      		  $HomeDir,	
      		  $Comment, 
      		  UF_NORMAL_ACCOUNT | UF_SCRIPT,	   # Flags 
      		  "c:\\scripts\\$User.bat") 	           # Logon script
		|| print "$User was not added.\n"; 
}

Just a few notes about the Win32::NetAdmin::UserCreate() function shown above: $Domain can be a domain name, a server or an empty string (the default domain); $PasswordAge is ignored and will not be used; the privilege must be USER_PRIV_USER (administrator privileges are assigned by group membership); and for the flags you must at specify at least these two flags ORed together: UF_NORMAL_ACCOUNT and UF_SCRIPT. Other flags that you can use are:

        UF_ACCOUNTDISABLE        Disable the account 
	UF_PASSWD_NOTREQD        No password is required 
	UF_PASSWD_CANT_CHANGE    User can't change the password
	UF_DONT_EXPIRE_PASSWD    The password never expires 

A relative to this extension is my Win32::AdminMisc extension, which provides some additional administrative methods. Among the additional functions are the ability to:

The Registry. Win32 platforms have a configuration database called the registry, a central repository for information about system, user, and application configurations. Nearly all operating system values are stored in this database, from the type of your mouse to your IP address. The Win32::Registry extension allows you access to registries on both local and remote machines. Once you know the basic nature of the Registry, the extension is easy to use. For instance, this code has Windows automatically log on the next time the log on screen appears:

use Win32::Registry; 
if ($HKEY_LOCAL_MACHINE->Create( 
    "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\WinLogon",
    $Key)) { 
    	$Key->SetValueEx("DefaultUserName", 0, REG_SZ,
                     "picard"); 
    $Key->SetValueEx("DefaultDomainName", 0, REG_SZ, 
                     "enterprise"); 	
    $Key->SetValueEx("DefaultPassword", 0, REG_SZ,
                     "hair"); 
    $Key->SetValueEx("AutoAdminLogin", 0, REG_SZ, 1);
    $Key->Close(); 
}

ODBC. The Win32::ODBC extension has become quite popular for both administration and web scripting. It's an interface into the ODBC API that allows your script to talk to heterogeneous databases - provided you have ODBC drivers for them. The extension is easy to use: you connect to a database, submit a query, retrieve the results, and close the database. Here's a simple example that retrieves a user's name and phone number from a fictitious database called PhoneNumbers:

Installing the Activeware Port

 
use Win32::ODBC; 
$O = new Win32::ODBC("PhoneNumbers") || die; 
if (!($O->Sql("select Name, Phone from List"))) { 
    	while($O->FetchRow()) { 
        undef %Data; 
        		%Data = $O->DataHash(); 
        print "$Data{Name}\t$Data{Phone}\n"; 
    	} 
} else { 
    print "Error: ". $O->Error(); 
}
$O->Close(); 

You can, however, use other functions to make more sophisticated multi-cursor queries. The ODBC extension assumes that you have at least a rudimentary understanding of SQL. Information can be found on my web site: http://www.roth.net/odbc.

Other Extensions. Some other popular extensions:

On http://www.netaxs.com/~joc/perlwin32.html, Joe Casadonte maintains FUtils, various file utilities that aren't part of Perl. And finally, Monte Mitzelfelt (monte@conchas.nm.org) maintains FileSecurity, an interface to file permissions.

Conclusion

Win32 Perl is a great way to automate your Win32 platform. If flexibility, power, and price aren't enough to convince NT administrators to give Win32 Perl a try, the enjoyment of programming in Perl should be.

_ _END_ _


When Dave Roth isn't collapsing wave functions he is preaching Perl to his clients. He can be reached at rothd@roth.net.
PREVIOUS  TABLE OF CONTENTS  NEXT