How to Extract Text Between Delimiters in PowerShell

To extract text between two delimiters in PowerShell, match it with a regular expression and a capture group. The part in parentheses lands in $Matches[1]:

$text = 'Invoice [1042] shipped to [Austin, TX]'

if ($text -match '\[(.*?)\]') {
    "Between the first brackets: $($Matches[1])"
}

Output:

Between the first brackets: 1042

The ? after .* matters more than it looks, and -match only returns the first match. Both are covered below. I ran every example in PowerShell 7.6 and Windows PowerShell 5.1, with the same results.

Greedy vs lazy: why you got too much text

.* grabs as much as it can, so it runs from the first bracket to the last one. .*? stops at the first closing bracket:

$text = 'Invoice [1042] shipped to [Austin, TX]'

if ($text -match '\[(.*)\]')  { "Greedy (.*):  $($Matches[1])" }
if ($text -match '\[(.*?)\]') { "Lazy (.*?):   $($Matches[1])" }

Output:

Greedy (.*):  1042] shipped to [Austin, TX
Lazy (.*?):   1042
PowerShell extract text between brackets with greedy vs lazy regex
Greedy .* grabs too much, lazy .*? stops at the first bracket (PowerShell 7)

Another safe pattern is “anything except the closing delimiter,” like \[([^\]]*)\]. Brackets and parentheses need a backslash in regex because they have special meanings.

Greedy and lazy matching are part of regex in general, covered in PowerShell regex.

Get every match, not just the first

-match stops at the first match. [regex]::Matches() returns all of them, and Groups[1] is the captured text:

$text = 'Invoice [1042] shipped to [Austin, TX]'

[regex]::Matches($text, '\[(.*?)\]') | ForEach-Object { $_.Groups[1].Value }

Output:

1042
Austin, TX

Extract text between parentheses

The same idea works for parentheses. Here it pulls the area code out of US phone numbers:

$phones = '(212) 555-0147', '(415) 555-0199', '555-0123'

foreach ($phone in $phones) {
    if ($phone -match '\((\d{3})\)') { "$phone -> area code $($Matches[1])" }
    else                             { "$phone -> no area code" }
}

Output:

(212) 555-0147 -> area code 212
(415) 555-0199 -> area code 415
555-0123 -> no area code

Watch out: $Matches keeps its old value

When -match fails, it doesn’t clear $Matches. The previous result is still there:

foreach ($text in 'Order (1042)', 'No parentheses here') {
    $null = $text -match '\((\d+)\)'
    "$text -> $($Matches[1])"
}

Output:

Order (1042) -> 1042
No parentheses here -> 1042
PowerShell $Matches keeps the previous value when -match fails
The second line shows the first line’s result (Windows PowerShell 5.1)

The second string has no parentheses, but it still printed 1042. Only read $Matches inside an if that checks the result of -match, as in the phone example.

Extract text without regex: IndexOf and Substring

For fixed delimiters in a predictable format, IndexOf() and Substring() work too:

$line = 'user=alice.johnson;dept=Sales;office=Austin'

$start = $line.IndexOf('dept=') + 'dept='.Length
$end   = $line.IndexOf(';', $start)
$line.Substring($start, $end - $start)

Output:

Sales

It’s more code than a regex, but there’s nothing to escape. Check that IndexOf didn’t return -1 before you call Substring, or the script will stop with an error.

Pull several parts out with named groups

Name each group with ?<name> and read it from $Matches by name. It’s clearer than counting groups:

$entry = '2026-09-28 14:05:12 [WARN] Disk C: is 91% full'

if ($entry -match '^(?<date>\S+) (?<time>\S+) \[(?<level>\w+)\] (?<message>.+)$') {
    "Level:   $($Matches.level)"
    "Message: $($Matches.message)"
}

Output:

Level:   WARN
Message: Disk C: is 91% full

about_Regular_Expressions covers groups, anchors and the other regex building blocks.

Extract lines between two strings in a file

To get the lines between two marker lines, loop through the file and switch a flag on and off:

$inside = $false
foreach ($line in Get-Content -Path C:\psfaqs\deploy.log) {
    if ($line -like '*END ERRORS*')   { $inside = $false }
    if ($inside)                      { $line }
    if ($line -like '*BEGIN ERRORS*') { $inside = $true }
}

Output:

09:01 Web01: timeout after 30 seconds
09:02 Web03: disk space low
PowerShell extract lines between two marker strings in a log file
The lines between the BEGIN and END markers (PowerShell 7)

The order of the three checks keeps the marker lines themselves out of the result. It reads one line at a time, so it works on large logs too.

Or read the file as one string

With -Raw, the whole file is one string, and (?s)</