Reputation: 26546
I have a process which sends output to a log file. I want to write a Powershell script to start this process, wait for it to finish, and whilst it is running, tail its log file to the console in real time.
I have tried this:
$procs = Start-Process "some_process.exe"
$logjob = Start-Job -Arg "some_logfile.log" -ScriptBlock {
param($file)
Get-Content $file -wait | foreach { Write-Host($_) }
}
$procs | Wait-Process
$logjob | Stop-Job
... and other similar arrangements, using jobs. But it looks like the output of jobs is only available either after the job is stopped, or by sending 'events' from it, which sound like they're not really meant for rapid things like log messages (e.g. 100,000 lines in ~1 min).
Is there some other way to do this? My next option is to just call 'tail' from Start-Process
, which would get the job done (I think - hopefully this doesn't have the same "wait for stop" output behaviour), but feels wrong.
Upvotes: 1
Views: 4536
Reputation: 1963
I had the same problem and worked out the following solution:
Function Start-FileTailUntilProcessExits($FileToTail, $ProcessToWatch, $TimeoutSeconds) {
$fs = $null
$sr = $null
$timeToAbort = (Get-Date).AddSeconds($TimeoutSeconds)
$timedOut = $false
while ($true) {
if (Test-Path $FileToTail) { # Wait for file to exist
if ($null -eq $sr) {
$fs = New-Object IO.FileStream ($FileToTail, [IO.FileMode]::Open, [IO.FileAccess]::Read, [IO.FileShare]::ReadWrite)
$sr = New-Object IO.StreamReader ($fs)
}
while ($true) {
$line = $sr.ReadLine()
if ($null -eq $line) {
# Read as many lines as are in the file currently
# and then break so we can check on the status of the process and timeout
# If lines are added later, the StreamReader will pick up where it left off and only log new lines.
break
}
Write-Host $line
}
}
if ($ProcessToWatch.HasExited) {
Write-Host "Process has exited, so stop tailing the log"
break
}
if ((Get-Date) -gt $timeToAbort) {
$timedOut = $true
break
}
Start-Sleep -Seconds 1
}
if ($null -ne $sr) {
$sr.Dispose()
}
if ($null -ne $fs) {
$fs.Dispose()
}
if ($timedOut) {
Write-Error "Process timed out"
}
}
Upvotes: 0
Reputation: 200453
Job output isn't displayed unless you retrieve it from the job. Why don't you simply reverse what you're doing: start the process as a job, and tail the log file on the console:
$job = Start-Job -ScriptBlock { & "some_process.exe" }
Get-Content "some_logfile.log" -Wait
If you want the tail to terminate when the process terminates you could run both as jobs, and retrieve output from the log job while the other job is running:
$pjob = Start-Job -ScriptBlock { & "some_process.exe" }
$ljob = Start-Job -ScriptBlock { Get-Content "some_logfile.log" -Wait }
while ($pjob.State -eq 'Running' -and $ljob.HasMoreData) {
Receive-Job $ljob
Start-Sleep -Milliseconds 200
}
Receive-Job $ljob
Stop-Job $ljob
Remove-Job $ljob
Remove-Job $pjob
Upvotes: 2