Ben Hymers
Ben Hymers

Reputation: 26546

Powershell tail log to console while process is running

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

Answers (2)

kevinpo
kevinpo

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

Ansgar Wiechers
Ansgar Wiechers

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

Related Questions