PowerShell,  Windows Bash,  WSL

💡PowerShell Tip for Large Files – The Get-Content -Tail and -Wait parameters

In PowerShell, you can display the last *n* lines of large text or ASCII files using the `-Tail` parameter of the `Get-Content` cmdlet:

In Powershell, you can display the last *n* lines of large tyt or ASCII files using the -Tail parameter of the Get-Content cmdlet:

PowerShell
<code>Get-Content meineRiesigeDatei.csv -tail 25</code>

Since PowerShell version 3, Get-Content (or its alias gc) includes the -Tail parameter. Unfortunately, there is no built-in equivalent to the Unix tail -f command for continuous updates.

PowerShell: Get-Content with -Wait

PowerShell’s Get-Content cmdlet supports the -Wait parameter, which continuously monitors a file for new content:

PowerShell
Get-Content -Path "C:\path\to\your\file.log" -Wait -Tail 10

This will display new lines as they are appended to the file.

PowerShell with filtering (advanced usage)

If you want something closer to tail -f | grep:

PowerShell
Get-Content "C:\path\to\file.log" -Wait | Select-String "ERROR"

Windows Subsystem for Linux (WSL)

If you’re already using WSL (which you mentioned earlier), just use the real thing:

Bash
tail -f /mnt/c/path/to/file.log

This is often the most predictable behavior, especially for large logs, but it can be tricky, if your are using mounted network drives. But that’s another story.

Using Git Bash / Cygwin

If you have Unix-like tooling installed:

  • Git Bash (from Git for Windows)
  • Cygwin

Then:

Bash
tail -f file.log

PowerShell tail -f-like function

Here’s a reusable PowerShell function that behaves much closer to tail -f, including:

  • continuous follow (-Wait)
  • configurable initial tail
  • file rotation handling (log recreated / truncated)
  • optional filtering (like grep)
  • resilient retry loop
PowerShell
function Tail-F {
    param(
        [Parameter(Mandatory=$true)]
        [string]$Path,

        [int]$Tail = 10,

        [int]$PollIntervalMs = 1000,

        [string]$Filter,

        [switch]$ShowFileName
    )

    if (-not (Test-Path $Path)) {
        Write-Warning "File not found. Waiting for creation: $Path"
        while (-not (Test-Path $Path)) {
            Start-Sleep -Milliseconds $PollIntervalMs
        }
    }

    $lastLength = 0

    while ($true) {
        try {
            $file = Get-Item $Path -ErrorAction Stop

            # Detect truncation or rotation
            if ($file.Length -lt $lastLength) {
                Write-Verbose "File truncated or rotated. Restarting..."
                $lastLength = 0
            }

            $stream = [System.IO.File]::Open($Path, 'Open', 'Read', 'ReadWrite')
            $reader = New-Object System.IO.StreamReader($stream)

            if ($lastLength -eq 0 -and $Tail -gt 0) {
                # Initial tail
                $lines = Get-Content $Path -Tail $Tail
                foreach ($line in $lines) {
                    if (-not $Filter -or $line -match $Filter) {
                        if ($ShowFileName) {
                            Write-Output "$Path`: $line"
                        } else {
                            Write-Output $line
                        }
                    }
                }
                $lastLength = $file.Length
            } else {
                # Continue from last position
                $stream.Seek($lastLength, 'Begin') | Out-Null

                while (-not $reader.EndOfStream) {
                    $line = $reader.ReadLine()
                    if (-not $Filter -or $line -match $Filter) {
                        if ($ShowFileName) {
                            Write-Output "$Path`: $line"
                        } else {
                            Write-Output $line
                        }
                    }
                }

                $lastLength = $stream.Position
            }

            $reader.Close()
            $stream.Close()
        }
        catch {
            Write-Warning "Error reading file. Retrying..."
        }

        Start-Sleep -Milliseconds $PollIntervalMs
    }
}

Usage examples

Basic follow (like tail -f)

PowerShell
Tail-F -Path "C:\logs\app.log"

Show last 50 lines initially

PowerShell
Tail-F -Path "C:\logs\app.log" -Tail 50

Filter like grep

PowerShell
Tail-F -Path "C:\logs\app.log" -Filter "ERROR|WARN"

Debug multiple logs (with filename prefix)

PowerShell
Tail-F -Path "C:\logs\app.log" -ShowFileName

Why this is better than Get-Content -Wait (IMHO)

Get-Content -Wait has limitations:

  • struggles with log rotation
  • can miss lines when files are truncated/recreated
  • no built-in filtering

This function:

  • reopens the file every cycle → handles rotation
  • tracks byte position → efficient for large logs
  • adds filtering inline

Make Tail-F permanently available

To make your Tail-F function permanently available in PowerShell, you should add it to your PowerShell profile ($PROFILE). That way it loads automatically in every session.

Locate your PowerShell profile

Run:

PowerShell
$PROFILE

Typical result (Windows PowerShell or PowerShell Core):

PowerShell
C:\Users\<your-user>\Documents\PowerShell\Microsoft.PowerShell_profile.ps1

2. Create the profile if it doesn’t exist

PowerShell
if (!(Test-Path $PROFILE)) {
    New-Item -ItemType File -Path $PROFILE -Force
}

3. Open the profile for editing

Example with Visual Studio Code:

PowerShell
code $PROFILE

Or with Notepad:

PowerShell
notepad $PROFILE

4. Add the Tail-F function

Paste your function at the end of the profile file. Save the file.

5. Reload your profile (without restarting shell)

PowerShell
. $PROFILE

6. Verify

PowerShell
Get-Command Tail-F

Then test:

PowerShell
Tail-F -Path "C:\temp\test.log"

Practical notes (important)

  • If you use both:
    • Windows PowerShell (5.1)
    • PowerShell 7+
    → each has its own profile path. Check both with:
PowerShell
$PROFILE | Format-List *

Related posts