2

I have multiple windows services deployed on my servers. I want to implement a PowerShell script which can do real time monitoring of logs of these services. It must look for keywords(Eg. Error, Exception) in the log file and as soon as there is any error, the script should send a notification to a preconfigured email address. I did basic search on the web and could find some freeware applications which can do this but I am not keen on installing those on the servers. If this can be done by a basic PowerShell script or a batch script and can run in background, it'll be great.

I've found the Get-Content and Type -wait commands which can watch the file in real time

Get-Content error.log -wait | where { $_ -match "ERROR" }

I'll really appreciate any assistance on the email notification part and if you could add some web links which may help.

A bit of complexity is that the log file will not be constant and a new log file is created every day and the script should automatically identify the latest file based on the file name or creation date etc.

File Name format is 8_05_2021.txt, 9_05_2021.txt, 10_05_2021.txt

Compo
  • 36,585
  • 5
  • 27
  • 39
Maverick
  • 1,396
  • 5
  • 22
  • 42
  • Will this script be a scheduled task? If so, what are the triggers for this task? Will it be running 24x7 or do you intent to break the script after the end of each day? – Santiago Squarzon May 09 '21 at 21:19
  • @SantiagoSquarzon, well it'll probably be a scheduled task or should be constantly running as the file monitoring needs to be real time. – Maverick May 09 '21 at 21:23
  • So the log is a new log each day inside a directory that contains specific keywords? Or is it the windows logs like system, application, etc? – Abraham Zinala May 09 '21 at 21:48
  • @AbrahamZinala These are not windows event logs, you are right, the logs files are under a directory and each day a new file is created. We need to look for errors within the log files. – Maverick May 09 '21 at 21:52
  • 1
    Use the right tool for the job. Why are you trying to script this (which is an infinite loop (just a bad idea) or a timed scheduled task a little better) vs using a log collection solution? Use an enterprise service for this. You can use [Windows FSRM (File Server Resource Manager)](https://learn.microsoft.com/en-us/windows-server/storage/fsrm/fsrm-overview) to monitor for these files and key off data in them. [FSRM Configure E-Mail Notifications](https://learn.microsoft.com/en-us/windows-server/storage/fsrm/configure-email-notifications) – postanote May 09 '21 at 23:48
  • I agree 100% with @postanote, scripts are not meant to run indefinitely. A better approach for a script with your particular need would be a task which triggers X times a day to validate if the file has any errors. I just posted what you asked for but I do not recommend using it. – Santiago Squarzon May 10 '21 at 00:50
  • @SantiagoSquarzon I agree. I am hesitant as well to keep the script running indefinitely. I can possible remove the 60 sec sleep and trigger the script from task schedules. However, what I am intending to achieve is real-time monitoring of logs, which triggers email as soon as it sees an error. Scheduled tasks will only check the file on a pre-configured schedule but it'll not be real time. – Maverick May 10 '21 at 00:59
  • Then something to improve on could be, run the `while loop` for an entire day, if the day changes `break` there and then the scheduled task should run once a day. I think that's a bit better approach. – Santiago Squarzon May 10 '21 at 01:17
  • @maverick. as for this... `However, what I am intending to achieve is real-time monitoring of logs`, this is what SEIM tools are for. Unless this is a learning effort, don't try and reinvent the wheel, unless you are sure it's going to be a better wheel. If you are insisting on doing this via a script, then that is what the ``FileSystemWatcher` is for, which allows you to monitor for new files being created, modified, etc, that you can take action on. Do this as a background job, or write your own service using your script. There are many articles showing how. – postanote May 10 '21 at 03:02
  • Lastly, well-defined Windows Services writes to the default event logs, or they create their own custom event log(s), which you can actively read just like any Event Log with Get-WinEvent. So, I'd be talking to the developers of those services and asking why. If it were me. Juuuuust say'in. – postanote May 10 '21 at 03:05

2 Answers2

2

If my logic is right I think this should work, this script would run indefinitely.

For sending Mails in PowerShell you have two options that I'm aware of, one is using the cmdlet designed for that: Send-MailMessage

However, this is important to be aware of:

Warning

The Send-MailMessage cmdlet is obsolete. This cmdlet does not guarantee secure connections to SMTP servers. While there is no immediate replacement available in PowerShell, we recommend you do not use Send-MailMessage. For more information, see Platform Compatibility note DE0005.

You can find the second option here using Net.Mail.MailMessage.

Now for the code of the script, here is something you can use:

# Define the full path of your logs folder
$logsFolder = 'fullPath\to\logsFolder'

# Function for monitoring and retrieving the newest log file Full Path
function Get-NewestLog ($LogPath) {
    Get-ChildItem $LogPath -Filter *.txt | Sort-Object CreationTime -Descending |
        Select-Object -First 1 -ExpandProperty FullName
}


# Get the newest log file
$logFilePath = Get-NewestLog -LogPath $logsFolder

while($true) {
    # If we don't have today's date stored
    # or the update trigger is True
    if($updateDate -or -not $today) {
        $today      = [datetime]::Today
        $updateDate = $false
    }
    
    if($today -lt [datetime]::Today) {
        # Trigger our previous condition
        $updateDate = $true

        # Get the new log file for this day
        $logFilePath = Get-NewestLog -LogPath $logsFolder
    }

    if((Get-Content $logFilePath -Raw) -match 'Error') {
        # Send mail message goes here
    }

    Start-Sleep -Seconds 60
}

It is important to note that, this would spam your inbox every minute if there is an error in the log file so it will probably be a good idea to add a new condition in this block:

if((Get-Content $logFilePath -Raw) -match 'Error') { 
    ....
}

For example something like this:

if((Get-Content $logFilePath -Raw) -match 'Error' -and -not $emailSentThisDay) {
    # Send mail message goes here

    # Here you set this bool to True so you don't get spammed :D
    $emailSentThisDay = $true
}

If this is something you will consider then you will need to reset the $emailSentThisDay bool every new day so:

if($today -lt [datetime]::Today) {
    # Trigger our previous condition
    $updateDate = $true

    # Reset the antispam bool if this is a new day
    $emailSentThisDay = $false
    
    # Get the new log file for this day
    $logFilePath = Get-NewestLog -LogPath $logsFolder
}
Santiago Squarzon
  • 41,465
  • 5
  • 14
  • 37
  • 1
    Unfortunately, `Net.Mail.MailMessage` is also based on [`System.Net.Mail.SmtpClient`](https://learn.microsoft.com/en-US/dotnet/api/System.Net.Mail.SmtpClient), just like `Send-MailMessage`, and is therefore equally obsolete. The [official recommendation](https://github.com/dotnet/platform-compat/blob/master/docs/DE0005.md) is to use third-party alternative [MailKit](https://github.com/jstedfast/MailKit). [This article](https://www.infoq.com/news/2017/04/MailKit-MimeKit-Official/) has background information. – mklement0 Jun 12 '21 at 13:58
1

Note: The solution below only monitors log-file additions going forward in time, it doesn't consider old log files and it doesn't keep persistent track of what log file up to what line has already been processed - such logic would require (substantial) additional effort.

You can use the following approach:

  • Use background jobs (created with Start-Job) or, preferably, thread [background] jobs (created with Start-ThreadJob) to monitor additions to a given log file.

  • In the foreground, run a loop that periodically:

    • polls the job for new log-file content of interest and sends an email, if found.
    • checks for the presence of a new log file, and, if found, terminates the current job and starts a new one for the new log file.

Note:

  • You can use Send-MailMessage to send emails, but note that this cmdlet is considered obsolete, because it "does not guarantee secure connections to SMTP servers." That said, if that isn't a concern in your case, it works, and, given PowerShell's commitment to backward compatibility, the cmdlet is unlikely to ever be removed.

  • The code runs indefinitely; when run interactively, you can terminate it with Ctrl-C, but in an automated invocation scenario such as a scheduled task you'll have to terminate the task.

$logFileDir = '.'
$job = $null
$currLogFile = $null

while ($true)
{

  # Check for new content of interest from the background job.
  if ($job -and ($result = Receive-Job $job)) {
    # Send an email here, e.g.:
    # Send-MailMessage -SmtpServer exchange.example.com -From alerts@example.com -To jdoe@example.com -Subject 'Error' -Body $result
  }

  # See if a new log file has appeared.
  # For simplicity, go by creation date.
  # If a new file only appears once a day, you could keep track
  # of the current calendar date and only look when it changes.
  $newLogFile = (Get-ChildItem -File "$logFile/*_*_*.txt" | 
                  Sort-Object -Descending CreationTime | 
                    Select-Object -First 1).FullName
  
  if ($newLogFile -ne $currLogFile) {

    # If there's a current job for the previous log file, terminate it now.
    if ($job) { Remove-Job $job -Force }

    # Create a job for the new log file.
    $currLogFile = $newLogFile
    $job = Start-Job {
       # Wait indefinitely for content to be added to the file,
       # and output lines matching the string of interest.
       Get-Content -LiteralPath $using:currLogFile -Wait |
         Where-Object { $_ -match "ERROR" }
    }

  }

  # Sleep a little.
  Start-Sleep -Seconds 1
}

mklement0
  • 382,024
  • 64
  • 607
  • 775