Weekend Scripter: Two Way-Cool PowerShell Text File Tricks: Tail and Wait


Summary: Microsoft Scripting Guy, Ed Wilson, talks about two way-cool new Windows PowerShell parameters: Tail and Wait.

Microsoft Scripting Guy, Ed Wilson, is here. Some things in Windows PowerShell are just so cool that I get all carried away with them and lose focus on whatever I am supposed to be doing. It happens that way sometimes. Luckily, it is the weekend, so I am covered for frittering away my time. But hey, it is for a good cause.

Note  Today I talk about reading from a log file as it updates. I also talk about reading from the end of a log file. I am using the script I wrote yesterday to add to the log file as I watch for updates. See yesterday’s article, Weekend Scripter: Creating a Sample Log File by Using PowerShell, for that script and for a discussion of the script.

Reading x number of lines from beginning or from end of log

For log files that log to the end of the file, the most recent information is at the bottom of the file. This can be a problem if I don’t know how many lines of stuff are in the log. Oh, I can do it, but even using Windows PowerShell, it requires a bit of code. Not anymore, because we now have the tail parameter that will retrieve a specific number of lines from the very bottom of the file. This technique is shown here.

PS C:\> Get-Content -Path C:\fso\mylogfile.log -Tail 1

Added sample 125 at 4:0:42

PS C:\> Get-Content -Path C:\fso\mylogfile.log -Tail 2

Added sample 123 at 4:0:42

Added sample 125 at 4:0:42

PS C:\> Get-Content -Path C:\fso\mylogfile.log -Tail 3

Added sample 121 at 4:0:42

Added sample 123 at 4:0:42

Added sample 125 at 4:0:42

If I am interested in the first lines in the file, I use the –head parameter and specify how many lines I want to obtain. This is shown here.

PS C:\> Get-Content -Path C:\fso\mylogfile.log -Head 2

Added sample 0 at 3:46:40

Added sample 1 at 3:46:40

But what about when the action is still occurring?

If the action is still occurring, that is, it is still being written to the log file, use the –wait parameter of Get-Content. The command is shown here.

Get-Content -Path C:\fso\mylogfile.log -Tail 1 –Wait

In this way, I retrieve the last written log file entry as it occurs.

To test this, I run the command above in the Windows PowerShell console. The Windows PowerShell console waits for new lines to be written to the file. I then switch to the Windows PowerShell ISE and run my Add-LogV2.ps1 script so that new entries are added to the file.

I switch back to the Windows PowerShell console and watch the entries add new lines to the Windows PowerShell console. The result is shown in the following image.

Image of command output

Well, that is about it for monitoring a log file. Join me tomorrow when I will talk about parsing the System event log.

I invite you to follow me on Twitter and Facebook. If you have any questions, send email to me at scripter@microsoft.com, or post your questions on the Official Scripting Guys Forum. See you tomorrow. Until then, peace.

Ed Wilson, Microsoft Scripting Guy

Comments (16)

  1. jrv says:

    @Ed

    I just love occasionally poking a bit of fun at Microsoft, especially PowerSHell.  It is so near perfect and yet the documentaion is all over the map.  There are so many undocumented features.  It is both cool and amusing.

  2. jrv says:

    @Ed –

    You might note that this is PowerShell V3 only and that the 'head' is not documented. In PowerShell v2 only 'wait' is supported.

    'Tail' is documented as V3 only in some online docs.  'Head' is not documented.

    technet.microsoft.com/…/hh849787.aspx

    In V2 the -wait is very annoying because it spools the compete file before waiting.  On large logfiles this can be an issue.

  3. jrv says:

    @brain

    Get-Content does not open the file exclusively. Like the tail command in *nix it is designed for monitoring logfiles.  If you are uisng VBScript to log to a CSV then the CSV file will be'tail'-able.  ZIf you are using some other mechanism that opens the file exclusivley like Excel then the file will not be viewable.

    If you are using Windows methods to log then the Get-Content 'shared/readonly' open method will not disrupt logging.  If youa re using ADO or ODBC to log then the file open might request 'exclusive' and it would fail.  'Exclusive' does not allow shared-read.  'shared-write' is allows a file to be opend shared-read. ADO allows us to choose the open method.

    Non-Windows utilities may not work correctly.

  4. brainimplant says:

    Hi Ed,

    I do some logging in my domain at logon, logoff, and at different intervals…The data is stored in CSV format but the logging process fails if i have the CSV file open while it is stil being written to. Do you think there is something I can do with PS that might relay the information back in real-time? seems doable, and i wish i had the time to do it the right way. push me in the right direction if you can.

    Cheers…

  5. Ed Wilson says:

    @JRV thanks for pointing this out. You are right, of course!

  6. rtc says:

    Thanks, very useful. Is it possible to "tail" the Windows Event Log as well in some way?

  7. someguy says:

    Hi, I find that this method does not work if new data is appended to the log file (via a server). The powershell script just hangs and never prints it out to the new text to the screen. Advice?

  8. Roberto says:

    Hi everybody.

    To tail text files in real time like the ‘tail’ Linux/Unix command, try this program:
    https://sourceforge.net/projects/wintail/

  9. nikku says:

    i tried to use PS C:> Get-Content -Path C:fsomylogfile.log -Tail 2 -Wait

    but it is not updating in real time, any solution for that

  10. JRG says:

    I was so excited to read about this tail like command that is native. I’m looking to take it one step further and write a script that will change color or insert line after each wait longer than x time. Any ideas?

  11. JRG says:

    This might be to ugly to post, but I did it. This will make the first line after a pause red. It specific to the date format of windowsupdate.log, but Im sure it could be modified to be more universal.
    Get-Content c:windowswindowsupdate.log -Wait -Tail 100 | ForEach-Object { $culture = Get-Culture; $format = "yyyy-MM-dd HH:mm:ss:fff"; %{ $line=$_.split(" ");$datetime=$line[0,1]}; if ($a -eq "") {$a=[DateTime]::ParseExact($datetime,$format,$null)}; $b=$a;
    $a=[DateTime]::ParseExact($datetime,$format,$null); $dif=$a – $b; if ($dif -gt ’00:05:00.0000000′) {write-host -ForegroundColor red $line} Else {write-host $line}}

  12. JRG says:

    This one changes the color for each section of text. Its random so its get some very hard to read colors. There is a lot of room for improvement. Switching between two easy to read colors would be easier on the eyes.

    Get-Content c:windowswindowsupdate.log -Wait -Tail 100 | ForEach-Object { $culture = Get-Culture; $format = "yyyy-MM-dd HH:mm:ss:fff"; %{ $line=$_.split(" ");$datetime=$line[0,1]}; if ($a -eq "") {$a=[DateTime]::ParseExact($datetime,$format,$null)}; $b=$a;
    $a=[DateTime]::ParseExact($datetime,$format,$null); $dif=$a – $b; if ($dif -gt ’00:05:00.0000000′) {$max = [System.ConsoleColor].GetFields().Count – 1; $color = [System.ConsoleColor](Get-Random -Min 0 -Max $max)}; write-host -ForegroundColor $color $line}

  13. JRG says:

    # https://support.microsoft.com/en-us/kb/902093
    #Color changes with changed caller identity

    # time lapsed statement inserted when time difference between lines is greater then $changethreshold’s value

    $WSUSlogfile = "c:windowswindowsupdate.log"
    $color = [System.ConsoleColor](13)
    $changethreshold = ’00:05:00.0000000′
    $format = "yyyy-MM-dd HH:mm:ss:fff";
    $host.ui.rawui.windowtitle=$WSUSlogfile

    $host.UI.RawUI.BufferSize = new-object System.Management.Automation.Host.Size(1024,50)

    Get-Content -path $WSUSlogfile -Wait -Tail 100 | ForEach-Object {%{ $line=$_.split(" ");$PIDTID=$line[2,3];$datetime=$line[0,1]}; if ($a -eq ”) {$a=$PIDTID}; $b=$a; $a=$PIDTID; if ($adt -eq ”) {$adt=[DateTime]::ParseExact($datetime,$format,$null)};$bdt=$adt;
    $adt=[DateTime]::ParseExact($datetime,$format,$null); $difdt=$adt – $bdt; if ($difdt -gt $changethreshold) {write-host -ForegroundColor DarkGray "—————————– $difdt lapsed ———————————–"}; if ("$b" -ne "$a") {if ($color
    -eq ‘White’) {$color = ‘Yellow’} else {$color = ‘White’}}; write-host -ForegroundColor $color $line}

  14. Darth Bob says:

    Hi, Ed!

    I’m attempting to use this technique to monitor some logs for a specific error for one of the team in our organization and send an alert when it’s spotted. I run the `Get-Content` in the background and poll the background job every minute to see if any errors
    have been found.

    The issue I’m having is that, as I watch memory consumption, it climbs pretty quickly. In < 10K lines in the monitored log, memory consumption is > 250M.

    Thoughts?

  15. Khakzoy says:

    I’m installing the sccm client with a powershell script on some remote servers. This works just fine, the only problem I have is that I manually check the log file to see if the client is installed ok. The installation of the client sometimes takes longer
    or shorter, depending on te machine.

    Can I use the Get-Content -wait to monitor the log until it finds ‘ccmsetup.exe returned error code 0’ and then write this result to my output file? Using Get-Content -Patch C:WindowsCCMSetupLogsCCMsetup.log I see the last line and the info I need, now
    I just need to get that in my script and make it so that when it finds this entry in the log, the wait is over en the Get-Content process can be stopped and the output written to my own output file. If anyone can help me with that it would be great!

  16. Here’s a posh function I put together that tails, colors and optionally launches output in a new window. It’s specifically tailored for log4net/log4j style logs with INFO, DEBUG, WARN, ERROR labels but it could easily be adapted:

    function Tail-File
    {
    param
    (
    [string] $Path,
    [switch] $NewWindow,
    [switch] $Wait,
    [int] $History = 10
    )

    $command = “Get-Content ‘$Path’ -Tail ” + $History + ” ” + (. {switch($wait){$true{” -Wait”} $false{“”}}}) + ” | ForEach { Write-Host -ForegroundColor (elseif (`$_.Contains(‘ INFO ‘)) {‘Gray’} elseif (`$_.Contains(‘ WARN ‘)) {‘DarkYellow’} elseif (`$_.Contains(‘ ERROR ‘)) {‘Red’} else {‘White’}}) `$_ }”

    if ($newWindow -eq $true)
    {
    $command = $command.Replace(“`$”, ““`$”)
    $command = $command.Replace(“”””, “”””””)
    $command = “start-process powershell.exe -argument “”-nologo -noprofile -executionpolicy bypass -command “`$Host.UI.RawUI.WindowTitle = ‘Tail: $Path’; $command”””
    }

    Write-Debug “Command = $command”
    Invoke-Expression $command
    }

Skip to main content