bldsystemtools/commonbldutils/AntiVirus.pm
changeset 0 83f4b4db085c
child 2 99082257a271
equal deleted inserted replaced
-1:000000000000 0:83f4b4db085c
       
     1 package AntiVirus;
       
     2 use strict;
       
     3 
       
     4 use File::Copy;
       
     5 use Win32::Process;
       
     6 
       
     7 require Exporter;
       
     8 our @ISA = qw(Exporter);
       
     9 our @EXPORT_OK = qw{Start Stop Scan WaitTillAntiVirusStarts};
       
    10 
       
    11 # Start:
       
    12 # Description: attempts to start all supported anti-virus services/processes
       
    13 # Revised April 2007 to support McAfee only, Sophos and WebRoot having passed into history!
       
    14 # Arguments: none
       
    15 # Returns: none
       
    16 sub Start()
       
    17 {
       
    18     my @iMcAfeeCommands = (
       
    19     {AppName => 'net.exe', Params => 'start McAfeeFramework'},
       
    20     {AppName => 'net.exe', Params => 'start McShield'},
       
    21     {AppName => 'net.exe', Params => 'start McTaskManager'},
       
    22     # Must supply full pathname, as these applications are not in "PATH"
       
    23     {AppName => 'C:\\Program Files\\Network Associates\\Common Framework\\UpdaterUI.exe', Params => '/StartedFromRunKey'},
       
    24     {AppName => 'C:\\Program Files\\Network Associates\\Common Framework\\UdaterUI.exe', Params => '/StartedFromRunKey'},
       
    25     {AppName => 'C:\\Program Files\\McAfee\\Common Framework\\UpdaterUI.exe', Params => '/StartedFromRunKey'},
       
    26     {AppName => 'C:\\Program Files\\McAfee\\Common Framework\\UdaterUI.exe', Params => '/StartedFromRunKey'},
       
    27     {AppName => 'C:\\Program Files\\Common Files\\Network Associates\\TalkBack\\tbmon.exe', Params => ''},
       
    28     {AppName => 'C:\\Program Files\\Network Associates\\VirusScan\\SHSTAT.EXE', Params => '/STANDALONE'},
       
    29     {AppName => 'C:\\Program Files\\McAfee\\VirusScan\\SHSTAT.EXE', Params => '/STANDALONE'});
       
    30 
       
    31     # Execute all "START" commands
       
    32     for my $iCmd(@iMcAfeeCommands)
       
    33     {
       
    34         ExecuteProcess(20,$iCmd);
       
    35     }
       
    36 
       
    37 }
       
    38 
       
    39 # WaitTillAntiVirusStarts
       
    40 # Description:  
       
    41 # Sometimes the  Antivirus::Stop() is invoked before the Antivirus services are started in the machine.
       
    42 # because of which the Antivirus starts when the build is ongoing, and the active Antivirus disrupts/slows down the build
       
    43 # 
       
    44 # If the Antivirus services fail to start beyond the specified timeout period,reports error and aborts the build.
       
    45 #
       
    46 # Arguments : Waiting time in seconds before next attempt is made to check AV service status, max number of attempts
       
    47 # Returns   :  returns nothing if AV running, aborts build if AV not running even after the waiting period
       
    48 sub WaitTillAntiVirusStarts{
       
    49     my $waitingTime  = shift;
       
    50     my $retries      = shift;
       
    51 	
       
    52 	my @waitList      = shift;
       
    53      
       
    54     my $defaultWaitingTime  = 30;  # Waiting time in seconds, before next attempt is made to check AV service status
       
    55     my $defaultRetries      = 5;   # Try upto 5 times
       
    56     
       
    57     # assign default values to waitingtime,retries if values not specified
       
    58     unless($waitingTime)
       
    59 	{
       
    60         $waitingTime = $defaultWaitingTime;
       
    61     }
       
    62     unless($retries)
       
    63 	{
       
    64         $retries = $defaultRetries;
       
    65     } 
       
    66 	
       
    67     my $attempt = 0; 
       
    68     ## If AV not active, wait and retry
       
    69     while(1)
       
    70 	{ 
       
    71         if(IsAntiVirusActive(@waitList))
       
    72 		{
       
    73             return; ## AV is active, all fine, proceed
       
    74         }
       
    75         $attempt++;
       
    76         if($attempt > $retries) 
       
    77 		{
       
    78             last;
       
    79         } 
       
    80         print  "REMARK: One or more Antivirus services not active yet (At ".scalar localtime().") \n";
       
    81         print  "Waiting $waitingTime secs before rechecking status (Attempt $attempt of $retries) ...\n\n";
       
    82         sleep($waitingTime);        
       
    83     } 
       
    84     
       
    85     ## Antivirus is not active even after waiting $waitingTime secs $retries times, report error and abort the build
       
    86     
       
    87     print "ERROR: RealTimeBuild : Antivirus is not active even after waiting for $waitingTime secs x $retries attempts\n";
       
    88     print "Antivirus cannot be reliably stopped,abandon build.";
       
    89 }
       
    90 
       
    91 
       
    92 # IsAntiVirusActive
       
    93 # Description:  
       
    94 # Runs 'net start' command to get a list of active services,checks if the Antivirus services are in the list
       
    95 # 
       
    96 # Arguments : None
       
    97 # Returns   : 
       
    98 # 			returns 0  even if one Antivirus Service is inactive
       
    99 # 			returns 1 if All AntiVirus Services active
       
   100 sub IsAntiVirusActive{
       
   101 
       
   102 	my @waitList = shift;
       
   103 	
       
   104     my @AntiVirusServices = @waitList;
       
   105 
       
   106     print "Checking Antivirus services status ...\n"; 
       
   107     my $iCmd={AppName => 'net.exe', Params => 'start'};
       
   108     
       
   109     # Check if net.exe is accessible
       
   110     unless(-e $iCmd->{'AppName'})
       
   111     {
       
   112        my $iExecutable = FindFileFromPath($iCmd->{'AppName'});
       
   113         unless ($iExecutable)
       
   114         {
       
   115             print "REMARK: File not found $iCmd->{'AppName'}\n";
       
   116             print "REMARK: Unable to check Antivirus status\n";
       
   117             return 0; 
       
   118         }
       
   119         $iCmd->{'AppName'} = $iExecutable;
       
   120     }
       
   121  
       
   122     my $iCommand = "\"$iCmd->{'AppName'}\" $iCmd->{'Params'}" ;
       
   123     print "Running: $iCommand\n";
       
   124     my @iOutput = `$iCommand`;
       
   125 	 
       
   126     return &ParseServiceStatus(\@iOutput,\@AntiVirusServices);
       
   127 }
       
   128 
       
   129 
       
   130 # ParseServiceStatus
       
   131 # Description:  
       
   132 # Runs  Compares output of 'net start' command to get a list of active services,checks if the Antivirus services are in the list
       
   133 # 
       
   134 # Arguments : Output of 'net start', Antivirus service list
       
   135 # Returns   : 
       
   136 # 			returns 0  even if one Antivirus Service is not in the list
       
   137 # 			returns 1 if All AntiVirus Services are in the list
       
   138 sub ParseServiceStatus($$)
       
   139 {
       
   140     my $iOutputRef = shift;
       
   141     my $iAntiVirusServiceList = shift;
       
   142     foreach(@$iOutputRef) 
       
   143     {
       
   144         chomp ;
       
   145         s/^[\t\s]+//g;  # strip whitespace at the beginning of the line
       
   146     } 
       
   147 	
       
   148     for my $serviceName(@$iAntiVirusServiceList) 
       
   149     { 
       
   150         my @match = grep (/^$serviceName$/i, @$iOutputRef); 
       
   151         unless(@match)
       
   152 		{
       
   153             print "Antivirus Service \'$serviceName\' inactive\n";
       
   154             return 0;
       
   155         }
       
   156     }
       
   157     print "All Antivirus services active.\n";  
       
   158     return 1;  
       
   159 }
       
   160 
       
   161 
       
   162 # Stop:
       
   163 # Description: attempts to stop all supported anti-virus services/processes
       
   164 # Revised April 2007 to support McAfee only, Sophos and WebRoot having passed into history!
       
   165 # Arguments: none
       
   166 # Returns: none
       
   167 sub Stop($$)
       
   168 {
       
   169 	# Need to check if the Antivirus services is installed or enabled.
       
   170 	my @iMcAfeeServices = (
       
   171     {AppName => 'net.exe', Params => 'stop McAfeeFramework', Services =>'McAfee Framework Service'},
       
   172     {AppName => 'net.exe', Params => 'stop McShield', Services =>'Network Associates McShield'},
       
   173     {AppName => 'net.exe', Params => 'stop McTaskManager', Services =>'Network Associates Task Manager'});
       
   174 
       
   175 	my @iPskillCommands = (    
       
   176     {AppName => 'pskill.exe', Params => 'UpdaterUI.exe'},
       
   177     {AppName => 'pskill.exe', Params => 'tbmon.exe'},
       
   178     {AppName => 'pskill.exe', Params => 'shstat.exe'});
       
   179 
       
   180 	my @waitList = ();
       
   181 	my @iMcAfeeCommands = ();
       
   182 
       
   183 	for my $iTestCmd(@iMcAfeeServices)
       
   184 	{  	
       
   185 		my $iExecutable = $iTestCmd->{'AppName'};
       
   186 		my $iParams = $iTestCmd->{'Params'};
       
   187 		my $iServices = $iTestCmd->{'Services'};
       
   188 
       
   189 		my @testCommand = `$iExecutable $iParams 2>&1`;   
       
   190 		my $iResponse = 0;     
       
   191 
       
   192 		foreach my $iLine (@testCommand) 
       
   193 		{
       
   194 			if ($iLine =~ m/does not exist/i)
       
   195 			{ 
       
   196 				print "REMARK: $iServices not installed, just proceed.\n";
       
   197 				$iResponse = 1;	 
       
   198 			} 
       
   199 
       
   200 			if ($iLine =~ m/it is disabled/i)
       
   201 			{ 
       
   202 				print "REMARK: $iServices not enabled, just proceed.\n";
       
   203 				$iResponse = 1;            
       
   204 			} 
       
   205 
       
   206 			if (($iLine =~ m/service is not started/i ) or ($iLine =~ m/not valid for this service/i ))
       
   207 			{  
       
   208 				print "REMARK: $iServices not started, just wait.\n";
       
   209 				push @waitList, $iServices;
       
   210 				push @iMcAfeeCommands, {AppName =>$iExecutable, Params =>$iParams};
       
   211 				$iResponse = 1;
       
   212 			}
       
   213 
       
   214 			if ($iLine =~ m/service was stopped successfully/i)
       
   215 			{ 
       
   216 				print "REMARK: Stop Success! $iServices was stopped successfully directly by the test command, just proceed.\n";
       
   217 				$iResponse = 1;			
       
   218 			}	
       
   219 			
       
   220 			if ($iLine =~ m/service could not be stopped/i)
       
   221 			{ 
       
   222 				print "REMARK: Stop Success! $iServices could not be stopped at the moment but will be stopped successfully directly by the test command very soon later, just proceed.\n";
       
   223 				$iResponse = 1;			
       
   224 			}
       
   225             
       
   226             if ($iLine =~ m/System error 5 has occurred/i)
       
   227 			{ 
       
   228 				print "WARNING: Access to $iServices is denied, but this won't affect the build, just proceed.\n";
       
   229 				$iResponse = 1;			
       
   230 			}
       
   231 			
       
   232 		}    
       
   233 		unless ($iResponse)
       
   234 		{
       
   235 			print "ERROR: Unable to parse the output of $iExecutable for $iParams!\n";
       
   236 			foreach my $iLine (@testCommand) 
       
   237 		    {
       
   238 				print $iLine;
       
   239 			}
       
   240 			#push @waitList, $iServices;
       
   241 		    #push @iMcAfeeCommands, {AppName =>$iExecutable, Params =>$iParams};
       
   242 		}
       
   243 	}
       
   244 
       
   245     # Wait for the waiting Antivirus services to load before attempting to stop the services
       
   246 	# If Antivirus fails to load after the grace period, the build should be aborted.
       
   247 	my $gWaitingTime = shift;
       
   248 	my $gRetries = shift;
       
   249 	if (@waitList)
       
   250 	{
       
   251 		WaitTillAntiVirusStarts($gWaitingTime, $gRetries, @waitList); 
       
   252 	}
       
   253 	
       
   254 	# Execute all "STOP" commands
       
   255 	push @iMcAfeeCommands, @iPskillCommands;
       
   256 	for my $iCmd(@iMcAfeeCommands)
       
   257 	{
       
   258 		ExecuteProcess(20,$iCmd);
       
   259 	}
       
   260 }
       
   261 
       
   262 # Scan:
       
   263 # Description: performs the virus scan on the specified directores
       
   264 # Revised April 2007 to support McAfee only, Sophos having passed into history!
       
   265 # Revised February 2008 to support further McAfee filename changes.
       
   266 # Arguments: directory in which to place scan output file (logfile);
       
   267 # array ref to array containing a list of the directories to scan
       
   268 # Returns: none
       
   269 sub Scan($$)
       
   270 {
       
   271     my $iOutFilesDir = shift;
       
   272     my $iDirsToScan = shift;
       
   273 
       
   274     # Name(s) of output files. User only supplies directories
       
   275     my $iOutFileName = 'Anti-virus';
       
   276 
       
   277     # Define McAfee commands, newest version first. McAfee keep changing the names of their files. Thus if an
       
   278     # executable is not found, we do not treat this as an error, we simply go on to thwe next command definition.
       
   279     # Note: Must supply full pathname, as these applications are not in "PATH"
       
   280     my @iMcAfeeCommands =(
       
   281     # CSScan.exe send most of its output to STDOUT. So define file a second time for us to write this data
       
   282     {AppName => 'C:\\Program Files\\McAfee\\VirusScan Enterprise\\csscan.exe',
       
   283      Params => join(" ", @$iDirsToScan) . " /SUB /CLEAN /ALLAPPS /LOG $iOutFilesDir\\$iOutFileName.log",
       
   284      LogFile => "$iOutFilesDir\\$iOutFileName"},  # Filename without .LOG extension
       
   285     {AppName => 'C:\\Program Files\\Common Files\\Network Associates\\Engine\\scan.exe',
       
   286      Params => join(" ", @$iDirsToScan) . " /SUB /NOBREAK /CLEAN /NORENAME /RPTCOR /RPTERR /REPORT=$iOutFilesDir\\$iOutFileName.log",
       
   287      LogFile => ''} # The older "SCAN.EXE" writes everything to its report file. So no further logging required.
       
   288     );
       
   289    
       
   290     print "Scanning: @$iDirsToScan\n"; 
       
   291 
       
   292     # Execute "SCAN" commands in order until one is successful.
       
   293     for my $iCmd(@iMcAfeeCommands)
       
   294     {
       
   295             # First remove/rename any existing output file. This is an unlikely situation in a normal build;
       
   296             # but it could cause confusion if, for example, a scan was re-run manually.
       
   297             unless (RemoveExistingFile($iCmd->{'LogFile'})) { next; }
       
   298             if (RunCommand($iCmd)) { return; }     # Success
       
   299     }
       
   300     # Arriving at the end of this loop means that no command succeeded! Report failure!
       
   301     print "WARNING: No Virus Scan accomplished. Possibly no suitable executable found.\n";
       
   302 }
       
   303 
       
   304 # RunCommand:
       
   305 # Description: Runs specified command using Perl "backticks"
       
   306 # passing its output for parsing to the sub ParseOutput().
       
   307 # Arguments: the command to execute as a hashref (filename of executable and parameters/args to pass to it)
       
   308 # Returns: TRUE on success
       
   309 sub RunCommand($)
       
   310 {
       
   311     my $iCmd = shift;   # Hashref - Command spec.
       
   312   
       
   313     unless(-e $iCmd->{'AppName'})
       
   314     {
       
   315         print "REMARK: File not found $iCmd->{'AppName'}\n";
       
   316         return 0;    # FALSE = Failure
       
   317     }
       
   318     my $iCommand = "\"$iCmd->{'AppName'}\" $iCmd->{'Params'}" ;
       
   319     print "Running: $iCommand\n";
       
   320     my @iOutput = `$iCommand`;
       
   321     
       
   322 	# Parse the output flagging any errors
       
   323     return &ParseOutput(\@iOutput,$iCmd->{'LogFile'});  # Return TRUE or FALSE
       
   324 }
       
   325 
       
   326 # ParseOutput:
       
   327 # Description: Parse output for warnings and errors.
       
   328 # The ScanLog-compatible "WARNING: " is prepended to any line containing
       
   329 # any of these messages or errors, and all lines are printed to STDOUT.
       
   330 # Arguments: reference to the array of output lines from executed command; Name of logfile (if any)
       
   331 # Returns: TRUE on Success
       
   332 # Remarks: note that errors in starting/stopping processes or services do not
       
   333 # constitute errors in terms of the build process. Failure to start or stop
       
   334 # the services will not affect the compilation except, at worst, to slow it
       
   335 # down if stopping the antivirus fails.
       
   336 sub ParseOutput($)
       
   337 {
       
   338     my $iOutputRef = shift;
       
   339     my $iLogFile = shift;
       
   340 
       
   341     my $fh = \*LOGFILE;
       
   342 
       
   343     if($iLogFile)
       
   344     {
       
   345         unless (open $fh, ">>$iLogFile.log")
       
   346         {
       
   347             print ("WARNING: Failed to open log file: $iLogFile");
       
   348             return 0;   # FALSE = Failure
       
   349         }
       
   350     }
       
   351     else
       
   352     {
       
   353         $fh = \*STDOUT;
       
   354     }
       
   355 
       
   356     for my $line(@$iOutputRef) # For each line of output...
       
   357     {
       
   358         $line =~ s/\s+$//;     # Remove trailing spaces (McAfee CSSCAN pads each line to 1024 chars!!)
       
   359         # Does it match an error as returned by PSKill or net stop/start
       
   360         if($line =~ /The specified service does not exist as an installed service\./i or
       
   361            $line =~ /The .*? service is not started\./i or
       
   362            $line =~ /The requested service has already been started./i or
       
   363            $line =~ /The service name is invalid\./i or
       
   364            $line =~ /Process does not exist\./i or
       
   365            $line =~ /Unable to kill process/i or
       
   366            $line =~ /error/i or
       
   367            $line =~ /is not recognized as an internal or external command/i)
       
   368         {
       
   369             print "WARNING: $line\n";
       
   370         }
       
   371         else # No errors/warnings so just print the line on its own
       
   372         {
       
   373             print $fh "$line\n";
       
   374         }
       
   375     }
       
   376     return 1;   # TRUE = Success
       
   377 }
       
   378 
       
   379 # ExecuteProcess:
       
   380 # Description: Executes specified command using Perl module Win32::Process
       
   381 # Arguments: the command to execute as a hashref (filename of executable and parameters/args to pass to it)
       
   382 # Returns: none
       
   383 sub ExecuteProcess
       
   384 {
       
   385     my $iWaitSecs = shift;
       
   386     my $iCmd = shift;           # Hashref - Command spec.
       
   387   
       
   388     my $iExecutable = $iCmd->{'AppName'};
       
   389     unless(-e $iExecutable)
       
   390     {
       
   391         $iExecutable = FindFileFromPath($iCmd->{'AppName'});
       
   392         unless ($iExecutable)
       
   393         {
       
   394             print "REMARK: File not found $iCmd->{'AppName'}\n";
       
   395             return; 
       
   396         }
       
   397     }
       
   398     my $iParams = $iCmd->{'Params'};
       
   399 
       
   400     #  my $iFlags = $iDebug? CREATE_NEW_CONSOLE: DETACHED_PROCESS;
       
   401     #  my $iFlags = $iDebug? 0: DETACHED_PROCESS;
       
   402     my $iFlags = 0;
       
   403 
       
   404     # Create a new Perl process (because fork does not work on some versions of Perl on Win32)
       
   405     # $^X is the path to the Perl binary used to process this script
       
   406     my $iProcess;
       
   407     unless (Win32::Process::Create($iProcess, "$iExecutable", "\"$iExecutable\" $iParams", 0, $iFlags, "."))
       
   408     {
       
   409         print "WARNING: Failed to create process for $iExecutable $iParams.\n";
       
   410         my $iExitCode = Win32::GetLastError();
       
   411         ReportProcessError($iExitCode);
       
   412         return;
       
   413     }
       
   414 
       
   415     my $iPID = $iProcess->GetProcessID(); 
       
   416     print "\nExecuting: $iExecutable $iParams .....\n";
       
   417     my $iRetVal = $iProcess->Wait($iWaitSecs * 1000);      # milliseconds. Return value is zero on timeout, else 1.
       
   418     if ($iRetVal == 0)    # Wait timed out
       
   419     {   # No error from child process (so far)
       
   420         print "Spawned: $iExecutable $iParams - PID=$iPID.\n";
       
   421         return 1;       # Success
       
   422     }
       
   423     else          # Child process terminated. Wait usually returns 1.
       
   424     {             # Error in child process?? If so, get exit code
       
   425         my $iExitCode;
       
   426         $iProcess->GetExitCode($iExitCode);
       
   427         unless($iExitCode)
       
   428         {
       
   429             print "Executed: $iExecutable $iParams - PID=$iPID.\n";
       
   430             return 1;           # Success
       
   431         }
       
   432         print "REMARK: Failed in execution of $iExecutable $iParams.\n";
       
   433         ReportProcessError($iExitCode);
       
   434         return 0;
       
   435     }
       
   436 }
       
   437 
       
   438 # ReportProcessError:
       
   439 # Description: prints error code to STDOUT followed by explanatory text, if avalable
       
   440 # Arguments: Windows error code
       
   441 # Returns: none
       
   442 sub ReportProcessError
       
   443 {
       
   444     my $iExitCode = shift;
       
   445 
       
   446     my $iMsg = Win32::FormatMessage($iExitCode);
       
   447     if (defined $iMsg)
       
   448     {
       
   449         printf "ExitCode: 0x%04x = %s\n", $iExitCode, $iMsg;
       
   450     }
       
   451     else
       
   452     {
       
   453         printf "ExitCode: 0x%04x\n", $iExitCode;
       
   454     }
       
   455 }
       
   456 
       
   457 # FindFileFromPath:
       
   458 # Description: Tries to find a file using PATH Environment Variable
       
   459 # Argument: Filename (Must include extension .EXE, .CMD etc)
       
   460 # Return: Full pathname of file, if found, otherwise undef
       
   461 sub FindFileFromPath
       
   462 {
       
   463     my $iFilename = shift;
       
   464     my $iPathname;
       
   465     my @iPathDirs = split /;/, $ENV{'PATH'};
       
   466     foreach my $iDir (@iPathDirs)
       
   467     {
       
   468         while ($iDir =~ s/\\$//){;}     # Remove any trailing backslash(es)
       
   469         $iPathname = "$iDir\\$iFilename";
       
   470         if (-e $iPathname)
       
   471         {
       
   472             return $iPathname;
       
   473         }
       
   474     }
       
   475     return undef;
       
   476 }
       
   477 
       
   478 # RemoveExistingFile:
       
   479 # Description: If specified .LOG file exists, rename to .BAK
       
   480 # Arguments: File to check
       
   481 # Returns: TRUE on Success
       
   482 sub RemoveExistingFile
       
   483 {
       
   484     my $iFilename = shift;       # Full pathname LESS extension
       
   485 
       
   486     unless (-e "$iFilename.log") { return 1; }  # Success!
       
   487 
       
   488     if (File::Copy::move("$iFilename.log","$iFilename.bak")) { return 1; }  # Success!
       
   489 
       
   490     print "WARNING: Failed to rename $iFilename.log\nto $iFilename.bak because of:\n$!\n";
       
   491     return 0;   # FALSE = Failure
       
   492 }
       
   493 
       
   494 1;