Table of Contents
Back to Blog

PowerShell Deep Dive

How to Count Lines in a File in PowerShell (And Why `-ReadCount` Matters)

Count lines in a file in PowerShell with Get-Content, Measure-Object -Line, -ReadCount tuning, -Raw, and .NET file APIs. Covers the Count-versus-Measure object-shape trap, large-file performance, and low-memory StreamReader patterns.

PowerShell 7.xWindows.NET
Published: May 14, 2026Updated: May 14, 202611 min readAuthor: Line Counter Editorial Team
PowerShellWindowsDevOps.NETPerformance

PowerShell has two line-counting commands that look interchangeable:

(Get-Content 'data.txt').Count
(Get-Content 'data.txt' | Measure-Object -Line).Lines

Most articles explain the difference as a trailing-newline edge case.

That is not the real distinction.

The real distinction is object shape:

  • Get-Content normally returns one string object per line
  • Get-Content -Raw returns one multiline string
  • Get-Content -ReadCount 1000 sends batches of lines

Once the shape changes, powershell count lines semantics change too.

That is why some people think powershell measure-object line is inconsistent. It is actually doing exactly what Microsoft documents: counting lines inside string input objects.

The second big issue is performance. Microsoft documents that Get-Content sends one line at a time by default because -ReadCount defaults to 1. On a huge log file, that means one pipeline handoff per line.

So the real powershell count lines in file question is not just "which one-liner works?"

It is:

  • am I counting returned objects or textual lines?
  • am I reading one line at a time or in batches?
  • do I need a string, a pipeline, or raw speed?
  • am I okay loading the whole file into memory?

If you just need the Windows-native overview first, the cross-platform guide already has a PowerShell section. This article goes deeper into powershell get-content count lines, powershell readcount performance, and the .NET fallbacks.

Quick Method Guide

ScenarioUse thisMain warning
Small file, fastest one-liner(Get-Content path).Countloads all lines into memory
Multiline string or -Raw contentMeasure-Object -Linecounts lines inside each string object, not returned objects
Large file with tuned batching`Get-Content path -ReadCount 1000ForEach-Object `
Small/medium file with .NET speed[System.IO.File]::ReadAllLines(path).Lengthstill reads the whole file
Huge file with low memoryStreamReader loopmore code, but predictable

For most powershell count lines in file scripts, the best answer is either (Get-Content path).Count for simplicity or a -ReadCount-tuned batch loop for large files.

Method 1: (Get-Content).Count - Short, Clear, and Object-Based

This is the shortest normal answer to powershell count lines:

$count = (Get-Content -Path 'data.txt').Count
Write-Host "Lines: $count"

Why it works:

  • Microsoft documents that Get-Content reads a file one line at a time
  • by default, newline characters are used as delimiters
  • PowerShell therefore gives you one output object per line

So powershell get-content count lines is really "count the objects returned by Get-Content."

For a normal file, that is exactly what most people want.

Where it stops being ideal

The problems start when files get large:

$count = (Get-Content 'huge.log').Count

This collects every line before the count is complete. For a very large log file, that means:

  • every line becomes a .NET string object
  • memory grows with file size
  • you do not get a streaming or low-memory path

So this version is best for:

  • quick admin commands
  • small and medium files
  • scripts where readability matters more than memory

Cross-version-safe empty-file handling

If you want a defensive cross-version shape, coerce the output to an array explicitly:

$lines = @(Get-Content -Path 'data.txt')
$count = $lines.Count

That keeps your powershell count lines in file script object-shape-safe even when no lines come back.

Method 2: Measure-Object -Line - Counts Textual Lines Inside String Objects

This method is often described as the "official streaming" answer:

$count = (Get-Content 'data.txt' | Measure-Object -Line).Lines

That works for ordinary Get-Content output.

But the real semantics matter. Microsoft says Measure-Object -Line counts the number of lines in the input objects, and Example 7 in the docs shows that a single string such as "OnenTwonThree" counts as 3 lines.

That means powershell measure-object line is not simply "count pipeline items." It is "count textual lines inside each string object."

Where Count and Measure diverge for real

This is the real trap:

$raw = Get-Content -Path '.\data.txt' -Raw
$raw.Count
# 1

($raw | Measure-Object -Line).Lines
# counts textual lines inside the single raw string

And the docs show the same shape difference explicitly:

  • Get-Content path -Raw returns one string
  • plain Get-Content path returns an array of newline-delimited strings

So the honest rule is:

  • plain Get-Content: .Count and Measure-Object -Line usually agree
  • -Raw or any multiline string object: they do different jobs

The normal file case

For a normal text file, this is still valid:

(Get-Content 'data.txt' | Measure-Object -Line).Lines

It is useful when the content is already moving through a pipeline or when you want to keep a consistent Measure-Object style with -Word and -Character.

Get-Content 'data.txt' | Measure-Object -Line -Word -Character

But if your task is only powershell count lines in file, (Get-Content path).Count is usually easier to reason about.

Method 3: -ReadCount Performance Tuning - The Big Large-File Lever

This is the parameter most PowerShell scripts ignore until a file gets big:

Get-Content 'large.log' -ReadCount 1000

Microsoft documents three critical facts:

  • -ReadCount controls how many lines are sent through the pipeline at a time
  • the default value is 1
  • increasing it increases time to first line, but decreases total time

That is the heart of powershell readcount performance.

Why the default can feel slow

Default behavior:

Get-Content 'large.log' | ForEach-Object {
  # one line at a time
}

That means:

  • one line
  • one pipeline hop
  • repeated hundreds of thousands or millions of times

That is why powershell get-content slow is a common complaint on large logs.

The common batch-counting pattern

Once you raise -ReadCount, PowerShell can send batches instead of one line at a time:

$count = 0

Get-Content 'large.log' -ReadCount 1000 | ForEach-Object {
  $count += $_.Count
}

$count

This is the large-file powershell count lines pattern most admins actually want.

Why it matters:

  • in batch mode, $_ can be an array of lines
  • $_ .Count then becomes the batch size
  • you avoid one pipeline round-trip per line

That is a better tuned answer to powershell count lines large file than piping a giant file through one-line-at-a-time defaults. If you adapt this pattern in production, inspect $_ .GetType().FullName once in your target PowerShell version so you know exactly what shape your pipeline is receiving.

-ReadCount 0

Microsoft also documents that 0 or negative values send all the content at one time:

$count = 0

Get-Content 'large.log' -ReadCount 0 | ForEach-Object {
  $count += $_.Count
}

$count

This can be very fast, but it gives up the memory advantages of chunked processing. Treat it as a read-all mode with different pipeline shape, not a magic streaming optimization.

Method 4: -Raw Plus -split - When You Actually Want One String

Microsoft documents that Get-Content -Raw returns the whole file as one string with newlines preserved:

$content = Get-Content 'data.txt' -Raw

This is useful when you need full-string operations, but it changes the whole line-counting model.

Prefer -split to naive .Split("n")`

PowerShell's -split operator uses regular expressions in the delimiter by default. That makes it a better cross-platform line-splitting tool than a naive literal split on newline:

$lines = $content -split '\r?\n'
$count = $lines.Count

if ($lines[-1] -eq '') {
  $count--
}

Why this is better:

  • it handles LF and CRLF
  • the regex is explicit
  • it matches how cross-platform files really behave

Why .Split() is easier to get wrong

The .NET String.Split() API is literal-separator-oriented. If you do this:

$lines = $content.Split("`n")

then Windows CRLF content can leave trailing ``r` characters on each line unless you normalize first.

So if you need a string-based powershell count lines in file approach, use:

function Count-LinesRaw {
  param([string]$Path)

  $content = Get-Content -Path $Path -Raw -ErrorAction Stop
  if ($content.Length -eq 0) { return 0 }

  $lines = $content -split '\r?\n'
  $count = $lines.Count

  if ($lines[-1] -eq '') {
    $count--
  }

  return $count
}

Method 5: .NET File APIs - Often the Best Admin-Script Upgrade

When you want a less shell-shaped answer and a more direct .NET answer, powershell net readalllines is a strong option:

$count = [System.IO.File]::ReadAllLines('C:\data\file.txt').Length

Microsoft's File.ReadAllLines() docs say exactly what it does:

  • opens the text file
  • reads all lines into a string array
  • closes the file

That makes it:

  • concise
  • accurate for logical lines
  • often faster than shell-heavy approaches on small and medium files

It does not require a full path. A full path is optional and mainly useful for clearer diagnostics:

function Count-LinesDotNet {
  param([string]$Path)

  $resolved = (Resolve-Path -LiteralPath $Path).Path
  return [System.IO.File]::ReadAllLines($resolved).Length
}

Low-memory .NET option: StreamReader

For huge files, use a streaming loop:

function Count-LinesStreamReader {
  param([string]$Path)

  $reader = [System.IO.StreamReader]::new($Path)
  $count = 0

  try {
    while ($reader.ReadLine() -ne $null) {
      $count++
    }
  }
  finally {
    $reader.Dispose()
  }

  return $count
}

This is usually the best low-memory answer to powershell count lines large file.

If you come from C# and want the same .NET model with richer application code, the C# StreamReader patterns guide shows the same idea in native C#.

Part 6: Practical Admin Scripts

Batch-count every file in a directory

function Get-LineCount {
  param(
    [Parameter(Mandatory)]
    [string]$Path,

    [string]$Filter = '*.*',

    [switch]$Recurse
  )

  $gciParams = @{
    Path   = $Path
    Filter = $Filter
    File   = $true
  }

  if ($Recurse) {
    $gciParams.Recurse = $true
  }

  Get-ChildItem @gciParams | ForEach-Object {
    [PSCustomObject]@{
      File     = $_.Name
      Lines    = [System.IO.File]::ReadAllLines($_.FullName).Length
      SizeMB   = [math]::Round($_.Length / 1MB, 2)
      FullPath = $_.FullName
    }
  } | Sort-Object Lines -Descending
}

Count non-empty lines in a large file

function Count-NonEmptyLines {
  param([string]$Path)

  $count = 0

  Get-Content $Path -ReadCount 1000 | ForEach-Object {
    foreach ($line in $_) {
      if ($line.Trim().Length -gt 0) {
        $count++
      }
    }
  }

  $count
}

Count matching log lines

function Count-MatchingLines {
  param(
    [string]$Path,
    [string]$Pattern
  )

  $count = 0

  Get-Content $Path -ReadCount 1000 | ForEach-Object {
    foreach ($line in $_) {
      if ($line -match $Pattern) {
        $count++
      }
    }
  }

  $count
}

This is the batch-safe way to do log filtering once -ReadCount is no longer 1.

Benchmark Shape: What Usually Wins

I could not run PowerShell itself in this environment, so I am not going to invent fake Windows timings.

What Microsoft docs and long-running community usage do support is the ranking shape:

MethodTime shapeMemory shapeBest fit
(Get-Content path).Countslowest large-file tierhightiny scripts, small files
`Get-Content pathMeasure-Object -Line`similar default-pipeline tiermoderate
`Get-Content path -ReadCount 1000ForEach-Object `much faster on big fileslow to moderate
[System.IO.File]::ReadAllLines(path).Lengthvery fast for small and medium fileshighquick .NET-backed scripts
StreamReader loopfast and steadyvery lowvery large files
Get-Content path -Raw plus -splitgood when string processing is already neededmedium to highfull-string transforms

The stable lesson is simple:

  • powershell get-content slow is usually a ReadCount problem
  • powershell measure-object line is an object-shape problem
  • powershell net readalllines is a convenience-versus-memory tradeoff

FAQ

How do I count lines in a file in PowerShell?

Use (Get-Content path).Count for the simplest powershell count lines in file answer. Move to -ReadCount or StreamReader when file size grows.

What is the difference between Count and Measure-Object -Line?

Count counts how many objects PowerShell returned. Measure-Object -Line counts textual lines inside string objects. That is why -Raw changes the meaning so dramatically.

Why is Get-Content slow for large files?

Because Get-Content sends one line at a time by default. Microsoft documents that -ReadCount defaults to 1, which is convenient but slow on very large files.

What is the best large-file PowerShell line counter?

For a shell-native answer, tune Get-Content -ReadCount and count batch sizes. For the lowest memory and most predictable behavior, use a StreamReader loop.

Is Measure-Object -Line wrong?

No. It is just doing a different job than many people assume. It counts lines in string input objects, not only pipeline items.

Should I use -Raw and -split?

Only when you actually want the entire file as one string. When you do, prefer -split '\r?\n' over a naive literal split on ``n`.

Sources Checked

Need to count lines in a log file right now, without PowerShell performance surprises?

Paste it into the Line Counter. No -ReadCount tuning. No object-shape gotchas. Just the number.

Frequently Asked Questions

How do I count lines in a file in PowerShell?

For a normal text file, (Get-Content path).Count is the shortest answer. For large files, tune Get-Content -ReadCount or use a StreamReader loop.

What is the difference between Count and Measure-Object -Line in PowerShell?

Count measures how many objects PowerShell returned. Measure-Object -Line counts line breaks inside string input objects. They often agree for plain Get-Content output, but they diverge when you use -Raw or multiline strings.

Why is Get-Content slow on large files?

Because the default ReadCount is 1, so PowerShell pushes one line at a time through the pipeline. That is convenient, but it adds heavy pipeline overhead on large files.

What is the fastest PowerShell line count method?

For small and medium files, .NET ReadAllLines is often the fastest simple option. For huge files, StreamReader or a tuned Get-Content batch loop usually makes more sense.

Should I use -Raw and -split?

Use -Raw only when you actually want the whole file as one string. Then prefer the regex-aware -split operator over a naive .Split("`n") when CRLF and LF both matter.

Does ReadAllLines require a full path?

No. It takes a path string. Resolving to a full path can make logs and errors clearer, but it is not required.

Related Guides