|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Announcements
Want a new Job?
Chapters
Services
Feature Zones
|
Chapter 3 - ToolsEvery 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 SystemThe 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 DirectoriesEver 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 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 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 InformationFor 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
perl volume.pl c:\ \\server\share In this case, the script displays volume information for both the
local machine's 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.
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 LinksUNIX 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 DirectoriesCreating
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 Dir1\Dir2 and
Even Win32
Perl fails to create any intermediary directories-most likely because it simply
calls into the C: library's 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, 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 StreamsOne 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
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 SizesInterestingly 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 StreamsThe 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 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 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 VersionsMany 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. } PermissionsWin32 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 PermissionsUNIX machines have always been capable of listing who has
permissions on files and directories. You get this listing by using the 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 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
Figure 3.3 shows the permissions on a specific directory ( 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:\>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.
| ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||