Click here to Skip to main content
Click here to Skip to main content

Tagged as

Win32 Perl Programming: The Administrator's Handbook. Chapter 3 - Tools

, 12 Mar 2001
Rate this:
Please Sign up or sign in to vote.
This chapter covers an assortment of scripts simplify and lessen the workload of an administrator.
<!-- Article image -->
Sample Image
Title Windows NT Win32 Perl Programming: The Administrator's Handbook
Authors Dave Roth
PublisherNew Riders
PublishedOct 2000
ISBN 1578702151
Price US 35.00
Pages 416

Chapter 3 - Tools

Every good administrator assembles a collection of tools that she simply cannot do without. These tools perform specific tasks that enable her to quickly perform her job. A perfect example of such a utility is the popular XCACLS.EXE program that comes with the Windows NT Resource Kit.

This utility (and its little brother CACLS.EXE) dumps any permissions that are applied to a file or directory. Without such a tool, you are doomed to open an Explorer window, select the object in question, right-click, select the Properties context menu, and then go to the Security tab on the resulting property page. If this procedure doesn't sound like very much work, you have not administered a network of thousands of machines with various security permissions applied to various directories and files.

This chapter covers an assortment of scripts that were created for my own toolkit. To be sure, I wrote each of these scripts to simplify and lessen my workload as an administrator. I included these scripts only for the sake of demonstrating how you can leverage Win32 Perl to solve problems that administrators face every day. When you scrutinize the code, it becomes obvious that there is almost no Win32 task that cannot be managed with a bit of Perl coding.

These scripts were not written to be simple or to be tutorials but instead to get into the nitty-gritty of task solving. I recommend that you use a good Win32 API reference while looking at the code that makes up these scripts; in particular, check out Microsoft's MSDN library (http://msdn.microsoft.com/) and, of course, my other book, Win32 Perl Programming: The Standard Extensions (New Riders Publishing).

The File System

The Win32 operating system makes copious use of its file system. As an administrator, you have the burden of managing this important resource. Your job covers the entire range of administrative tasks, ranging from securing files and directories to creating directories and updating outdated files. Many different tools and utilities available from various companies can help you administer your machine's file system. However, you will not be able to easily find some tools.

Many of the existing tools are confusing because they reflect aspects of the file system that are not commonly known, such as alternate data streams. Some tools are not used simply because administrators have no idea that they exist, such as tools used to discover hidden directory shares on remote machines. The following sections focus on file system-related tools that can save you a great deal of work.

Discovering Shared Directories

Ever since Windows for Workgroups was released, sharing directories for others to connect to has been quite simple. From a Windows NT/2000 command line, you can easily share a directory by using the net share command. The following line shares a temporary directory with the name TempDir:

net share TempDir=c:\temp

Alternatively, you can share directories from the File Manager, the Explorer, and the Service Manager GUI applications.

The Win32 world permits you to hide shared directories-that is, share a directory that nobody can see when browsing the network. The only way to access the directory is to know it has been shared and explicitly access the hidden share. You hide directories by appending a dollar sign ($) to the end of the share name. For example, the following command line creates a hidden shared directory called MyHiddenShare$:

net share MyHiddenShare$=c:\temp

Now, only users who know about this hidden share can access it.

You can see which directories your machine is sharing by using this command line:

net share

This line displays all the shared directories on the local machine. However, there is no easy way to discover shared directories on a remote machine. You can always open an Explorer window for a particular machine, but this solution is bulky and displays only nonhidden directories.

Let's say that you need to see what shares are available on some remote machine. Explorer won't help you in this case. You could walk over to the machine, log on, and issue the net share command, but this method is not very convenient, especially if the machine is across town or across the country. And assuming that you don't have some type of Telnet daemon installed, this problem is quite serious.

At this point, you can turn to the DumpShares.pl script (shown in Example 3.1). The actual core of the script revolves around line 11, where a call is made into the Win32::Lanman extension to get a list of shared directory names.

As long as you can physically connect to the remote machine, you can see all its shared directories, regardless of whether or not you are granted administrative privileges on the remote machine. If you lack admin privileges on the remote machine, the shared directory's paths are not visible.

The script takes in any number of remote machine names and tries to resolve each passed-in machine name. If nothing is passed in, the local machine's shared directories are displayed.

Example 3.1 Displaying All Shared Directories on a Remote Machine

01. use Win32;
02. use Win32::Lanman;
03.
04. push( @ARGV, Win32::NodeName() ) if( ! scalar @ARGV );
05. foreach my $Machine ( @ARGV )
06. {
07.   my @List;
08.   $Machine =~ s#^[\\/]*#\\\\#;
09.  
10.   print "\nShare list for ‘$Machine'\n";
11.   if( Win32::Lanman::NetShareEnum( $Machine, \@List ) )
12.   {
13.     foreach my $Share ( @List )
14.     {
15.       my( $Remark, $Path, $NetName );
16.      
17.       $NetName = $Share->{netname};
18.       if( "" ne $Share->{remark} )
19.       {
20.         $Remark = "($Share->{remark})";
21.       }
22.       if( "" ne $Share->{path} )
23.       {
24.         $Path = $Share->{path};
25.       }
26.       else
27.       {
28.         $Path = "No permission to display";
29.       }
30.       push( @ShareList, { name => $NetName,
31.                           remark => $Remark,
32.                           path => $Path } );
33.     }
34.     $~ = Share_Header;
35.     write;
36.
37.     $~ = Share_Info;   
38.     $iCount = 0;
39.
40.     foreach $Share ( sort( {
41.                             lc $a->{name} cmp lc $b->{name}
42.                            } @ShareList ) )
43.     {
44.         $iCount++;
45.         write;
46.     }
47.   }
48.   else
49.   {
50.       print "...not available : ";
51.       print Win32::FormatMessage( Win32::Lanman::GetLastError() );
52.   }
53.  
54.   print "\n";
55. }
56.
57. format Share_Info =
58. @>>> @<<<<<<<<<<<<<<<<< ^<<<<<<<<<<<<<<<<<<<<<< ^<<<<<<<<<<<<<<<<<<<<<<<
59. $iCount, $Share->{name}, $Share->{remark}, $Share->{path} 
60. ~                       ^<<<<<<<<<<<<<<<<<<<<<< ^<<<<<<<<<<<<<<<<<<<<<<<
61.                         $Share->{remark}, $Share->{path} 
62. ~                       ^<<<<<<<<<<<<<<<<<<<<<< ^<<<<<<<<<<<<<<<<<<<<<<<
63.                         $Share->{remark}, $Share->{path} 
64. .
65.      
66. format Share_Header =
67. @||| @<<<<<<<<<<<<<<<<< @<<<<<<<<<<<<<<<<<<<<<< @<<<<<<<<<<<<<<<<<<<<<<<
68. "Num", "Share Name", "Comment", "Path"
69. ---- ------------------ ----------------------- ------------------------
70. .

Volume Information

For an administrator, discovering details of hard drives on remote machines can be essential. Determining how large the volume is, as well as how much space is available, is an important part of drive management. Example 3.2 retrieves such necessary information and other relevant data, such as the volume name, file system type (NTFS, FAT, FAT32, and so on), serial number, and the volume's flags. The flags indicate what features are available for the drive. The flags and their descriptions are listed in Table 3.1.

Table 3.1 Volume Flags

              Flag                                              Description

FS_CASE_IS_PRESERVED The file system preserves the case of filenames when it places a name on disk.
FS_CASE_SENSITIVE The file system supports case-sensitive file names.
FS_UNICODE_STORED_ON_DISK The file system supports Unicode in filenames as they appear on disk.
FS_PERSISTENT_ACLS The file system preserves and enforces Access Control Lists (ACLs-also known as permissions). For example, NTFS preserves and enforces ACLs, but FAT does not.
FS_FILE_COMPRESSION The file system supports file-based compression.
FS_VOL_IS_COMPRESSED The specified volume is a compressed volume-for example, a DoubleSpace volume.
FILE_NAMED_STREAMS The file system supports named streams.
FILE_SUPPORTS_ENCRYPTION The file system supports the Encrypted File System (EFS).
FILE_SUPPORTS_OBJECT_IDS The file system supports object identifiers.
FILE_SUPPORTS_REPARSE_POINTS The file system supports reparse points. This capability allows for features such as using NTFS links and moving infrequently accessed data to long-term storage such as tape backup.
FILE_SUPPORTS_SPARSE_FILES The file system supports sparse files. Sparse files have disk storage allocated only for the parts of the file that contain data. Therefore, large blocks of 0s are not stored to disk to conserve drive storage space.
FILE_VOLUME_QUOTAS The file system supports space allocation limits on a per user basis.


The script accepts a list of drive letters or UNCs like this:

perl volume.pl c:\ \\server\share

In this case, the script displays volume information for both the local machine's C:\ drive as well as for the volume that is shared by the UNC \\server\share. Figure 3.1 illustrates the output from this code. If no parameter is passed in, information regarding the volume for the current directory is displayed.

The code uses a Win32 API function that was introduced with Windows NT 4.0 and Windows 95 OEM Service Release 2 (OSR2). It will most likely fail on platforms earlier than this.

C:\>perl volume.pl c:\ \\server\share\

c:\:
FS    Serial    Volume Name                    Flags
----- --------- ------------------------------ ----------------------------
NTFS  DCF6-AF23 This is my test volume         FS_CASE_SENSITIVE
Total Size: 4,301,788,672                      FS_CASE_IS_PRESERVED
Avail Size: 726,140,928                        FS_UNICODE_STORED_ON_DISK
                                               FILE_VOLUME_QUOTAS
                                               FS_FILE_COMPRESSION
                                               FS_PERSISTENT_ACLS
                                               FILE_SUPPORTS_OBJECT_IDS
                                               FILE_SUPPORTS_SPARSE_FILES
                                               FILE_SUPPORTS_REPARSE_POINTS
                                               FILE_SUPPORTS_ENCRYPTION

\\server\share\:
FS    Serial    Volume Name                    Flags
----- --------- ------------------------------ ----------------------------
NTFS  8063-FA56 New Volume                     FS_CASE_SENSITIVE
Total Size: 18,202,226,688                     FS_CASE_IS_PRESERVED
Avail Size: 12,755,464,192                     FS_UNICODE_STORED_ON_DISK
                                               FILE_VOLUME_QUOTAS
                                               FS_FILE_COMPRESSION
                                               FS_PERSISTENT_ACLS
                                               FILE_SUPPORTS_OBJECT_IDS
                                               FILE_SUPPORTS_SPARSE_FILES
                                               FILE_SUPPORTS_REPARSE_POINTS
                                               FILE_SUPPORTS_ENCRYPTION

Figure 3.1 Displaying volume information.


Example 3.2 Retrieving Volume Information

01. use Win32::API::Prototype;
02.
03. ApiLink( ‘kernel32.dll', ‘BOOL GetVolumeInformation(
04.                                   LPCTSTR lpRootPathName,
05.                                   LPTSTR lpVolumeNameBuffer,
06.                                   DWORD nVolumeNameSize,
07.                                   LPDWORD lpVolumeSerialNumber,
08.                                   LPDWORD lpMaximumComponentLength,
09.                                   LPDWORD lpFileSystemFlags,
10.                                   LPTSTR lpFileSystemNameBuffer,
11.                                   DWORD nFileSystemNameSize )' )
12.     || die "Can not link to GetVolumeInformation()";
13. ApiLink( ‘kernel32.dll', ‘BOOL GetDiskFreeSpaceEx(
14.                                   LPCTSTR lpDirectoryName,
15.                                   PVOID lpFreeBytesAvailable,
16.                                   PVOID lpTotalNumberOfBytes,
17.                                   PVOID lpTotalNumberOfFreeBytes )' )
18.     || die "Wrong version of NT";
19.
20. %FLAGS = (
21.   0x00000001  =>  ‘FS_CASE_SENSITIVE',
22.   0x00000002  =>  ‘FS_CASE_IS_PRESERVED',
23.   0x00000004  =>  ‘FS_UNICODE_STORED_ON_DISK',
24.   0x00000008  =>  ‘FS_PERSISTENT_ACLS',
25.   0x00000010  =>  ‘FS_FILE_COMPRESSION',
26.   0x00000020  =>  ‘FILE_VOLUME_QUOTAS',
27.   0x00000040  =>  ‘FILE_SUPPORTS_SPARSE_FILES',
28.   0x00000080  =>  ‘FILE_SUPPORTS_REPARSE_POINTS',
29.   0x00008000  =>  ‘FS_VOL_IS_COMPRESSED',
30.   0x00000000  =>  ‘FILE_NAMED_STREAMS',
31.   0x00020000  =>  ‘FILE_SUPPORTS_ENCRYPTION',
32.   0x00010000  =>  ‘FILE_SUPPORTS_OBJECT_IDS',
33. );
34.
35. push( @ARGV, "\\" ) if( 0 == scalar @ARGV );
36.
37. foreach my $Path ( @ARGV )
38. {
39.   my $dwVolSize = 256;
40.   my $szVolName = NewString( $dwVolSize );
41.   my $pdwSerialNum = pack( "L", 0 );
42.   my $pdwMaxLength = pack( "L", 0 );
43.   my $pdwFlags = pack( "L", 0 );
44.   my $dwFSNameSize = 256;
45.   my $szFSName = NewString( $dwFSNameSize );
46.   
47.   $Path .= "\\" unless( $Path =~ /\\$/ );
48.   next unless( -d $Path );
49.   $Path = NewString( $Path );
50.  
51.   print "\n$Path:\n";
52.   $~ = DumpVol;
53.   if( GetVolumeInformation( $Path, $szVolName,
54.                             $dwVolSize, $pdwSerialNum,
55.                             $pdwMaxLength, $pdwFlags,
56.                             $szFSName, $dwFSNameSize ) )
57.   {
58.     local %Vol = (
59.       path        =>  $Path,
60.       volume_name =>  CleanString( $szVolName ),
61.       serial_num  =>  sprintf( "\U%04x-%04x",
62.                                reverse( unpack( "S2",
63.                                                 $pdwSerialNum ) ) ),
64.       max_length  =>  unpack( "L", $pdwMaxLength ),
65.       flags       =>  DecodeFlags( unpack( "L", $pdwFlags ) ),
66.       fs_name     =>  CleanString( $szFSName )
67.     );
68.     my $pFree  = pack( "L2", 0, 0 );
69.     my $pTotal = pack( "L2", 0, 0 );
70.     my $pTotalFree = pack( "L2", 0, 0 );
71.     if( GetDiskFreeSpaceEx( $Path, $pFree, $pTotal, $pTotalFree ) )
72.     {
73.       $Vol{free} = FormatNumber( MakeLargeInt( unpack( "L2",
74.                                                        $pTotalFree ) ) );
75.       $Vol{total} = FormatNumber( MakeLargeInt( unpack( "L2",
76.                                                         $pTotal ) ) );
77.     }
78.     write;
79.   }
80. }
81.
82. sub MakeLargeInt
83. {
84.   my( $Low, $High ) = @_;
85.   return( $High * ( 1 + 0xFFFFFFFF ) + $Low );
86. }
87.
88. sub DecodeFlags
89. {
90.   my( $Flags ) = @_;
91.   my $String = "";
92.   foreach my $Key ( keys( %FLAGS ) )
93.   {
94.     if( $Flags & $Key )
95.     {
96.       $String .= sprintf( "%- 30s\n", $FLAGS{$Key} );
97.     }
98.   }
99.   return( $String );       
100. }
101.
102. sub FormatNumber
103. {
104.   my( $Num ) = @_;
105.   {} while( $Num =~ s/^(-?\d+)(\d{3})/$1,$2/ );
106.   return( $Num );
107. }
108.
109. format DumpVol =
110. FS    Serial    Volume Name                       Flags
111. ----- --------- --------------------------- ----------------------------
112. @<<<< @<<<<<<<< @<<<<<<<<<<<<<<<<<<<<<<<<<< ^<<<<<<<<<<<<<<<<<<<<<<<<<<<
113. $Vol{fs_name}, $Vol{serial_num}, $Vol{volume_name}, $Vol{flags}
114. Total Size: @<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< ^<<<<<<<<<<<<<<<<<<<<<<<<<<<
115. $Vol{total}                                 $Vol{flags
116. Avail Size: @<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< ^<<<<<<<<<<<<<<<<<<<<<<<<<<<
117. $Vol{free}                                  $Vol{flags}
118. ~                                           ^<<<<<<<<<<<<<<<<<<<<<<<<<<<
119.                                             $Vol{flags}
120. ~                                           ^<<<<<<<<<<<<<<<<<<<<<<<<<<<
121.                                             $Vol{flags}
122. ~                                           ^<<<<<<<<<<<<<<<<<<<<<<<<<<<
123.                                             $Vol{flags}
124. ~                                           ^<<<<<<<<<<<<<<<<<<<<<<<<<<<
125.                                             $Vol{flags}
126. ~                                           ^<<<<<<<<<<<<<<<<<<<<<<<<<<<
127.                                             $Vol{flags}
128. ~                                           ^<<<<<<<<<<<<<<<<<<<<<<<<<<<
129.                                             $Vol{flags}
130. ~                                           ^<<<<<<<<<<<<<<<<<<<<<<<<<<<
131.                                             $Vol{flags}
132. ~                                           ^<<<<<<<<<<<<<<<<<<<<<<<<<<<
133.                                             $Vol{flags}
134. ~                                           ^<<<<<<<<<<<<<<<<<<<<<<<<<<<
135.                                             $Vol{flags}
136. .

Creating Hard File Links

UNIX users could always create hard links between files, and starting with Windows 2000, so can users of the Win32 platform. Previous versions of Windows NT were able to create links by utilizing a POSIX subsystem; however, with Windows 2000 the link functionality is built directly into the Win32 API. Unfortunately, these links are not as rich as their UNIX brethren. You can create links only for files but not directories. And like UNIX's hard links, they must be located on the same volume. This means that a link must reside on the same hard drive as the file it is linked to. Win32, however, does not have the capability to symbolically link across volumes. The closest equivalent is a shortcut, but that is interpreted by an application, not the operating system (therefore, you cannot rely on all programs to understand how to use them).

This hard-linking capability is supported only on NTFS volumes. Example 3.3 creates such hard links. The script takes in two parameters like this:

perl ln.pl c:\temp\MyExistingFile.txt c:\MyNewLink.txt

The first parameter is the existing file, and the second parameter is the path to where the new linked file will be created. This new link file will be (for all practical purposes) the same file as specified in the first parameter. Editing either of these files results in both files being modified. This makes sense because you really have only one file, just two separate file system entries for it.

After a hard link has been created on an NTFS volume, it is literally linked to the original file. Even if you move the original or linked file, they both still point to the same data. Additionally, you can modify the content of either file, and it updates the same data. This can cause concurrency problems if you are accessing multiple files all linked together. Because the file is only a link to the original file, it can be deleted without affecting the original file.

Example 3.3 Creating Hard Links

01. use Win32::API::Prototype;
02.
03. if( 2 != scalar @ARGV )
04. {
05.     print "Syntax:\n  $0 <File to link to> <New Link File>\n";
06.     exit();
07. }
08.
09. my( $NewLink, $File ) = @ARGV;
10. ApiLink( ‘kernel32.dll',
11.          ‘BOOL CreateHardLink( LPCTSTR lpFileName,
12.                                LPCTSTR lpExistingFileName,
13.                                LPSECURITY_ATTRIBUTES lpSa )' )
14.   || die "This version of Windows does not support CreateHardLink()";
15. if( CreateHardLink( $File, $NewLink, 0 ) )
16. {
17.     print "Link successfully created.\n";
18. }
19. else
20. {
21.     print "Failed to create link.\nError: ";
22.     print Win32::FormatMessage( Win32::GetLastError() ), "\n";
23. }

Creating Directories

Creating directories is not a new concept. All disk-based operating systems such as UNIX, Win32, DOS, and Macintosh have the concept of some file-based container (for example, a directory or folder). You must create a container so that you can move objects such as files and other directories into it. You usually do so by using a command such as md or mkdir. Win32 exposes this functionality through its Win32 API CreateDirectory() and CreateDirectoryEx() functions. It is also available from a DOS box when you use the mkdir or md command. These techniques, however, fail to create intermediary directories. For example, if you run

md Dir1\Dir2

and Dir1 does not already exist, then Dir2 will not be created.

Tip

On the Windows NT and 2000 platforms, if command extensions are enabled, the DOS box md and mkdir commands create any intermediary directories. For more information, run cmd.exe /h.

Even Win32 Perl fails to create any intermediary directories-most likely because it simply calls into the C: library's mkdir() function, which, in turn, calls the Win32 API's CreateDirectory() function.

Example 3.4 creates as many directories as it needs in order to create the passed-in directory. There is nothing Win32-specific about this particular script; therefore, it could run on any platform that runs Perl. This script accepts only one parameter, which must be either a full path (for example, C:\temp\Dir1\Dir2), a relative path (for example, ..\Dir1\Dir2), or a UNC (for example, \\server\share\Dir1\Dir2).

To use this script, just pass in the name of the path to create-for example,

perl makedir.pl \\server\share\dir1\dir2
Example 3.4  Creating Intermediary Directories
01. $Dir = shift @ARGV || die "Syntax: $0 <Dir>";
02. $Result = 1;
03. ( $Root, $Path ) = ( $Dir =~ /^(\w:\\?|\\\\.+?\\.+?\\|\\)?(.*)$/ );
04. print "Creating directory ‘$Dir'...\n";
05.
06. if( -d $Dir )
07. {
08.     print "Directory already exists.\n";
09.     exit;
10. }
11.
12. @DirList = split( /\\/, $Path );
13. $Path = $Root;
14.
15. while( $Result && scalar @DirList )
16. {
17.     $Path .= ( shift @DirList ) . "\\";
18.
19.     next if( -d $Path );
20.     $Result = mkdir( $Path, 0777 );
21. }
22.
23. print ( ( $Result )? "Success" : "Failure (Error: $!)" ), "\n";

NTFS Streams

One of the greatest joys of using the NTFS file system is the ability to create multiple streams in a file. However, most administrators, let alone users, are not even aware of this capability.

Just like any file system, an NTFS file contains data. However, unlike other file systems, in this one the data is stored in what is called an NTFS stream. This stream is simple data that is associated with the file by means of a name. This is very similar to the way a Perl hash works. A stream name is much like a hash key. Data is associated with a hash key, just as data can be associated with a file stream name. By default, all NTFS files have only one stream, the default stream, which is the place data is stored. However, NTFS allows additional streams to be created in a file. Because each stream has a name (with the exception of the default stream), you access the stream by name.

Thinking of multiple data segments for a single file may be mind-bending-but only because it is not common. Think of multiple streams as you would a tar file, which is basically an archive of multiple data files. You could create one file with multiple streams, each stream consisting of data from different files. This would enable you to create a log file for, say, a Web server that consists of only one file. But each day the Web server creates a new log, it creates a new stream in the log file. The end result is that you could have an entire year's worth of Web server log entries consolidated into one convenient file. Then you can easily copy all the logged data from one place to another because you are copying only one file.

A Windows NT/2000 machine can store Macintosh files on its NTFS drives this way. By using alternate data streams, the operating system can store both the Macintosh data and resource forks in one file.

To access an NTFS stream you must refer to the NTFS file as you normally would, but you use a colon followed by the stream name. The format is

path\file_name:stream_name

Here's an example:

C:\temp\MyTestFile.txt:MyFirstStream

Perl can read and write to such NTFS streams using the normal file functions. You can test this capability by running Example 3.5 and Example 3.6 on an NTFS drive. Example 3.5 creates a text file with two streams: the main data stream and an alternative stream.

Example 3.6 reads back the data from both streams in the text file. If you examine the resulting text file, you will notice that the file size reflects only the data in the default data stream. If you were unaware that the file contained multiple streams, there would normally be no way to discover this fact. As you will see, Win32 Perl can be used to discover such alternate data streams.

Example 3.5 Creating a Test File with Multiple Streams

01. $File = "StreamTest.txt";
02. $Stream = "AltDataStream";
03. if( open( STREAM1, "> $File" ) )
04. {
05.   print "Writing to the main data stream.\n";
06.   print STREAM1 "\tWelcome to the main data stream.\n";
07.   close( STREAM1 );
08.  
09.   if( open( STREAM2, "> $File:$Stream" ) )
10.   {
11.     print "Writing to alternative stream ‘$Stream'.\n";
12.     print STREAM2 "\tWelcome to the alternative data stream ‘$Stream'\n";
13.     close( STREAM2 );
14.   }
15.   else
16.   {
17.     print "Error: $!\n";
18.   }
19. }
20. else
21. {
22.   print "Error: $!\n";
23. }

Example 3.6 Reading Multiple Streams from a Test File

01. $File = "StreamTest.txt";
02. $Stream = "AltDataStream";
03. if( open( STREAM1, "< $File" ) )
04. {
05.   print  "This is the main data stream for $File.\n";
06.   while( my $Line = <STREAM1> )
07.   {
08.     print $Line;
09.   }
10.   close( STREAM1 );
11.  
12.   if( open( STREAM2, "< $File:$Stream" ) )
13.   {
14.     print "\n\nThis is the data stream $File:$Stream:\n";
15.     while( my $Line = <STREAM2> )
16.     {
17.       print $Line;
18.     }
19.     close( STREAM2 );
20.   }
21.   else
22.   {
23.     print "Error: $!\n";
24.   }
25. }
26. else
27. {
28.   print "Error: $!\n";
29. }

On Windows NT/2000, the more command can be coaxed into displaying alternate data streams. You do so by redirecting the input into the more command from a data stream like this:

more < test.txt:MyStream

Tip

For some reason, most programs do not permit you to enter valid NTFS pathnames that include alternate streams. For example, an attempt to load test.txt:MyStream into Notepad, Microsoft Word, or various other programs will fail.

Example 3.7 is a replacement for the DOS type command. This script functions the same way, except that it can display NTFS data streams-something that type cannot do.

The script accepts any number of paths to display. This includes paths with wildcards. For example,

perl streamdump.pl myfile.txt:altStream1 c:\temp\*.txt

Example 3.7 Displaying NTFS Alternate Data Stream Content

01. foreach my $Mask ( @ARGV )
02. {
03.   my @List;
04.
05.   # Must be careful since glob() does not support
06.   # paths with embedded spaces.
07.   if( @List = glob( $Mask ) )
08.   {
09.     push( @FileList, @List );
10.   }
11.   else
12.   {
13.     push( @FileList, $Mask );
14.   }
15. }
16. foreach my $Path ( @FileList )
17. {
18.   print $Path, "\n";
19.   print "-" x length( $Path ), "\n";
20.   if( open( FILE, "< $Path" ) )
21.   {
22.     my $Buffer;
23.
24.     binmode( FILE );
25.     while( read( FILE, $Buffer, 1024 ) )
26.     {
27.       print $Buffer;
28.     }
29.     close( FILE );
30.   }
31.   else
32.   {
33.     print "\tError: $!\n";
34.   }
35.   print "\n";
36. }

NTFS, Alternative Data Streams, and File Sizes

Interestingly enough, the reported size of a file does not reflect the contents of alternative data streams. For example, if you create a 32-byte file and add an alternative data stream that is 500,000 bytes in size, the file still reports only 32 bytes. This makes it even more impractical for an administrator to determine exactly how much space a file truly takes up.

Just to help complicate NTFS size reporting issues, due to the nature of NTFS, a directory listing may report unexpected available disk space values. For example, if you create this text file

C:\>echo Big Old Test > test.txt

a directory listing shows how large the file is and how much space is available on the disk:

C:\>dir test.txt
 Volume in drive C is unnamed
 Volume Serial Number is DCF6-AF23

 Directory of C:\

06/01/2000  09:14a                  15 test.txt
               1 File(s)             15 bytes
               0 Dir(s)     592,346,112 bytes free

If you rewrite the file to a different file size,

C:\>echo This is really a test > test.txt

a directory listing yields

C:\>dir test.txt
 Volume in drive C is unnamed
 Volume Serial Number is DCF6-AF23

 Directory of C:\

06/01/2000  09:14a                  24 test.txt
               1 File(s)             17 bytes
               0 Dir(s)     592,346,112 bytes free

Notice that even though the file size changes, the amount of available drive space does not because NTFS calculates two different size values for each file: how large the file is and how much disk space the file takes up. In this case, the file size is either 15 or 24 bytes. However, the file takes up (at least) one sector of disk space. And each sector (on this particular volume) is made up of 1 kilobyte (1024 bytes) of space. Therefore, it takes 1024 bytes of disk space to store only 15 bytes of data. When NTFS computes the available disk space, that amount is based on the amount of unallocated disk space. Because disk space is taken up in 1KB blocks, the available free space is always rounded down to a multiple of 1024.

Enumerating NTFS Streams

The one problem with NTFS streams is that there is no way to enumerate the streams in a file. This problem is rather tragic, considering how powerful NTFS streams can be. Basically, this means that if you forget which streams you have added to a file, there is no way to discover them. This situation is akin to removing the dir command from the DOS box: How would you possibly discover which files are in a directory? Yes, bizarre as it sounds, this is the reality of Win32's lack of proper support for NTFS streams.

The good news is that you can use a hack to enumerate streams. This solution involves reading the file as backup software does, using the Win32 API's BackupRead() function.

The Streams.pl script (see Example 3.8) displays the names and sizes of all streams in an NTFS file. The script accepts any number of parameters from the command line. Each parameter is a path to a file that is to be examined for alternate streams. The file path can contain wildcards.

Example 3.8 Enumerating Streams in an NTFS File

01. use Win32::API::Prototype;
02.
03. $OPEN_EXISTING = 3;
04. $GENERIC_READ  = 0x80000000;                                    
05. $BACKUP_DATA   = 0x00000001;
06. $BACKUP_ALTERNATE_DATA = 0x00000004;
07.
08. ApiLink( ‘kernel32.dll',
09.          ‘HANDLE CreateFile( LPCTSTR pszPath,
10.                              DWORD dwAccess,
11.                              DWORD dwShareMode,
12.                              PVOID SecurityAttributes,
13.                              DWORD dwCreationDist,
14.                              DWORD dwFlags,
15.                              HANDLE hTemplate )' )
16.     || die "Can not locate CreateFile()";
17. ApiLink( ‘kernel32.dll',
18.          ‘BOOL CloseHandle( HANDLE hFile )' )
19.     || die "Can not locate CloseHandle()";
20. ApiLink( ‘kernel32.dll',
21.          ‘BOOL BackupRead( HANDLE hFile,
22.                            LPBYTE pBuffer,
23.                            DWORD dwBytesToRead,
24.                            LPDWORD pdwBytesRead,
25.                            BOOL bAbort,
26.                            BOOL bProcessSecurity,
27.                            LPVOID *ppContext)' )
28.     || die "Can not locate BackupRead()";
29. ApiLink( ‘kernel32.dll',
30.          ‘BOOL BackupSeek( HANDLE hFile,
31.                            DWORD dwLowBytesToSeek,
32.                            DWORD dwHighBytesToSeek,
33.                            LPDWORD pdwLowByteSeeked,
34.                            LPDWORD pdwHighByteSeeked,
35.                            LPVOID *pContext )' )
36.     || die "Can not create BackupSeek()";
37.
38. # Generate a list of all files to process
39. # therefore we have to expand any wildcards
40. foreach my $Mask ( @ARGV )
41. {
42.     foreach my $Path ( glob( $Mask ) )
43.     {
44.         push( @Files, $Path ) if( -f $Path );
45.     }
46. }
47.
48. foreach my $File ( @Files )
49. {
50.   print "$File\n";
51.  
52.   $hFile = CreateFile( $File,
53.                        $GENERIC_READ,
54.                        0,
55.                        undef,
56.                        $OPEN_EXISTING,
57.                        0,
58.                        0 ) || die "Can not open the file ‘$File'\n";
59.
60.   # If CreateFile() failed $hFile is a negative value
61.   if( 0 < $hFile )
62.   {
63.     my $iStreamCount = 0;
64.     my $iTotalSize = 0;
65.     my $pBytesRead = pack( "L", 0 );
66.     my $pContext = pack( "L", 0 );
67.     my $pStreamIDStruct = pack( "L5", 0,0,0,0,0,0 );
68.    
69.     while( BackupRead( $hFile,
70.                        $pStreamIDStruct,
71.                        length( $pStreamIDStruct ),
72.                        $pBytesRead,
73.                        0,
74.                        0,
75.                        $pContext ) )
76.     {
77.       my $BytesRead = unpack( "L", $pBytesRead );
78.       my $Context = unpack( "L", $pContext );
79.       my %Stream;
80.       my( $pSeekBytesLow, $pSeekBytesHigh ) = ( pack( "L", 0 ),
81.                                                 pack( "L", 0 ) );
82.       my $StreamName = "";
83.       my $StreamSize;
84.      
85.       # No more data to read
86.       last if( 0 == $BytesRead );
87.
88.       @Stream{ id, attributes,
89.                size_low, size_high,
90.                name_size } = unpack( "L5", $pStreamIDStruct );
91.      
92.       if( $BACKUP_ALTERNATE_DATA == $Stream{id} )
93.       {
94.         $StreamName = NewString( $Stream{name_size} );
95.         if( BackupRead( $hFile,
96.                         $StreamName,
97.                         $Stream{name_size},
98.                         $pBytesRead,
99.                         0,
100.                         0,
101.                         $pContext ) )
102.         {
103.           my $String = CleanString( $StreamName, 1 );
104.           $String =~ s/^:(.*?):.*$/$1/;
105.           $StreamName = $String;
106.         }
107.       }
108.       elsif( $BACKUP_DATA == $Stream{id} )
109.       {
110.         $StreamName = "<Main Data Stream>";
111.       }
112.       $StreamSize = MakeLargeInt( $Stream{size_low}, $Stream{size_high} );
113.       $iTotalSize += $StreamSize;
114.
115.       printf( "  % 3d) %s (%s bytes)\n",
116.               ++$iStreamCount,
117.               $StreamName,
118.               FormatNumber( $StreamSize )  ) if( "" ne $StreamName );
119.      
120.       # Move to next stream...
121.       if( ! BackupSeek( $hFile,
122.                         $Stream{size_low},
123.                         $Stream{size_high},
124.                         $pSeekBytesLow,
125.                         $pSeekBytesHigh,
126.                         $pContext ) )
127.       {
128.         last;
129.       }
130.       $pBytesRead = pack( "L2", 0 );
131.       $pStreamIDStruct = pack( "L5", 0,0,0,0,0 );
132.     }
133.     printf( "       Total file size: %s bytes\n",
134.             FormatNumber( $iTotalSize ) );
135.     # Abort the backup reading. Win32 API claims we MUST do this.
136.     BackupRead( $hFile, undef, 0, 0, 1, 0, $pContext );
137.     CloseHandle( $hFile );
138.   }
139.   print "\n";
140. }
141.
142. sub FormatNumber
143. {
144.   my( $Num ) = @_;
145.   {} while( $Num =~ s/^(-?\d+)(\d{3})/$1,$2/ );
146.   return( $Num );
147. }
148.
149. sub MakeLargeInt
150. {
151.   my( $Low, $High ) = @_;
152.   return( $High * ( 1 + 0xFFFFFFFF ) + $Low );
153. }

Discovering File Versions

Many binary files, such as programs and DLLs, can have version information branded into them. Such branding is not required but is incredibly helpful. When you install software that has updated binary files, it usually checks the version information on files to make sure it is not overwriting a file with an older version.

Figure 3.2 shows all the version information on a given binary file. This particular display is useful in determining not only what version the file is, but also what platform the file was created for (NT, Win32, Win16, DOS).

C:\>perl filever.pl c:\winnt\system32\AUTPRX32.DLL
1) c:\winnt\system32\AUTPRX32.DLL
        Comments............January 15, 1997
        CompanyName.........Microsoft Corporation
        FileDescription.....Microsoft Remote Automation Proxy for Windows
                            NT(TM) Operating System
        FileVersion.........5.00.3715
        InternalName........AUTPRX32.DLL
        LangID..............0x0409
        Language............English (United States)
        LegalCopyright......Copyright ¬ Microsoft Corp. 1995.
        LegalTrademarks.....Microsoft« is a registered trademark of
                            Microsoft Corporation. Windows(TM) is a
                            trademark of Microsoft Corporation
        ProductName.........Microsoft Remote Automation
        ProductVersion......5.00.3715
        Internal Info:
            DateStamp.......0
            Debug...........0
            FileType........dll
            FileVersion.....5.0.37.15
            OS..............nt
            Patched.........0
            PreRelease......0
            ProductVersion..5.0.37.15
            SpecialBuild....0

Figure 3.2 Displaying version information on a DLL file.

Example 3.9 accepts any number of parameters, each representing a path to a binary file. The paths can include wildcards and can be UNCs to access version information residing on remote machines.

Example 3.9 Displaying File Version Information

01. use Win32::AdminMisc;
02.
03. my $iCount = 0;
04. if( 0 == scalar @ARGV )
05. {
06.   Syntax();
07.   exit;
08. }
09.
10. foreach my $File ( @ARGV )
11. {
12.   my %Info;
13.
14.   $iCount++;
15.   print "$iCount) $File  ";
16.   if( -f $File )
17.   {
18.     if( Win32::AdminMisc::GetFileInfo( $File, \%Info ) )
19.     {
20.       print "\n";
21.       foreach my $Attr ( sort( keys( %Info ) ) )
22.       {
23.         next if( ref $Info{$Attr} );
24.         Display( $Attr, $Info{$Attr} );
25.       }
26.
27.       if( $Info{FileInfo} )
28.       {
29.         Display( "Internal Info:" );
30.         foreach my $Attr ( sort( keys( %{$Info{FileInfo}} ) ) )
31.         {
32.           next if( ref $Info{FileInfo}->{$Attr} );
33.           Display( "  $Attr", $Info{FileInfo}->{$Attr} );
34.         }
35.       }
36.     }
37.     else
38.     {
39.       print "unable to get version.\n";
40.     }
41.   }
42.   else
43.   {
44.     print "unable to locate file.\n";
45.   }
46.
47. }
48.
49. sub Display
50. {
51.   my( $Attr, $Data ) = @_;
52.
53.   print "\t$Attr";
54.   if( "" ne $Data )
55.   {
56.     print "." x ( 20 - length( $Attr ) ), "$Data";
57.   }
58.   print "\n";
59. }
60.
61. sub Syntax
62. {
63.   my( $Line ) = "-" x length( $0 );
64.
65.   print <<EOT;
66.
67. $0
68. $Line
69. Display version information on a particular file.
70.
71. Syntax:
72.   perl $0 File[, File2[, ... ]]]
73.
74. EOT
75. }

Permissions

Win32 security is, in part, based on permissions. Permissions, which are placed on securable objects (such as files, directories, Registry keys, and network shares), designate which users have access to perform actions. The topic of permissions is quite involved and can easily take up an entire book by itself, so I won't go into detail on how they work. For this type of information, you can consult any number of different resources, including http://www.roth.net/perl/perms/security/.

Displaying Permissions

UNIX machines have always been capable of listing who has permissions on files and directories. You get this listing by using the ls command. Win32's equivalent to ls is dir, but it does not display any permission information.

Example 3.10 displays permission information on any path passed in on the command line. This script accepts a number of parameters representing paths to securable objects, as in this example:

perl perm.pl c:\temp\*.txt \\server\HKLM\Software\ActiveState \\server2\Temp$

This example displays the permissions for all text files in the c:\temp directory as well as the HKEY_LOCAL_MACHINE\Software\ActiveState Registry key on the machine \\server and the Temp$ share on the machine \\server2.

Example 3.10 displays the permissions in the form of an array of letters. Each letter indicates the type of permission that the specified user has been granted. Table 3.2 describes these letters.

Table 3.2 Permissions by Letter

           Letter                     Permission

R User is allowed to open the object for reading.
r User is not allowed to open the object for reading.
W User is allowed to open the file for writing.
w User is not allowed to open the file for writing.
D User is allowed to delete the object.
d User is not allowed to delete the object.
X User is allowed to execute the object. When this permission is applied to a directory or Registry key, the user is allowed to access its contents.
x User is not allowed to execute the object. When this permission is applied to a directory or Registry key, the user is not allowed to access its contents.
P User is allowed to change the object's permissions.
p User is not allowed to change the object's permissions.
O User is allowed to take ownership of the object.
o User is not allowed to take ownership of the object.
A User has been granted all permissions. This permission may be set in addition to other flags.
a User has been denied all permissions. This permission may be set in addition to other flags.

Figure 3.3 shows the permissions on a specific directory (C:\temp). The script first prints the name of the owner and group of the object (in this case, a directory). Next, it prints each user and group account that has been granted or denied access to the object. The type of object that the permission relates to follows the account name. In this example, the Administrator account is granted directory permissions, and the Perltest7 account has been granted both directory and file permissions.

The permissions are then displayed. This seven-letter string indicates which permissions are granted, denied, or not addressed. A capital letter indicates that the related permission has been explicitly granted, and a lowercase letter indicates that the permission has been explicitly denied. You can see that Perltest7 has been granted read and execute but denied write permissions on files in the C:\temp directory. Additionally, the account has been denied permission to take ownership of the directory. On the other hand, the Administrator account has been granted all permissions. The letter A means that the account has been granted all permissions. This means that there was no need to explicitly grant the read, write, execute, and delete permissions. Notice that the Joel account has been granted all access (just like the Administrator); however, the delete permission has been explicitly denied. Because explicitly denied access always overrides allowed access, the user has full control over the directory, except that he cannot delete it.

C:\>perl perm.pl c:\temp

Permissions for ‘c:\temp':
  Owner: BUILTIN\Administrators
  Group: BUILTIN\Domain Admins
         Administrator (Directory)  RWXD--A
                  Joel (Directory)  RWXd--A
             Perltest7 (Directory)  -----o-
             Perltest7 (File)       RwX----

Figure 3.3 Displaying permissions on a directory.


Notes on Files and Directories

For files and directory paths, wildcards are acceptable; however, for Registry keys, they are not.

You can specify files and directories on remote machines by using a UNC. However, if you're specifying a UNC's root directory, make sure that you terminate the path with a backslash like this:

\\server\share\

If you do not append the backslash, the permissions of the network share itself are referenced.

Notes on Shared Directories and Printers

Shared directory and printer pathnames must not end with a backslash. If you specify a shared directory path with an ending backslash, the permissions for the shared directory's root directory are displayed, not the permissions for the network share itself. An attempt to reference a printer with an ending backslash simply fails.

Also, note that shared directories have neither an owner nor a group.

Notes on Registry Keys

You specify Registry keys with a full Registry key path like this:

HKEY_LOCAL_MACHINE\Software

You can abbreviate the Registry root by shortening it like this:

HKLM\Software

You also can specify Registry keys on remote machines by prepending the machine name like this:

\\server\HKEY_LOCAL_MACHINE\Software

or

\\server\HKLM\Software

Note

When accessing remote registries, only the HKEY_USERS and HKEY_LOCAL_MACHINE hives are accessible.

Example 3.10 Displaying Permission Information

01. use Win32::Perms;
02.
03. # The %PERM hash maps a permission type to its position in the
04. # permission string array. Notice that there is no 0 position
05. # since that is reserved for unhandled permission types (and
06. # we won't print it).
07. %PERM = (
08.   R   =>  1,
09.   W   =>  2,
10.   X   =>  3,
11.   D   =>  4,
12.   P   =>  5,
13.   O   =>  6,
14.   A   =>  7,
15. );
16.
17. %MAP = (
18.   ‘FILE_READ_DATA'    =>  ‘R',
19.   ‘GENERIC_READ'      =>  ‘R',
20.   ‘KEY_READ'          =>  ‘R',
21.   ‘DIR_READ'          =>  ‘R',
22.  
23.   ‘FILE_WRITE_DATA'   =>  ‘W',
24.   ‘KEY_WRITE'         =>  ‘W',
25.   ‘GENERIC_WRITE'     =>  ‘W',
26.   ‘FILE_APPEND_DATA'  =>  ‘W',
27.   ‘DIR_ADD_SUBDIR'    =>  ‘W',
28.   ‘DIR_ADD_FILE'      =>  ‘W',
29.  
30.   ‘DELETE'            =>  ‘D',
31.   ‘FILE_DELETE_CHILD' =>  ‘D',
32.  
33.   ‘FILE_EXECUTE'      =>  ‘X',
34.   ‘FILE_TRAVERSE'     =>  ‘X',
35.   ‘GENERIC_EXECUTE'   =>  ‘X',
36.   ‘DIR_TRAVERSE'      =>  ‘X',
37.   ‘DIR_EXECUTE'       =>  ‘X',
38.  
39.   ‘CHANGE_PERMISSION' =>  ‘P',
40.  
41.   ‘TAKE_OWNERSHIP'    =>  ‘O',
42.  
43.   ‘FILE_ALL_ACCESS'   =>  ‘A',
44.   ‘GENERIC_ALL'       =>  ‘A',
45.   ‘DIR_ALL_ACCESS'    =>  ‘A',
46.   ‘STANDARD_RIGHTS_ALL' => ‘A',
47. );
48.
49. push( @ARGV, "*.*" ) unless( scalar @ARGV );
50. foreach my $Mask ( @ARGV )
51. {
52.   my( @List ) = glob( $Mask );
53.   if( ! scalar @List )
54.   {
55.     push( @List, $Mask );
56.   }
57.   foreach my $Path ( @List )
58.   {
59.     print "\nPermissions for ‘$Path':\n";
60.     ReportPerms( $Path );
61.     print "\n\n";
62.   }
63. }
64.
65. sub ReportPerms
66. {
67.   my( $Path ) = @_;
68.   my( $Acct, @List );
69.   my( $Perm ) = new Win32::Perms( $Path );
70.   my( %PermList ) = ();
71.   my( $MaxAcctLength ) = 1;
72.
73.   if( ! $Perm )
74.   {
75.       print "Can not obtain permissions for ‘$Path'\n";
76.       return;
77.   };
78.
79.   printf( "  Owner: %s\n  Group: %s\n",
80.           $Perm->Owner(),
81.           $Perm->Group() );
82.   $Perm->Dump( \@List );
83.   foreach $Acct ( @List )
84.   {
85.     my( $PermMask );
86.     my( $Mask, @M, @F );
87.     my( $DaclType );
88.     my $bAllowAccess = ( "Deny" ne $Acct->{Access} );
89.     my $String;
90.     my $Account;
91.    
92.     next if( $Acct->{Entry} ne "DACL" );
93.    
94.     if( "" eq $Acct->{Account} )
95.     {
96.       $Account = $Acct->{SID};
97.     }
98.     else
99.     {
100.       $Account = "$Acct->{Domain}\\" if( "" ne $Acct->{Domain} );
101.       $Account .= $Acct->{Account};
102.     }
103.     if( length( $Account ) > $MaxAcctLength )
104.     {
105.       $MaxAcctLength = length( $Account )
106.     }
107.     $iTotal++;
108.     DecodeMask( $Acct, \@M, \@F );
109.     foreach $Mask ( @M )
110.     {
111.       $PermMask |= 2**$PERM{ $MAP{$Mask} };
112.     }
113.     $DaclType = $Acct->{ObjectName};
114.     if( 2 == $Acct->{ObjectType} )
115.     {
116.       # We have either a file or directory. Therefore we need to
117.       # figure out if this DACL represents an object (file) or
118.       # a container (dir)...
119.       if( $Acct->{Flag} & DIR )
120.       {
121.         $DaclType = "Directory";
122.       }
123.       else
124.       {
125.         $DaclType = "File";
126.       }
127.     }
128.     if( ! defined $PermList{$Account}->{$DaclType} )
129.     {
130.       # Create the permission string array. The first element in the
131.       # array must be blank since all unhandled permissions will default
132.       # to that position (and we won't print it).
133.       my $TempHash = [
134.                        " ",
135.                        split( //, "-" x scalar( keys( %PERM ) ) )
136.                      ];
137.       $PermList{$Account}->{$DaclType} = $TempHash;
138.                                    
139.     }
140.     foreach $Mask ( keys( %PERM ) )
141.     {
142.       if( $PermMask & 2**$PERM{$Mask} )
143.       {
144.         $String = $PermList{$Account}->{$DaclType};
145.         # If we already have a denied permission then skip this step
146.         # since denied access overrides any explicitly allowed access
147.         if( $String->[$PERM{$Mask}] !~ /[a-z]/ )
148.         {
149.           my $TempMask = $Mask;
150.           $TempMask = lc $Mask if( 0 == $bAllowAccess );
151.           $String->[$PERM{$Mask}] = $TempMask ;
152.         }
153.       }
154.     }
155.   }
156.  
157.   if( ! $iTotal )
158.   {
159.     # There are no DACL entries therefore...
160.     print "\t Everyone has full permissions.\n";
161.   }
162.   else
163.   {
164.     foreach my $Permission ( sort( keys( %PermList ) ) )
165.     {
166.       foreach my $DaclType ( sort( keys( %{$PermList{$Permission}} ) ) )
167.       {           
168.         my $String = $PermList{$Permission}->{$DaclType};
169.         printf( "  % " . $MaxAcctLength . "s % -11s %s\n",
170.                 $Permission,
171.                 "($DaclType)",
172.                 join( ‘', @$String ) );
173.       }
174.     }
175.   }
176. }

Displaying Verbose Permissions

Example 3.10 in the "Displaying Permissions" section displays the permissions for various securable objects, such as files and directories. That particular script distills the complex world of permissions into a few very basic and simple permission sets. Using that script, you can easily get an idea of which users have which permissions. However, to display the permissions in a simple way, many assumptions and interpretations must be made, which can leave out details that may be critical for administrators to successfully maintain true security.

Example 3.11 displays the full set of permissions that an object has. No interpretations are made, and all permissions, flags, and types are displayed in their full, confusing glory. If you're not familiar with the different permissions that can be set on an object, the display may be more than just confusing.

Figure 3.4 shows the output this script produces. To be sure, it is not for the faint at heart. This particular example is a permission dump for the same c:\temp directory shown in Figure 3.3. Unlike Example 3.10, this script displays all the permission information. Some of these permissions may not make much sense, but they are documented in the Win32 API SDK (http://msdn.microsoft.com/).

C:\>perl vperm.pl c:\temp

Permissions for ‘c:\temp':
  Owner: BUILTIN\Administrators
  Group: BUILTIN\Domain Admins
            Account                  Permissions                Flags
------------------------------ ---------------------- -----------------------
CREATOR OWNER                   DELETE                 INHERIT_ONLY_ACE
Deny Access                                            OBJECT_INHERIT_ACE
(Directory)                                            CONTAINER_INHERIT_ACE


ROTH.NET\Perltest7              FILE_WRITE_DATA        INHERIT_ONLY_ACE
Deny Access                     FILE_APPEND_DATA       OBJECT_INHERIT_ACE
(File)                          FILE_WRITE_EA
                                FILE_WRITE_ATTRIBUTES

ROTH.NET\Perltest7              DIR_ADD_SUBDIR         OBJECT_INHERIT_ACE
Deny Access                     TAKE_OWNERSHIP         CONTAINER_INHERIT_ACE
(Directory)                     CHANGE_PERMISSION


CREATOR OWNER                   DIR_READ               INHERIT_ONLY_ACE
Allow Access                    DIR_ADD_FILE           OBJECT_INHERIT_ACE
(Directory)                     DIR_ADD_SUBDIR         CONTAINER_INHERIT_ACE
                                DIR_READ_EATTRIB
                                DIR_WRITE_EATTRIB
                                DIR_EXECUTE
                                DIR_DEL_SUBDIR
                                DIR_READ_ATTRIB
                                DIR_WRITE_ATTRIB
                                READ_CONTROL
                                TAKE_OWNERSHIP

ROTH.NET\Administrator          DIR_READ               OBJECT_INHERIT_ACE
Allow Access                    DIR_ADD_FILE           CONTAINER_INHERIT_ACE
(Directory)                     DIR_ADD_SUBDIR
                                DIR_READ_EATTRIB
                                DIR_WRITE_EATTRIB
                                DIR_EXECUTE
                                DIR_DEL_SUBDIR
                                DIR_READ_ATTRIB
                                DIR_WRITE_ATTRIB
                                STANDARD_RIGHTS_ALL

ROTH.NET\Perltest7              FILE_READ_DATA         INHERIT_ONLY_ACE
Allow Access                    FILE_READ_EA           OBJECT_INHERIT_ACE
(File)                          FILE_EXECUTE
                                FILE_READ_ATTRIBUTES
                                READ_CONTROL
                                SYNCHRONIZE

Figure 3.4 The true permissions placed on a file.

Example 3.11 accepts any number of paths on the command line. Each path can contain wildcards. Additionally, an optional /F switch is accepted; it reports the so-called friendly permissions. Most administrators are familiar with these permissions, such as FULL_CONTROL, CHANGE, and READ. Such friendly permissions are, however, far from complete and do not necessarily explain the true nature of the permissions. For example, no friendly permission describes that a file has both READ and DELETE access.

Refer to the "Displaying Permissions" section to learn how to address different paths.

Example 3.11 Displaying the Full Details of an Object's Permissions

01. use Getopt::Long;
02. use Win32::Perms;
03.
04. # Turn off aggressive domain controller lookups
05. Win32::Perms::LookupDC( 0 );
06. Configure( \%Config );
07. if( $Config{help} )
08. {
09.     Syntax();
10.     exit();
11. }
12.
13. foreach my $Mask ( @{$Config{masks}} )
14. {
15.     my( @List ) = glob( $Mask );
16.     if( ! scalar @List )
17.     {
18.         push( @List, $Mask );
19.     }
20.     foreach $Path ( @List )
21.     {
22.         print "\nPermissions for ‘$Path':\n";
23.         ReportPerms( $Path );
24.         print "\n\n";
25.     }
26. }
27.
28. sub ReportPerms
29. {
30.   my( $Path ) = @_;
31.   my( $Acct, @List );
32.   my( $Perm ) = new Win32::Perms( $Path );
33.
34.   if( ! $Perm )
35.   {
36.     print "Can not obtain permissions for ‘$Path'\n";
37.     return;
38.   };
39.
40.   printf( "  Owner: %s\n  Group: %s\n", $Perm->Owner(), $Perm->Group() );
41.   $Perm->Dump( \@List );
42.   $~ = PermissionHeader;
43.   write;
44.   foreach $Acct ( @List )
45.   {
46.     my( $PermMask );
47.     my( @String ) = split( //, "-" x scalar( keys( %PERM ) ) );
48.     my( $Mask, @Permissions, @Friendly );
49.     local $DaclType;
50.     local $Access = $Acct->{Access};
51.     local $Account;
52.
53.     $~ = Permission;
54.
55.     next if( $Acct->{Entry} ne "DACL" );
56.     $iTotal++;
57.
58.     DecodeMask( $Acct, \@Permissions, \@Friendly );
59.     DecodeFlag( $Acct, \@Flag );
60.     # Pad each flag and permission with spaces so that they correctly
61.     # break into one permission/flag per line when printed. This is a
62.     # trick when working with write/formats
63.     $Flags = join( " " x 10 . "\n", @Flag );
64.     $Perms = join( " " x 10 . "\n",
65.                    ( $Config{friendly} )? @Friendly : @Permissions );
66.     if( "" eq $Acct->{Account} )
67.     {
68.       $Account = $Acct->{SID};
69.     }
70.     else
71.     {
72.       $Account = "$Acct->{Domain}\\" if( "" ne $Acct->{Domain} );
73.       $Account .= $Acct->{Account};
74.     }
75.     $DaclType = $Acct->{ObjectName};
76.     if( 2 == $Acct->{ObjectType} )
77.     {
78.       # We have either a file or directory. Therefore we need to
79.       # figure out if this DACL represents an object (file) or
80.       # a container (dir)...
81.       if( $Acct->{Flag} & DIR )
82.       {
83.         $DaclType = "Directory";
84.       }
85.       else
86.       {
87.         $DaclType = "File";
88.       }
89.     }
90.     write;
91.     print "\n";
92.   }
93.
94.   if( ! $iTotal )
95.   {
96.     print "\t Everyone has full permissions.\n";
97.   }
98. }
99.
100. sub Configure
101. {
102.   my( $Config ) = @_;
103.   my $Result;
104.
105.   Getopt::Long::Configure( "prefix_pattern=(-|\/)" );
106.   $Result = GetOptions( $Config,
107.                         qw(
108.                           friendly|f
109.                           help|?|h
110.                         )
111.   );
112.   push( @{$Config->{masks}}, @ARGV );
113.   $Config->{help} = 1 unless( $Result &&  scalar @{$Config->{masks}} );
114. }
115.
116. sub Syntax
117. {
118.   my( $Script ) = ( $0 =~ /([^\\\/]*?)$/ );
119.   my( $Line ) = "-" x length( $Script );
120.
121.   print <<EOT;
122.
123. $Script
124. $Line
125. Displays verbose permissions set on securable objects.
126.
127. Syntax:
128.     perl $Script [-f] Path [ Path2 ... ]
129.         -f..........Show "friendly" permissions.
130.         Path........The path to a securable object.
131.                     This path can consist of ? and * wildcards.
132.
133. EOT
134. }
135.
136. format PermissionHeader =
137. @||||||||||||||||||||||||| @||||||||||||||||||||| @||||||||||||||||||||||
138. "Account",                 "Permissions",         "Flags"
139. -------------------------- ---------------------- -----------------------
140. .
141.
142. format Permission =
143. ^<<<<<<<<<<<<<<<<<<<<<<<<< ^<<<<<<<<<<<<<<<<<<<<< ^<<<<<<<<<<<<<<<<<<<<<<
144. $Account,                  $Perms,                $Flags
145. ^<<<<<<<<<<<<<<<<<<<<<<<<< ^<<<<<<<<<<<<<<<<<<<<< ^<<<<<<<<<<<<<<<<<<<<<<
146. $Account,                  $Perms,                $Flags
147. @<<<<<<<<<<<<<<            ^<<<<<<<<<<<<<<<<<<<<< ^<<<<<<<<<<<<<<<<<<<<<<
148. "$Access Access",          $Perms,                $Flags
149. @<<<<<<<<<<<<<<            ^<<<<<<<<<<<<<<<<<<<<< ^<<<<<<<<<<<<<<<<<<<<<<
150. "($DaclType)",             $Perms,                $Flags
151. ~                          ^<<<<<<<<<<<<<<<<<<<<< ^<<<<<<<<<<<<<<<<<<<<<<
152.                            $Perms,                $Flags
153. ~                          ^<<<<<<<<<<<<<<<<<<<<< ^<<<<<<<<<<<<<<<<<<<<<<
154.                            $Perms,                $Flags
155. ~                          ^<<<<<<<<<<<<<<<<<<<<< ^<<<<<<<<<<<<<<<<<<<<<<
156.                            $Perms,                $Flags
157. ~                          ^<<<<<<<<<<<<<<<<<<<<< ^<<<<<<<<<<<<<<<<<<<<<<
158.                            $Perms,                $Flags
159. ~                          ^<<<<<<<<<<<<<<<<<<<<< ^<<<<<<<<<<<<<<<<<<<<<<
160.                            $Perms,                $Flags
161. ~                          ^<<<<<<<<<<<<<<<<<<<<< ^<<<<<<<<<<<<<<<<<<<<<<
162.                            $Perms,                $Flags
163. ~                          ^<<<<<<<<<<<<<<<<<<<<< ^<<<<<<<<<<<<<<<<<<<<<<
164.                            $Perms,                $Flags
165. ~                          ^<<<<<<<<<<<<<<<<<<<<< ^<<<<<<<<<<<<<<<<<<<<<<
166.                            $Perms,                $Flags
167. .


Ownership

Almost all securable objects have an owner. The owner is an account (either a user or group account) that has ultimate access over the file. The owner of an object can never be denied access to it. He can read, write, delete, and modify permissions, as well as do anything else that is possible.

To discover who is the owner of a particular object (file, directory, or Registry key), you need to locate the security information for the object. For files and directories, you typically use the Explorer program to select the object's properties and then select the Security tab on the displayed property page. For a Registry key, you display the key's permission information from within the REGEDT32.EXE program.

Ownership of an object was designed such that it could only be taken, not given. For example, an administrator could become the owner of a file (take ownership) so that she could usurp permissions placed on the file. However, because she could not assign ownership to another user (give ownership), it would be obvious as to who had bypassed security because the administrator would remain the owner until someone else took ownership. Because an administrator must grant a user account with the privilege to take ownership (the SE_TAKE_OWNERSHIP_NAME privilege to be precise), this paradigm was deemed safe.

It did not take long, however, for programmers to discover that backup software somehow had to be able to restore a file with its original owner intact. That meant that there was a way to give ownership! The technique for doing so lies in the ability for a process to act like backup software. This, of course, requires that the user has a special privilege (SE_RESTORE_NAME).

All this leads to Example 3.12, which enables you not only to discover who is the owner for a given object but also to change the object's owner (provided that you have permission to do so). Any user can use this script to view who is the owner, but a user requires the SE_RESTORE_NAME privilege to change the ownership of a secured object.

This script accepts a number of parameters representing paths to securable objects. To set the owner on the specified objects, you pass in an -s switch followed by the account name of the new owner. Consider these two examples:

perl owner.pl c:\temp\*.txt \\server\HKLM\Software\ActiveState

perl owner.pl -s JOEL c:\temp\*.txt \\server\HKLM\Software\ActiveState

The first example displays the owner for all text files in the c:\temp directory as well as the HKEY_LOCAL_MACHINE\Software\ActiveState Registry key on the machine \\server.

The second example is essentially the same as the first example, except that it first sets all the objects' owners to Joel and then displays who is the owner. If the user running the script has the appropriate privileges, Joel is displayed as the new owner for each object.

Tip

When referring to an account name, you can pass in the name of an account and have Win32 decide if the account is a domain or local machine account. Alternatively, you can explicitly specify what domain or machine the account belongs to.

To specify a domain account, refer to it by specifying the domain followed by a backslash and account name as in

                MyDomain\joel
                "Some Domain\Domain Admins"

To specify a particular machine account, pass in the server name beginning with double backslashes. Then append a backslash and account name as in

                \\Server1\joel
                "\\Server1\Domain Admins"

Refer to the notes in the "Displaying Permissions" section for information regarding how to address different paths.

Example 3.12 Retrieving and Setting the Owner of a Securable Object

01. use Getopt::Long;
02. use Win32::Perms;
03.
04. # Turn off intense domain lookups
05. Win32::Perms::LookupDC( 0 );
06.
07. Configure( \%Config );
08. if( $Config{help} )
09. {
10.   Syntax();
11.   exit();
12. }
13.
14. $~ = OwnerHeader;
15. write;
16. $~ = Owner;
17. foreach $Mask ( @{$Config{masks}} )
18. {
19.   my( @List ) = glob( $Mask );
20.   if( ! scalar @List )
21.   {
22.     push( @List, $Mask );
23.   }
24.   foreach $Path ( @List )
25.   {
26.     my $Perm;
27.
28.     if( ! ( $Perm = new Win32::Perms( $Path ) ) )
29.     {
30.       $Owner = "Can't get permissions.\n";
31.     }
32.     else
33.     {
34.       if( "" ne $Config{owner} )
35.       {
36.         $Perm->Owner( $Config{owner} );
37.         $Perm->Set();
38.       }
39.       $Owner = $Perm->Owner();
40.     }
41.     write;
42.   }
43. }
44.
45. sub Configure
46. {
47.   my( $Config ) = @_;
48.   my $Result;
49.
50.   Getopt::Long::Configure( "prefix_pattern=(-|\/)" );
51.   $Result = GetOptions( $Config,
52.                         qw(
53.                           owner|o|s=s
54.                           help|?|h
55.                         )
56.   );
57.   push( @{$Config->{masks}}, @ARGV );
58.   $Config->{help} = 1 unless( $Result &&  scalar @{$Config->{masks}} );
59. }
60.
61. sub Syntax
62. {
63.   my( $Script ) = ( $0 =~ /([^\\\/]*?)$/ );
64.   my( $Line ) = "-" x length( $Script );
65.
66.   print <<EOT;
67.
68. $Script
69. $Line
70. Displays (and sets) the owner of securable objects.
71.
72. Syntax:
73.     perl $Script [-s Account] Path [ Path2 ... ]
74.         -s Account..Set the owner to the specified account.
75.         Path........The path to a securable object.
76.                     This path can consist of ? and * wildcards.
77.
78. EOT
79. }
80.
81. format OwnerHeader =
82. @<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< @<<<<<<<<<<<<<<<<<<<<<<<<<<<
83. "Path", "Owner"
84. -------------------------------------- ----------------------------
85. .
86.
87. format Owner =
88. @<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< @<<<<<<<<<<<<<<<<<<<<<<<<<<<
89. $Path, $Owner
90. .


DCOM Configurations

Most users never know that their programs communicate with each other using COM. They paste spreadsheets inside their word processing documents without ever knowing that this capability is made possible by object linking and embedding (OLE) technology, which is based on COM. However, you need to be vigilant regarding security because recent versions of Windows have enabled Distributed COM (DCOM). This technology enables one computer to communicate with a program running on another machine. This capability is ideal for administration purposes but potentially dangerous if renegade hackers break into a machine.

Windows provides the DCOMCNFG.EXE utility, which is used to configure DCOM security. This tool enables you to select who has access to a computer via DCOM. It has two problems, though: it is GUI-based, and it runs only on a local machine.

Because the program is GUI-based, getting a quick overview of how a machine's DCOM is configured is difficult. Instead, you must move from tab to tab clicking on buttons. Doing so is not only annoying but a time burden as well.

Because the utility reports only the DCOM configuration for the machine it is running on, its use is quite limited. This limitation requires that you walk to each server, log on, and then run the utility if you want to check your server farm.

Example 3.13 displays a machine's DCOM configuration quickly and without any GUI. You can also check a remote machine's DCOM configuration by passing in the machine's name when running the script.

Note

Every machine that is a member of a domain has a machine account (similar to user account) in the domain. When the machine is booted up and comes online, it logs into the domain using the machine account and a password. When examining a machine's DCOM configuration, you might find references to user, group, and machine accounts.

Figure 3.5 shows the output of Example 3.13. Notice that the Guest account has been explicitly denied in all the security-related configurations. The script shows whether access was granted or denied.

C:\>perl dcom.pl \\server1

DCOM settings for \\Server1:
  DCOM is enabled.
  COM Internet Services are enabled.

  The following accounts are allowed to launch COM applications:
    Allow  NT AUTHORITY\SYSTEM
    Allow  NT AUTHORITY\INTERACTIVE
    Deny   SERVER1\Guest
    Allow  BUILTIN\Administrators
    Allow  SERVER1\IUSR_SERVER1
    Allow  SERVER1\IWAM_SERVER1
  Accounts with access to this machine via DCOM:
    Allow  NT AUTHORITY\SYSTEM
    Allow  NT AUTHORITY\INTERACTIVE
    Deny   SERVER1\Guest
    Allow  BUILTIN\Administrators
    Allow  SERVER1\IUSR_SERVER1
    Allow  SERVER1\IWAM_SERVER1
    Allow  BUILTIN\Administrators

  Accounts with access to COM/OLE configuration:
    Allow  BUILTIN\Administrators
    Allow  CREATOR OWNER
    Deny   SERVER1\Guest
    Allow  Everyone
    Allow  BUILTIN\Power Users
    Allow  NT AUTHORITY\SYSTEM
    Allow  BUILTIN\Users

  The following protocols are permitted for use with DCOM:
    ncacn_ip_tcp
    ncacn_spx
    ncacn_nb_nb
    ncacn_nb_ipx

Figure 3.5 Examining a DCOM configuration.



Example 3.13 Displaying DCOM Configurations

01. use Win32::Registry;
02. use Win32::Perms;
03.
04. # Prevent aggressive domain controller lookups
05. Win32::Perms::LookupDC( 0 );
06. %PATH = (
07.     ole     =>  ‘SOFTWARE\Microsoft\Ole',
08.     rpc     =>  ‘SOFTWARE\Microsoft\RPC',
09. );
10.
11. $Perm = new Win32::Perms() || die "Cannot create Permissions object";
12. if( scalar @ARGV )
13. {
14.     $Machine = "\\\\" . shift @ARGV;
15. }
16. else
17. {
18.     $Machine = "\\\\" . Win32::NodeName();
19. }
20. $Machine =~ s/^\\+/\\\\/;
21. $HKEY_LOCAL_MACHINE->Connect( $Machine, $Root )
22.     || die "Can not connect";
23. print "\nDCOM settings for $Machine:\n";
24. if( $Root->Open( $PATH{ole}, $Key ) )
25. {
26.   my( $Flag, $Type, $Value, $Sd );
27.
28.   undef $Value;
29.   $Key->QueryValueEx( "EnableDCOM", $Type, $Value );
30.   print "  DCOM is ", ( "y" eq lc $Value )? "":"not ", "enabled.\n";
31.  
32.   $Key->QueryValueEx( "EnableDCOMHTTP", $Type, $Value );
33.   print "  COM Internet Services are ",
34.         ( "y" eq lc $Value )? "":"not ", "enabled.\n";
35.
36.   print "\n  The following accounts are allowed to "
37.         . "launch COM applications:\n";
38.   if( $Key->QueryValueEx( "DefaultLaunchPermission", $Type, $Sd ) )
39.   {
40.     DisplayPerms( $Sd ) if( REG_BINARY == $Type );
41.   }
42.
43.   print "\n  Accounts with access to this machine via DCOM:\n";
44.   if( $Key->QueryValueEx( "DefaultAccessPermission", $Type, $Sd ) )
45.   {
46.     DisplayPerms( $Sd ) if( REG_BINARY == $Type );
47.   }
48.
49.   print "\n  Accounts with access to COM/OLE configuration:\n";
50.   DisplayPerms( "$Machine\\HKEY_CLASSES_ROOT" );
51.
52.   print "\n  The following protocols are permitted "
53.         . "for use with DCOM:\n";
54.   if( $Root->Open( $PATH{rpc}, $Key2 ) )
55.   {
56.     my $MString;
57.     if( $Key2->QueryValueEx( "DCOM Protocols", $Type, $MString ) )
58.     {
59.       foreach my $Protocol ( split( "\x00", $MString ) )
60.       {
61.         print "    $Protocol\n";
62.       }
63.     }
64.     $Key2->Close();
65.   }
66.   $Key->Close();
67. }
68. $Root->Close();
69.
70. sub DisplayPerms
71. {
72.   my( $Sd ) = @_;
73.   my @List;
74.
75.   $Perm->Import( $Sd );
76.   $Perm->Dump( \@List );
77.
78.   foreach my $Ace ( @List )
79.   {
80.     my $Account;
81.     if( "" ne $Ace->{Account} )
82.     {
83.       $Account = "$Ace->{Domain}\\" if( "" ne $Ace->{Domain} );
84.       $Account .= $Ace->{Account};
85.     }
86.     else
87.     {
88.       $Account = $Ace->{SID};
89.     }
90.
91.     # Object Type of 1 represents a DACL
92.     next if( 1 != $Ace->{ObjectType} );
93.
94.     printf( "    %-6s %-30s\n", $Ace->{Access}, $Account );
95.   }
96.   return;
97. }


Registry

Win32 platforms make use of the Registry to store configuration information. Programs and system services alike populate Registry keys with values that represent various settings of some sort. Information on services and system drivers are stored along with details on how they are loaded and where their files are located. The Registry is where you can find COM component configurations, TCP settings, and auto logon settings, just to name a few.

Regfind

The Registry is a vast wasteland of configuration information. It's so vast, in fact, that locating anything held therein can be quite daunting. Because the Registry is a database of configuration data, you would hope that you could search it and efficiently locate necessary tidbits of information. However, you would be disappointed to learn that this is not possible. The Win32 API does not have any mechanism that enables such searching. Considering that this database, even on a notebook, can grow several tens of megabytes in size, locating data is comparable to finding a needle in a haystack.

The lack of such search functionality is probably due, in part, to the lack of any real need. After all, the repository of information was designed for processes to hold configuration data-what used to be held in .INI files. The presumption here is that software would know where it stored data in the Registry, so it would never need to look for it. This presumption, however, is based on the notion that software behaves itself.

All too often when you uninstall software, it leaves, intact, traces of itself, such as COM registration information. Under these circumstances, searching the Registry for a given GUID or string can be quite valuable. To give another, but altogether pertinent, example, often, for some odd reason, some software moves a window to a location just off of the screen. Such repositioning of a window may occur if you are using dual monitors but have to temporarily resort to using only one. This could also occur if, from time to time, you use a higher resolution display. Either way, every time the application is run, the window the user interacts with is located where it cannot be seen. Because most users do not know where, say, Notepad may locate its configuration store in the Registry, a search becomes quite important.

Example 3.14 performs such searches. When you pass in Registry paths and search strings, the script scans the Registry and displays its findings as it proceeds. You can pass in multiple paths and multiple search strings. All you need to do is specify each path with a -p switch-as many of them as necessary. Then pass in as many strings as required.

You can locate each path on a remote machine by prepending the machine name to the path. You can also abbreviate the Registry root keys as in Table 3.3.

Table 3.3 Registry Roots and Their Abbreviations

               Registry Root                            Abbreviation

HKEY_LOCAL_MACHINE HKLM
HKEY_CURRENT_USER HKCR
HKEY_CLASSES_ROOT HKCU
HKEY_USERS HKU
HKEY_CURRENT_CONFIG HKCC

Consider this example:

perl regfind.pl -p HKEY_LOCAL_MACHINE\Software

-p \\server1\HKLM\Software winamp "windows media"

This particular instance searches the HKEY_LOCAL_MACHINE\Software Registry paths on the current machine as well as on \\Server1. The Registry is searched for the string winamp or "windows media". The search is not case sensitive, so WinAmp also triggers a match. The search traverses the entire specified Registry paths. Therefore, it could take some time to complete.

Output is printed to STDOUT, but information about how the search is proceeding is printed to STDERR. This way, you can redirect output to a file while still observing the findings of the search.

Example 3.14 Locating Strings in the Registry

01. use Getopt::Long;
02. use Win32::Registry;
03.
04. %HIVE_LIST = (
05.   HKEY_LOCAL_MACHINE  =>  $HKEY_LOCAL_MACHINE,
06.   HKEY_CURRENT_USER   =>  $HKEY_CURRENT_USER,
07.   HKEY_CLASSES_ROOT   =>  $HKEY_CLASSES_ROOT,
08.   HKEY_USERS          =>  $HKEY_USERS,
09.   HKEY_CURRENT_CONFIG =>  $HKEY_CURRENT_CONFIG,
10. );
11. %HIVE_LIST_ABBR = (
12.   HKLM        =>  $HKEY_LOCAL_MACHINE,
13.   HKCU        =>  $HKEY_CURRENT_USER,
14.   HKCR        =>  $HKEY_CLASSES_ROOT,
15.   HKU         =>  $HKEY_USERS,
16.   HKCC        =>  $HKEY_CURRENT_CONFIG,
17. );
18. foreach my $Key ( keys( %HIVE_LIST ) )
19. {
20.   $HIVE_LIST_REVERSE{$HIVE_LIST{$Key}} = $Key;
21. }
22. $iTotalKeys = $iTotalMatch = 0;
23. Configure( \%Config );
24. if( $Config{help} )
25. {
26.   Syntax();
27.   exit();
28. }
29.
30. while( my $Path = shift @{$Config{paths}} )
31. {
32.   my( $Temp, $Machine, $Hive ) = ( $Path =~ /^((\\\\.*?)\\)?(.*)/ );
33.   my( $Hive, $Temp2, $Path ) = ( $Hive =~ /^([^\\]*)(\\(.*))?/ );
34.   my $Root = OpenHive( $Hive, $Machine );
35.   if( 0 != $Root )
36.   {
37.     my @Keys;
38.     my @List;
39.
40.     if( "" ne $Machine )
41.     {
42.       $Hive = "$Machine\\$Hive";
43.     }
44.    
45.     if( $Root->Open( $Path, $Key ) )
46.     {
47.       $Path = "\\$Path" if( "" ne $Path );
48.       print "Scanning $Hive$Path\n";
49.       ProcessKey( $Key, "$Hive$Path", $KeyName );
50.     }
51.     else
52.     {
53.       print "Unable to open $Hive\\$Path\n";
54.     }
55.     $Root->Close();
56.   }
57.   else
58.   {
59.       # Can not connect.
60.   }
61. }
62.
63. print STDERR "\n-------------------\n";
64. print STDERR "Total values checked: $iTotalKeys\n";
65. print STDERR "Total values matching criteria: $iTotalMatch\n";
66.
67. sub OpenHive
68. {
69.   my( $Hive, $Machine ) = @_;
70.   my $Root;
71.   my $HiveKey = $HIVE_LIST_ABBR{uc $Hive};
72.
73.   if( "Win32::Registry" ne ref $HiveKey )
74.   {
75.     $HiveKey = $HIVE_LIST{uc $Hive}
76.   }
77.
78.   if( "" ne $Machine )
79.   {
80.     if( ! $HiveKey->Connect( $Machine, $Root ) )
81.     {
82.       print STDERR "Could not connect to ‘$Machine'\n";
83.       $Root = 0;
84.     }
85.   }
86.   else
87.   {
88.     $Root = $HiveKey;
89.   }
90.   return( $Root );
91. }
92.
93. sub ProcessKey
94. {
95.   my( $Key, $Path, $KeyName ) = @_;
96.   my $TempKey;
97.  
98.   if( $Key->Open( $KeyName, $TempKey ) )
99.   {
100.     my( $SubKey, $Value, @Keys, %Values, $iCount );
101.     my $ThisPath = $Path;
102.     $ThisPath .= "\\$KeyName" if( "" ne $KeyName );
103.     if( $TempKey->GetValues( \%Values ) )
104.     {
105.       foreach my $Value ( sort( keys( %Values ) ) )
106.       {
107.         my( $Name, $Type, $Data ) = @{$Values{$Value}};
108.         $iTotalKeys++;
109.         printf( STDERR "% 10s keys checked; %s matched.\r",
110.                        FormatNumber( $iTotalKeys ),
111.                        FormatNumber( $iTotalMatch ) );
112.         foreach my $Target ( @{$Config{target}} )
113.         {
114.           if( "$Name\x00$Data" =~ /$Target->{find}/i )
115.           {
116.             if( REG_BINARY == $Type )
117.             {
118.               $Data = "<Binary Data>";
119.             }
120.             print STDERR " " x 60, "\r";
121.             printf( STDOUT " % d) %s\\%s: (%s) ‘%s'\n",
122.                            ++$iTotalMatch,
123.                            $ThisPath,
124.                            $Value,
125.                            ValueType( $Type ),
126.                            $Data );
127.           }
128.         }
129.       }
130.     }
131.     $TempKey->GetKeys( \@Keys );
132.  
133.     foreach $SubKey ( sort( @Keys ) )
134.     {
135.         ProcessKey( $TempKey, $ThisPath, $SubKey );
136.     }
137.     $TempKey->Close();
138.   }
139. }
140.
141. sub ValueType
142. {
143.   my( $Type ) = @_;
144.   my( $ValueType );
145.  
146.   if( REG_SZ == $Type )
147.   {
148.     $ValueType = "REG_SZ";
149.   }
150.   elsif( REG_EXPAND_SZ == $Type )
151.   {
152.     $ValueType = "REG_EXPAND_SZ";
153.   }
154.   elsif( REG_MULTI_SZ == $Type )
155.   {
156.     $ValueType = "REG_MULTI_SZ";
157.   }
158.   elsif( REG_DWORD == $Type )
159.   {
160.     $ValueType = "REG_DWORD";
161.   }
162.   elsif( REG_BINARY == $Type)
163.   {
164.     $ValueType = "REG_BINARY";
165.   }
166.   else
167.   {
168.     $ValueType = "Unknown Type";
169.   }
170.   return( $ValueType );
171. }
172.
173. sub FormatNumber
174. {
175.   my( $Number ) = @_;
176.   while( $Number =~ s/^(-?\d+)(\d{3})/$1,$2/ ){};
177.   return( $Number );
178. }
179.
180. sub Configure
181. {
182.   my( $Config ) = @_;
183.   my $Result;
184.
185.   Getopt::Long::Configure( "prefix_pattern=(-|\/)" );
186.   $Result = GetOptions( $Config,
187.                         qw(
188.                           paths|p=s@
189.                           help|?|h
190.                         )
191.   );
192.   foreach my $Target ( @ARGV )
193.   {
194.     push( @{$Config->{target}}, { find => $Target } );
195.   }
196.   if( ! scalar @{$Config->{paths}} )
197.   {
198.     push( @{$Config->{paths}}, ‘HKLM' );
199.   }
200.   $Config->{help} = 1 unless( scalar @{$Config->{target}} );
201.   $Config->{help} = 1 unless( $Result );
202.   return;
203. }
204.
205. sub Syntax
206. {
207.     my( $Script ) = ( $0 =~ m#([^\\/]+)$# );
208.     my $Line = "-" x length( $Script );
209.     print << "EOT";
210.
211.     $Script
212.     $Line
213.     Locates specified strings in the registry.
214.     Syntax: $Script [-p <path>]<find> [<find2> ...]
215.       Path..........Registry path to look into such as
216.                     HKEY_LOCAL_MACHINE or HKEY_CURRENT_USER
217.                     Abbreviations are allowed (eg. HKLM).
218.                     Default: HKEY_LOCAL_MACHINE
219.       Find..........String to search for.
220.
221.       Remote registries are allowed by prepending a machine name
222.       such as: \\\\server1\\hklm\\software
223.
224. EOT
225. }


Setting Autologon

There is a little-known ability to configure a Windows NT/2000 machine to autologon. This means that when a machine boots up, it automatically logs on as a specific user. You can set this autologon by manipulating some settings in the Registry. Using such a configuration is a terrific way to autoconfigure a machine.

Say that you need to configure 100 brand new machines. You could create an administrator-like account with a special logon script (covered in Chapter 6, "Logon Scripts"). The logon script would perform any functions that it needs to properly configure the machine-creating proper directories, copying files from a master file server, installing ODBC drivers and other software packages, and so on. Often, however, many programs need to reboot the computer before they can finish installing. Autologon handles this task. Before the logon script finishes, it sets the necessary information in the Registry and reboots the computer. The computer reboots and logs on automatically using the specified user ID. That user ID causes a logon script to run; the script recognizes the state of the machine and finishes whatever it needs to do. Finally, the logon script removes the autologon settings and either logs off or reboots again.

Using this particular example, all you need to do is walk over to each machine and log on with the special account. You do so 100 times, and you're finished. Consider this to be a poor man's version of Microsoft's Systems Management Server (SMS).

The dark side of autologon is that a username and password are added into the Registry, and they can be discovered with relative ease. The information is not encoded, so anyone with permission to view the Registry key can see both the username and password. You can remedy this by applying permissions so that only administrators and the System accounts have read access on the key and can both change permissions and take ownership.

Example 3.15 turns on or off the autologon feature, depending on what you pass into the script. The syntax is

perl setlogon.pl [-m Machine] [-clear] [<Domain\User | User> <Password>]

The switches are described in Table 3.4. If a username is specified, a password must be specified as well. The user account's domain should be specified but does not need to be. However, a logon problem may occur if another user account in the local machine's user database has the same name as one in the default domain.

Table 3.4 Autologon Switches

            Switch                                         Description

-m Machine This switch specifies for which machine to configure autologon. If this switch is not specified, the default is the local machine.
clear This switch clears the autologon configuration. The username, domain, and password are cleared, and autologon is turned off.
User The username of the account to log on automatically.
Domain The domain of the account to log on automatically. This switch is optional.
Password The password of the account to log on automatically.

Example 3.15 Setting the Autologon User Account

01. use Getopt::Long;
02. use Win32::Registry;
03.
04. $PATH = "Software\\Microsoft\\Windows NT\\CurrentVersion\\Winlogon";
05. Configure( \%Config );
06. if( $Config{help} )
07. {
08.   Syntax();
09.   exit();
10. }
11.
12. my $Root = $HKEY_LOCAL_MACHINE;
13. my $Key;
14.
15. if( "" ne $Config{machine} )
16. {
17.   print "Connecting to $Config{machine}...\n";
18.   if( ! $Root->Connect( $Config{machine}, $Root ) )
19.   {
20.     print "Can not connect to $Machine\n";
21.     exit();
22.   }
23. }
24.
25. if( $Root->Create( $PATH, $Key ) )
26. {
27.   foreach my $Param ( keys( %{$Config{params}} ) )
28.   {
29.     my $Result = $Key->SetValueEx( $Param,
30.                                    0,
31.                                    REG_SZ,
32.                                    $Config{params}->{$Param} );
33.     print "Setting $Param to ‘$Config{params}->{$Param}': ",
34.           ( $Result )? "success":"failure", "\n";
35.   }
36.   $Key->Close();
37. }
38. else
39. {
40.   print "Unable to open the $PATH key.\n";
41. }
42.
43. if( "" ne $Config{machine} )
44. {
45.   $Root->Close();
46. }
47.
48. sub Configure
49. {
50.   my( $Config ) = @_;
51.   my $Result;
52.   my $Params = {
53.     DefaultDomainName => "",
54.     DefaultUserName => "",
55.     DefaultPassword => "",
56.     AutoAdminLogon => 0,
57.   };
58.
59.   Getopt::Long::Configure( "prefix_pattern=(-|\/)" );
60.   $Result = GetOptions( $Config,
61.                         qw(
62.                           machine|m=s
63.                           clear|c
64.                           help|?|h
65.                         )
66.   );
67.   $Config->{help} = 1 unless( $Result );
68.  
69.   if( ! $Config->{clear} )
70.   {
71.     $Params->{DefaultDomainName} = shift @ARGV;
72.     if( "" ne $Params->{DefaultDomainName} )
73.     {
74.       ( $Params->{DefaultDomainName},
75.         $Params->{DefaultUserName} ) = ( $Params->{DefaultDomainName}
76.                                           =~ /((.*)\\)?(.*)$/ )[1..3];
77.     }
78.     $Params->{DefaultPassword} = shift @ARGV;
79.     $Params->{AutoAdminLogon} = 1;
80.   }
81.   else
82.   {
83.     $Params->{AutoAdminLogon} = 0;
84.   }
85.   if( ( "" eq $Params->{DefaultUserName} )
86.       && ( ! $Config->{clear} ) )
87.   {
88.     $Config->{help} = 1;
89.   }
90.   $Config->{params} = $Params;
91.   return;
92. }
93.
94. sub Syntax
95. {
96.   my( $Script ) = ( $0 =~ m#([^\\/]+)$# );
97.   my $Line = "-" x length( $Script );
98.   my $WhiteSpace = " " x length( $Script );
99.   print << "EOT";
100.
101.   $Script
102.   $Line
103.   This utility will set the default logon information such as
104.   Userid, Domain, Password and whether or not the computer
105.   automatically logs on the default user.
106.  
107.   Syntax: $Script [-m Machine] <Domain\\User|User> <Password>
108.           $WhiteSpace -clear [-m Machine]
109.
110.   User............The userid to autologon as
111.   Domain..........The domain of the autologon userid
112.   Password........The autologon userid's password
113.   -m Machine......Specifies what machine to set/unset.
114.   -clear..........Remove the domain, username and password
115.  
116. EOT
117. }


TCP/IP Configuration

You can configure multiple network cards on Windows machines. You also can bind multiple TCP/IP addresses to a given network card, which can make it difficult to track which computer has what IP address. All this information is held in the Registry on each computer. Being able to track this information, however, can be quite tedious.

Example 3.16 displays a machine's TCP/IP configuration, including addresses procured by means of DHCP. All addresses for all network cards are displayed. The information from other, remote machines can be displayed in addition to the local machine.

The script takes a single parameter that represents which machine to query. If no machine name is passed in, the local machine is assumed. For example,

perl tcpinfo.pl \\server1

In this case, the TCP configuration information is obtained for the machine \\server1.

Example 3.16 Displaying a Machine's TCP/IP Configuration

01. use Win32::Registry;
02.
03. %KeyName = (
04.   serviceroot         =>  ‘System\CurrentControlSet\Services',
05.   tcplink             =>  ‘Tcpip\Linkage',
06.   tcplink_disabled    =>  ‘Tcpip\Linkage\Disabled',
07.   win2k_tcplink       =>  ‘Tcpip\Parameters\Interfaces',
08.   deviceparam_tcp     =>  ‘Parameters\Tcpip',
09. );
10.
11. $Root = $HKEY_LOCAL_MACHINE;
12.
13. if( $Machine = $ARGV[0] )
14. {
15.   $HKEY_LOCAL_MACHINE->Connect( $Machine, $Root )
16.       || die "Could not connect to the registry on ‘$Machine'\n";
17. }
18.
19. if( $Root->Open( $KeyName{serviceroot}, $ServiceRoot ) )
20. {
21.   # First check if this is Win2k...
22.   if( $ServiceRoot->Open( $KeyName{win2k_tcplink}, $Links ) )
23.   {
24.     my @Interfaces;
25.     if( $Links->GetKeys( \@Interfaces ) )
26.     {
27.       foreach my $DeviceName ( @Interfaces )
28.       {
29.         push( @Devices, [
30.                           "$KeyName{win2k_tcplink}\\$DeviceName",
31.                           $DeviceName
32.                         ] );
33.       }
34.     }
35.     $Links->Close();
36.   }
37.   # Get the device names of the cards tcp is bound to...
38.   else
39.   {
40.     if( $ServiceRoot->Open( $KeyName{tcplink}, $Links ) )
41.     {
42.       my( $Data );
43.       if( $Links->QueryValueEx( "Bind", $DataType, $Data ) )
44.       {
45.         $Data =~ s/\n/ /gs;
46.         $Data =~ s/\\Device\\//gis;
47.         $Data =~ s/^\s+(.*)\s+$/$1/gs;
48.         foreach my $DeviceName ( split( /\c@+/, $Data ) )
49.         {
50.           push( @Devices,
51.                 [
52.                   "$DeviceName\\$KeyName{deviceparam_tcp}",
53.                   $DeviceName
54.                 ] );
55.         }
56.       }
57.       $Links->Close();
58.     }
59.     # Get the device names of cards that tcp is bound to but disabled...
60.     if( $ServiceRoot->Open( $KeyName{tcplink_disabled}, $Links ) )
61.     {
62.       my( $Data );
63.    
64.       if( $Links->QueryValueEx( "Bind", $DataType, $Data ) )
65.       {
66.         $Data =~ s/\s+//gs;
67.         $Data =~ s/\\Device\\//gis;
68.         foreach my $DeviceName ( split( /\c@+/, $Data ) )
69.         {
70.           push( @Devices,
71.                 [
72.                   "$DeviceName\\$KeyName{deviceparam_tcp}",
73.                   $DeviceName
74.                 ] );
75.         }
76.       }
77.       $Links->Close();
78.     }
79.   }
80.  
81.   foreach my $Device ( @Devices )
82.   {
83.     my( $DeviceTCPKey );
84.     my( $Path ) = $Device->[0];
85.     my( $DeviceName ) = $Device->[1];
86.    
87.     if( $ServiceRoot->Open( $Path, $DeviceTCPKey ) )
88.     {
89.       my %Hash;
90.       my( @IP, @Subnet, @Gateway, @DHCP, @DNS );
91.       my( $Data, $DataType, $Domain );
92.    
93.       # Get the domain...
94.       $DeviceTCPKey->QueryValueEx( "Domain", $DataType, $Domain );
95.      
96.       # Get the IP addresses...
97.       if( $DeviceTCPKey->QueryValueEx( "IPAddress",
98.                                        $DataType,
99.                                        $Data ) )
100.       {
101.         $Data =~ s/\s+//g;
102.         $Data =~ s/0\.0\.0\.0//g;
103.         $Data =~ s/\c@+/\c@/g;
104.         push( @IP, split( /\c@/, $Data ) );
105.         push( @DHCP, ( "no" ) x scalar split( /\c@/, $Data ) );
106.       }
107.    
108.       # Get the Subnet masks...
109.       if( $DeviceTCPKey->QueryValueEx( "SubnetMask",
110.                                        $DataType,
111.                                        $Data ) )
112.       {
113.         $Data =~ s/\s+//g;
114.         $Data =~ s/0\.0\.0\.0//g;
115.         $Data =~ s/\c@+/\c@/g;
116.         push( @Subnet, split( /\c@/, $Data ) );
117.       }
118.                       # Get the default gateways...
119.                       if( $DeviceTCPKey->QueryValueEx( "DefaultGateway",
120.                                        $DataType,
121.                                        $Data ) )
122.                           {
123.                           $Data =~ s/\s+//g;
124.                           $Data =~ s/0\.0\.0\.0//g;
125.                           $Data =~ s/\c@+/\c@/g;
126.                           push( @Gateway, split( /\c@/, $Data ) );
127.                           }
128.       # Get the name servers...
129.       if( $DeviceTCPKey->QueryValueEx( "NameServer",
130.                                        $DataType,
131.                                        $Data ) )
132.       {
133.         $Data =~ s/0\.0\.0\.0//g;
134.         push( @DNS, split( /\s+/, $Data ) );
135.       }
136.
137.       # Query DHCP stats
138.       if( $DeviceTCPKey->QueryValueEx( "EnableDHCP",
139.                                        $DataType,
140.                                        $Data ) )
141.       {
142.         $Hash{dhcp} = (( $Data )? "yes":"no" );
143.
144.          # Get the DHCP domain...
145.         $DeviceTCPKey->QueryValueEx( "DhcpDomain",
146.                                      $DataType,
147.                                      $Domain );
148.
149.         # Get the DHCP IP Addresses...
150.         if( $DeviceTCPKey->QueryValueEx( "DHCPIPAddress",
151.                                          $DataType,
152.                                          $Data ) )
153.         {
154.           $Data =~ s/\s+//g;
155.           $Data =~ s/0\.0\.0\.0//g;
156.           $Data =~ s/\c@+/\c@/g;
157.           push( @IP, split( /\c@/, $Data ) );
158.           push( @DHCP, ( "yes" ) x scalar split( /\c@/, $Data ) );
159.         }
160.                
161.         # Get the DHCP subnet masks...
162.         if( $DeviceTCPKey->QueryValueEx( "DHCPSubnetMask",
163.                                          $DataType,
164.                                          $Data ) )
165.         {
166.           $Data =~ s/\s+//g;
167.           $Data =~ s/0\.0\.0\.0//g;
168.           $Data =~ s/\c@+/\c@/g;
169.           push( @Subnet, split( /\c@/, $Data ) );
170.         }
171.        
172.         # Get the DHCP gateways...
173.         if( $DeviceTCPKey->QueryValueEx( "DHCPDefaultGateway",
174.                                          $DataType,
175.                                          $Data ) )
176.         {
177.           $Data =~ s/\s+//g;
178.           $Data =~ s/0\.0\.0\.0//g;
179.           $Data =~ s/\c@+/\c@/g;
180.           push( @Gateway, split( /\c@/, $Data ) );
181.         }
182.         
183.         # Get the DHCP name servers...
184.         if( $DeviceTCPKey->QueryValueEx( "DHCPNameServer",
185.                                          $DataType,
186.                                          $Data ) )
187.         {
188.           $Data =~ s/0\.0\.0\.0//g;
189.           push( @DNS, split( /\s+/, $Data ) );
190.         }
191.       }
192.       next if( 0 == scalar @IP );
193.       $Hash{name} = $DeviceName;
194.       $Hash{ip} = join( " " x 8, @IP );
195.       $Hash{subnet} = join( " " x 8, @Subnet );
196.       $Hash{gateway} = join( " " x 8, @Gateway );
197.       $Hash{dhcp} = join( " " x 8, @DHCP );
198.       $Hash{dns} = join( " " x 8, @DNS );
199.       $Hash{domain} = $Domain;
200.  
201.       # Push our newfound data onto the stack...
202.       push( @TCPConfig, \%Hash );
203.      
204.       $DeviceTCPKey->Close();   
205.     }
206.   }
207.   print "The machine $Machine has the following IP addresses:\n";
208.   foreach $IP ( @TCPConfig )
209.   {
210.     print "\nInterface: $IP->{name}\n";
211.     print "Domain: $IP->{domain}\n";
212.  
213.     $~ = TCPHeader;
214.     write;
215.     $~ = TCPDump;
216.     write;
217.   }
218.   $ServiceRoot->Close();
219. }
220.
221. format TCPHeader =
222.   @<<< @<<<<<<<<<<<<<< @<<<<<<<<<<<<<< @<<<<<<<<<<<<<< @<<<<<<<<<<<<<<
223.   "DHCP", "IP Address", "Subnet Mask", "Gateway", "DNS"
224.   ---- --------------- --------------- --------------- ---------------
225. .
226.
227. format TCPDump =
228.   ^<<< ^<<<<<<<<<<<<<< ^<<<<<<<<<<<<<< ^<<<<<<<<<<<<<< ^<<<<<<<<<<<<<<
229.   $IP->{dhcp}, $IP->{ip}, $IP->{subnet}, $IP->{gateway}, $IP->{dns}
230. ~ ^<<< ^<<<<<<<<<<<<<< ^<<<<<<<<<<<<<< ^<<<<<<<<<<<<<< ^<<<<<<<<<<<<<<
231.   $IP->{dhcp}, $IP->{ip}, $IP->{subnet}, $IP->{gateway}, $IP->{dns}
232. ~ ^<<< ^<<<<<<<<<<<<<< ^<<<<<<<<<<<<<< ^<<<<<<<<<<<<<< ^<<<<<<<<<<<<<<
233.   $IP->{dhcp}, $IP->{ip}, $IP->{subnet}, $IP->{gateway}, $IP->{dns}
234. ~ ^<<< ^<<<<<<<<<<<<<< ^<<<<<<<<<<<<<< ^<<<<<<<<<<<<<< ^<<<<<<<<<<<<<<
235.   $IP->{dhcp}, $IP->{ip}, $IP->{subnet}, $IP->{gateway}, $IP->{dns}
236. ~ ^<<< ^<<<<<<<<<<<<<< ^<<<<<<<<<<<<<< ^<<<<<<<<<<<<<< ^<<<<<<<<<<<<<<
237.   $IP->{dhcp}, $IP->{ip}, $IP->{subnet}, $IP->{gateway}, $IP->{dns}
238. ~ ^<<< ^<<<<<<<<<<<<<< ^<<<<<<<<<<<<<< ^<<<<<<<<<<<<<< ^<<<<<<<<<<<<<<
239.   $IP->{dhcp}, $IP->{ip}, $IP->{subnet}, $IP->{gateway}, $IP->{dns}
240. ~ ^<<< ^<<<<<<<<<<<<<< ^<<<<<<<<<<<<<< ^<<<<<<<<<<<<<< ^<<<<<<<<<<<<<<
241.   $IP->{dhcp}, $IP->{ip}, $IP->{subnet}, $IP->{gateway}, $IP->{dns}
242. ~ ^<<< ^<<<<<<<<<<<<<< ^<<<<<<<<<<<<<< ^<<<<<<<<<<<<<< ^<<<<<<<<<<<<<<
243.   $IP->{dhcp}, $IP->{ip}, $IP->{subnet}, $IP->{gateway}, $IP->{dns}
244. .


Conclusion

There are dozens of different tools that an administrator can use to make her job easier. Tools ranging from open source code to commercial shrink-wrapped packages provide the necessary utilities to accomplish an administrative goal.

This chapter demonstrates Perl's remarkable capability to exercise the Win32 API to accomplish tasks. We have uncovered ways to use Perl to manage the Win32 file system, permissions, and the Registry. The tools discussed are useful and help define techniques that can be modified to target other tasks.

Copyright © 2000 New Riders Publishing

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here

Share

About the Author

New Riders

United States United States
No Biography provided

Comments and Discussions

 
Generalsome wrong on 2t disk simulator Pinmembernhchmg15-Dec-10 23:52 
GeneralIndentation problems PinsussSkurfur5-Dec-07 12:14 
Questionhow can I know disk space? Pinmemberazusakt2-Mar-04 18:38 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

Info
| Advertise | Privacy | Terms of Use | Mobile
Web01 | 2.8.141223.1 | Last Updated 13 Mar 2001
Article Copyright 2001 by New Riders
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid