Powershell Read-Host Not adding or multiplying correctly? - powershell

What exactly am I doing wrong here, it seems to be subtracting just fine but adding and multiplying seems to not work at all.
How do I get it to do the calculations correct and allow the if statement to also work as it seems to always run even if the numbers are incorrect size.
$a = Read-Host "What is your name?"
$b = Read-Host "Enter a 2 digit number"
$c = Read-Host "Enter a 3 digit number"
if (($b -ge 10) -and ($b -le 99) -and ($c -ge 100) -and ($c -le 999)){
$d = $b + $c
$e = $b * $c
$g = $b - $c
$d
$e
$g
Write-host "Here you go $a"
}
else {
write-host "Enter the numbers correctly"
}
Here the results I get

Read-Host always outputs a string.
In order to treat the output as a number, you must explicitly convert it to one:
$a = Read-Host "What is your name?"
# Note: Add error handling with try / catch
# and a retry loop to deal with invalid input.
[int] $b = Read-Host "Enter a 2 digit number"
[int] $c = Read-Host "Enter a 3 digit number"
The above type-constrains variables $b and $c to integer values (by placing the [int] cast to the left of the target variable in the assignment), which automatically converts Read-Host's [string] output to [int].
To spell it out with a concrete example that prompts until a two-digit (decimal) number is entered:
do {
try {
[int] $b = Read-Host "Enter a 2 digit number"
} catch {
continue # Not a number - stay in the loop to prompt again.
}
if ($b -ge 10 -and $b -le 99) { break } # OK, exit the loop.
} while ($true)
Note: Strictly speaking, the [int] cast accepts anything that would work as a number literal in PowerShell, which includes hexadecimal representations, such as 0xA, as well as number with a type suffix, such as 10l - see this answer for more information.
As for what you tried:
Except for -, all the operators used in your code have string-specific overloads (meaning); note that it is sufficient for the LHS to be of type [string] to trigger this behavior.[1]
-lt / -ge perform lexical comparison with strings; e.g., '10' -gt '2' yields $false, because, in lexical sorting, string '10' comes before string '2'.
-and / -or treat empty strings as $false, and any nonempty string as $true; e.g., '0' -and '0' is $true, because '0' is a nonempty string.
+ performs string concatenation; e.g., '1' + '0' is '10'.
* performs string replication; e.g., '1' * 3 is '111' - the LHS is repeated as many times as specified by the number on the RHS; note that '1' * '3' works the same, because the RHS is coerced to an [int] in this case.
- is the only exception: it always performs a numeric operation, if possible; e.g, '10' - '2' yields 8, because both operands were implicitly converted to [int]s.
[1] Typically, it is the LHS of an operation that determines its data type, causing the RHS to be coerced to a matching type, if necessary.

Related

Verifying a string is not empty/null in an If condition

I'm writing a script that will accept user input via Read-Host (set to $String), and I want to avoid any issues that could be caused by having a blank value for the variables. Since I'll be using this a lot, I want to implement it into a function that verifies no invalid characters are being used.
I thought I could use an if statement with ![string]::IsNullOrEmpty($String) as one of the conditions:
Function Test-ValidCharacters ($String, $ValidCharacters) {
if (($String -match $ValidCharacters) -and (!([string]::IsNullOrEmpty($String)))) {
return $true
}
else {return $false}
}
I also tried this:
Function Test-ValidCharacters ($String, $ValidCharacters) {
if (($String -match $ValidCharacters) -and ($String -ceq "")) {
return $true
}
else {return $false}
}
In both of these cases, I can just hit enter when presented with the $String's Read-Host prompt and the script will behave as if the function returned $True (and then later encounter fatal errors). The other half works - if I include characters not specified by $ValidCharacters the function returns $False as expected.
I am sure I'm missing something here. I even tried doing a second nested if statement and got the same result.
Edit: Here's the code snippet where I call the function and notice the issue.
$ValidCharacters = '[^a-zA-Z0-9]'
$FirstN = Read-Host -Prompt "New user's first name"
While (Test-ValidCharacters $FirstN $ValidCharacters -eq $false) {
Write-Output "$FirstN contains illegal characters. A-Z, a-z, and 0-9 are accepted."
$FirstN = Read-Host -Prompt "New user's first name"
}
Assuming $ValidCharacters isn't itself an empty string and contains an anchored character-range regex (regular expression) that covers the entire input string, such as ^[a-z0-9./:]+$, given that the -match operator matches any substring by default (note that a better name for the parameter is therefore something like $ValidationRegex):[1]
In the first function definition, the RHS of your -and operation is redundant - it adds nothing to the conditional, because if $String -match $ValidCharacters is $true, then so is ! [string]::IsNullOrEmpty($String), by definition.
Conversely, in the second function definition your -and operation always returns $false, because $String -ceq "" is by definition $false, if the LHS returned $true.
Assuming that your intent is to prevent empty or all-whitespace input and to ensure that any string - trimmed of incidental leading and/or trailing whitespace - is composed only of expected characters, use the following:
Function Test-ValidCharacters ($String, $ValidCharacters) {
# Note: no strict need for `return`.
$String.Trim() -match $ValidCharacters
}
[1] Alternatively, stick with $ValidCharacters and pass a regex that describes only a single valid character, such as '[a-z0-9./:]', and construct the entire-string matching regex inside the function with '^' + $ValidCharacters + '+$'

validate for a 2-digit number

I need to in powershell
-Ask the user for a 2-digit number
-Validate this number is two numeric digits
-Take into account leading zeroes
-If the number is invalid, have the user try again
It seemed that using
$2digit = read-host
$2digit -match "[0-9][0-9]"
was working but it stopped out of nowhere. Any advice?
You are probably getting an a false result when you entered more than 2 characters.
eg:
This is because you have not specified length.
Resolution:
$2digit = read-host
($2digit.Length -le 2) -and ($2digit -match "[0-9][0-9]")
You can also change your regex pattern
$2digits -match "^[0-9][0-9]$"
^ - start of string or line
$ - end of string or line
I was able to figure it out
do{
do {
write-host -nonewline "Enter the two digit number: "
$2d = read-host
$value = $2d -as [Double]
$ok = $value -ne $NULL
if ( -not $ok ) { write-host "!!ERROR:You must enter numeric values!!" }
}
until ( $ok )
if ($2d.Length -eq 2){$holder = $true}
elseif($2d.Length -ne 2){Write-host "!!ERROR:The number must be 2 digits!!"}
}
while ( $holder -ne $true )
The first do loop will verify that the input is numeric and the second do loop will check it to be 2 numbers.
Regular expressions are your friend. This will only accept digits 0-9 with a length of two. Anything else will not be accepted.
do {
$input = Read-Host -Prompt 'Enter the two digit number(0-9)'
if ( $input -match '^\d{2}$' ) {
break
}
Write-Host "The value must be a two digit number; $input is invalid"
} while ( $true )

Why is pressing "Enter" equivalent to the number 0?

I don't understand why pressing enter key is the same as pressing 0 on the keyboard.
[int] $Choice = -1
$Count = 2
while ($Choice -lt 0 -or $Choice -gt $Count)
{
Write-Host "Input number"
$Choice = Read-Host
Write-Host "choice:"
Write-Host $Choice
}
The output will be 0 even if just press enter. I want the user to explicitly input 0.
In the very first statement:
[int] $Choice = -1
... you type-cast $choice to [int].
When you apply a cast to the left-hand side of an assignment (to the left of the variable name), PowerShell will "remember" it as a type-constraint and treat the variable as strongly typed - PowerShell will attempt to convert anything you assign to $choice from there on out to an [int].
Hitting enter in the prompt without any other input results in Read-Host returning an empty string (like "")
The conversion logic treats the empty string as $null, and casting $null to [int] results in the value 0. You can see this by casting an empty string to [int] directly:
PS C:\> [int]""
0
You should probably validate the input from Read-Host before assigning to $Choice if you explicitly require a number:
$inputString = Read-Host
if($inputString -notmatch '^\d+$') {
Write-Host "Digits only please!"
continue
}

Powershell: Limit input to a range of numeric values (int or decimal)

I have seen this here for different scripting languages, however I have my problem in Powershell and as follows:
I want to limit the input of a numeric value, which can either be a decimal or an integer one, to a specific range.
I tried the following:
Do { $value = Read-host "Specify a value between 23.976 and 60"}
while ((23.976..60) -notcontains $value )
However it seems that like this it doesn't accept any decimal values at all but only integers. For instance, when I try to enter 29.97 or 29.970 it stays in the loop.
How can I do this right?
PowerShell's range operator - .. - generates an array of discrete values from the range endpoints, which can have the following types (in a given expression, both endpoints must have the same type):
[int] (System.Int32)
e.g., 1..3 creates array 1, 2, 3
Note: While the minimum and maximum endpoint values allowed are [int]::MinValue and [int]::MaxValue, respectively, the resulting array must additionally not have more than 2,146,435,071 elements (2+ billion), which is the max. element count for a single-dimensional [int] array in .NET - see this answer for background information.
Alternatively, only in PowerShell [Core, 6+]: characters, which are enumerated between the endpoints by their underlying code points ("ASCII values").
e.g., 'a'..'c' creates array [char] 'a', [char] 'b', [char] 'c'
Instances of numeric types other than [int] are quietly coerced to the latter - if they can fit into the [int] range - in which case half-to-even midpoint rounding is performed for non-integral types; e.g., implicit [double] instances 23.5 and 24.5 are both coerced to 24.
Because [int] 23.976 is 24, your 23.976..60 expression creates array 24, 25, 26, ..., 60 which is not your intent.
In short: You cannot use .. to describe an uncountable range of non-integers (fractional numbers).
Instead, use -ge (greater than or equal) and -le (less than or equal) to test against the endpoints:
-not ($value -ge 23.976 -and $value -le 60)
Additionally, in order to make the -ge and -le operations work as intended, convert the return value from Read-Host, which is always a string, to a number.
If you were to use $value as directly returned by Read-Host, you'd get lexical (text sort order-based) comparison, not numeric comparison.
Therefore, cast the Read-Host return value to [double]:
$value = try { [double] (Read-Host 'Specify a value between 23.976 and 60') }
catch {}
Note: The try / catch handles the case when the user enters text that cannot be interpreted as a [double] representation.
To put it all together:
do {
$value = try { [double] (Read-Host 'Specify a value between 23.976 and 60') }
catch {}
} while (-not ($value -ge 23.976 -and $value -le 60))
OK guys, this is how it works ;)
Do { $value = Read-host "Specify a value between 23.976 and 60"}
while (( $value -gt 60 ) -or ( $value -lt 23.976 ))

In PowerShell, how can I test if a variable holds a numeric value?

In PowerShell, how can I test if a variable holds a numeric value?
Currently, I'm trying to do it like this, but it always seems to return false.
add-type -Language CSharpVersion3 #'
public class Helpers {
public static bool IsNumeric(object o) {
return o is byte || o is short || o is int || o is long
|| o is sbyte || o is ushort || o is uint || o is ulong
|| o is float || o is double || o is decimal
;
}
}
'#
filter isNumeric($InputObject) {
[Helpers]::IsNumeric($InputObject)
}
PS> 1 | isNumeric
False
You can check whether the variable is a number like this: $val -is [int]
This will work for numeric values, but not if the number is wrapped in quotes:
1 -is [int]
True
"1" -is [int]
False
If you are testing a string for a numeric value then you can use the a regular expression and the -match comparison. Otherwise Christian's answer is a good solution for type checking.
function Is-Numeric ($Value) {
return $Value -match "^[\d\.]+$"
}
Is-Numeric 1.23
True
Is-Numeric 123
True
Is-Numeric ""
False
Is-Numeric "asdf123"
False
Modify your filter like this:
filter isNumeric {
[Helpers]::IsNumeric($_)
}
function uses the $input variable to contain pipeline information whereas the filter uses the special variable $_ that contains the current pipeline object.
Edit:
For a powershell syntax way you can use just a filter (w/o add-type):
filter isNumeric($x) {
return $x -is [byte] -or $x -is [int16] -or $x -is [int32] -or $x -is [int64] `
-or $x -is [sbyte] -or $x -is [uint16] -or $x -is [uint32] -or $x -is [uint64] `
-or $x -is [float] -or $x -is [double] -or $x -is [decimal]
}
You can do something like :
$testvar -match '^[0-9]+$'
or
$testvar -match '^\d+$'
Returns True if $testvar is a number.
If you want to check if a string has a numeric value, use this code:
$a = "44.4"
$b = "ad"
$rtn = ""
[double]::TryParse($a,[ref]$rtn)
[double]::TryParse($b,[ref]$rtn)
Credits go here
PS> Add-Type -Assembly Microsoft.VisualBasic
PS> [Microsoft.VisualBasic.Information]::IsNumeric(1.5)
True
http://msdn.microsoft.com/en-us/library/microsoft.visualbasic.information.isnumeric.aspx
-is and -as operators requires a type you can compare against. If you're not sure what the type might be, try to evaluate the content (partial type list):
(Invoke-Expression '1.5').GetType().Name -match 'byte|short|int32|long|sbyte|ushort|uint32|ulong|float|double|decimal'
Good or bad, it can work against hex values as well (Invoke-Expression '0xA' ...)
filter isNumeric {
$_ -is [ValueType]
}
-
1 -is [ValueType]
True
"1" -is [ValueType]
False
-
function isNumeric ($Value) {
return $Value -is [ValueType]
}
isNumeric 1.23
True
isNumeric 123
True
isNumeric ""
False
isNumeric "asdf123"
False
-
(Invoke-Expression '1.5') -is [ValueType]
$itisint=$true
try{
[int]$vartotest
}catch{
"error converting to int"
$itisint=$false
}
this is more universal, because this way you can test also strings (read from a file for example) if they represent number. The other solutions using -is [int] result in false if you would have "123" as string in a variable. This also works on machines with older powershell then 5.1
If you know the numeric type you want to test against (such as int for example in the code below), you can do it like this:
> [bool]("42" -as [int])
True
> [bool](42 -as [int])
True
> [bool]("hi" -as [int])
False
But note:
> [bool](42.1 -as [int])
True
Careful!:
It was pointed out that the code above fails to identify 0 as an int. You would need to add a guard for 0:
> $n -eq 0 -or $n -as [int]
Where $n is the object you are testing.
Thank you all who contributed to this thread and helped me figure out how to test for numeric values. I wanted to post my results for how to handle negative numbers, for those who may also find this thread when searching...
Note: My function requires a string to be passed, due to using Trim().
function IsNumeric($value) {
# This function will test if a string value is numeric
#
# Parameters::
#
# $value - String to test
#
return ($($value.Trim()) -match "^[-]?[0-9.]+$")
}
I ran into this topic while working on input validation with read-host. If I tried to specify the data type for the variable as part of the read-host command and the user entered something other than that data type then read-host would error out. This is how I got around that and ensured that the user enters the data type I wanted:
do
{
try
{
[int]$thing = read-host -prompt "Enter a number or else"
$GotANumber = $true
}
catch
{
$GotANumber = $false
}
}
until
($gotanumber)
"-123.456e-789" -match "^\-?(\d+\.?\d*)(e\-?\d+)?$|^0x[0-9a-f]+$"
or
"0xab789" -match "^\-?(\d+\.?\d*)(e\-?\d+)?$|^0x[0-9a-f]+$"
will check for numbers (integers, floats and hex).
Please note that this does not cover the case of commas/dots being used as separators for thousands.
Each numeric type has its own value. See TypeCode enum definition:
https://learn.microsoft.com/en-us/dotnet/api/system.typecode?view=netframework-4.8
Based on this info, all your numeric type-values are in the range from 5 to 15.
This means, you can write the condition-check like this:
$typeValue = $x.getTypeCode().value__
if ($typeValue -ge 5 -and $typeValue -le 15) {"x has a numeric type!"}
Testing if a value is numeric or a string representation of a numeric value.
function Test-Number
{
Param
(
[Parameter(Mandatory=$true,
Position=0)]
[ValidatePattern("^[\d\.]+$")]
$Number
)
$Number -is [ValueType] -or [Double]::TryParse($Number,[ref]$null)
}
Testing if a value is numeric.
function Test-Number
{
Param
(
[Parameter(Mandatory=$true,
Position=0)]
[ValidatePattern("^[\d\.]+$")]
$Number
)
$Number -is [ValueType]
}