1

Is there a way to get Read-Host to recognize PowerShell multipliers like KB, MB,GB,TB, and PB? The first example works just fine if I populate the $freespace variable by hand but does not work in the second example using Read-Host to populate the $freespace variable. The has to be a simple solution to get prompt for user input and have the value recognized as an integer rather than a string.

#this works just fine. The PowerShell multiplier GB is recognized if typed in.

$freespace = 6GB

If ($freeSpace -le 5GB) {
    Write-Host “Free disk space is less than 5 GB”
} ElseIf ($freeSpace -le 10GB) {
    Write-Host “Free disk space is less than 10 GB”
} ElseIf ($freeSpace -le 20GB) {
    Write-Host “Free disk space is less than 20 GB”
} Else {
    Write-Host “Free disk space is more than 20 GB”
}

#This does not work. The variable is populated by Read-Host as a [string] 

$freespace = Read-Host -Prompt 'Please enter a value for Freespace'

If ($freeSpace -le 5GB) {
    Write-Host “Free disk space is less than 5 GB”
} ElseIf ($freeSpace -le 10GB) {
    Write-Host “Free disk space is less than 10 GB”
} ElseIf ($freeSpace -le 20GB) {
    Write-Host “Free disk space is less than 20 GB”
} Else {
    Write-Host “Free disk space is more than 20 GB”
}
  • `$freespace = Invoke-Expression $freespace`. Using *iex* here as I'm not sure the *invocation operator* would work (`&`) in this scenario (*not at my desk to test*). You're passing it a string and it will be read *as is*. You have to allow the expression to be executed. – Abraham Zinala Nov 18 '21 at 20:46
  • 1
    @AbrahamZinala - That would work but it would also execute `remove-item c:\* -rec -force -ea continue`. Depending on how the script is presented to users, this could be security risk. – Lieven Keersmaekers Nov 18 '21 at 20:47
  • @Lieven, not sure how you could enter an entire cmdlet based off a prompt that reads *"Please enter a value for Freespace"*. Would be no different than just saying it would be dangerous to execute `Remove-Item c:\ -Recurse` in the shell itself... but, I see your point. We can work around it by parsing the data and evaluating what was passed to the variable. What would you recommend? – Abraham Zinala Nov 18 '21 at 20:55
  • The usual workaround is to cast to a numeric value. A safe way to do that is to use the `-as` operator (see my answer). – Bill_Stewart Nov 18 '21 at 20:59
  • @AbrahamZinala - I should have focused more on the *depending how the script is presented to users* . If the script is running with alternate credentials, it could allow a user to execute commands he otherwise would not have the rights to. – Lieven Keersmaekers Nov 18 '21 at 21:17
  • The answer you accepted may contain the best solution for your particular use case, but it doesn't match _what you asked_, which is, "Is there a way to get Read-Host to recognize PowerShell multipliers like KB, MB,GB,TB, and PB?" For the benefit of future readers it is better to accept answers based on whether they address the question _as asked_. – mklement0 Dec 07 '21 at 19:55

4 Answers4

1

When you do this:

$freespace = Read-Host -Prompt 'Please enter a value for Freespace'

The value stored in the $freespace variable will be of type [String], whereas you probably want [UInt64] (positive integer). You can convert the [String] type to the [UInt64] type using code like the following:

do {
  $entry = Read-Host "Please enter a value for freespace, in GB"
  $freespace = $entry -as [UInt64]
  $ok = ($null -ne $freespace) -and ($freespace -lt 1024)
  if ( -not $ok ) {
    Write-Host "Please enter a value in the range 0 to 1023"
  }
until ( $ok )
$freespace *= 1GB

First, you prompt for a [String] and store it in $entry. Next, we cast the string value to an [UInt64] value using the -as operator. If the conversion fails, the $freespace variable will contain $null. The do loop will repeat until a numeric value is entered.

With the above code in your script, $freespace will be a numeric value after the do loop completes. The final line converts it to GB.

Bill_Stewart
  • 22,916
  • 4
  • 51
  • 62
0

your read host is a string not an int try the below

[int]$freespace = Read-Host -Prompt 'Please enter a value for Freespace'

John Boyd
  • 51
  • 2
  • 1
    This will work but only if you enter a numeric value. If you enter a non-numeric value, it will throw an error (which is probably not what you want). I would recommend casting using `-as` (see my answer). – Bill_Stewart Nov 18 '21 at 20:58
  • Also note that the OP's desire is to recognize strings such as `'6GB'` as the equivalent number (`6442450944`), which in _Windows PowerShell_ doesn't work with a cast / type constraint such as `[long] '6GB'`; it does work in _PowerShell (Core) 7+_, however. – mklement0 Nov 18 '21 at 22:03
0

To answer the original question:

Is there a way to get Read-Host to recognize PowerShell multipliers like KB, MB, GB, TB, and PB?

Windows PowerShell recognizes the binary multiplier suffixes - kb, mb, gb, tb, pb - in number literals only, not also when (implicitly) converting from a string, the latter being what the Read-Host cmdlet invariably returns.

The only context where the string form is recognized is an operator-based expression in which a string operand is implicitly converted to a number, which enables a fairly simple and safe workaround.[1]

# 0 + forces the RHS string to be converted to a number, which
# in this particular context *does* work with binary multiplier suffixes 
PS> 0 + '1kb'
1024

This workaround also works in PowerShell (Core) 7+, but there you could simply use
[long] '1kb'
, because such strings are now consistently supported during implicit to-number conversion.

Applied to your code:

while ($true) {
  try { 
    $freeSpace = 0 + (Read-Host -Prompt 'Please enter a value for Freespace')
    break # Valid input received; exit the loop.
  } catch { 
    Write-Warning 'Please enter a valid number.' # Prompt again.
  }
}

If ($freeSpace -lt 5GB) {
    "Free disk space is less than 5 GB"
} ElseIf ($freeSpace -lt 10GB) {
    "Free disk space is less than 10 GB"
} ElseIf ($freeSpace -lt 20GB) {
    "Free disk space is less than 20 GB"
} Else {
    "Free disk space is more than 20 GB"
}

[1] While using Invoke-Expression (iex) would technically work too, this cmdlet should generally be avoided and used only as a last resort, due to its inherent security risks. Superior alternatives are usually available. If there truly is no alternative, only ever use it on input you either provided yourself or fully trust - see this answer.

mklement0
  • 382,024
  • 64
  • 607
  • 775
0

Correction

$freeSpace = $null
do {
 [int]$freeSpaceRaw = Read-Host -Prompt 'Please enter a value for Freespace in GB example 5 for 5GB '
  try { $freeSpace = 0 + $freeSpaceRaw } catch { Write-Warning 'Please enter a valid number.' }
} while ($null -eq $freeSpace)

If ($freeSpace -lt 5) {
    "Free disk space is less than 5 GB"
} ElseIf ($freeSpace -lt 10) {
    "Free disk space is less than 10 GB"
} ElseIf ($freeSpace -lt 20) {
    "Free disk space is less than 20 GB"
} Else {
    "Free disk space is more than 20 GB"
}
John Boyd
  • 51
  • 2
  • Remove the `[int]` cast, or you'll get an error that in effect exits the loop if the users enters a value that can't be interpreted as a string. On a meta note: to improve your answers after posting them, _edit_ them - don't post the update _as another answer_. Please delete your original answer. – mklement0 Dec 07 '21 at 20:01