-Contains not working as expected [duplicate] - powershell

Consider the following snippet:
"12-18" -Contains "-"
You’d think this evaluates to true, but it doesn't. This will evaluate to false instead. I’m not sure why this happens, but it does.
To avoid this, you can use this instead:
"12-18".Contains("-")
Now the expression will evaluate to true.
Why does the first code snippet behave like that? Is there something special about - that doesn't play nicely with -Contains? The documentation doesn't mention anything about it.

The -Contains operator doesn't do substring comparisons and the match must be on a complete string and is used to search collections.
From the documentation you linked to:
-Contains
Description: Containment operator. Tells whether a collection of reference values includes a single test value.
In the example you provided you're working with a collection containing just one string item.
If you read the documentation you linked to you'll see an example that demonstrates this behaviour:
Examples:
PS C:\> "abc", "def" -Contains "def"
True
PS C:\> "Windows", "PowerShell" -Contains "Shell"
False #Not an exact match
I think what you want is the -Match operator:
"12-18" -Match "-"
Which returns True.
Important: As pointed out in the comments and in the linked documentation, it should be noted that the -Match operator uses regular expressions to perform text matching.

-Contains is actually a collection operator. It is true if the collection contains the object. It is not limited to strings.
-match and -imatch are regular expression string matchers, and set automatic variables to use with captures.
-like, -ilike are SQL-like matchers.

You can use like:
"12-18" -like "*-*"
Or split for contains:
"12-18" -split "" -contains "-"

like is best, or at least easiest.
match is used for regex comparisons.
Reference: About Comparison Operators

Related

Check if a string exists in an array even as a substring in PowerShell

I'm trying to work out if a string exists in an array, even if it's a substring of a value in the array.
I've tried a few methods and just can't get it to work, not sure where I'm going wrong.
I have the below code, you can see that $val2 exists within $val1, but I always get a FALSE when I run it.
$val1 = "folder1\folder2\folder3"
$val2 = "folder1\folder2"
$val3 = "folder9"
$val_array = #()
$val_array += $val1
$val_array += $val3
$null -ne ($val_array | ? { $val2 -match $_ }) # Returns $true
I also tried:
foreach ($item in $val_array) {
if ($item -match $val2) {
Write-Host "yes"
}
}
The -Match operator does a regular expression comparison. Where the backslash character (\) has a special meaning (it escapes the following character).
Instead you might use the -Like operator:
$val_array -Like "*$val2*"
Yields:
folder1\folder2\folder3
iRon's helpful answer offers the best solution to your problem, using wildcard matching via the -like operator.
Note:
The need to escape select characters in a search pattern in order for the pattern to be taken verbatim in principle also applies to the wildcard-based -like operator, not just to the regex-based -match operator, but since wildcard expressions have far fewer metacharacters than regexes - namely just *, ?, and [ - the need for such escaping doesn't often arise in practice; whereas regexes require \ as the escape characters, wildcards use `, and programmatic escaping can be achieved with [WildcardPattern]::Escape()
Unfortunately, as of PowerShell 7.2, there is no dedicated operator for verbatim substring matching:
A workaround for this limitation is to call the [string] .NET type's .Contains() method (on a single input string only), however, this performs case-sensitive matching, whereas PowerShell operators are case-insensitive by default, but offer case-sensitive variants simply by prefixing the operator name with c (e.g., -clike, -cmatch).
In Windows PowerShell, .Contains() is invariably case-sensitive, but in PowerShell (Core) 7+ an additional overload is available that offers case-insensitive matching:
'Foo'.Contains('fo') # -> $false, due to case difference
# PowerShell (Core) 7+ *only*:
'Foo'.Contains('fo', 'InvariantCultureIgnoreCase') # -> $true
Caveat: Despite the name similarity, PowerShell's -contains operator does not perform substring matching; instead, it tests whether a collection contains a given element (in full).
As for what you tried:
Your primary problem is that you've accidentally swapped the -match operator's operands: the search pattern - which is invariably interpreted as a regex (regular expression) - must be on the RHS.
As iRon points out, in order for your search pattern to be taken verbatim (literally), you need to escape regex metacharacters with \, and the robust, programmatic way to do this is with [regex]::Escape().
Therefore, the immediate fix would have been (? is a built-in alias of the Where-Object cmdlet):
# OK, but SLOW.
$val_array | ? { $_ -match [regex]::Escape($val2) }
However, this solution is inefficient (it involves the pipeline and a cmdlet).
Fortunately, PowerShell's comparison operators can be applied to arrays (collections) directly, in which case they act as filters, i.e. they return the sub-array of matching elements - see the docs.
iRon's answer uses this technique with -like, but it equally works with -match, so that your expression can be simplified to the following, much more efficient form:
# MUCH FASTER.
$val_array -match [regex]::Escape($val2)
Try the string method Contains:
$null -ne ($val_array | ? { $_.Contains($val2) })

Powershell if String.StartsWith() multiple strings

I am very new to PowerShell and i am trying run some code if a string does not start with a certain character, however i can not get this to work with multiple characters.
This is the code that works fine.
if (-Not $recdata.StartsWith("1"))
{
//mycode.
}
But what i want is multiple checks like this
if (-Not $recdata.StartsWith("1") -Or -Not $recdata.StartsWith("2"))
{
//mycode.
}
But this does not work it breaks the whole function eventhough powershell does not throw any errors. I have tried multiple things but i cant find any solution
MundoPeter has pointed out the logic flaw in your approach - -or should be -and - and Santiago Squarzon has offered an alternative based on the regex-based -match operator.
Let me offer the following PowerShell-idiomatic solutions, taking advantage of the fact that PowerShell's operators offer negated variants simply by prepending not to their names:
$recdata[0] -notin '1', '2' # check 1st char of LHS against RHS array
$recdata -notlike '[12]*' # check LHS against wildcard expression
$recdata -notmatch '^[12]' # check LHS against regex
See also:
-in, the is-the-LHS-contained-in-the-RHS-collection operator
-like, the wildcard matching operator
-match, the regular-expression matching operator

PowerShell to check the string in the array [duplicate]

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?
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:
# 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:
The above shows a robust, generic way of ensuring that your search string is treated as a literal value 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'.
Chances are that AD group names do not require escaping, but it's important to be aware of the need for it in general.
As with all operators in PowerShell, by default the matching is case-insensitive; use the -cmatch variant for case-sensitive matching.
The [bool] type constrained above is used to ensure that the result of the -match operation is converted to a Boolean:
While -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.
The answer was -match insted of contains. Now the output is true.

Arrays and -contains - test for substrings in the elements of an array

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?
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:
# 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:
The above shows a robust, generic way of ensuring that your search string is treated as a literal value 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'.
Chances are that AD group names do not require escaping, but it's important to be aware of the need for it in general.
As with all operators in PowerShell, by default the matching is case-insensitive; use the -cmatch variant for case-sensitive matching.
The [bool] type constrained above is used to ensure that the result of the -match operation is converted to a Boolean:
While -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.
The answer was -match insted of contains. Now the output is true.

Generate Inactive user report with some exclusions - Powershell [duplicate]

Consider the following snippet:
"12-18" -Contains "-"
You’d think this evaluates to true, but it doesn't. This will evaluate to false instead. I’m not sure why this happens, but it does.
To avoid this, you can use this instead:
"12-18".Contains("-")
Now the expression will evaluate to true.
Why does the first code snippet behave like that? Is there something special about - that doesn't play nicely with -Contains? The documentation doesn't mention anything about it.
The -Contains operator doesn't do substring comparisons and the match must be on a complete string and is used to search collections.
From the documentation you linked to:
-Contains
Description: Containment operator. Tells whether a collection of reference values includes a single test value.
In the example you provided you're working with a collection containing just one string item.
If you read the documentation you linked to you'll see an example that demonstrates this behaviour:
Examples:
PS C:\> "abc", "def" -Contains "def"
True
PS C:\> "Windows", "PowerShell" -Contains "Shell"
False #Not an exact match
I think what you want is the -Match operator:
"12-18" -Match "-"
Which returns True.
Important: As pointed out in the comments and in the linked documentation, it should be noted that the -Match operator uses regular expressions to perform text matching.
-Contains is actually a collection operator. It is true if the collection contains the object. It is not limited to strings.
-match and -imatch are regular expression string matchers, and set automatic variables to use with captures.
-like, -ilike are SQL-like matchers.
You can use like:
"12-18" -like "*-*"
Or split for contains:
"12-18" -split "" -contains "-"
like is best, or at least easiest.
match is used for regex comparisons.
Reference: About Comparison Operators