PowerShell. Clear previous Read-Host - powershell

I have the following code in which the user has to enter either Y or N. If it's ENTER or anything else it requests again. The request is made every time on the same line.
My problem is that after I enter a wrong answer, when I'm prompted again I have to write over my previous answer.
I've tried clearing the keyboard buffer before Read-Host with no luck. I don't want to use Clear-Host or a new line, it has to be on the same line.
My question is, can I have a clean line after a wrong answer?
function clrkb
{
while($Host.UI.RawUI.KeyAvailable)
{
$Host.UI.RawUI.ReadKey() | Out-Null
}
}
$y_temp +=1;
do
{
clrkb
[Console]::SetCursorPosition(2,$y_temp);write-host Is this a service job? Y/N -f Magenta;[Console]::SetCursorPosition(29,$y_temp);$sj = read-host
}until($sj -eq "Y" -or $sj -eq "N")

function clrkb{
while(
$Host.UI.RawUI.KeyAvailable){$Host.UI.RawUI.ReadKey() | Out-Null
}
}
$y_temp +=1;
do{
clrkb
[Console]::SetCursorPosition(2,$y_temp);write-host Is this a service job? Y/N -f Magenta;[Console]::SetCursorPosition(29,$y_temp);[Console]::Write(' ' * [Console]::WindowWidth);[Console]::SetCursorPosition(29,$y_temp);$sj = read-host
}until($sj -eq "Y" -or $sj -eq "N")
Use spaces to overwrite wrong answer.
Maybe [Console]::WindowWidth is not always big enough, so $sj.length can be used instead.
function clrkb{
while(
$Host.UI.RawUI.KeyAvailable){$Host.UI.RawUI.ReadKey() | Out-Null
}
}
$y_temp +=1;
$sj='';
do{
clrkb
[Console]::SetCursorPosition(2,$y_temp);write-host Is this a service job? Y/N -f Magenta;[Console]::SetCursorPosition(29,$y_temp);[Console]::Write(' ' * $sj.length);[Console]::SetCursorPosition(29,$y_temp);$sj = read-host
}until($sj -eq "Y" -or $sj -eq "N")

Related

Powershell executing If Statement when condition is not met

I am having an issue with my If/Else statement where it will successfully prompt the user until either a "Y/y" or an "N/n" are entered and will store the proper response in the $input variable outside of the Do/Until loop but will execute the first block of code in the following If Statement whether $input is equal to "Y/y" or "N/n"
How can I make it so the If Statement will only execute when $input is equal to "Y/y" and otherwise if it's "N/n" just execute the empty else and move on to the next $program in $InstallList?
I've tried using an ElseIf and checking for "N/n" but it still only executes the first If Statement.
I've also put Write-Host for $input as a check after it leaves the Do/Until loop and it is the correct input but all falls apart when it moves on to executing the If/Else Statement.
Please help.
foreach($program in $InstallList){
if($program -notin $Installed){
$input = ""
do {
$input = Read-Host -Prompt "$($Program) is not installed would you like to install now? (y/n)"
}
until(($input -eq "y") -or ($input -eq "n"))
if($input -eq "y")
{
Write-ProgressHelper -Message "Installing $($Program)" -StepNumber ($stepCounter++)
Start-Sleep -Seconds 3
Start-Process $Software.$program -Wait
Write-Host "$($Software) installed`n"
}
else {}
else{}
}
Abraham Zinala correctly states that use of $input as a custom variable should be avoided, because it is an automatic variable whose value is managed by PowerShell itself, namely to reflect pipeline input.
It is unfortunate that PowerShell even lets you assign a value, which is a problem that affects other automatic variables too - see this answer.
Technically, as long as you stay within the same scope, you can get away with using $input for custom purposes - though it should still be avoided.
Since this constraint is fulfilled in your code, use of $input is not the source of your problem; there is no obvious problem with your code.
Here's version of your code that avoids use of $input and streamlines ensuring that the user input is valid via the -in operator:
foreach ($program in $InstallList) {
if ($program -notin $Installed) {
do {
$response = Read-Host -Prompt "$($Program) is not installed would you like to install now? (y/n)"
} until ($response -in 'y', 'n')
if ($response -eq 'y') {
'yes' # ... install
}
}
}

Color Guessing Game in Powershell [closed]

Closed. This question is not reproducible or was caused by typos. It is not currently accepting answers.
This question was caused by a typo or a problem that can no longer be reproduced. While similar questions may be on-topic here, this one was resolved in a way less likely to help future readers.
Closed last year.
Improve this question
I'm working on writing a script in PowerShell for a Color guessing game. The computer randomly picks a color then the player tries to guess the color. I had it working up until I switched some lines of code in the script in an attempt to get these two variables to display correctly. Now, I can't get the code to run past the point where a player declares if they want to play the game or not. My current error is with a do loop, where the console doesn't see that I have a while loop, and so throws an error and won't run the rest of the code.
I managed to get the formatting fixed with Visual Studio Code's format document feature, but I still can't get this one while loop problem figured out.
Write-Host ''; 'Hello again my friend!'; ''
$name = Read-Host "What is your name?"
Write-Host ''; "It's good to see you again, $name! Would you like to guess my favorite color?"; ''
$command = Read-Host #'
"How do you answer? (Yes or No?)"
1. Yes (y)
2. No (n)
3. Quit (q)
Enter Choice
'#
switch -Wildcard ($command) {
'Y' { 'Yes!'; '' }
{ $_ -eq 'y' -or $_ -like 'Ye*' } {
Write-Host "This will be fun! Let us begin!"
Break
}
'N' { 'No!'; '' }
{ $_ -eq 'n' -or $_ -like 'No*' } {
Write-Host "That's too bad, perhaps another day!"
Exit
}
'Q' { 'Quit'; '' }
{ $_ -eq 'q' -or $_ -like 'qu*' } {
Write-Host 'So long!'
Exit
}
default {
'Invalid Command, Please start over.'
Exit
}
}
[string]$playagain = 'y'
[int]$playerwins = 0
[int]$compwins = 0
[int]$totalguesses = 0
[int]$playergames = 0
[int]$compgames = 0
[int]$round = 1
[int]$game = 1
$cpuchoice = $color
while ($playagain -eq 'y') {
Write-Host ''; "Game $game!"; ''
$cpuchoice = #([System.Enum]::GetValues([System.ConsoleColor])) | Get-Random -Count 1
do {
Write-Host "Round $round! What is my favorite color?"; ''
$listcolor = Read-Host "Would you like to see a list of available colors? Choose 'y' for yes, and 'n' for no."
if ($listcolor -eq 'y') {
[System.Enum]::GetValues([System.ConsoleColor])
}
elseif ($listcolor -eq 'n') {
Write-Host "Suit yourself, let's start."
}
else {
Write-Host "Your choice was invalid. Please choose 'y' for yes, or 'n' for no."
}
do {
$playerchoice = Read-host "Enter your guess"
} while (([System.Enum]::GetValues([System.ConsoleColor])) -notcontains $playerchoice) {
if ($playerchoice -eq $cpuchoice ) {
Write-Host "You win, my favorite color is $cpuchoice." -ForegroundColor $cpuchoice; ''
$playerwins = $playerwins + 1
$totalguesses = $totalguesses + 1
}
elseif ($playerchoice -ne $cpuchoice ) {
Write-Host "You lose, try again."; ''
$playerguesses += $playerchoice
$playerguesses = $playerguesses.Split(',')
$totalguesses = $totalguesses + 1
Write-Host "Here are your guesses so far: "
$playerguesses
''
}
$round = $round + 1
}
until($playerwins -eq 1) {
$playergames = $playergames + 1
Write-Host "You've won this round and have won $playergames games." -ForegroundColor Green
Write-Host "Your total guesses: $totalguesses."
Write-Host "Your wins - $playergames" -ForegroundColor Yellow
Write-Host "Computer wins - $compgames" -ForegroundColor Yellow
''
}
$playagain = Read-Host "I enjoyed this game. Would you like to challenge again, $name? Y or N"
while (("y", "n") -notcontains $playagain) {
if ($playagain -eq "y") {
Write-Host "I look forward to our next battle!"; ''
$playerwins = 0
$compwins = 0
$game = $game + 1
}
elseif ($playagain -eq "n") {
Write-Host "Thank you for playing!"
exit
}
}
}
}
The do loop that causes the error is the one that starts with "Write-Host "Round $round!" after the first while statement.
Any help is appreciated! Thanks!
Your program is being parsed like this:
while ($playagain -eq 'y')
{
# do loop #1
do
{
# do loop #2
do {
}
while (([System.Enum]::GetValues([System.ConsoleColor])) -notcontains $playerchoice)
# floating script block #1
# (doesn't execute the scriptblock, but it gets sent to the output stream instead)
{
if ($playerchoice -eq $cpuchoice ) {
... etc ...
}
# try to invoke a cmdlet "until" with 2 parameters
# i.e. ($playerwins -eq 1) and { ... }
until ($playerwins -eq 1) {
$playergames = $playergames + 1
... etc ...
}
# while loop #1
while (("y", "n") -notcontains $playagain) {
...
}
}
}
The error is telling you the first do (do loop #1) doesn't have a trailing while or until.
There's no clear and simple fix I can offer to make your code run short of a significant rework because there's a number of issues (e.g. the floating script block #1, the dangling until and the while \ until-less do), but that's what the current error is saying anyway...

If not empty jump to somewhere in PS

Hello and good morning(:
I'm looking to see if I'm able to jump to somewhere in PS without wrapping it in a ScriptBlock; hell, I'd even be okay with that but, I'm just unsure on how to go about it.
What I'm trying to do is: add a Parameter Set to a function and if something is supplied to the parameter -GrpSelec(I know imma change it), then just skip the rest of the script and go to my $swap variable to perform the switch.
$Group1 = #("1st Group", "2nd Group")
$Group2 = #("3rd Group", "4th Group")
Function Test-Group{
param(
[ValidateSet("Group1","group2")]
[array]$GrpSelec)
if($GrpSelec){ &$swap }
$AllGroups = #("Group1", "Group2")
for($i=0; $i -lt $AllGroups.Count; $i++){
Write-Host "$($i): $($AllGroups[$i])"}
$GrpSelec = Read-Host -Prompt "Select Group(s)"
$GrpSelec = $GrpSelec -split " "
$swap = Switch -Exact ($GrpSelec){
{1 -or "Group1"} {"$Group1"}
{2 -or "Group2"} {"$Group2"}
}
Foreach($Group in $swap){
"$Group"}
}
Is something like this even possible?
I've googled a couple of similar questions which point to the invocation operator &(as shown above), and/or, a foreach which is definitely not the same lol.
take it easy on me, im just experimenting(:
How about a simple if statement?
function Test-Group {
param(
[string[]]$GrpSelec
)
if(!$PSBoundParameters.ContainsKey('GrpSelect')){
# no argument was passed to -GrpSelec,
# populate $GrpSelec in here before proceeding with the rest of the script
}
# Now that $GrpSelec has been populated, let's do the work
$swap = Switch -Exact ($GrpSelec){
{1 -or "Group1"} {"$Group1"}
{2 -or "Group2"} {"$Group2"}
}
# rest of function
}

How can I allow only Y/N to be input in Read-Host without having to press Enter in Powershell?

I'm trying to find a way to have something like a Read-Host to ask the user if they want to output to the file listed or not. With this I want them to either press y or n and then the code continues rather than then pressing y/n then pressing enter as well. At the moment this all works well but again it's not quite what I'm wanting.
I've tried looking into Readkey and SendKeys (to push Enter for the user) but neither work as they seem to only execute after the user has pushed Enter on the Read-Host. I'm still very new to Powershell so I'm not entirely sure whether it's actually possible or not and I've spent too much time googling/testing to find an answer that works. If I was to use Write-Host or something to do this, it needs to not show up in the log.
I've included the necessary part of my script below. It basically asks the user if the file location is correct. If it is they press y and it uses it for the output, otherwise if they push n then it loads the FolderBrowserDialog for them to select the folder they want.
I should also note this is all within a Tee-object as this code is what determines where the Tee-object output goes to.
$OutputYN = Read-Host "Do you want the output file generated to $startDirectory\FolderList.txt? (Y/N)"
If (“y”,”n” -notcontains $OutputYN) {
Do {
$OutputYN = Read-Host "Please input either a 'Y' for yes or a 'N' for no"
} While (“y”,”n” -notcontains $OutputYN)
}
if ($OutputYN -eq "Y") {
$OutputLoc = $startDirectory
}
elseif ($OutputYN -eq "N") {
$OutputLocDir = New-Object System.Windows.Forms.FolderBrowserDialog
$OutputLocDir.Description = "Select a folder for the output"
$OutputLocDir.SelectedPath = "$StartDirectory"
if ($OutputLocDir.ShowDialog() -eq "OK") {
$OutputLoc = $OutputLocDir.SelectedPath
$OutputLoc = $OutputLoc.TrimEnd('\')
}
}
EDIT:
I should have been a little more clear. I had already tried message box type stuff as well but I'd really prefer if there is a way that the user types in a y or a n. I'm not really interested in a popup box that the user has to click. If it's not possible then so be it.
Readkey is the right way.
Use the following as template.
:prompt while ($true) {
switch ([console]::ReadKey($true).Key) {
{ $_ -eq [System.ConsoleKey]::Y } { break prompt }
{ $_ -eq [System.ConsoleKey]::N } { return }
default { Write-Error "Only 'Y' or 'N' allowed!" }
}
}
write-host 'do it' -ForegroundColor Green
:prompt gives the outer loop (while) a name which can be used in the switch statement to directly break out entirely via break prompt (and not within the switch statement).
Alternative (for Windows):
Use a MessageBox.
Add-Type -AssemblyName PresentationFramework
$messageBoxResult = [System.Windows.MessageBox]::Show("Do you want the output file generated to $startDirectory\FolderList.txt?" , 'Question' , [System.Windows.MessageBoxButton]::YesNo , [System.Windows.MessageBoxImage]::Question)
switch ($messageBoxResult) {
{ $_ -eq [System.Windows.MessageBoxResult]::Yes } {
'do this'
break
}
{ $_ -eq [System.Windows.MessageBoxResult]::No } {
'do that'
break
}
default {
# stop
return # or EXIT
}
}
Not sure if this is possible in the console. But when I need the user to write one answer of a specified set, I use a do-until-loop like:
Do {
$a = Read-Host "Y / N"
} until ( 'y', 'n' - contains $a )
try this:
$title = 'Question'
$question = 'Do you want the output file generated to $startDirectory\FolderList.txt?'
$choices = New-Object Collections.ObjectModel.Collection[Management.Automation.Host.ChoiceDescription]
$choices.Add((New-Object Management.Automation.Host.ChoiceDescription -ArgumentList '&Yes'))
$choices.Add((New-Object Management.Automation.Host.ChoiceDescription -ArgumentList '&No'))
$decision = $Host.UI.PromptForChoice($title, $question, $choices, 1)
if ($decision -eq 0) {
Write-Host 'Yes'
} else {
Write-Host 'No'
}
If you are on Windows, you can do it :
[System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms") | Out-Null
$result = [System.Windows.Forms.MessageBox]::Show('Do you want the output file generated to $startDirectory\FolderList.txt?' , "Question" , [System.Windows.Forms.MessageBoxButtons]::YesNo, [System.Windows.Forms.MessageBoxIcon]::Question)
if ($result -eq 'Yes') {
"Yes"
}
else
{
"No"
}

How to configure a timeout for Read-Host in PowerShell

Like I said, this code works in PowerShell version 2, but not in PowerShell version 5.
function wait
{
$compte = 0
Write-Host "To continue installation and ignore configuration warnings type [y], type any key to abort"
While(-not $Host.UI.RawUI.KeyAvailable -and ($compte -le 20))
{
$compte++
Start-Sleep -s 1
}
if ($compte -ge 20)
{
Write-Host "Installation aborted..."
break
}
else
{
$key = $host.ui.rawui.readkey("NoEcho,IncludeKeyup")
}
if ($key.character -eq "y")
{Write-Host "Ignoring configuration warnings..."}
else
{Write-Host "Installation aborted..."
}}
The official documentation or Read-Host -? will tell that it's not possible to use Read-Host in that manner. There is no possible parameter to tell it to run with some kind of timeout.
But there are various other questions detailing how to do this in PowerShell (usually utilizing C#).
The idea seems to be to check whenever the user pressed a key using $Host.UI.RawUI.KeyAvailable and check that for the duration of your timeout.
A simple working example could be the following:
$secondsRunning = 0;
Write-Output "Press any key to abort the following wait time."
while( (-not $Host.UI.RawUI.KeyAvailable) -and ($secondsRunning -lt 5) ){
Write-Host ("Waiting for: " + (5-$secondsRunning))
Start-Sleep -Seconds 1
$secondsRunning++
}
You could use $host.UI.RawUI.ReadKey to get the key that was pressed. This solution probably would not be acceptable if you need more complex input than a simple button press. See also:
Windows PowerShell Tip of the Week - Pausing a Script Until the User Presses a Key
PowerTip: Use PowerShell to Wait for a Key Press (Hey, Scripting Guy!)
Seth, thank you for your solution. I expanded on the example you provided and wanted to give that back to the community.
The use case is a bit different here - I have a loop checking if an array of VMs can be migrated and if there are any failures to that check the operator can either remediate those until the checks clear or they can opt to "GO" and have those failing VMs excluded from the operation. If something other than GO is typed state remains within the loop.
One downside to this is if the operator inadvertently presses a key the script will be blocked by Read-Host and may not be immediately noticed. If that's a problem for anyone I'm sure they can hack around that ;-)
Write-Host "Verifying all VMs have RelocateVM_Task enabled..."
Do {
$vms_pivoting = $ph_vms | Where-Object{'RelocateVM_Task' -in $_.ExtensionData.DisabledMethod}
if ($vms_pivoting){
Write-Host -ForegroundColor:Red ("Some VMs in phase have method RelocateVM_Task disabled.")
$vms_pivoting | Select-Object Name, PowerState | Format-Table -AutoSize
Write-Host -ForegroundColor:Yellow "Waiting until this is resolved -or- type GO to continue without these VMs:" -NoNewline
$secs = 0
While ((-not $Host.UI.RawUI.KeyAvailable) -and ($secs -lt 15)){
Start-Sleep -Seconds 1
$secs++
}
if ($Host.UI.RawUI.KeyAvailable){
$input = Read-Host
Write-Host ""
if ($input -eq 'GO'){
Write-Host -ForegroundColor:Yellow "NOTICE: User prompted to continue migration without the blocked VM(s)"
Write-Host -ForegroundColor:Yellow "Removing the following VMs from the migration list"
$ph_vms = $ph_vms | ?{$_ -notin $vms_pivoting} | Sort-Object -Property Name
}
}
} else {
Write-Host -ForegroundColor:Green "Verified all VMs have RelocateVM_Task method enabled."
}
} Until(($vms_pivoting).Count -eq 0)
Also note that all this $Host.UI stuff doesn't work from the Powershell ISE.
To find out from within a script you could test for $Host.Name -eq "ConsoleHost". When true you can use the code from this topic. Otherwise you could use $Host.UI.PromptForChoice or any other way of showing a dialog box. With System.Windows.Forms.Timer you can then set a timer, and code to close the dialog box or form can be run when it expires.