Reputation: 177
I am trying to filter out users that are in a specific group.
I got the following output in a variable:
Group1
Group2
etc...
One group for each line saved in an array. Im trying to filter out only one specific group. But when I use -contains
it always says $false
, even tho the group is there.
My Code:
$group = get-aduser -identity name -properties memberof |
select-object -expandproperty memberof | %{ (get-adgroup $_).name }
$contains = $group -contains "string"
$contains
is $false
even if the array has elements that contain the string...
What am I missing?
Upvotes: 2
Views: 5263
Reputation: 437278
It looks like your misconception was that you expected PowerShell's -contains
operator to perform substring matching against the elements of the LHS array.
Instead, it performs equality tests - as -eq
would - against the array's elements - see this answer for details.
In order to perform literal substring matching against the elements of an array, use the
-match
operator with escaping, if needed:
# With non-literal search strings:
[bool] $contains = $group -match ([regex]::Escape($someString))
# With a string literal that doesn't contain regex metachars.,
# escaping isn't needed.
[bool] $contains = $group -match 'foo'
# With a string literal with metachars., you must individually \-escape them.
[bool] $contains = $group -match 'foo\.bar'
Note:
An alternative is to use the wildcard-based -like
operator, which, however performs whole-string matching by default and therefore requires you to enclose the search term in *...*
for substring matching; e.g.:
[bool] $contains = $group -like '*foo*'
-like
too, except that (a) escaping is less likely to be needed, given that only *
, ?
, [
/ ]
and `
are metacharacters and (b) character-individual escaping requires `
and programmatic escaping requires [WildcardPattern]::Escape()
The above shows a robust, generic way of ensuring that your search string is treated as a literal value (verbatim) using [regex]::Escape()
, which is necessary because -match
expects a regex (regular expression) as its RHS (the search pattern).
Escaping isn't always necessary; specifically, only the presence of so-called metacharacters (those with special meaning in a regex, such as .
) requires it, and when you're using a string literal, you can opt to directly \
-escape them; e.g., to search for literal substring a.b
, you can pass 'a\.b'
.
As with all operators in PowerShell, by default the matching is case-insensitive; use the -cmatch
variant for case-sensitive matching.
The [bool]
type constraint above is used to ensure that the result of the -match
operation is converted to a Boolean:
-match
directly returns a Boolean with a scalar (non-array) LHS, with an array LHS it acts as a filter, and returns the matching array elements instead; interpreted in a Boolean context, such as in an if
conditional, that usually still gives the expected result, because a non-empty array is interpreted as $true
, whereas an empty one as $false
; again, however it's important to know the difference.This will rarely be a performance concern in practice, but it is worth noting that -match
, due to acting as a filter with arrays, always matches against all array elements - it doesn't stop once the first match is found, the way that the -contains
and -in
operators do.
On the plus side, you can use -match
to obtain the matching elements themselves.
The mistaken expectation of -contains
performing substring matching may have arisen from confusion with the similarly named, but unrelated String.Contains()
method, which indeed performs literal substring matching; e.g., 'foo'.Contains('o')
yields $true
.
Also note that .Contains()
is case-sensitive - invariably in Windows PowerShell, by default in PowerShell (Core) 7+.
PowerShell has no operator for literal substring matching.
However, you could combine PowerShell's generic array-filtering features with the .Contains()
string method - but note that this will typically perform (potentially much) worse than the -match
approach.
A reasonably performant alternative is to use the PSv4+ .Where()
array method as follows:
# Note: Substring search is case-sensitive here.
[bool] $contains = $group.Where({ $_.Contains("string") }, 'First')
On the plus side, this approach stops matching once the first match is found.
Upvotes: 4