Multiple Variable Conditions for If Statement in PowerShell - powershell

Thanks for the help in this, I think i have over complicated the below but the logic just isn't responding how my mind is telling it too.
Logic in Question:
$a = "One"
$b = "Two"
$c = "Three"
$d = "Four"
If( {$a -and $b} -ne {$c and $d} ) {
Write-Host "Values are Different"
} Else {
Write-Host "values are the same"
}
I want the If statement to run when $a and $b are different to $c and $d, If the are the same see below, I want it to output that the values are the same
$a = "One"
$b = "One"
$c = "One"
$d = "One"
Thanks in advance!

You can use Compare-Object to compare the value pairs as arrays:
if (Compare-Object $a, $b $c, $d -SyncWindow 0) {
'different'
} else {
'same'
}
Note that this is convenient, but relatively slow, which may matter in a loop with many iterations.
The Compare-Object cmdlet compares two arrays and by default returns information about their differences.
-SyncWindow 0 compares only directly corresponding array elements; in other words: $a must equal $c, and $b must equal $d; without -SyncWindow, the array elements would be compared in any order so that 1, 2 would be considered equal to 2, 1 for instance.
Using the Compare-Object call's result as a conditional implicitly coerces the result to a Boolean, and any nonempty result - indicating the presence of at least 1 difference - will evaluate to $True.
As for what you tried:
Use of { ... } in your conditional is not appropriate.
Expressions enclosed in { ... } are script blocks - pieces of code you can execute later, such as with & or .
Even if you used (...) instead to clarify operator precedence (-ne has higher precedence than -and), your conditional wouldn't work as expected, however:
($a -and $b) -ne ($c -and $d) treats all variables as Booleans; in effect, given PowerShell's implicit to-Boolean conversion, you're comparing whether one value pair has at least one empty string to whether the other doesn't.

In addition to the answer from mklement0 and avoiding the rather slow Compare-Object cmdlet:
In what you tried, you will need to compare one specific value with each of the rest of the vales:
($a -eq $b) -and ($a -eq $c) -and ($a -eq $d)
Because the Comparison Operators (-eq) take a higher precedence than the Logical Operators (-and), you can leave the brackets and simplify it to:
$a -eq $b -and $a -eq $c -and $a -eq $d
To make this code DRY and easily expandable for even more values:
if ($a, $b, $c | Where {$_ -ne $d}) {
'different'
} else {
'same'
}

Just remove these {} brackets from the if statement
$a = "One"
$b = "One"
$c = "One"
$d = "One"
If($a -and $b -ne $c -and $d) {
Write-Host "Values are Different"
} Else {
Write-Host "values are the same"
}

Related

Finding Middle Number in PowerShell

I am practicing in PowerShell and am making a user response input where one of the options is to input 3 numbers, and the program will return the middle number. I have done this a million times and it seems I cannot get it to return the middle number consistently.
For example when my numbers are 1, 23452342 and 3, it says that 3 is the middle number.
Here is my code:
if ($response -eq 1) {
$a = Read-Host "Enter a number "
$b = Read-Host "Enter a second number "
$c = Read-Host "Enter a third number "
if (($a -gt $b -and $a -lt $c) -or ($a -lt $b -and $a -gt $c)) {
Write-Host "$a is the middle number"
}
if (($b -gt $a -and $b -lt $c) -or ($b -gt $c -and $b -lt $a)) {
Write-Host "$b is the middle number"
}
if (($c -gt $a -and $c -lt $b) -or ($c -gt $b -and $c -lt $a)) {
Write-Host "$c is the middle number"
}
}
Instead of doing a number of individual comparisons simply sorting the three values and picking the second element will give you the median right away. But I suspect what's actually messing up the results for you is that Read-Host returns strings when you need them to be numeric values. Sort order of strings ("1" < "20" < "3") is different from numeric sort order (1 < 3 < 20), because characters at corresponding positions are compared rather than the whole number.
Casting the entered values to integers (or doubles if you expect floating point numbers) should resolve the issue:
if ($response -eq 1) {
[int]$a = Read-Host 'Enter a number'
[int]$b = Read-Host 'Enter a second number'
[int]$c = Read-Host 'Enter a third number'
$n = ($a, $b, $c | Sort-Object)[1]
Write-Host "$n is the median."
}
As an additional solution that would work on any array where u need the middle item you could just solve it like this:
$arr = 1..50
($arr | Sort-Object)[[int](($arr.count -1) /2)]
If your array comes in a format that does not need the sorting, just leave this part out.
edit: Obviously you would have to insert the data into an array on the first step.
Best regards

reusing variables in foreach loop

Realizing it probably it may not be best practices, is there any other reason to not reuse the current item variable in a foreach as such:
foreach ($a in $something)
{Write-host $a}
.
.
.
foreach ($a in $somethingelse)
{Write-host $a}
In other words, is it always going to be best to in that second case use a different variable ($b?)
As briantist mentions this is fine. However when I do a foreach loop I tend to keep it specified to whatever you're taking it from. I find this will make it easier to comprehend further down the line.
#Example 1
foreach ($item in $array)
{Write-Host $item}
#Example 2
foreach ($user in $userList)
{Write-Host $user}
#Example 3
foreach ($breed in $dogBreed)
{Write-Host $breed}
There's nothing wrong with that. The variable $a will always be set to the current item inside the foreach loop's body.
One reason why this isn't a idea is that variable $a retains it's value (within the assigned scope) until it is reassigned. That may not sound like a big issue, but occasionally PowerShell does something unexpected and it just isn't worth the time troubleshooting to figure something like that out. Consider this function:
function reuse-vars {
$arrayA = 0..3
$arrayB = 10..13
$arrayC = 20..23
foreach ( $a in $arrayA ){
Write-Host "`$a is $a"
Write-Host "`$b is $b"
Write-Host "`$c is $c"
}
foreach ( $b in $arrayB ){
Write-Host "`$a is $a"
Write-Host "`$b is $b"
Write-Host "`$c is $c"
}
foreach ( $c in $arrayC ){
Write-Host "`$a is $a"
Write-Host "`$b is $b"
Write-Host "`$c is $c"
}
}
$a will retain the value from the last foreach iteration until it is reassigned, that includes within nested foreach or if they are in series.
C:\> reuse-vars
$a is 0
$b is
$c is
$a is 1
$b is
$c is
$a is 2
$b is
$c is
$a is 3
$b is
$c is
$a is 3
$b is 10
$c is
$a is 3
$b is 11
$c is
$a is 3
$b is 12
$c is
$a is 3
$b is 13
$c is
$a is 3
$b is 13
$c is 20
$a is 3
$b is 13
$c is 21
$a is 3
$b is 13
$c is 22
$a is 3
$b is 13
$c is 23
It may not matter depending on what you're doing but the possibility for this to cause an issue makes it worthwhile to dream up a different variable name for each foreach (like $b).
I agree with the others that this should work just fine, but that using different names is better practice.
However, if you're paranoid you can always clear or delete the variable manually between the loops:
foreach ($a in $something)
{Write-host $a}
.
$a = $null;
.
foreach ($a in $somethingelse)
{Write-host $a}
Or:
foreach ($a in $something)
{Write-host $a}
.
Remove-Variable -Name a;
.
foreach ($a in $somethingelse)
{Write-host $a}

How to match three or more items

Can't seem to get my head around this. I want to compare three (or more) items - example below - how can I get it to state true? If I do two items it works fine
$a = 2
$b = 2
$c = 2
$a -match $b -match $c
False
Looking at $Matches it only contains two items. I tried brackets around $a and $b but still get the same thing - it keeps on only looking at the first two and ignores the third.
PS C:\Windows\system32> $Matches
Name Value
---- -----
0 2
Your snippet is not working as expected because :
comparing two variables in the first place, gives you true or false,
that means 1 or 0. So, if you'll compare 1 or 0 with 2, it will obviously give false.
In simple terms :
$a -match $b -match $c equates to :
$a -match $b
true
true -match $c
false
So, as Martin has answered, you need to do it this way if you need it for regular expression comparison:
$a -match $b -and $a -match $c
true
But since you are comparing values so you need to use, -eq .
$a -match $b -match $c
actually results in the following:
[bool] $r1 = $a -match $b
[string] $r1 -match $c
Which is probably not what you want. In fact, I'm quite unsure what you actually want. The -match operator performs a regex match of the left operand. Do you perhaps mean something like
$a -eq $b -and $b -eq $c
?
You need to combine your checks using -and. Also use -eq (equals) here:
$a -eq $b -and $a -eq $c

Check if a string contains any substring in an array in PowerShell

I am studying PowerShell. I want to know how to check if a string contains any substring in an array in PowerShell. I know how to do the same in Python. The code is given below:
any(substring in string for substring in substring_list)
Is there similar code available in PowerShell?
My PowerShell code is given below.
$a = #('one', 'two', 'three')
$s = "one is first"
I want to validate $s with $a. If any string in $a is present in $s then return True. Is it possible in PowerShell?
Using the actual variables in the question for simplicity:
$a = #('one', 'two', 'three')
$s = "one is first"
$null -ne ($a | ? { $s -match $_ }) # Returns $true
Modifying $s to not include anything in $a:
$s = "something else entirely"
$null -ne ($a | ? { $s -match $_ }) # Returns $false
(That's about 25% fewer characters than chingNotCHing's answer, using the same variable names of course :-)
($substring_list | %{$string.contains($_)}) -contains $true
should strictly follow your one-liner
For PowerShell ver. 5.0+
Instead of,
$null -ne ($a | ? { $s -match $_ })
try this simpler version:
$q = "Sun"
$p = "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"
[bool]($p -match $q)
This returns $True if substring $q is in the array of string $p.
Another Example:
if ($p -match $q) {
Write-Host "Match on Sun !"
}
Michael Sorens' code answer works best to avoid the pitfall of partial substrings matching. It just needs a slight regex modification. If you have the string $s = "oner is first", the code would still return true since 'one' would match 'oner' (a match in PowerShell means the second string contains the first string.
$a = #('one', 'two', 'three')
$s = "oner is first"
$null -ne ($a | ? { $s -match $_ }) # Returns $true
Add some regex for word boundary '\b' and the r on 'oner' will now return false:
$null -ne ($a | ? { $s -match "\b$($_)\b" }) # Returns $false
(I know it's an older thread but at least I might help people looking at this in the future.)
Any response given that uses -match will produce incorrect answers.
Example: $a -match $b will produce false negatives if $b is "."
A better answer would be to use .Contains - but it's case sensitive so you'd have to set all strings to upper or lower case before comparing:
$a = #('one', 'two', 'three')
$s = "one is first"
$a | ForEach-Object {If ($s.toLower().Contains($_.toLower())) {$True}}
Returns $True
$a = #('one', 'two', 'three')
$s = "x is first"
$a | ForEach-Object {If ($s.toLower().Contains($_.toLower())) {$True}}
Returns nothing
You could tweak it to return $True or $False if you'd want, but IMO the above is easier.
I'm amazed that in 6 years nobody has given this more simple and readable answer
$a = #("one","two","three")
$s = "one1 is first"
($s -match ($a -join '|')) #return True
So simply implode the array into a string using vertical bar "|" , as this is the alternation (the "OR" operator) in regex.
https://www.regular-expressions.info/alternation.html
https://blog.robertelder.org/regular-expression-alternation/
Also keep in mind that the accepted answer will not search for exact match. If you want exact match you can use the \b (word boundary)
https://www.regular-expressions.info/wordboundaries.html
$a = #("one","two","three")
$s = "one1 is first"
($s -match '\b('+($a -join '|')+')\b') #return False
It is possible to select a subset of strings containing any of the strings like this:
$array = #("a", "b")
$source = #("aqw", "brt", "cow")
$source | where {
$found = $FALSE
foreach($arr in $array){
if($_.Contains($arr)){
$found = $TRUE
}
if($found -eq $TRUE){
break
}
}
$found
}
One way to do this:
$array = #("test", "one")
$str = "oneortwo"
$array|foreach {
if ($str -match $_) {
echo "$_ is a substring of $str"
}
}

powershell comparing multiple arrays

I've a case where multiple arrays need to be compared.There is one master array that contains all the elements that the child arrays have and also some extra elements.
In below example, $a is the master array and $b, $c are child arrays.
I need to compare these arrays and get the list of those extra elements in $a that are not present in $b and $c.
Practically, in my case there are 10 child arrays and a master array.
$a="dhaw","roh","kohl","faf","abd","steyn","gupt","baz","kane","benn","brendn","joyc"
$b="roh","dhaw","kohl"
$c="steyn","abd","faf","roh","dhaw"
A viable solution could be using -notcontains operator as suggested by arco444, cycle through $a array elements and check if they are contained at least in one of the other arrays.
Here is a slice of code
foreach($a_value in $a) {
if (($b -notcontains $a_value) -and ($c -notcontains $a_value)) {
"$a_value is extra"
}
}
Something like this?
Compare-Object -ReferenceObject $a -DifferenceObject ($b + $c)
If you just want to get raw objects:
(Compare-Object -ReferenceObject $a -DifferenceObject ($b + $c) |
Where-Object {$_.SideIndicator -eq '<='}).InputObject
A regex solution:
$a="dhaw","roh","kohl","faf","abd","steyn","gupt","baz","kane","benn","brendn","joyc"
$b="roh","dhaw","kohl"
$c="steyn","abd","faf","roh","dhaw"
$b_regex = ‘(?i)^(‘ + (($b |foreach {[regex]::escape($_)}) –join “|”) + ‘)$’
$c_regex = ‘(?i)^(‘ + (($c |foreach {[regex]::escape($_)}) –join “|”) + ‘)$’
Then, for elements of $a that aren't in $b:
$a -notmatch $b_regex
faf
abd
steyn
gupt
baz
kane
benn
brendn
joyc
For elements of $a that aren't in $c:
$a -notmatch $c_regex
kohl
gupt
baz
kane
benn
brendn
joyc
And for elements of $a that aren't in $b or $c:
$a -notmatch $b_regex -notmatch $c_regex
gupt
baz
kane
benn
brendn
joyc
Runtime Regex
Note: this is just provided for demonstration for the people who left comments about it. This substantially faster than the -contains / -notcontains solutions, but for a single instance comparison it's probably overkill. It can produce substantial performance gains inside a loop where you're comparing one array to many other arrays.