Why is this do/until loop exiting with values starting 1? - powershell

Built a menu system which for the most part works fine but I've hit a weird validation error and I'm scratching my head as to why this function is escaping when you answer with "11" (or indeed any number starting with 1)
function Get-MenuSelection {
$totalOptions = 2
do {
$input = Read-Host "[1 <-> $totalOptions | (Q)uit - FIRST]"
while (!$input) {
$input = Read-Host "[1 <-> $totalOptions | (Q)uit - LOOP]"
}
} until ($input -lt $totalOptions -or $input -eq $totalOptions -or $input -eq "q")
Write-Host "exiting"
}
Get-MenuSelection
Output I'm getting:
./wtf.ps1
[1 <-> 2 | (Q)uit - FIRST]:
[1 <-> 2 | (Q)uit - LOOP]:
[1 <-> 2 | (Q)uit - LOOP]: test
[1 <-> 2 | (Q)uit - FIRST]: 22
[1 <-> 2 | (Q)uit - FIRST]: 9090
[1 <-> 2 | (Q)uit - FIRST]: 11
exiting
I'm clearly doing something wrong but just can't figure out what.
Solution
For those reading this some time in the future, I ended up with this - I chose to drop the 'q' options since it was just over-complicating the logic. Thanks to #AdminofThings and #mklement0 for the input. Appreciated.
function Get-MenuSelection {
param (
$output
)
[int]$totalOptions = $output.Count
do {
try { [int]$answer = Read-Host "Options: [1 <-> $totalOptions]" }
catch { }
if ($answer -eq "0" -or $answer -gt $totalOptions) {
Write-Host "Invalid input detected. Ctrl+C to quit."
}
} while ($answer -gt $totalOptions -or !$answer)
$returnedAnswer = "menu_$answer"
return $returnedAnswer
}

Since $input is an automatic/reserved variable, your code will not execute as intended. $input will likely result in an empty value during retrieval.
If we theoretically assume that $input is replaced by something that is not reserved, then a corresponding issue here is $input is a string and $totaloptions is an int. When PowerShell is faced with a comparison operation and both sides of the comparison don't match types, it will attempt to convert the righthand side (RHS) type to match the lefthand side (LHS). To get around this, you need to either cast $input as an [int] or bring $totaloptions to the LHS.
until ([int]$input -lt $totalOptions -or $input -eq $totalOptions -or $input -eq "q")
# OR
until ($totalOptions -gt $input -or $input -eq $totalOptions -or $input -eq "q")
An example of your situation:
#Unexpected Outcome
> [string]11 -lt [int]2
True
#Expected Outcome
> [int]11 -lt [int]2
False
#Expected Outcome
> [int]2 -gt [string]11
False

Related

Operator -gt and -le / Problem with negative numbers

i have the following code snippet where i change the values in a column (named G) of a csv to Y if the integer value is greater then 1 and to N if it is equal to 1 and smaller.
ForEach-Object {if ($_.G -gt '1') {$_.G = 'Y'} if ($_.G -le '1') {$_.G = 'N'} $_}
It works fine with the exception of negative numbers. I always get a Y. I don't have any idea. Example data:
F,G
item1, -58
item2, -77
item3, 562
Does anyone have an idea?
Regards, Hubertus
In order to evaluate the $_.G property as a number you need to specify the type as [int]. Example using your code:
$testObject = New-Object PSObject -Property #{
G='-1'
}
$testObject| %{
if ([int]$_.G -gt 1)
{
$out = "{0} is greater than 1" -f $_.G
Write-Host $out -ForegroundColor Green
[string]$_.G = "Y"
}
elseif ([int]$_.G -le 1)
{
$out = "{0} is Less than 1" -f $_.G
Write-Host $out -ForegroundColor Green
[string]$_.G = "N"
}
}
Note: In order to assign $_.G as a string you have to change the type to [string]. In my opinion, I would use another property to indicate "Y/N" instead of flipping the type back and forth on the property.
The left side of -le or -gt controls the type for both sides, int32 (integer) in this case. You probably want an else in there, to not look at the G again after changing it.
'G
-1
1
2' |
convertfrom-csv |
ForEach-Object {
if (1 -le $_.G)
{$_.G = 'Y'}
else
{$_.G = 'N'}
$_
}
G
-
N
Y
Y

Check if value falls within a specific range powershell

I am having a code which is giving me 2 values like below
$pattern = '\s(-?\d+.?\d+)\s'
$RX_Val = [regex]::Matches($RXTX_Data[0], $pattern).Value
$TX_Val = [regex]::Matches($RXTX_Data[1], $pattern).Value
PS C:\Windows\system32> $RX_Val
-3.4
PS C:\Windows\system32> $TX_Val
-2.3
Need get RX and TX value should fall under range -1 to -7
like the above 2 values falls within the range
if the values are like 1 and -8 respecively, then it should give error
I tried below code, but not getting the proper response
if((($RX_Val -gt -1) -and ($RX_Val -lt -7)))# -and (($TX_Val -gt '-1') -and ($TX_Val -lt '-7')))
{
Write-Host "OK"
}
else
{
Write-Host "NOT OK"
}
also tried
$RX_Val -In -1..-7
please let me know what i am missing here
Tried given solution in below way
$RX_Val = [int][regex]::Matches($RXTX_Data[0], $pattern).Value
$TX_Val = [int][regex]::Matches($RXTX_Data[1], $pattern).Value
if(($RX_Val -in -1..-7) -and ($TX_Val -in -1..-7))
{
Write-Host "OK"
}
else
{
$RXTX_Data | Out-File -FilePath "E:\$file_name" -Force
}
but failed for below scenario as the values are converted to int. it is suppose to print OK
$RX_Val=-0.1
$TX_Val=-1.5
The issue is that the value return by your regex extraction is a STRING. You need to convert it to an INT to be able to do your calculation.
$RX_Val = [int][regex]::Matches($RX, $pattern).Value
$TX_Val = [int][regex]::Matches($TX, $pattern).Value
Sure you can make the logic work from there.
As a bonus, the cast to [int] will also take care of the whitespace left from the regex.

Powershell IF with multiple conditions not working

It's not making any sense.
If I put this lines in my script
it will still accept a number as 123 even if it's supposed to be just between 1 and 32.
It's the line until ($setSubNetMask -cle 32 -and $setSubNetMask -cge 1) I'm having the problem with.
What's going on here?
do
{
$setSubNetMask = 0
$setSubNetMask = Read-Host -Prompt "Subnetmask (CIDR)"
if ($setSubNetMask -cge 32 -or $setSubNetMask -cle 2)
{
write-host "Felaktig CIDR (1-32)"
}
}
until ($setSubNetMask -cle 32 -and $setSubNetMask -cge 1)
Read-Host is giving you a string. You're comparing it to a number, which is being implicitly converted to a string because it's on the right side of the comparison.
What you want to do is convert to a number first:
do
{
$setSubNetMask = 0
[int]$setSubNetMask = Read-Host -Prompt "Subnetmask (CIDR)"
if ($setSubNetMask -cge 32 -or $setSubNetMask -cle 2)
{
write-host "Felaktig CIDR (1-32)"
}
}
until ($setSubNetMask -cle 32 -and $setSubNetMask -cge 1)
PowerShell also supports ranges, so your conditionals might better be expressed as:
do
{
$setSubNetMask = 0
$setSubNetMask = Read-Host -Prompt "Subnetmask (CIDR)"
if (1..32 -notcontains $setSubNetMask)
{
write-host "Felaktig CIDR (1-32)"
}
}
until (1..32 -contains $setSubNetMask)
In PowerShell v3 and higher, you can reverse the order using the -in operator instead of -contains, depending on what feels more natural to you:
do
{
$setSubNetMask = 0
$setSubNetMask = Read-Host -Prompt "Subnetmask (CIDR)"
if ($setSubNetMask -notin 1..32)
{
write-host "Felaktig CIDR (1-32)"
}
}
until ($setSubNetMask -in 1..32)
Note that in these examples, I removed the [int] cast; it's not needed and the conversion will be done implicitly in either case.
As #briantist noted, it's due to the Read-Host returning a string value, and then comparing that to an [int].
When comparing dissimilar object types, Powershell will attempt to do the perform the operation by trying to cast the value on the RH side to match the type on the LH side. This means you can implicitly re-cast the strings to [int] by reversing the order of your comparison arguments so that the [int] is on the LH side:
do
{
$setSubNetMask = 0
$setSubNetMask = Read-Host -Prompt "Subnetmask (CIDR)"
if (32 -le $setSubNetMask -or 2 -gt $setSubNetMask)
{
write-host "Felaktig CIDR (1-32)"
}
}
until (32 -le $setSubNetMask -and 1 -le $setSubNetMask)

Powershell function argument default: Weirdness when it has a type constraint

If I have a function parameter WITHOUT the type constraint:
> function a ($s=$null) {if ($s -eq $null) {Write-Host "HI"} if ($s -eq "") {Write-Host "KK"}}
> a
HI
Now if I add the type constraint to it, the $null is interpreted differently:
> function a ([string]$s=$null) {if ($s -eq $null) {Write-Host "HI"} if ($s -eq "") {Write-Host "KK"}}
> a
KK
I can't find doc that explain this. It's also not consistent.
In your first example (function a), $s is equivalent to $null - it's truly null.
In your second example (function b), because you're casting $s to a [string] object, it's actually an empty String (equivalent to [String]::Empty), not $null.
You can check this by adding the following to each of your functions:
if($s -eq [String]::Empty){"empty!"};
Only b will print empty! - a will evaluate this to $false
Alternately, add this:
$s|get-member
a will actually throw an error - the same error you'll get if you run $null|get-member. b will show you that $s is a string and list all of the members of that class.

The PowerShell -and conditional operator

Either I do not understand the documentation on MSDN or the documentation is incorrect.
if($user_sam -ne "" -and $user_case -ne "")
{
Write-Host "Waaay! Both vars have values!"
}
else
{
Write-Host "One or both of the vars are empty!"
}
I hope you understand what I am attempting to output. I want to populate $user_sam and $user_case in order to access the first statement!
You can simplify it to
if ($user_sam -and $user_case) {
...
}
because empty strings coerce to $false (and so does $null, for that matter).
Another option:
if( ![string]::IsNullOrEmpty($user_sam) -and ![string]::IsNullOrEmpty($user_case) )
{
...
}
Try like this:
if($user_sam -ne $NULL -and $user_case -ne $NULL)
Empty variables are $null and then different from "" ([string]::empty).
The code that you have shown will do what you want iff those properties equal "" when they are not filled in. If they equal $null when not filled in for example, then they will not equal "". Here is an example to prove the point that what you have will work for "":
$foo = 1
$bar = 1
$foo -eq 1 -and $bar -eq 1
True
$foo -eq 1 -and $bar -eq 2
False