This is a script written in response to a question posted in the Official Scripting Guys Forum, looking for a PS replacement for a VB script. 

The original thread is here:  http://social.technet.microsoft.com/Forums/en/ITCG/thread/c9603bdd-95bf-40d7-9e61-cf65aba528d1

It will monitor a collection of servers read from a .txt file, for monitored events read from a .csv file as source/id pairs.

 It keeps track of the last index number of the log for each server, and can be set to run once, run in a continuous, 0 delay loop, or run in a continuous loop at a specified minute interval.

Run from the console, it will display running stats of the time it took to process each server, how many log entries were processed, and how many monitored alert events were found, and optionally display the alert events found.

To run, update the script with  the appropriate From and To email addressed, and smtp server hosname and credentials if required.

Change to log name if you want to monitor a different log. 

For the input files, the server list (monitored_comptuers.txt) is a simple .txt file list of the computernames you want to monitor.

The event list (alert_events.csv) is a 2 column (Source,ID) csv file.

The script will create an additional file <logname>_histfile.csv.  This is an export-clixml of the hash table used to store the server name and last index read.  On the first run, it will read the logs to whatever depth is specified in $seed_depth and use that for it's initial pass, saving the current index in the hash table and then to the file.  If no key is found for a server, it will create one.  If the index delta calculates to a negative value, it will issue a warning that the event log may have been cleared, and re-seed.

mjolinor

 

 UPDATE 5/27/2010

In response to feedback, I have modified the script to use eventid, rather than instanceid to identify the monitored events.  I also added a -useinstanceid parameter if you do want to run the script against log file types that only return an instanceid.  The format of the alert_events.csv file has not changed, it still uses a header row of "source","id" but now takes the eventid number as the event identifier, rather than the event instanceid by default.

More details as to why instanceid was used initially are in the Discussions.  Since most people seemed to be finding the events they wanted to monitor from Event Viewer, and using the EventID number from there, it seemed like a good idea to make it work the way people wanted to use it.

PowerShell
Edit|Remove
## Powershell Log Monitor Script ##
## Contributing authors - mck74,mjolinor,

param([switch]$ShowEvents = $false,[switch]$NoEmail = $false,[switch]$useinstanceid = $false)


$log = "Application"
$hist_file = $log + "_loghist.xml"
$seed_depth = 200

#run interval in minutes - set to zero for runonce, "C" for 0 delay continuous loop.
$run_interval = 1

$EmailFrom = "<user@domain.tld>"
$EmailTo = "<user@domain.tld>"
$EmailSubject = "Server event notification"  
  
$SMTPServer = "smtphost.domain.tld"
$SMTPAuthUsername = "username"
$SMTPAuthPassword = "password"

$computers = @(gc monitored_computers.txt)
$event_list = @{}
Import-Csv alert_events.csv |% {$event_list[$_.source + '#' + $_.id] = 1}


#see if we have a history file to use, if not create an empty $histlog
if (Test-Path $hist_file){$loghist = Import-Clixml $hist_file}
 else {$loghist = @{}}


$timer = [System.Diagnostics.Stopwatch]::StartNew()

function send_email {
$mailmessage = New-Object system.net.mail.mailmessage 
$mailmessage.from = ($emailfrom) 
$mailmessage.To.add($emailto)
$mailmessage.Subject = $emailsubject
$mailmessage.Body = $emailbody
$mailmessage.IsBodyHTML = $true
$SMTPClient = New-Object Net.Mail.SmtpClient($SmtpServer, 25)  
$SMTPClient.Credentials = New-Object System.Net.NetworkCredential("$SMTPAuthUsername", "$SMTPAuthPassword") 
$SMTPClient.Send($mailmessage)
}
#START OF RUN PASS
$run_pass = {

$EmailBody = "Log monitor found monitored events. `n"

$computers |%{
$timer.reset()
$timer.start()

Write-Host "Started processing $($_)"

#Get the index number of the last log entry
$index = (Get-EventLog -ComputerName $_ -LogName $log -newest 1).index

#if we have a history entry calculate number of events to retrieve
#   if we don't have an event history, use the $seed_depth to do initial seeding
if ($loghist[$_]){$n = $index - $loghist[$_]}
 else {$n = $seed_depth}
 
if ($n -lt 0){
 Write-Host "Log index changed since last run. The log may have been cleared. Re-seeding index."
 $events_found = $true
 $EmailBody += "`n Possible Log Reset $($_)`nEvent Index reset detected by Log Monitor`n" | ConvertTo-Html
 $n = $seed_depth
 }
 
Write-Host "Processing $($n) events."

#get the log entries

if ($useinstanceid){
$log_hits = Get-EventLog -ComputerName $_ -LogName $log -Newest $n |
? {$event_list[$_.source + "#" + $_.instanceid]}
}

else {$log_hits = Get-EventLog -ComputerName $_ -LogName $log -Newest $n |
? {$event_list[$_.source + "#" + $_.eventid]}
}

#save the current index to $loghist for the next pass
$loghist[$_] = $index

#report number of alert events found and how long it took to do it
if ($log_hits){
 $events_found = $true
 $hits = $log_hits.count
 $EmailBody += "`n Alert Events on server $($_)`n"
 $log_hits |%{
  $emailbody += "<br><br>"
  $emailbody += $_ | select MachineName,EventID,Message | ConvertTo-Html 
 $emailbody += "<br><br>"
 }
 }
 else {$hits = 0}
$duration = ($timer.elapsed).totalseconds
write-host "Found $($hits) alert events in $($duration) seconds."
"-"*60
" "
if ($ShowEvents){$log_hits | fl | Out-String |? {$_}}
}

#save the history file to disk for next script run 
$loghist | export-clixml $hist_file

#Send email if there were any monitored events found
if ($events_found -and -not $NoEmail){send_email}

}
#END OF RUN PASS

Write-Host "`n$("*"*60)"
Write-Host "Log monitor started at $(get-date)"
Write-Host "$("*"*60)`n"

#run the first pass
$start_pass = Get-Date
&$run_pass

#if $run_interval is set, calculate how long to sleep before the next pass
while ($run_interval -gt 0){
if ($run_interval -eq "C"){&$run_pass}
 else{
 $last_run = (Get-Date) - $start_pass
 $sleep_time = ([TimeSpan]::FromMinutes($run_interval) - $last_run).totalseconds
 Write-Host "`n$("*"*10) Sleeping for $($sleep_time) seconds `n"
 
#sleep, and then start the next pass
 Start-Sleep -seconds $sleep_time
 $start_pass = Get-Date 
 &$run_pass
 }
 }