Diogo Silva
Diogo Silva

Reputation: 35

Scanning a .log for specific strings in latest lines using Powershell

I have a .log file that constantly adds lines to itself and I am trying to make a Powershell script that will launch 1 of two batch scripts when the respective string of characters is detected in the latest line of the .log file. Here's what I have so far:

while ($True) {
    Write-Output 'Enter <ctrl><c> to break out of this loop.'
    Start-Sleep -Seconds 1
    Copy-Item -LiteralPath "C:\LocationOfFile\latest.log" -Destination "C:\Users\Diogo\Desktop\Detector"
    Rename-Item -Path "C:\Users\Diogo\Desktop\Detector\latest.log" -NewName "latest.txt"
    Get-Content -Path "latest.txt" -tail 1 -wait | Select-String -Quiet '§6§lP§e§lrof'
    if (System.Boolean -eq True) {
        Invoke-Item result1.bat
        Read-Host -Prompt "Press Enter to continue"
    }
    else {
        Get-Content -Path "latest.txt" -tail 1 -wait | Select-String -Quiet 'spawned'
        if (System.Boolean -eq True) {
            Invoke-Item result2.bat
            Read-Host -Prompt "Press Enter to continue"
        }
        else {
        }
    }
}

I first copy the .log file from it's location and Change it into a .txt. Then I search for the strings ("§6§lP§e§lrof" and "spawned") And finally I try to get it to do it over again, but this doesn't seem to be working as well as the seearching.

Any help? Thanks in advance <3

EDIT:

Thank you so much for the comprehensive reply, that really helped me grasp some Powershell concepts and it worked flawlessly. The second script was a tiny overkill tho, I actually have the exact opposite problem: the lines are added quite slowly. In a perfect world I want the script to keep going after finding one result and not have me keep resetting it after each result found. There is another rule about the log file that is really interesting: Lines with the strings I'm after never occur one after another, there is always one in between, at least. This means if the script finds the same string twice in a row, it's just the same line and I don't want my batch script to go off. The PowerShell script I am using right now (which is the code you showed me with minor changes to make it loop) is at the end and it is working with only a single small hiccup: If I'm using my computer for something else Powershell becomes the window on top when it finds a result and I would like that not to happen, could you help me with that last thing? Thank you very much in advance!

while ($True) {
    Write-Output 'Enter <ctrl><c> to break out of this loop.'
    Start-Sleep -Seconds 1
    $LastLogLine = Get-Content -Path "C:\LocationOfFile\latest.log" -tail 1
    if ($LastLogLine -ne $LastLine) {
        if ($LastLogLine -like '*§6§lP§e§lrof*') {
            Start-Process -FilePath "result1.bat" -WindowStyle Minimized
            $LastLine = $LastLogLine
      } elseif ($LastLogLine -like '*spawned*') {
            Start-Process -FilePath "result2.bat" -WindowStyle Minimized
            $LastLine = $LastLogLine
            }
    }
}

Upvotes: 1

Views: 1336

Answers (1)

anto418
anto418

Reputation: 175

First off, your script doesn't work for two reasons:

  • Get-Content -Path "latest.txt" -tail 1 -wait | Select-String -Quiet '§6§lP§e§lrof' Get-Content -Wait will keep running as long as the file it reads exists or until it gets Ctrl-C'd, so your script will never go beyond that. You can just remove -Wait here.
  • if (System.Boolean -eq True) I don't see what you're trying to do here. Collect the results from the previous Select-String ? Select-String does not set any variable or flag on it's own. Also, you're comparing a type to a string: you're asking "is the concept of a boolean equal to the string 'True' ?". What you can do is store the result of Select-String and just do if ($Result -eq $True) (emphasis on $True, not "True").

Additionally, a couple things I would rewrite or correct in your script:

  • Copy-Item every second: Is it necessary ? Why not just read the original file and store it in a variable ? If it is just so you can change the extension from .log to .txt, know that powershell does not care about the extension and will happily read anything.
  • Select-String: have you considered just using the comparison operator -like, as in if ($MyString -like "*$MyKeyword*") {...} ?
  • If blocks do not need an Else block. If your Else does nothing, you can just not write it. And there is an elseif block that you can use instead of chaining an else and an if.
  • Code style: Please pick an indentation style and stick to it. The one I see most of the time is 1TBS, but K&R or Allman are well known too. I may or may not have requested an edit to get some indentation on your question :p

So, we end up with this:

while ($True) {
    Write-Output 'Enter <ctrl><c> to break out of this loop.'
    Start-Sleep -Seconds 1
    $LastLogLine = Get-Content -Path "C:\LocationOfFile\latest.log" -tail 1
    if ($LastLogLine -like '*§6§lP§e§lrof*') {
        Invoke-Item result1.bat
        Read-Host -Prompt "Press Enter to continue"
    } elseif ($LastLogLine -like '*spawned*') {
        Invoke-Item result2.bat
        Read-Host -Prompt "Press Enter to continue"
    }
}

However, this will not work if the program that writes your logs can write faster than you can process the lines, batch script included. If it does that, your script will skip lines as you only handle the last line. If two lines get written you won't see the second to last.

To solve that, we can do a bit of asynchronous magic using Powershell jobs, and we'll be able to see all lines written since the last loop, be it 1 line written, 0 lines, or 100 lines. about_jobs is a very good primer on Powershell jobs and asynchronous operations, read it.

  $stream = Start-Job -ArgumentList $LogPath -Name "StreamFileContent" -ScriptBlock {Get-Content $args -Wait}
  Receive-Job -Job $Stream                              # Discard everything that was already written in the file, we only want the stuff that is added to the file after we've started.
  while($true) {                                        # As long as the script is left running
      foreach($NewLine in (Receive-Job -Job $stream)) { # Fetch the lines that Get-Content gave us since last loop
          if ($NewLine -like '*§6§lP§e§lrof*') {        # Check for your keyword
              C:\MyScriptPath\MyScript1.bat             # Start batch script
          } elseif ($NewLine -like '*spawned*') {
              C:\MyScriptPath\MyScript2.bat
          }
      }
  }

Upvotes: 1

Related Questions