Powershell while and until loop counting - powershell

I am new to powershell and trying to learn loops but i'm currently confused and stuck trying to create a while and until loop that,Asks input from the user, Allow input until a sentinel value is reached, Count the number of times the loop went thru, print out the user input onto new lines, then print the loop number.
while ($UserNumber = Read-Host -Prompt "Input a number from 1 to 10" )
echo "You have entered $UserNumber, now it will count until 20."
This is only the start and I have no idea how to continue. Any and all help would be greatly appreciated.

This may help
$loopCount = 0
do{
$answer = Read-Host "Input a number 1 - 10"
Write-Host "You entered $answer"
$loopCount++
if($answer -eq 4){
$answered = $true
}
} until ($answered -eq $true)
Write-Host ("Looped {0} times" -f $loopCount)

What's in your while (or until) statement needs to result in a boolean value, much like an if statement.
What you're likely going to want to do here is first ask the user for a number:
[int] $UserNumber = Read-Host -Prompt "Input a number from 1 to 10"
Write-Host "You have entered $UserNumber, now it will count until 20."
Note that I've specified that the $UserNumber is an integer... this is to stop PowerShell treating it as a string, which it would be default.
Then you'll want to construct a loop that runs while a certain condition is true (I'm assuming that the number is "less than 20" is what you're thinking about here.)
while ($UserNumber -lt 20) {
$UserNumber += 1 # Increase the value stored in the variable by 1.
$UserNumber
}
That loop will continue, incrementing that number by one (and output it) each iteration, until it reaches 20 or higher. The last time it is output, it will display "20".

$num=0
$total=0
$counter=0
do
{
$num = Read-Host " Input Number"
$total=$total + $num
$counter++
Echo "Counter : $counter and Total : $total"
}
until($total -ge 20 -or $counter -eq 10)
Write-Host "Output : $total and Number Input : $counter"
Results

Related

Is there any way to colour text previously written using "Write-Host" in powershell?

I want to create the "Select-Multiple" function.
The function takes some parameters, but the most important one is the list of options.
Let's say
#("First Option", "Second Option")
Then the function will display something like:
a All
b First Option
c Second Option
d Exit
Choose your option: > ...
The "Choose your option: > ..." text, will be repeated as long as:
User choose "All" or "Exit" option
User will choose all possible options (other than "All" and "Exit")
At the end the function returns the List of options chosen by the user.
Simple. But... I'd like to highlight the options already chosen by the user.
So if the user chose "b", then "b First Option" gets green colour.
Is it possible to do something like that, without using Clear-Host, as I don't want to clear previous steps?
I attach you my "Select-Multiple" function in powershell, sorry if that's ugly written, but I don't use powershell that often.
function Select-Multiple {
Param(
[Parameter(Mandatory=$false)]
[string] $title,
[Parameter(Mandatory=$false)]
[string] $description,
[Parameter(Mandatory=$true)]
[ValidateNotNullOrEmpty()]
$options,
[Parameter(Mandatory=$true)]
[string] $question
)
if ($title) {
Write-Host -ForegroundColor Yellow $title
Write-Host -ForegroundColor Yellow ("-"*$title.Length)
}
if ($description) {
Write-Host $description
Write-Host
}
$chosen = #()
$values = #()
$offset = 0
$all = "All"
$values += #($all)
Write-Host -ForegroundColor Yellow "$([char]($offset+97)) " -NoNewline
Write-Host $all
$offset++
$options.GetEnumerator() | ForEach-Object {
Write-Host -ForegroundColor Yellow "$([char]($offset+97)) " -NoNewline
$values += #($_)
Write-Host $_
$offset++
}
$exit = "Exit"
$values += #($exit)
Write-Host -ForegroundColor Yellow "$([char]($offset+97)) " -NoNewline
Write-Host $exit
$answer = -1
while($chosen.Count -ne $options.Count) {
Write-Host "$question " -NoNewline
$selection = (Read-Host).ToLowerInvariant()
if (($selection.Length -ne 1) -or (([int][char]($selection)) -lt 97 -or ([int][char]($selection)) -gt (97+$offset))) {
Write-Host -ForegroundColor Red "Illegal answer. " -NoNewline
}
else {
$answer = ([int][char]($selection))-97
$value = $($values)[$answer]
if ($value -eq $exit) {
return $chosen
}
if ($value -eq $all) {
return $options
}
else {
if ($chosen.Contains($value)) {
Write-Host -ForegroundColor Red "The value $value was already chosen."
}
else {
$chosen += ($value)
}
}
}
if ($answer -eq -1) {
Write-Host -ForegroundColor Red "Please answer one letter, from a to $([char]($offset+97))"
}
$answer = -1;
}
return $chosen
}
Because of how the console window works, you can't just recolor an existing line. Once you've written something to the console, the only way you can modify it is by overwriting it. This is no different when applying colors.
To understand why this is the case, let's go over how text is colored in PowerShell. Let's use the following command as an example:
Write-Host "Test" -ForegroundColor Green
Here is a (simplified) step by step overview of what this command will do:
Set the ForegroundColor property of the console to green
Write "Test" to the console
Set the ForegroundColor property of the console to whatever it was previously
This explains why we are unable to change the color of text that has already been written to the console. If you want to color a piece of text, you are required to set the console color before writing the text to the console.
However, there are a couple ways to create the same visual effect. In fact there are exactly two ways. One of them is clearing the console and re-writing everything which you mentioned you don't want to do, so let's talk about the other way first.
Overwriting Individual Lines
Let me preface this by saying that this does not work very well with the PowerShell ISE. If you decide to use this, you will have to debug it by either using the normal PowerShell console, or an IDE that supports this. This is also the more complicated option, so if you don't want to deal with the complexity of it, the second option would be the way to go.
The console window allows you to retrieve and set the cursor position by using Console.CursorLeft and Console.CursorTop. You can also use Console.SetCursorPosition() if you need to set both of them at the same time. There is also $Host.UI.RawUI.CursorPosition, but it's long and has some strange side effects when paired with Read-Host, so I would not recommend using it. When you write output to the console, it will write the output to wherever the cursor happens to be. We can use this to our advantage by setting the cursor's position to the beginning of the line we want to change the color of, then overwriting the normal text with colored text or vice versa.
In order to do this, all we need to do is keep track of which option is on which line. This is pretty simple, especially if you have an array of options that is in the same order that you printed them to the console in.
Here is a simple script I made that does exactly this:
$options = $("Option 1", "Option 2", "Option 3")
$initialCursorTop = [Console]::CursorTop
# An array to keep track of which options are selected.
# All entries are initially set to $false.
$selectedOptionArr = New-Object bool[] $options.Length
for($i = 0; $i -lt $options.Length; $i++)
{
Write-Host "$($i + 1). $($options[$i])"
}
Write-Host # Add an extra line break to make it look pretty
while($true)
{
Write-Host "Choose an option>" -NoNewline
$input = Read-Host
$number = $input -as [int]
if($number -ne $null -and
$number -le $options.Length -and
$number -gt 0)
{
# Input is a valid number that corresponds to an option.
$oldCursorTop = [Console]::CursorTop
$oldCursorLeft = [Console]::CursorLeft
# Set the cursor to the beginning of the line corresponding to the selected option.
$index = $number - 1
[Console]::SetCursorPosition(0, $index + $initialCursorTop)
$choice = $options[$index]
$isSelected = $selectedOptionArr[$index]
$choiceText = "$($number). $($choice)"
if($isSelected)
{
Write-Host $choiceText -NoNewline
}
else
{
Write-Host $choiceText -ForegroundColor Green -NoNewline
}
$selectedOptionArr[$index] = !$isSelected
[Console]::SetCursorPosition($oldCursorLeft, $oldCursorTop)
}
# Subtract 1 from Y to compensate for the new line created when providing input.
[Console]::SetCursorPosition(0, [Console]::CursorTop - 1)
# Clear the input line.
Write-Host (' ' * $Host.UI.RawUI.WindowSize.Width) -NoNewline
[Console]::CursorLeft = 0
}
The main advantage of this approach is that it doesn't need to clear the entire console in order to update text. This means you can display whatever you want above it without worrying about it being cleared every time the user inputs something. Another advantage is that this performs a minimal number of operations in order to accomplish the task.
The main disadvantage is that this is relatively volatile. This requires you to use exact line numbers, so if something happens that offsets some of the lines (such as one option being multiple lines), it will more than likely cause some major issues.
However, these disadvantages can be overcome. Since you have access to $Host.UI.RawUI.WindowSize.Width which tells you how many characters you can put on a single line, we know that any string with a length greater than this will be wrapped onto multiple lines. Another option is just to keep track of which line the cursor starts on, then you can clear all text between the starting position and where the cursor currently is.
Clearing the Console
This approach is much simpler because you don't have to worry about what is on which line or where the cursor is at all. The idea is that you simply clear the entire console, then re-write everything with the changes you want to make. This is the nuclear approach, but it's also the most reliable.
Here is the same example as above using this approach instead:
$options = $("Option 1", "Option 2", "Option 3")
# An array to keep track of which options are selected.
# All entries are initially set to $false.
$selectedOptionArr = New-Object bool[] $options.Length
while($true)
{
Clear-Host
for($i = 0; $i -lt $options.Length; $i++)
{
if($selectedOptionArr[$i])
{
Write-Host "$($i + 1). $($options[$i])" -ForegroundColor Green
}
else
{
Write-Host "$($i + 1). $($options[$i])"
}
}
Write-Host # Add an extra line break to make it look pretty
Write-Host "Choose an option>" -NoNewline
$input = Read-Host
$number = $input -as [int]
if($number -ne $null -and
$number -le $options.Length -and
$number -gt 0)
{
# Input is a valid number that corresponds to an option.
$index = $number - 1
$choice = $options[$index]
$selectedOptionArr[$index] = !$selectedOptionArr[$index]
}
}
The main advantage of this approach is that it's super simple and easy to understand.
The main disadvantage is that this clears the entire console every time the user inputs something. In most cases this isn't a huge problem, but it can be with large data sets.
I agree with Trevor Winge, I it is most likely not possible to change earlier console outputs appearance, but it is for certain not worth your while. There are certain limitations of the console, and that is ok since that is what GUIs are for. I hope you feel encouraged by this situation to look into Windows.Froms or WPF. Make use of the controls! checkboxes would be interesting for your scenario. I know that is not exactly what you asked for, but it is garanteed a journey that is worth your time. When i started my first GUI with powershell, i was astonished how much i could accomplish with little afford. Stackoverflow is full of examples.

PowerShell "DO...Until" Try and Catch for valid data not working

I'm trying to use PowerShell to check if a user-provided position number exists. I want to loop this until the user provides a valid position. I am fairly new to PowerShell and I don't understand why it's not working...
#Start Store and Check Budget Position Number User Template#
Clear-Host
Do{
Try{
# Find the user template
$budgetpositionnumber = Read-Host "
What budget position number is the user filling?
"
Write-Host "
You entered budget position number: $budgetpositionnumber
"
# Find the position on Your.Domain
Get-ADuser $budgetpositionnumber
}
Catch{
Write-Host ("Failed to find position number " + $budgetpositionnumber) -ForegroundColor Red -ErrorAction Stop
}
} Until ($budgetpositionnumber -ne $Null)
#End Store and Check Budget Position Number Template#
Even if I enter invalid data, it still continues with the rest of the script. I want it to stop or loop until its' a vaild position number.
##Edited for clarity
"Even if I enter invalid data, it still continues with the rest of the script"
The do loop always ends because it's until condition always evaluates to $true no matter what the input is, this is because any string, even if Empty, is not equal to $null.
The condition:
Until ($budgetpositionnumber -ne $Null)
Should be testing if Get-ADuser found any object instead of testing if there was an input provided in Read-Host.
As for how you can approach the code:
do {
$userinput = Read-Host "something here..."
Write-Host "You entered budget position number: $userinput"
try {
$found = Get-ADuser $userinput
}
catch {
Write-Warning "No account with Name '$userinput' exists..."
}
} until ($found)

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
}

Read-Host in While loop with if statement

This should be really simple, but I cannot make it work. I'm new to the PowerShell so my mistake is probably obvious. I'm trying to ask a user for an input using a while loop, and prompting the user to try again if what was entered was not numeric. In bash it would look like this:
while read -p "What is the first number? " first; do
if [[ $first = *[[:digit:]]* ]]; then
break # if $first is numeric, break loop
else
echo "$first is not numeric. Enter a numeric value. "
fi
done
Here's what I have for the PS:
While ($first = (Read-Host "What is the first number?") -as [double]) {
if ($first -eq $null) { Write-Host "Please enter a numeric value." }
else { break }
}
In the example above, I can break the loop fine when a number is entered, but I can't see the Write-Host statement if I type a string.
Looking at the While line:
While ($first = (Read-Host "What is the first number?") -as [double])
This will only enter/continue the loop body when the input is already a double, because it rolls up the cast to double as part of the loop condition. You want to enter the loop body on any input, and only check if it's a double afterwards. At least, that's what the bash code does:
While ($first = (Read-Host "What is the first number?")) {
if ( ($first -as [double]) -eq $null) { Write-Host "Please enter a numeric value." }
else { break }
}
Or you could continue using the cast as part of the condition by negating the whole expression, and thus avoid the need for the awkward break:
While (-not ($first = (Read-Host "What is the first number?") -as [double])) {
Write-Host "Please enter a numeric value."
}
You could use this to keep prompting for a valid number input as you are looking for.
Do {
"Please enter a numeric value." | Out-Host
$first = (Read-Host "What is the first number?") -as [double]
} While($null -eq $first)

Simple increment/decrement counter

I would like to make a simple counter in PowerShell. It must prompt the user whether they'd like a higher or lower number.
The starting number must be 0, and it can't be lower than zero or higher than 10. If the user wants a higher number, it must increment the number with 1, if lower decrement by 1. Then it must be able to stop at the desired number. With this number I can set a registry value.
I don't know an efficient way to prompt the user. I can use the Read-Host cmdlet to ask if they typed "higher" or "lower", but is there a more efficient way to accomplish this?
For example,
$i = 0
while (($i -gt 0) -or ($i -lt 10)){
$j = Read-Host "The current number is $i, would you like a higher/lower number, or quit?"
if ($j -eq "higher") {
$i++
Write-Host "The current number is $i"
} elseif ($j -eq "lower") {
$i--
Write-Host "The current number is $i"
} elseif ($j -eq "quit") {
Write-Host "Final number is: $i"
break
}
}
How can I do this?
You could use the Yes/No Prompt Window to get user input.
$a = new-object -comobject wscript.shell
$intAnswer = $a.popup("Do you want to delete these files?", 0, "Delete Files", 4)
If ($intAnswer -eq 6) {
$a.popup("You answered yes.")
}
else {
$a.popup("You answered no.")
}
If you replace the '3' in the fourth parameter of the popup() function you will get Yes, No, and Cancel buttons in the prompt window.
Reference: Provide a Yes/No Prompt in Windows PowerShell