#  Streams.pl
#  Example 3.8:
#  ----------------------------------------
#  From "Win32 Perl Scripting: Administrators Handbook" by Dave Roth
#  Published by New Riders Publishing.
#  ISBN # 1-57870-215-1
#
#  This script displays all NTFS streams in a file.
#
print "From the book 'Win32 Perl Scripting: The Administrator's Handbook' by Dave Roth\n\n";


use Win32::API::Prototype;

$OPEN_EXISTING = 3;
$GENERIC_READ  = 0x80000000;                                     
$BACKUP_DATA   = 0x00000001;
$BACKUP_ALTERNATE_DATA = 0x00000004;

ApiLink( 'kernel32.dll', 
         'HANDLE CreateFile( LPCTSTR pszPath, 
                             DWORD dwAccess,
                             DWORD dwShareMode, 
                             PVOID SecurityAttributes,
                             DWORD dwCreationDist, 
                             DWORD dwFlags,
                             HANDLE hTemplate )' ) 
    || die "Can not locate CreateFile()";
ApiLink( 'kernel32.dll', 
         'BOOL CloseHandle( HANDLE hFile )' ) 
    || die "Can not locate CloseHandle()";
ApiLink( 'kernel32.dll', 
         'BOOL BackupRead( HANDLE hFile, 
                           LPBYTE pBuffer, 
                           DWORD dwBytesToRead, 
                           LPDWORD pdwBytesRead, 
                           BOOL bAbort, 
                           BOOL bProcessSecurity, 
                           LPVOID *ppContext)' ) 
    || die "Can not locate BackupRead()";
ApiLink( 'kernel32.dll', 
         'BOOL BackupSeek( HANDLE hFile, 
                           DWORD dwLowBytesToSeek,
                           DWORD dwHighBytesToSeek,
                           LPDWORD pdwLowByteSeeked,
                           LPDWORD pdwHighByteSeeked,
                           LPVOID *pContext )' ) 
    || die "Can not create BackupSeek()";

# Generate a list of all files to process
# therefore we have to expand any wildcards
foreach my $Mask ( @ARGV )
{
    foreach my $Path ( glob( $Mask ) )
    {
        push( @Files, $Path ) if( -f $Path );
    }
}

foreach my $File ( @Files ) 
{
  print "$File\n";
  
  $hFile = CreateFile( $File, 
                       $GENERIC_READ, 
                       0, 
                       undef, 
                       $OPEN_EXISTING, 
                       0, 
                       0 ) || die "Can not open the file '$File'\n";

  # If CreateFile() failed $hFile is a negative value
  if( 0 < $hFile )
  {
    my $iStreamCount = 0;
    my $iTotalSize = 0;
    my $pBytesRead = pack( "L", 0 );
    my $pContext = pack( "L", 0 );
    my $pStreamIDStruct = pack( "L5", 0,0,0,0,0,0 );
    
    while( BackupRead( $hFile, 
                       $pStreamIDStruct, 
                       length( $pStreamIDStruct ), 
                       $pBytesRead, 
                       0, 
                       0, 
                       $pContext ) )
    {
      my $BytesRead = unpack( "L", $pBytesRead );
      my $Context = unpack( "L", $pContext );
      my %Stream;
      my( $pSeekBytesLow, $pSeekBytesHigh ) = ( pack( "L", 0 ), 
                                                pack( "L", 0 ) );
      my $StreamName = "";
      my $StreamSize;
      
      # No more data to read
      last if( 0 == $BytesRead );

      @Stream{ id, attributes, 
               size_low, size_high, 
               name_size } = unpack( "L5", $pStreamIDStruct );
      
      if( $BACKUP_ALTERNATE_DATA == $Stream{id} )
      {
        $StreamName = NewString( $Stream{name_size} );
        if( BackupRead( $hFile, 
                        $StreamName, 
                        $Stream{name_size}, 
                        $pBytesRead, 
                        0, 
                        0, 
                        $pContext ) )
        {
          my $String = CleanString( $StreamName, 1 );
          $String =~ s/^:(.*?):.*$/$1/;
          $StreamName = $String;
        }
      }
      elsif( $BACKUP_DATA == $Stream{id} )
      {
        $StreamName = "<Main Data Stream>";
      }
      $StreamSize = MakeLargeInt( $Stream{size_low}, $Stream{size_high} );
      $iTotalSize += $StreamSize;

      printf( "  % 3d) %s (%s bytes)\n", 
              ++$iStreamCount, 
              $StreamName,
              FormatNumber( $StreamSize )  ) if( "" ne $StreamName );
      
      # Move to next stream...
      if( ! BackupSeek( $hFile, 
                        $Stream{size_low}, 
                        $Stream{size_high}, 
                        $pSeekBytesLow, 
                        $pSeekBytesHigh, 
                        $pContext ) )
      {
        last;
      }
      $pBytesRead = pack( "L2", 0 );
      $pStreamIDStruct = pack( "L5", 0,0,0,0,0 );
    } 
    printf( "       Total file size: %s bytes\n", 
            FormatNumber( $iTotalSize ) );
    # Abort the backup reading. Win32 API claims we MUST do this.
    BackupRead( $hFile, undef, 0, 0, 1, 0, $pContext );
    CloseHandle( $hFile );
  }
  print "\n";
}

sub FormatNumber 
{
  my( $Num ) = @_;
  {} while( $Num =~ s/^(-?\d+)(\d{3})/$1,$2/ );
  return( $Num );
}

sub MakeLargeInt
{
  my( $Low, $High ) = @_;
  return( $High * ( 1 + 0xFFFFFFFF ) + $Low );
}
