Powershell find string in log file - powershell

I'm completely new to Powershell and trying to accomplish a small task. I'm trying to find a string in the last two lines of a log file, and if the value doesn't match, take and action. If it matches, end. I think that's the simplest way to explain it. :-) I'm searching for certain words, if not found, open notepad, if found, exit. I've pieced some of it together, but I'm not sure how to handle the If False, run notepad portion. Would love some help. Thanks.
if (Test-Path C:\windows\ccm\logs\CcmEval.log) {
Get-Content 'C:\windows\ccm\logs\CcmEval.log' -Tail 2 | Select-String "Updating MDM_ConfigSetting.ClientHealthStatus with value 7" | % { $_ -notmatch "value 7" }
Start-Process C:\windows\notepad.exe
}

You can do that with that with one more if else condition. If condition not matches it will open notepad otherwise it will execute your command to exit.
if (Test-Path C:\windows\ccm\logs\CcmEval.log) {
$condition = Get-Content 'C:\windows\ccm\logs\CcmEval.log' -Tail 2 | Select-String "Updating MDM_ConfigSetting.ClientHealthStatus with value 7" | % { $_ -notmatch "value 7" }
if (!$condition) {
Start-Process C:\windows\notepad.exe
}
else {
"Do something here"
}
}

You can drop the Test-Path if you add -ErrorAction SilentlyContinue to the Get-Content, so it will quietly return nothing if the file is not found. This means you can use the same code whether or not the file exists.
-match takes a regular expression pattern, which is powerful enough that you can build "not 7" into it in one go using [^7] to mean "any character except 7".
$logFile = 'C:\windows\ccm\logs\CcmEval.log'
$lines = Get-Content -Path $logFile -Tail 2 -ErrorAction SilentlyContinue
if ($lines -match "Updating MDM_ConfigSetting.ClientHealthStatus with value [^7]") {
Start-Process C:\windows\notepad.exe
}

Related

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
}
}
}
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
}
}
}

Overwrite PowerShell output strings onto the same line

I have a piece of PS code which takes the 7-Zip extraction output and filters it down so only percentage "%" progress update lines get printed. I've managed to reduce it down to just the percentage outputs:
& $7ZipPath "x" $filePath "-o$extractionPath" "-aos" "-bsp1" | out-string -stream | Select-String -Pattern "\d{1,3}%" -AllMatches | ForEach-Object { $_.Matches.Value } | Write-Host -NoNewLine
At the moment the console output looks like this:
0%1%5%9%14%17%20%23%26%31%37%43%46%48%50%52%54%56%59%61%63%65%67%70%72%74%76%78%80%81%82%83%85%86%87%89%90%91%92%94%95%96%97%98%99%
Is there a way of keeping these outputs in the same place, on the same line, making them just overwrite each other? It's tricky because the output is being piped from the 7-Zip application. I'm afraid I can't use Expand-Archive as I am dealing with .7z files
Many thanks!
You could use the .Net System.Console class:
[System.Console]::SetCursorPosition(0, [System.Console]::CursorTop)
So your code would have to be:
& $7ZipPath "x" $filePath "-o$extractionPath" "-aos" "-bsp1" | out-string -stream | Select-String -Pattern "\d{1,3}%" -AllMatches | ForEach-Object { $_.Matches.Value } | foreach {
[System.Console]::SetCursorPosition(0, [System.Console]::CursorTop)
Write-Host $_ -NoNewLine
}
Note: As long as the next output is equal or greater length, which is true in your case, this is all you need. Otherwise you would have to clear the last output first.
marsze's helpful answer works well, but there's a simpler alternative that uses a CR character ("`r") to reset the cursor position to the start of the line.
Here's a simple demonstration that prints the numbers 1 through 10 on the same line:
1..10 | ForEach-Object { Write-Host -NoNewline "`r$_"; Start-Sleep -Milliseconds 100 }
[Console]::Write(...) instead of Write-Host -NoNewline ... works too, as Bacon Bits points out.
The same constraint applies, however: if previous output lines happened to be longer, the extra characters linger.
To solve this problem too, you must pad any output line to the length of the console window's buffer width:
'loooooooong', 'meeedium', 'short' | ForEach-Object {
Write-Host -NoNewline ("`r{0,-$([console]::BufferWidth)}" -f $_)
Start-Sleep -Milliseconds 500
}

Powershell replace is duplicating results

I have a simple script that is suppose to find text and if the right conditions are met, it will replace the text:
foreach ($line in $file){
$line.replace("â‡","+") | Out-File -FilePath $destination -Append
if ($line -eq "â‡RECORD_IDENTIFIER:"){
$line.replace("â‡RECORD_IDENTIFIER:","+RECORD_IDENTIFIER: A") | Out-File -FilePath $destination -Append
}
if ($line -eq "â‡END_TAG"){
$line.replace("â‡END_TAG","+END_TAG;") | Out-File -FilePath $destination -Append
}
but the result is this:
+START_TAG:
+DATA_FILE_(DATE/TIME):2017-02-13T13:44:44.489-08:00
+RECORD_IDENTIFIER:
+RECORD_IDENTIFIER: A
+CLIENT_NUM:8802
+SOLOMON_ID:TRUG01
I only want it to produce one RECORD_IDENTIFIER.
This is where you need to discover the Switch operator. I think it's an operator, maybe a cmdlet? Whatever it is, its what you need! What it does is compare the current iteration of a collection against multiple cases, and applies the cases that match. For example:
Switch ($file) {
"â‡RECORD_IDENTIFIER:" {
$_.Replace("â‡RECORD_IDENTIFIER:", "+RECORD_IDENTIFIER: A") |
Out-File -FilePath $destination -Append
Continue
}
"â‡END_TAG" {
$_.Replace("â‡END_TAG", "+END_TAG;") |
Out-File -FilePath $destination -Append
Continue
}
default {
$_.Replace("â‡", "+") |
Out-File -FilePath $destination -Append
}
}
The Continue commands tell the loop to move to the next item, so if it matches the first case it will do your replace, output it to file, and then move to the next line. If it does not match the first case it tries the second, and if it matches it will do the replace for that, output to file, and move to the next line. If it doesn't match either of the first two cases it reaches the default line, and everything has that scriptblock applied to it, so anything that didn't match the first two cases has the replace("â‡","+") performed, and output to file.

Check text file content in PowerShell

The PowerShell command
Get-ADFSRelyingPartyTrust | select Name | out-file C:\listOfNames.txt
generates a file as follows:
Name
----
AustriaRP
BahamasRP
BrazilRP
CanadaRP
[...]
Now, how can I check if BrazilRP has been extracted and C:\listOfNames.txt contains it?
The Get-Content and then Select-String should help. If the string is in the file it will get returned. If not then the command will returned empty value.
Get-Content C:\listOfNames.txt | Select-String "BrazilRP"
If the "BrazilRP" occurs more than once all the occurrences will returned so you know if you got any duplicates. Same holds if the string is a part of a longer expression. For example if you search for "zil" then "BrazilRP" will be returned as well.
Also you can pipe the results out to another file:
Get-Content C:\listOfNames.txt | Select-String "BrazilRP" | Out-File C:\myResults.txt
I found a solution (but thanks to PiotrWolkowski to suggest me the Get-Content function):
$file = Get-Content "C:\listOfNames.txt"
$containsWord = $file | %{$_ -match "BrazilRP"}
if ($containsWord -contains $true) {
Write-Host "There is!"
} else {
Write-Host "There ins't!"
}
If you want to easily see if a file contains your text try this
The [bool] type returns the data as either true or false instead of returning the actual data your searching for
if ([bool]((Get-Content -Path "C:\listOfNames.txt") -like '*BrazilRP*')) {
write-host "found it"
}
else {
write-host "didnt find it"
}

Colour-coding get-content results

I've got a powershell script that monitors a log file, filters out the interesting bits and then presents those bits to me as and when they are written to the file. Works wonderfully. The line of interest is:
get-content "$logFile" -wait | where { select-string $searchTerm -inp $_ }
Now I want to get fancy!
I would like the font colour to change everytime a particular term is encountered. I can set the font colour easily enough, but how would you do it on-the-fly with the above statement?
Edit: Figured it out, but can't post an answer for 8 hours. Will upload it tomorrow.
If you're looking for something that provides selective color coding, then try something like this.
First, set up a helper function to select an appropriate color:
function Get-LogColor {
Param([Parameter(Position=0)]
[String]$LogEntry)
process {
if ($LogEntry.Contains("DEBUG")) {Return "Green"}
elseif ($LogEntry.Contains("WARN")) {Return "Yellow"}
elseif ($LogEntry.Contains("ERROR")) {Return "Red"}
else {Return "White"}
}
}
Then execute a line that looks like this:
gc -wait $logFile | ForEach {Write-Host -ForegroundColor (Get-LogColor $_) $_}
Try
Get-Content $logFile -Wait |
Select-String $searchTerm |
ForEach {write-host -ForegroundColor red $_.line}