I'm trying to send new lines added to a log file to slack if they have a certain word(s) in it - see in slack if I have errors.
What I hoped would work is:
$message = Get-Content '\\path\to\file.log' -Wait -Tail 0 | Select-String -Pattern 'ERROR'
foreach($line in $message) { Send-SlackMsg -Text "$($line)" -Channel $channel }
Sadly, it does not. I've replaced Send-SlackMsg with Write-Host just to see if that is the issue but it's not.
Get-Content '\\path\to\file.log' -Wait -Tail 0 | Select-String -Pattern 'ERROR'
Works like a charm in my console.
How can I make PowerShell perform an action when a new line appears and it matches the pattern?
Per the comment from Mathias, you could use ForEach-Object:
Get-Content '\\path\to\file.log' -Wait -Tail 0 | Select-String -Pattern 'ERROR' | ForEach-Object { Send-SlackMsg -Text $_ -Channel $channel }
The problem with your code was that because of the -Wait switch the code was never proceeding past the $message = line. However ForEach-Object processes objects via the pipeline, so as soon as a new line is written to the file which matches 'ERROR' it is passed down the pipeline and in to the ForEach-Object.
Related
I have a small script that checks the communication date of the mcafee agent:
$GetLastCommunication = & "C:\Program Files\McAfee\Agent\cmdagent" -i | Select-String -Pattern LastASCTIME | Out-String
$GetLastCommunication = $GetLastCommunication.Replace("`n", "").Replace("`r", "").Replace(" ","")
Start-Job -ScriptBlock {
$newagentdir = (Get-ChildItem -Recurse "\\IP\Downloads\" -Include "FramePkg.exe").FullName
$arg = "/forceinstall /install=agent /silent"
Copy-Item -Path $newagentdir -Destination $env:TEMP
Start-Process -Wait $env:TEMP\FramePkg.exe -ArgumentList $arg
}
while ($GetLastCommunication = "LastASCTime:N/A"){
sleep -Seconds 5
}
Reload-Form
after the installation, the LastASCTime line changes automatically from "N/A" to the time of communication, after 15 to 20 seconds.
I need for him to reload the form when the LastASCTime line changes from N/A to the comm time.
Currently, is getting stuck in the while loop and doesn’t reload the form.
Any ideas why?
Thanks
Your code is essentially...
$lastKnownState = Get-SomeState
while ($lastKnownState -eq $operationInProgressState)
{
sleep -Seconds 5
}
Your loop is waiting 5 seconds between checking if the operation has completed, but because $lastKnownState is just a snapshot of the operation's state and not modified inside the loop, the loop will never terminate.
Instead, you need to actually refresh the state after the timeout...
$lastKnownState = Get-SomeState
while ($lastKnownState -eq $operationInProgressState)
{
sleep -Seconds 5
$lastKnownState = Get-SomeState
}
In your code, after correcting the = assignment operator to the -eq comparison operator, that would look like this...
while ($GetLastCommunication -eq "LastASCTime:N/A"){
sleep -Seconds 5
$GetLastCommunication = & "C:\Program Files\McAfee\Agent\cmdagent" -i | Select-String -Pattern LastASCTIME | Out-String
$GetLastCommunication = $GetLastCommunication.Replace("`n", "").Replace("`r", "").Replace(" ","")
}
Also, instead of chaining calls to Replace() with...
$GetLastCommunication = $GetLastCommunication.Replace("`n", "").Replace("`r", "").Replace(" ","")
...you can use the -replace operator with a regular expression that will match and remove CR, LF, or space characters...
$GetLastCommunication = $GetLastCommunication -replace '[\r\n ]'
Those characters are being escaped for the .NET regular expression engine and not PowerShell, which is why backslashes are used instead of backticks. If what you really want to do is match LF optionally preceded by a CR (i.e. CRLF or LF) that can be matched with \r?\n...
$GetLastCommunication = $GetLastCommunication -replace '\r?\n| '
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
}
I have a tool that logs some data onto a file. I'd like to tail the file and send the last line of data via mosquitto_pub.
I've used powershell "Get-Content" command without succes.
Here's my command:
Get-Content -Path "C:\test.txt" -Wait | .\mosquitto_pub.exe -t "Events"
But nothing is published by mosquitto_pub.
If I use Get-Content -Path "C:\test.txt" -Wait
I see the tail of the file in stdout.
What's wrong with my solution?
Thanks!
Read this Q and A.
An alternate approach
$minsToRunFor = 10
$secondsToRunFor = $minsToRunFor * 60
foreach ($second in $secondsToRunFor){
$lastline = Get-Content -Path "C:\test.txt" | Select-Object -last 1
# added condition as per VonPryz's good point
# (otherwise will add lastline regardless of whether it's new or not)
if ($lastline -ne $oldlastline){
.\mosquitto_pub.exe -t "Events" -m "$lastline"
}
$oldlastline = $lastline
Start-Sleep 100
}
I have a PowerShell script where I get the count of certain string from a file, then go to next step or execute next step only if the string count is 30.
I do have the code for getting the count of the string, as well the code for executing the next step. The only thing missing is to incorporate if statement.
To get the count of string, I am using the following:
$FileContent = Get-Content "YourFile.txt"
$Matches = Select-String -InputObject $FileContent -Pattern "/export" -AllMatches
To do the next step, I am using following;
"d:/scripts/plink.exe" -ssh %1 -l hpov -pw NDIA123 -m %com%|find "host" >>%lnm%
How to include if condition in between above two codes, so that last command will follow, only if the count of the string is more than 30?
First off, don't use $Matches for anything user-defined - it's an automatic variable.
With that out of the way: since Select-String may return multiple lines, you should sort them by length and test the length of the longest one:
$FileContent = Get-Content "YourFile.txt"
$LongestMatch = Select-String -InputObject $FileContent -Pattern "/export" -AllMatches |Sort-Object {$_.Line.Length} |Select-Object -Last 1
if($LongestMatch.Line.Length -gt 30){
# We found a match in a string longer than 30 chars!
# run plink here!
}
This may be too simplistic. I feel like there is something I must have missed in the question.
$FileContent = Get-Content "YourFile.txt"
$Matches = Select-String -InputObject $FileContent -Pattern "/export" -AllMatches
if ($Matches -eq 30) {
& "d:/scripts/plink.exe" -ssh %1 -l hpov -pw NDIA123 -m %com%|find "host" >>%lnm%
}
I would like to pipe the output of Get-Content $file -Wait to a custom PowerShell script. The script looks like this.
$lines = ($input | Out-String) -replace "`r", "" -split "`n"
foreach ($line in $lines) {
#Process $line
Write-Host $line
}
Basically the idea is to take the input, format it nicely and then process the output before it gets printed to the console.
The problem is nothing is getting sent to my script when I call it like cat $file -Wait | MyScript. If I do cat $file -Wait or cat $file | MyScript, everything works as expected. But combining the pipe and the wait parameter doesn't work.
Is there some syntax I need to use to allow processing the -Wait parameter? I tried using Out-String -Stream, but that doesn't work either.
The problem is with $input.
If you do this :
Get-Content $file -Wait | Get-Member -InputObject $input
Or
Get-Content $file -Wait | Get-Member -InputObject $_
You will get :
Get-Member : The input object cannot be bound to any parameters for the command either because the command does not take pipeline input or the input and its properties do not match any of the parameters that take pipeline input.
If Get-Member is unable to read the object going through the pipeline, you know that something is very wrong with the object (or the pipelining).
Let's try piping $input to Out-String, like you are doing in your script :
Get-Content $file -Wait | Out-String $input
You will get :
Out-String : A positional parameter cannot be found that accepts argument 'System.Collections.ArrayList+ArrayListEnumeratorSimple'.
At line:1 char:52
+ get-content '.\netstat anob.txt' -wait | Out-String <<<< $input
+ CategoryInfo : InvalidArgument: (:) [Out-String], ParameterBindingException
+ FullyQualifiedErrorId : PositionalParameterNotFound,Microsoft.PowerShell.Commands.OutStringCommand
So, indeed, "Get-Content" -Wait gives you a weird kind of object : a System.Collections.ArrayList+ArrayListEnumeratorSimple .
It looks like it's the result of the GetEnumerator() method from a System.Collections.ArrayList object, or something like that.
Given the fact that Get-Member or even "Get-Member -Force" is unable to read this kind of "Object", from Powershell's point of view, it's not a object.
The workaround would be to drop the -Wait parameter from Get-Content and find another way of achieving what you want, possibly by running Get-Content and then, running "Get-Content -Tail 1" several times in a loop.
This is possible if your script accepts pipeline input. You can see it as you have mentioned when you pipe to other cmdlets like Select-String. For example defining script.ps1 as:
process { Write-Host "line: $input" }
Then running
1..200 | foreach { add-content -Path test.txt -Value "$_"; start-sleep 1 }
in one PowerShell session and
gc test.txt -wait | .\script.ps1
in another, you can see that each line is piped to the script.
I don't see any way to do what you are asking. -Wait initiates a loop that never ends, the only way to stop is to manually kill it. Since it will always be stuck inside the loop anything you try to do after initiating the loop is never going to process.
The problem is in this line:
Write-Host $line
You should use Write-Output instead. Write-Output sends objects to pipeline, Write-Host directly to host (console).