##################################################################################################################
#
# Perl script to search for libraries and binaries which has manifest file associated with it.
# The manifest is generated by each library/binary if that project uses CRT,MFC,ATL related functionality.
# 
# perl $0 <SEARCH_BASE_DIR> <IN_CONF_FILE> <IN_MANIFEST_FILE> <OUT_FILE> <INJECT>
#
#        SEARCH_BASE_DIR  => Directory to search for files as per FILE_EXT specified IN_CONF_FILE
#        IN_CONF_FILE     => Configuration file which specifies filters and other parameters
#        IN_MANIFEST_FILE => Input Manifest file to inject into all libraries/binaries
#        OUT_FILE         => Output file to store full path of each seach result
#        INJECT           => 1 to inject MANIFEST_FILE, 0 otherwise
#
##################################################################################################################

use File::Find;
use File::Copy;
use Cwd;

# Show usage Help
sub ShowHelp()
{
 print "\n *****************************************  USAGE  ******************************************\n\n";
 print "  perl $0 <SEARCH_BASE_DIR> <IN_CONF_FILE> <IN_MANIFEST_FILE> <OUT_FILE> <INJECT> \n\n\n";
 print "  SEARCH_BASE_DIR  => Directory to search for files as per FILE_EXT specified IN_CONF_FILE \n\n";
 print "  IN_CONF_FILE     => Configuration file which specifies filters and other parameters\n\n";
 print "  IN_MANIFEST_FILE => Input Manifest file present in PWD to inject into all libraries and binaries\n\n";
 print "  OUT_FILE         => Output file to store full path of each seach result\n\n";
 print "  INJECT           => 0 => Donot inject, 1 => Inject MANIFEST_FILE, 2=> Backup and Inject\n\n";
 print "\n ********************************************************************************************\n\n";
}


# Returns string with whitespaces removed from both ends
sub Trim
{
     	my $string = shift;
      	$string =~ s/^\s+//;
        $string =~ s/\s+$//;
        return $string;
}


# Print error
sub PrintError
{
  print "\n"." ERROR : ". $_[0] ."\n";
}


# Read configuration parameters from file and update configuration parameters.
sub ReadConf
{
 open(INFILE, "< $IN_CONF_FILE");
 $section  = 0;
 prt( "Reading config file [$IN_CONF_FILE] ...\n" );

 while (<INFILE>)
 {
    $line = $_;

    if ( $line =~ m/^MT_DOT_EXE=/i )
    {
     $MT_DOT_EXE = "\"".Trim(substr($line,length("MT_DOT_EXE=")))."\"";
     prt( "Found MT_DOT_EXE = [$MT_DOT_EXE]\n" );
     next;
    }

    if ( $line =~ m/^#FILE_EXT/i )
    {
     $section = 1;
     next;
    }

    if ( $line =~ m/^#END_FILE_EXT/i )
    {
     $section = 0;
     next;
    }

    if ( $line =~ m/^#STRIP_EXT/i )
    {
     $section = 2;
     next;
    }

    if ( $line =~ m/^#END_STRIP_EXT/i )
    {
     $section = 0;
     next;
    }

    if ( $line =~ m/^#ARCH/i )
    {
     $section = 3;
     next;
    }

    if ( $line =~ m/^#END_ARCH/i )
    {
     $section = 0;
     next;
    }

    if ( $line =~ m/^#TYPE/i )
    {
     $section = 4;
     next;
    }

    if ( $line =~ m/^#END_TYPE/i )
    {
     $section = 0;
     next;
    }

    if ( $line =~ m/^#STRICT/i )
    {
     $section = 5;
     next;
    }

    if ( $line =~ m/^#END_STRICT/i )
    {
     $section = 0;
     next;
    }

    if( $section >=1 && $section <= 5)
    {
        if( $section == 1 )		{
            $line =~ s/\./\\./g;
            prt( "Added:1: FILE_EXT ".($#FILE_EXT + 1)." ".Trim($line)."\n" );
            $FILE_EXT[$#FILE_EXT+1] = Trim($line)."\$";
        }
        if( $section == 2 )		{
            prt( "Added:2: STRIP_EXT ".($#STRIP_EXT + 1)." ".Trim($line)."\n" );
            $STRIP_EXT[$#STRIP_EXT+1] = Trim($line); $line =~ s/\./\\./g;
            $STRIP_REG_EXT[$#STRIP_REG_EXT+1] = Trim($line);
        }
        if( $section == 3 )		{
            prt( "Added:3: ARCH ".($#ARCH + 1)." ".Trim($line)."\n" );
            $ARCH[$#ARCH+1]           = Trim($line);
        }
        if( $section == 4 )		{
            prt( "Added:4: TYPE ".($#TYPE + 1)." ".Trim($line)."\n" );
            $TYPE[$#TYPE+1]           = Trim($line);
        }
        if( $section == 5 )		{ 
            prt( "Added:5: STRICT ".($#STRICT + 1)." ".Trim($line)."\n" );
            $STRICT[$#STRICT+1]       = Trim($line);
        }
    }
 }
 
 close(INFILE);
}


# ProcessFileDir function which does all the processing...
# This function is inovked as when each entry is either file or directory hit
sub ProcessFileDir
{
    $step = 0;
    $lib_type = 0;

    $dir_name = $File::Find::dir;
    $file_name = $_;
    $full_path = $File::Find::name;

    # return if $full_path is not file
    #return unless -f $full_path;
    if ( ! -f $full_path ) {
        print "ERROR: Returned because [$full_path] is NOT a file ...\n";
    }

    $full_path =~ s,/,\\,g;
    $dir_name  =~ s,/,\\,g;

    ############################################
    # Check for FILE_EXT configuration parameter....
    ############################################
    if ( $#FILE_EXT < 0 ) { $step = 1; }
    for($ind=0;$ind<=$#FILE_EXT;$ind++)
    {
        prt( "Match [$file_name] with ".$FILE_EXT[$ind]." ...\n" );
     if( $file_name =~ m/$FILE_EXT[$ind]/i )
     {
      $step = 1;

      if ( $file_name =~ m/\.exe\./i )
      {
       $lib_type = 1;
      }
      ###elsif ( $file_name =~ m/\.dll\./i )
      elsif ( $file_name =~ m/\.dll$/i )
      {
       $lib_type = 2;
      }
      elsif ( $file_name =~ m/\.dlm\./i )
      {
       $lib_type = 2;
      }
      
      last;
     }
    }
    #return unless $step == 1;
    if ($step != 1) {
        print "ERROR: step not set to 1 ... file name = [$file_name] ... lib type [$lib_type] ...\n";
        return;
    }
    
    ############################################
    # Check for ARCH configuration parameter....
    ############################################
    if ( $#ARCH < 0 ) { $step = 2; }
    for($ind=0;$ind<=$#ARCH;$ind++)
    {
     if( $full_path =~ m/$ARCH[$ind]/i )
     {
      $step = 2;
      last;
     }
    }
    #return unless $step == 2;
    if ($step != 2) {
        print "ERROR: step not set to 2 ... No match to [$ARCH] ...\n";
        return;
    }

    ############################################
    # Check for TYPE configuration parameter....
    ############################################
    if ( $#TYPE < 0 ) { $step = 3; }
    for($ind=0;$ind<=$#TYPE;$ind++)
    {
     if( $full_path =~ m/$TYPE[$ind]/i )
     {
      $step = 3;
      last;
     }
    }
    #return unless $step == 3;
    if ($step != 3) {
        print "ERROR: step not set to 3 ...\n";
        return;
    }

    ############################################
    # Check for STRICT configuration parameter....
    ############################################
    if ( $#STRICT < 0 ) { $step = 4; }
    for($ind=0;$ind<=$#STRICT;$ind++)
    {
     if( $full_path =~ m/$STRICT[$ind]/i )
     {
      $step = 4;
      last;
     }
    }
    #return unless $step == 4;
    if ($step != 4) {
        print "ERROR: step not set to 4 ...\n";
        return;
    }

    for($ind=0;$ind<=$#STRIP_REG_EXT;$ind++)
    {
     if( $full_path =~ m/$STRIP_REG_EXT[$ind]$/i )
     {
      $step = 5;
      last;
     }
    }
    #return unless $step == 5;
    if ($step != 5) {
        print "ERROR: step not set to 5 ...\n";
        return;
    }

    # Now write to Console , $OUT_FILE.txt and $OUT_FILE.bat
    print MANIFEST_OUT $full_path."\n";

    #$path_to_library = substr($file_name,0,length($file_name)-length($STRIP_EXT[$ind]));
    prt( "path [$full_path] and name [$file_name] ... lib type [$lib_type] \n" );
    #$path_to_library = substr($full_path,0,length($full_path)-length($STRIP_EXT[$ind]));
    #$file_name = substr($file_name,0,length($file_name)-length($STRIP_EXT[$ind]));
    #prt( "Adjusted to path [$path_to_library] and name [$file_name] ...\n" );
    $path_to_library = $full_path;

    if ( $lib_type != 0 && -f $path_to_library)
    {
      print "   ".$full_path."\n";

      $VALID_COUNT++;
      print COMMAND_OUT "$MT_DOT_EXE -nologo -manifest \"$IN_MANIFEST_FILE\" -outputresource:\"$path_to_library\";#$lib_type"."\n";
      $command_param = "-nologo -manifest \"$IN_MANIFEST_FILE\" -outputresource:\"$path_to_library\";#$lib_type";
      
      # backup original file before injecting manifest file & save backup file information in $OUT_FILE_backup.bat
      if( $INJECT == 2 )
      {
        copy("$path_to_library","$path_to_library".".backup") or die "Backup failed: $!";
        print BACKUP_OUT "del \"$path_to_library\""."\n";
        print BACKUP_OUT "rename \"$path_to_library.backup\"  \"$file_name\""."\n";
      }
      
      # Now inject the manifest file
      if ( $INJECT >= 1 )
      {
        if( system("$MT_DOT_EXE $command_param") != 0 )
        {
         print LOG_OUT "ERROR : Injecting manifest failed for $path_to_library"."\n";
        }
        else
        {
         print LOG_OUT "SUCCESS : Injected manifest into $path_to_library"."\n";
        }
      } else {
         print LOG_OUT "NOTHING DONE : Inject = $INJECT"."\n";
         prt( "NOTHING DONE : Inject = $INJECT"."\n" );
      }
    } else {
        if ($lib_type != 0) {
            prt( "Rejected because [$path_to_library] is NOT a file ...\n" );
        } else {
            prt( "Rejected because lib_type is ZERO ...\n" );
        }
    }

    $COUNT++;
}

################################################# ENTRY POINT ####################################################

for ($count = 0; $count <=$#ARGV; $count++)
{
 if( lc($ARGV[$count]) eq "help" || lc($ARGV[$count]) eq "h" || lc($ARGV[$count]) eq "-h" || lc($ARGV[$count]) eq "--h" || lc($ARGV[$count]) eq "/?" || lc($ARGV[$count]) eq "--help" || lc($ARGV[$count]) eq "-help" )
 {
 ShowHelp();
 exit;
 }
}

if( $#ARGV != 4 )
{
 PrintError("Wrong number of parameters.");
 ShowHelp();
 exit;
}

# Genric paremeters
$COUNT = 0;
$VALID_COUNT = 0;
$CUR_DIR = getcwd . "/";  #$CUR_DIR =~ s,/,\\,g;

# Process command line parameters
$SEARCH_BASE_DIR  = $ARGV[0];
$IN_CONF_FILE     = $ARGV[1];
$IN_MANIFEST_FILE = $CUR_DIR.$ARGV[2];
$OUT_FILE         = $ARGV[3];
$INJECT           = $ARGV[4];

# Configuration file parameteres...
$MT_DOT_EXE;
@FILE_EXT;
@STRIP_EXT;
@STRIP_REG_EXT;
@ARCH;
@TYPE;
@STRICT;

# Return if $SEARCH_BASE_DIR is not a directory
if( ! -d $SEARCH_BASE_DIR )
{
  PrintError("\"$SEARCH_BASE_DIR\" is not a directory. Refer to usage.");
  exit;
}

# Return if $IN_CONF_FILE is not a file
if( ! -f $IN_CONF_FILE )
{
  PrintError("\"$IN_CONF_FILE\" is not a file. Refer to usage.");
  exit;
}

# Return if $IN_MANIFEST_FILE is not a file
if( ! -f $IN_MANIFEST_FILE )
{
  PrintError("\"$IN_MANIFEST_FILE\" is not a file. Refer to usage.");
  exit;
}

# Check whether $INJECT value range from 0 to 2
if( ! ($INJECT >=0 && $INJECT <= 2) )
{
  PrintError("INJECT = \"$INJECT\" is out of range. Refer to usage.");
  exit;
}

# Now read configuration file and update configuration parameters.
ReadConf();

$MT_DOT_EXE =~ s,\",,g;

if( ! -f $MT_DOT_EXE )
{
  PrintError("$MT_DOT_EXE does not exist. Refer to conf file.");
  exit;
}

$MT_DOT_EXE = "\"". $MT_DOT_EXE ."\"";

if ( $#STRIP_EXT < 0 )
{
  PrintError("Section 2 is empty in configuration file \"$IN_CONF_FILE\".");
  exit;
}

print "\n ********************************************************************************\n\n";
print "   Searching for pattern files specified in \"$IN_CONF_FILE\" under \"".$SEARCH_BASE_DIR."\"\n\n";
print " ********************************************************************************\n\n";

# Create two output files.
# $OUT_FILE.txt will contain found pattern files
# $OUT_FILE.bat will contain command which can be executed manullay if
# user wants to inject manifest file manually.
open(LOG_OUT,  "> $OUT_FILE.log");
open(MANIFEST_OUT, "> $OUT_FILE.txt");
open(COMMAND_OUT,  "> $OUT_FILE.bat");

print MANIFEST_OUT "This file contains all the files that are selected before the pattern match.\n\n";

if( $INJECT == 1 )
{
 print "\n   "."$IN_MANIFEST_FILE will be injected into selected libraries..."."\n\n\n";
 print " ********************************************************************************\n\n";
}

if( $INJECT == 2 )
{
 print "\n   "."Original libraries will be backed up..!!!";
 print "\n   "."$IN_MANIFEST_FILE will be injected into selected libraries..."."\n\n\n";
 print " ********************************************************************************\n\n";
 open(BACKUP_OUT,  "> $OUT_FILE.backup.bat");
}

find({ wanted => \&ProcessFileDir }, $SEARCH_BASE_DIR);

print "\n   Found \"".$COUNT."\" pattern files under \"".$SEARCH_BASE_DIR."\"\n\n";

print "\n   Only \"".$VALID_COUNT."\" file(s) will be injected with \"".$IN_MANIFEST_FILE."\"";
print "\n   \"".($COUNT-$VALID_COUNT)."\" file(s) either don't match the pattern or not available."."\n\n";
print " ********************************************************************************\n\n";

close(LOG_OUT);
close(MANIFEST_OUT);
close(COMMAND_OUT);

if( $INJECT == 2)
{
 close(BACKUP_OUT,  "> $OUT_FILE.bak");
}

sub prt {
    my ($txt) = shift;
    print $txt;
}

##################################################################################################################
