To count occurrences of a substring in a string in PowerShell, escape the substring and count the regex matches. This one line returns how many times $find appears in $text:
[regex]::Matches($text, [regex]::Escape($find)).Count
The count you get depends on case, special characters and overlaps, and a few popular shortcuts quietly get it wrong. I ran each method below in PowerShell 7 and Windows PowerShell 5.1 and noted where they disagree.
Count a substring with [regex]::Matches
Matches returns one object per hit, so its Count property is your answer. It’s case-sensitive by default, and the IgnoreCase option changes that:
$text = 'Austin, Denver, austin, Seattle, AUSTIN'
$find = 'Austin'
"Case sensitive: $([regex]::Matches($text, $find).Count)"
"Case insensitive: $([regex]::Matches($text, $find, 'IgnoreCase').Count)"
Output:
Case sensitive: 1
Case insensitive: 3

Only one “Austin” matches exactly, while all three match when case is ignored. If you only need a yes-or-no answer, check whether a string contains a substring instead of counting.
Why escape the substring before counting?
Matches treats your text as a regular expression. A dot means “any character”, and a plus sign is a quantifier, so plain text can count the wrong things or throw:
$text = 'Version 1.0 shipped, 1x0 is a typo, 1.0 again'
"Without Escape: $([regex]::Matches($text, '1.0').Count)"
"With Escape: $([regex]::Matches($text, [regex]::Escape('1.0')).Count)"
try { [regex]::Matches('C++ and C++', 'C++').Count }
catch { "Failed. $($_.Exception.InnerException.Message)" }
Output:
Without Escape: 3
With Escape: 2
Failed. Invalid pattern 'C++' at offset 3. Nested quantifier '+'.
Without escaping, 1.0 also matched the typo 1×0. Searching for C++ fails in both shells, but the message differs: PowerShell 7 says “Invalid pattern ‘C++’ at offset 3”, and Windows PowerShell 5.1 says “parsing “C++” – Nested quantifier +”.
[regex]::Escape fixes both. More ways to handle these characters are in escaping special characters in PowerShell.
Count occurrences with the -split operator
Splitting on the substring gives one more piece than there are matches, so subtract one. The -split operator ignores case, and -csplit doesn’t:
$text = 'Austin, Denver, austin, Seattle, AUSTIN'
"-split: $(($text -split 'Austin').Count - 1)"
"-csplit: $(($text -csplit 'Austin').Count - 1)"
$formula = 'f(x) + g(x)'
"Regex (x): $(($formula -split '(x)').Count - 1)"
"SimpleMatch: $(($formula -split '(x)', 0, 'SimpleMatch').Count - 1)"
Output:
-split: 3
-csplit: 1
Regex (x): 4
SimpleMatch: 2
The last two lines show the trap. The -split delimiter is a regex too, and parentheses make it keep a captured x in the output, so the count jumps from 2 to 4.
The SimpleMatch option treats the delimiter as plain text, but it only works when you also pass the max-substrings value, 0 for all.
Edge matches still count right, since -split returns empt