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