Table of Contents
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 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-Contentnormally returns one string object per lineGet-Content -Rawreturns one multiline stringGet-Content -ReadCount 1000sends 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
| Scenario | Use this | Main warning |
|---|---|---|
| Small file, fastest one-liner | (Get-Content path).Count | loads all lines into memory |
Multiline string or -Raw content | Measure-Object -Line | counts lines inside each string object, not returned objects |
| Large file with tuned batching | `Get-Content path -ReadCount 1000 | ForEach-Object ` |
| Small/medium file with .NET speed | [System.IO.File]::ReadAllLines(path).Length | still reads the whole file |
| Huge file with low memory | StreamReader loop | more 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-Contentreads 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 -Rawreturns one string- plain
Get-Content pathreturns an array of newline-delimited strings
So the honest rule is:
- plain
Get-Content:.CountandMeasure-Object -Lineusually agree -Rawor 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:
-ReadCountcontrols 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 $_ .Countthen 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
LFandCRLF - 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:
| Method | Time shape | Memory shape | Best fit |
|---|---|---|---|
(Get-Content path).Count | slowest large-file tier | high | tiny scripts, small files |
| `Get-Content path | Measure-Object -Line` | similar default-pipeline tier | moderate |
| `Get-Content path -ReadCount 1000 | ForEach-Object ` | much faster on big files | low to moderate |
[System.IO.File]::ReadAllLines(path).Length | very fast for small and medium files | high | quick .NET-backed scripts |
StreamReader loop | fast and steady | very low | very large files |
Get-Content path -Raw plus -split | good when string processing is already needed | medium to high | full-string transforms |
The stable lesson is simple:
powershell get-content slowis usually aReadCountproblempowershell measure-object lineis an object-shape problempowershell net readalllinesis 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
- Microsoft Learn
Get-Contentdocs: https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.management/get-content?view=powershell-7.4 - Microsoft Learn
Get-Content -Rawexample showing one string versus an array of newline-delimited strings: https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.management/get-content?view=powershell-7.4#example-6-get-raw-content - Microsoft Learn
Get-Content -ReadCountdocs stating the default is1,0sends all content at once, and larger values reduce total time: https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.management/get-content?view=powershell-7.4#-read-count - Microsoft Learn
Measure-Object -Linedocs and Example 7 showing that line counts are measured inside string input objects: https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.utility/measure-object?view=powershell-7.5#example-7-measure-strings - Microsoft Learn
about_Splitdocs stating that-splituses regular expressions by default: https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_split?view=powershell-7.5 - Microsoft Learn
.NETFile.ReadAllLines()docs: https://learn.microsoft.com/en-us/dotnet/api/system.io.file.readalllines?view=net-10.0 - Microsoft Learn
.NETStreamReader.ReadLine()docs: https://learn.microsoft.com/en-us/dotnet/api/system.io.streamreader.readline?view=net-9.0 - Stack Overflow discussion showing why
Get-Content -ReadCountchanges pipeline behavior by sending arrays of lines: https://stackoverflow.com/questions/68787126/powershell-get-content-readcount - Stack Overflow discussion illustrating that
Measure-Object -LineversusCountdifferences are really about string-versus-array input shape: https://stackoverflow.com/questions/52119081/powershell-count-lines-in-file
Related Guides and Tools
- Cross-platform file counting on Linux, macOS, and Windows
- C# StreamReader patterns
- Python file I/O
- Node.js readline patterns
- Line Counter tool
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
18 min read
How to Count Lines in a File on Linux, macOS, and Windows
Count lines in any file on Linux, macOS, or Windows using wc -l, PowerShell, CMD, and GUI tools. Includes large file methods, CSV row counting, and cross-platform scripts.
15 min read
How to Count Lines in a File Using C# (And the Int32 Overflow Trap Nobody Warns You About)
Count lines in a file using C# — File.ReadAllLines, File.ReadLines, StreamReader, and async methods. Includes the Int32 overflow trap, GC pressure benchmarks, and .NET 6+ best practices.
20 min read
How to Count Lines in Python: 7 Methods, Benchmarked and Battle-Tested
Count lines in Python strings, text files, large files, and directories. Includes real performance benchmarks, empty file handling, splitlines vs split, and production-ready functions.
11 min read
How to Count Lines in a File in TypeScript (And the readFileSync Buffer Type Trap)
Count lines in a file in TypeScript with readFileSync, readline streams, and Buffer scanning. Covers the Buffer-versus-string type trap, readline behavior when the last line has no newline, and when callback-style Node.js APIs are still worth preferring.