Looping for user input similar to goto in batch files - loops

I am new to power shell. I am trying to use the function like the goto in batch files.
In my script where I have$input1 = Read-Host "Please select an option 1, 2, 3. "
I want to Clear-Host and restart script if the use does not enter 1, 2, 3.
Write-Host 1. Convert Byte to Megabyte.
Write-Host
Write-Host 2. Convert Byte to Gigabyte.
Write-Host
Write-Host 3. Conver Byte to Terabyte.
#
#
# Assign value to the variables
#
$b2MB = '1048576'
$b2GB = '1073741824'
$b2TB = '1099511627776'
$input1 = ""
#
#
# prompts user for input from the main screen.
#
$input1 = Read-Host "Please select an option 1, 2, 3. "
#
#
# Depending on what the user input will execute a certain conversion.
#
#
if ( $input1 -eq 1 ) {
$mb2 = Read-Host " Enter how many bytes you want to convert to Megabytes? "
$bytesToMb = $mb2 / $b2MB
Write-Host $mb2 'is equal to '$bytesToMb' Megabytes'
} elseif ($input1 -eq 2) {
$mb3 = Read-Host " Enter how many bytes you want to convert to Gigabytes? "
$bytesToGb = $mb3 / $b2GB
Write-Host $mb3 'is equal to '$bytesToGb' Gigabytes'
} elseif ( $input1 -eq 3) {
$mb4 = Read-Host " Enter how many bytes you want to convert to Terabytes? "
$bytesToTb = $mb4 / $b2TB
Write-Host $mb4 'is equal to '$bytesToTb' Terabytes'
} else {
Write-Host " You have entered an invalid option. "
}

I believe you want something like this:
# Start a continuous loop
while ($true) {
# Write messages
Write-Host 1. Convert Byte to Megabyte.
Write-Host
Write-Host 2. Convert Byte to Gigabyte.
Write-Host
Write-Host 3. Conver Byte to Terabyte.
# Get the input
$input1 = Read-Host "Please select an option 1, 2, 3. "
# See if the input equals 1, 2, or 3. If so, break the loop.
if (1..3 -contains $input1) { break }
# If we get here, then the input was bad.
# So, we clear the host and let the loop start-over
Clear-Host
}
#
#
# Assign value to the variables
#
$b2MB = '1048576'
$b2GB = '1073741824'
$b2TB = '1099511627776'
#
#
# Depending on what the user input will execute a certain conversion.
#
#
if ( $input1 -eq 1) {
$mb2 = Read-Host " Enter how many bytes you want to convert to Megabytes? "
$bytesToMb = $mb2 / $b2MB
Write-Host $mb2 'is equal to '$bytesToMb' Megabytes'
} elseif ($input1 -eq 2) {
$mb3 = Read-Host " Enter how many bytes you want to convert to Gigabytes? "
$bytesToGb = $mb3 / $b2GB
Write-Host $mb3 'is equal to '$bytesToGb' Gigabytes'
} elseif ( $input1 -eq 3) {
$mb4 = Read-Host " Enter how many bytes you want to convert to Terabytes? "
$bytesToTb = $mb4 / $b2TB
Write-Host $mb4 'is equal to '$bytesToTb' Terabytes'
} else {
Write-Host " You have entered an invalid option. "
}

Easy enough, use a Do/Until loop.
#
#
# Assign value to the variables
#
$input1 = ""
# Start the loop to get the user's choice (with validation)
Do{
cls
Write-Host 1. Convert Byte to Megabyte.
Write-Host
Write-Host 2. Convert Byte to Gigabyte.
Write-Host
Write-Host 3. Conver Byte to Terabyte.
#
#
# prompts user for input from the main screen.
#
$input1 = Read-Host "Please select an option 1, 2, 3. "
}Until(#(1,2,3) -contains $input1)
Switch($input1){
1 {Read-Host "Enter how many bytes you want to convert to Megabytes? "|%{$Out = "{0:N} bytes is equal to {1:n4} Megabytes" -f [double]$_,($_/1MB);Write-Host "$Out"}}
2 {Read-Host "Enter how many bytes you want to convert to Gigabytes? "|%{$Out = "{0:N} bytes is equal to {1:n4} Gigabytes" -f [double]$_,($_/1GB);Write-Host "$Out"}}
3 {Read-Host "Enter how many bytes you want to convert to Terabytes? "|%{$Out = "{0:N} bytes is equal to {1:n4} Terabytes" -f [double]$_,($_/1TB);Write-Host "$Out"}}
}
I also used a Switch instead of a bunch of If/Then statements, and did the math with ###/1MB or /1GB or /1TB since Powershell can do that calculation for you.
Edit: Oh yeah, I also formatted the numbers. Adding commas makes it easier to read large numbers, and I truncated the calculated value to 4 places past the decimal. That can be changed by altering the 4 in {1:n4} to whatever you want the decimal to go to, or remove the 4 to include everything. If you make it a large decimal it will fill with zeros if that many do not exist. Such as 4.23 when formatted through {1:n5} comes out as 4.23000 since it has to have 5 places past the decimal.
Ok, the Switch loop is used instead of nested if statements like you were using. The syntax is
Switch(array to parse) {
Value or scriptblock {action to perform on match}
Different value or scriptblock {action to perform on match}
}
So in your case there is only one thing that we are parsing, and that is the users selection. So let's say they choose 2 to convert to Gigabytes. $input2 = 2. Switch starts comparisons at the top, and checks if the value is 1. It is not so it moves to the next line. It checks to see if the value is 2. It is, so the switch command performs the actions in the accompanying scriptblock there. We will get to that in a moment. After it finished that scriptblock it moves to the next line and checks if the value is 3. It is not, and that was the last option so it finishes that loop. There is not another value to parse since $input2 only had one value so it exits the switch loop. Switch works on arrays so there could have potentially have been more things to compare against the three options in the switch loop.
That is the switch loop. Let's look at the scriptblock that it ran when the value matched up for the second option. I'll break it down a bit here...
Read-Host "Enter how many bytes you want to convert to Gigabytes? "|
%{
$Out = "{0:N} bytes is equal to {1:n4} Gigabytes" -f [double]$_,($_/1GB);
Write-Host "$Out"
}
That first line is pretty much yours if I remember right, but instead of storing the value in a variable for use later I pipe it to a for each loop (%{...} is short for foreach{...}). Then, since write-host doesn't really allow formatting I do some formatting and save the string in $out. So this is where it might get intimidating. "{0:N}" works with the -f which is short for -format. It works like a place holder that says to look at the -f command at the end of the string and get the first thing provided to it (arrays in powershell start at record 0, so usually a reference to item 0 is looking for the first item). Once it gets that first value that follows the -f it applies the formatting specified after the colon N which means it formats it like a number putting a comma every three digits.
Ok, there is some text in there and then {1:N4} which is another formatting placeholder. The part before the colon specifies which item to fetch, and since 0 was the first value to be formatted, 1 must be the second. So it gets the second value and applies the formatting after the colon. N4 again says it is a number, but specifies to only show the first 4 numbers past the decimal.
Then it has the -f which says that there are some things to format. The first item is $_ which is the value that the user entered which was piped into the foreach loop. The [double] before it specifies the type of object it is. Double is a kind of number, like int32 or hex, but it has very loose guidelines so the user can enter very large values and not have issues. That gets passed directly to the first placeholder. Then comes $_/1GB which is the number that the user entered divided by Gigabytes, because powershell has that ability built in. It finds the result of that and passes it to the second placeholder. So now we have the text that we want to pass back to the user, with the numbers nicely formatted and easy to read, and stored that string into $out. Last thing in that scriptblock is to write $out to the host.
Now you may ask why I had to specify that $_ as a [double] the first time, but not the second. The reason is that the second time I was performing math on it, so powershell figures that it must be a number if I'm dividing it, so I don't have to declare it there.
I hope that makes the script make more sense now.

Related

Powershell: Read-Host to select an Array Index

This is how I modify my Powershell array:
ForEach ($userID in $usersList) {
$allUsers += [pscustomobject]#{ID=$usersCounterTable;UserID=$userID;Name=$userInfo.DisplayName;Ext=$userInfo.ipPhone;Cellphone=$userInfo.mobile;Enabled=$isEnabled;VDI=$computerType;Title=$userTitle;}
$usersCounter += 1
$usersCounterTable = "[$usersCounter]"
}
Later in the code, the table is displayed and I want the user to be able to type a number to open the value, the number actually being the array index/offset (minus 1). I cannot find out how to do this.
$userID is actually the user's selection, because they can also type another employee's code to search, or search for his name for instance. I want the user to be able to select the array index number.
if (($userID.length -gt 0) -and ($userID.length -lt 5)) {
$indexNumber = ($userID - 1)
[????] $userFinalChoice = $allUsers[$userID].Name # NOT VALID
}
Above code works, if the user enter a number between 1 and 9999...
And then I would like to do this: $allUsers[$userID] ($userID being the number the user selected with Read-Host). Only, $allUsers[$userID].Name is not valid, but $allUsers[1].Name is. If I can figure this out, I'll be able to fix the rest of it and search for the return value.
Also need to make sure user doesn't input an index that is out of bounds of $usersList (Using $ErrorActionPreference = "SilentlyContinue" would probably work as it just reject the search reject but it's not that clean.)
From what I understand, I'm actually looking for the reverse of $usersList.IndexOf(‘David’), I want to provide the index and get returned the name.
Much appreciated - Powershell beginner.
The first code block you show us is really confusing, since you seem to grab user details from just... somewhere, so there is no telling if this info indeed belongs to the same user or not.
Also, I don't really think it is a good idea to use a formatted table as selection menu, especialy if the list gets large. Maybe you should think of building a form with a listbox or use the Out-GridView for this as Lee_Dailey suggested.
Anyway, if you want it as console menu, first make sure the ID number (really the index to select) starts with 1
$usersCounter = 1
# collect an array of PsCustomObjects in variable $allUsers
$allUsers = foreach ($userID in $usersList) {
# don't use $allUsers += , simply output the object
[PsCustomObject]#{
ID = "[$usersCounter]"
UserID = $userID
Name = $userInfo.DisplayName
Ext = $userInfo.ipPhone
Cellphone = $userInfo.mobile
Enabled = $isEnabled
VDI = $computerType
Title = $userTitle
}
$usersCounter++ # increment the counter
}
Next, show this as table so folks can select one of the users by typing the number displayed in the 'ID' column.
Do this in a loop, so when someone types anything else than a valid number, the menu is displayed again.
# start an endless loop
while ($true) {
Clear-Host
$allUsers | Format-Table -AutoSize
$userID = Read-Host "Enter the [ID] number to select a user. Type 0 or Q to quit"
if ($userID -eq '0' -or $userID -eq 'Q') { break } # exit from the loop, user quits
# test if the input is numeric and is in range
$badInput = $true
if ($userID -notmatch '\D') { # if the input does not contain an non-digit
$index = [int]$userID - 1
if ($index -ge 0 -and $index -lt $allUsers.Count) {
$badInput = $false
# everything OK, you now have the index to do something with the selected user
# for demo, just write confirmation to the console and exit the loop
Clear-Host
Write-Host "You have selected $($allUsers[$index].Name)" -ForegroundColor Green
break
}
}
# if you received bad input, show a message, wait a couple
# of seconds so the message can be read and start over
if ($badInput) {
Write-Host "Bad input received. Please type only a valid number from the [ID] column." -ForegroundColor Red
Start-Sleep -Seconds 4
}
}

How can I use PowerShell to copy a range of files where the file name is as sequence of numbers

How can I use PowerShell to copy a range of files where the file name is as sequence of numbers?
For example, say I have a bunch of files where the names are numbers starting at 23540987577 to 27495847547388. However I only want to copy files where the middle 5 numbers are between 43565 and 43769. I have made a few attempts, but it either copies everything, or errors out.
So far I have the following :
$START = read-host -prompt "Enter starting number"
$END = read-host -Prompt "Enter ending number"
$Files = Get-ChildItem
$Files = $Files.name
$i = 1
foreach ($i in $Files) {
if ($Files[$i] -ge "*$START*" -and $Files[$i] -le "*$END*") {
/
Copy-Item $Files[$i] .\pulled
$i++
}
else {
Write-Host "no"
}
}
I have a list of files where the file name is a large sequence of numbers. Within said sequence (some where towards the middle) is a transaction number. I need to find and copy a small subset of transaction numbers that are within a specific range.
If I am searching for said files manually in Windows Explorer I would have search for each number in the range as follows:
*43565*
*43566*
*43567*
*43568*
and so on...
I want to automate this process as it takes a long time to search for each transaction number with larger batches.
what about the following:
it's not regex but I dont see any advantage of using regex here:
I didn't use much Powershell yet, therefore I stick to Pseude Code, but you should be able to adapt this to Powershell script easily:
CopyFiles(int lowerBound, int upperBound)
{
foreach (file in fileList)
{
int filename = (int)file.filename.substring(1,X) // you have to know the length of your substring, maybe pass it also as parameter
if(filename >= lowerBound && filename <= upperBound)
{
move (file.filename, new location)
}
}
}

Expand array so user can select best option and input into variable

I am trying to figure out how to do the following.
Read a text file with phone numbers in a list that are available each number on a separate line.
Output the available list to the script user to be able to select the best one.
Input the $number into another command to add it to the new user.
I have the following so far
$numbers = gc 'C:\temp\file.txt'
I can output the data by using $numbers[1], $numbers[2], etc., but how can I output this to look good and allow the user to select the best number with all options in the text file? I can do it manually obviously, but if I do 3 x $numbers[1], $numbers[2], $numbers[3] it will miss the numbers 4, 5, 6 etc.
try Something like this :
$selected=Get-Content "c:\temp\phone.txt" | Out-Gridview -Title "Select your choice" -OutputMode Single
if ($selected)
{
Write-Host "your choice is $selected"
}
Something like this:
$numbers = Get-Content 'C:\temp\file.txt'
$i = 1
foreach($n in $numbers){
Write-host "Number $i - $n"
$i++
}
$n = Read-host "Choose a number"
$chosenNumber = $number[$n-1]

Writing a program in bash shell in UNIX

I have to write a program that lets the user enter in as many numbers as he wants and determine which is the largest, smallest, what the sum is, and the average of all the numbers entered. Am I forced to use arrays to do this or is there another way? If I have to use an array, can someone help me out with an example of how I should be approaching this question? Thanks
You do not need an array. Just keep the largest and smallest number so far, the count of numbers and the sum. The average is simply sum/count.
To read the input, you can use read in a while loop.
Simple straight forward attempt, with some issues:
#!/bin/bash
echo "Enter some numbers separated by spaces"
read numbers
# Initialise min and max with first number in sequence
for i in $numbers; do
min=$i
max=$i
break
done
total=0
count=0
for i in $numbers; do
total=$((total+i))
count=$((count+1))
if test $i -lt $min; then min=$i; fi
if test $i -gt $max; then max=$i; fi
done
echo "Total: $total"
echo "Avg: $((total/count))"
echo "Min: $min"
echo "Max: $max"
Also tested with /bin/sh, so you don't actually need bash, which is a much larger shell. Also note that this only works with integers, and average is truncated (not rounded).
For floating point, you could use bc. But instead of dropping into a different interpreter multiple times, why not just write it in something a bit more suitable to the problem, such as python or perl, eg in python:
import sys
from functools import partial
sum = partial(reduce, lambda x, y: x+y)
avg = lambda l: sum(l)/len(l)
numbers = sys.stdin.readline()
numbers = [float(x) for x in numbers.split()]
print "Sum: " + str(sum(numbers))
print "Avg: " + str(avg(numbers))
print "Min: " + str(min(numbers))
print "Max: " + str(max(numbers))
You could embed it in bash using a here document, see this question: How to pipe a here-document through a command and capture the result into a variable?

Powershell check if input is integer and > 0

How do I check if the user input is an integer and greater than zero and maximum 10?
I need to make a script which asks for a number from 0 to 10. This will be the amount of times the user can enter a number.
So if he enters 3, he has to be asked 3 times which number he wants to enter. When he entered the last number, the average has to been shown of the given number, and then the script ends.
Like this?
while ( (1..10) -notcontains $g)
{
$g = read-host "How many numbers do you want to put in? (value from 1 to 10) "
}
$ar=#()
for ($i=0; $i -lt $g; $i++)
{
$ar += read-host "Enter value $($i+1)"
}
$averageArray = ($ar | Measure-Object -Average).average
write-host "Average is : $averageArray"

Resources