In the original script, I was attempting to search for a string in a text file in a running log. It worked fine however, since there is a -wait parameter in the loop, it wasn't easy to find a solution that would allow for the same script to search over multiple text files. Since then the following script was introduced to me that incorporates Runspaces:
using namespace System.Management.Automation.Runspaces
using namespace System.Threading
# get the log files here
$LogGroup = ('C:\log 0.txt', 'C:\Log 1.txt', 'C:\Log 2.txt')
# this help us write to the main log file in a thread safe manner
$lock = [SemaphoreSlim]::new(1, 1)
# define the logic used for each thread, this is very similar to the
# initial script except for the use of the SemaphoreSlim
$action = {
param($path)
$PSDefaultParameterValues = @{ "Get-Date:format" = "yyyy-MM-dd HH:mm:ss" }
Get-Content $path -Tail 1 -Wait | ForEach-Object {
if($_ -match 'down') {
# can I write to this file?
$lock.Wait()
try {
Write-Host "Down: $_ - $path" -ForegroundColor Green
Add-Content "path\to\mainLog.txt" -Value "$(Get-Date) Down: $_ - $path"
}
finally {
# release the lock so other threads can write to the file
$null = $lock.Release()
}
}
}
}
try {
$iss = [initialsessionstate]::CreateDefault2()
$iss.Variables.Add([SessionStateVariableEntry]::new('lock', $lock, $null))
$rspool = [runspacefactory]::CreateRunspacePool(1, $LogGroup.Count, $iss, $Host)
$rspool.ApartmentState = [ApartmentState]::STA
$rspool.ThreadOptions = [PSThreadOptions]::UseNewThread
$rspool.Open()
$res = foreach($path in $LogGroup) {
$ps = [powershell]::Create($iss).AddScript($action).AddArgument($path)
$ps.RunspacePool = $rspool
@{
Instance = $ps
AsyncResult = $ps.BeginInvoke()
}
}
# block the main thread
do {
$id = [WaitHandle]::WaitAny($res.AsyncResult.AsyncWaitHandle, 200)
}
while($id -eq [WaitHandle]::WaitTimeout)
}
finally {
# clean all the runspaces
$res.Instance.ForEach('Dispose')
$rspool.ForEach('Dispose')
}
The Runspaces allow for additional threads allowing for multitasking but I am not very skilled and I need help adding an elseif clause after an if statement. But my attempts were rewarded with the following error:
cmdlet ForEach-Object at command pipeline position 2
Supply values for the following parameters:
Process[0]:
Here’s the best I could come up with so far:
$action = {
param($path)
$PSDefaultParameterValues = @{ "Get-Date:format" = "yyyy-MM-dd HH:mm:ss" }
$lock.Wait()
# can I write to this file?
try {
Get-Content $path -Tail 1 -Wait | ForEach-Object
if($_ -match 'down) {
Write-Host "Down: $_ - $path" -ForegroundColor Red
Add-Content "C:\ log_down.txt" -Value "$(Get-Date) Down: $_ - $path"
}
elseif($_ -match 'up') {
Write-Host "Down: $_ - $path" -ForegroundColor Green
Add-Content "C:\ log_up.txt" -Value "$(Get-Date) up: $_ - $path"
}
$lock.Wait()
}
finally {
# release the lock so other threads can write to the file
$null = $lock.Release()
}
}
Thanks in advance for any help!
CodePudding user response:
Here is the only change you need to do, below code only addresses the $action
Script Block. Rest of the code should remain the same.
Make sure you're using the Full Paths of the logs.
$action = {
param($path)
$PSDefaultParameterValues = @{ "Get-Date:format" = "yyyy-MM-dd HH:mm:ss" }
Get-Content $path -Tail 1 -Wait | ForEach-Object {
# wait to enter the SemaphoreSlim
$lock.Wait()
try {
if($_ -match 'down') {
Write-Host "Down: $_ - $path" -ForegroundColor Red
Add-Content "C:\log_down.txt" -Value "$(Get-Date) Down: $_ - $path"
}
elseif($_ -match 'up') {
Write-Host "Up: $_ - $path" -ForegroundColor Green
Add-Content "C:\log_up.txt" -Value "$(Get-Date) up: $_ - $path"
}
# more conditions can go here
}
finally {
# release the lock so other threads can write to the file
$null = $lock.Release()
}
}
}
After closer look at your attempt, it seems almost right:
- Missing an opening
{
afterForEach-Object
. - Missing a closing
}
before thefinally
block. .Wait()
should be inside the loop instead of outside.