I have a script that's waiting for user input then depending on what they press does something. But, the problem is the script continues no matter what they press (but if it's not one of the two keys it's expecting, I just get errors). Here's the snippint I'm working on
Write-Host "What do you want to do next?" -nonewline
Write-Host "
u - Search for another user
c - Enter a computer name
"
# Prompt for an action
Write-Host ">> Select shortcut action: " -nonewline
$key = [Console]::ReadKey()
$value = $key.KeyChar
switch($value) {
c { $c = Read-Host "Enter computer or IP"}
u { $u = Read-Host "Enter user" }
}
# now we continue on with the code depending on what was pressed
What I'd like is if anything beyond c or u are pressed tell the user that's not a valid key and go back to the top of this snippint and prompt the user again for what action to do next.
Just wrap your code in a do-while loop that continues as long as $value is not c or u:
do
{
$key = [Console]::ReadKey($true)
$value = $key.KeyChar
switch($value) {
c { $c = Read-Host "Enter computer or IP"}
u { $u = Read-Host "Enter user" }
}
}
while ($value -notmatch 'c|u')
Related
I have a script to create a user account in my domain.
I have added a Middle name which is optional, but is required if the username is going to be a duplicate, so will take the middle name initial to differentiate.
The problem is that the following code keeps the last value. So if I specify first, middle and last name, then the next user is first and last name, then it still keeps the middle name.
Is there something that keeps the middle name empty when it runs the next time from scratch?
Do
{
#Getting variable for the First Name
Do
{
$firstname = Read-Host "Enter in the First Name"
Write-Host
if ($firstname -ge 1) {
Write-Host "First name is $firstname"
}
else
{
Write-Host "Please enter the first name" -ForegroundColor:Green
}
}
Until ($firstname -ge 1)
$option = Read-Host "Does the person have a middle name? (y/n)"
if ($option -eq "y")
{
#Getting variable for the Middle Name
Do
{
$middlename = Read-Host "Please enter the middle name"
Write-Host
if ($middlename -ge 1)
{
Write-Host "Middle name is $middlename"
}
else
{
Write-Host "Please enter the middle name" -ForegroundColor:Green
}
}
Until ($middlename -ge 1)
}
else
{
Write-Host "No middle name required"
}
#Getting variable for the Last Name
Do
{
$lastname = Read-Host "Enter in the Last Name"
Write-Host
if ($lastname -ge 1)
{
Write-Host "Last name is $lastname"
}
else
{
Write-Host "Please enter the last name" -ForegroundColor:Green
}
}
Until ($lastname -ge 1)
#Setting Full Name (Display Name) to the users first and last name
if ($middlename)
{
$fullname = "$firstname $middlename $lastname"
Write-Host $fullname
sleep 5
}
else
{
$fullname = "$firstname $lastname"
}
#Write-Host
#Setting username to first initial of first name along with the last name.
$i = 1
$logonname = $firstname.substring(0,$i) + $lastname
Just clear the variable at the beginning of your script.
Either:
$middlename = ""
Or use Remove-Variable
Remove-Variable middlename
This behavior comes from how you choose to launch a script. You have essentially two options in PowerShell, Invocation (Running) or dot-sourcing (Loading).
Take this short script.
#stack.ps1
"my variable should be empty $myVar"
$myvar = "cat"
"my variable should have a value $myVar"
When I run it / invoke it in PowerShell using the Invocation operator, look what happens:
PS> & .\stack.ps1
my variable should be empty
my variable should have a value cat
PS> & .\stack.ps1
my variable should be empty
my variable should have a value cat
PowerShell runs the script and so the script stores its variable values in the script scope. Meaning that when the script ends, the variables disappear.
Versus Dot-Sourcing
Now, compare that to dot-sourcing, which runs a script in your present context.
PS> . .\stack.ps1
my variable should be empty
my variable should have a value cat
PS> . .\stack.ps1
my variable should be empty cat #!!!! the value persisted!
my variable should have a value cat
Because it runs in your present context, the values persist when the script ends.
If I had to guess, I'd say you might be dot-sourcing your script instead of invoking it. It is a very easy thing to do and would explain what you're seeing.
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"
}
Good morning guys,
I'm new to powershell scripting. And i can't figure out what I'm doing wrong.
I tried to write a .ps1 script to compare the hash value of a stream. I used the microsoft documentation for help and modify it to a runable script so i don't need to write it over and over again.
$wc = [System.Net.WebClient]::new()
$pkgurl = Read-Host "Please enter Package Url: "
$publishedHash = Read-Host "Enter Published Hash: "
$FileHash = Get-FileHash -InputStream ($wc.OpenRead($pkgurl))
if ($FileHash.Hash -eq $publishedHash) {
Write-Host "File Hash is equal to published Hash."
}
else {
Write-Host "File Hash NOT equal to published Hash."
}
When i run the script and enter the package url and the published Hash, the program all of a sudden abruptly shuts down.
Please, anyone an idea?
The script ends as it has nothing else to do.
You can add read-host at the end to wait for user input before it closes. (it wont do anything with the input, this just forces it to stay open until input has been made.)
Alternatively if you want to use it multiple times without it closing you can create a loop:
$KeepOpen = $true
While($KeepOpen -eq $true){
$wc = [System.Net.WebClient]::new()
$pkgurl = Read-Host "Please enter Package Url: "
$publishedHash = Read-Host "Enter Published Hash: "
$FileHash = Get-FileHash -InputStream ($wc.OpenRead($pkgurl))
if ($FileHash.Hash -eq $publishedHash) {
Write-Host "File Hash is equal to published Hash."
}
else {
Write-Host "File Hash NOT equal to published Hash."
}
$user_input = Read-Host "Please enter Y to run again"
if($user_input -ne "Y"){
$KeepOpen = $false
}
}
This will keep the script open so you can see the results and if you want it to run again insert Y and hit enter and you should be back to where you start.
just wondering if its possible to escape a read-host in a while loop by pressing escape.
I've tried doing an do-else loop but it will only recognize button presses outside of the read-host.
This is basically what I have
#Import Active Directory Module
Import-Module ActiveDirectory
#Get standard variables
$_Date=Get-Date -Format "MM/dd/yyyy"
$_Server=Read-Host "Enter the domain you want to search"
#Request credentials
$_Creds=Get-Credential
while($true){
#Requests user input username
$_Name=Read-Host "Enter account name you wish to disable"
#rest of code
}
I want to be able to escape it if I want to change the domain
Using Read-Host you cannot do this, but you might consider using a graphical input dialog instead of prompting in the console. After all, the Get-Credential cmdlet also displays a GUI.
If that is an option for you, it can be done using something like this:
function Show-InputBox {
[CmdletBinding()]
Param (
[Parameter(Mandatory = $true, Position = 0)]
[string]$Message,
[string]$Title = [System.IO.Path]::GetFileNameWithoutExtension($MyInvocation.PSCommandPath),
[string]$defaultText = ''
)
Add-Type -AssemblyName 'Microsoft.VisualBasic'
return [Microsoft.VisualBasic.Interaction]::InputBox($Message, $Title, $defaultText)
}
while($true) {
$_Name = Show-InputBox "Enter account name you wish to disable"
if ([string]::IsNullOrWhiteSpace($_Name)) {
# the box was cancelled, so exit the loop
break
}
# proceed with the rest of the code
}
If the user presses the Esc key, clicks Cancel, or leaves the input blank, you can exit the while loop, otherwise proceed with the code.
You cannot do it with Read-Host, but you can do it via the PSReadLine module (ships with Windows PowerShell version 5 or higher on Windows 10 / Windows Server 2016) and PowerShell Core) and its PSConsoleHostReadline function:
Important:
As of PSReadLine v2.0.0-beta3, the solution below is a hack, because the PSConsoleHostReadline only supports prompting for PowerShell statements, not open-ended user input.
This GitHub feature suggestion asks for the function to be optionally usable for general-purpose user input as well, which would allow for a greatly customizable end-user prompting experience. Make your voice heard there, if you'd like see this suggestion implemented.
The hack should work in your case, since the usernames you're prompting for should be syntactically valid PowerShell statements.
However, supporting arbitrary input is problematic for two reasons:
Inapplicable syntax coloring will be applied - you could, however, temporarily set all configurable colors to the same color, but that would be cumbersome.
More importantly, if an input happens to be something that amounts to a syntactically incomplete PowerShell statement, PSConsoleHostReadline won't accept the input and instead continue to prompt (on a new line); for instance, input a| would cause this problem.
Also:
Whatever input is submitted is invariably added to the command history.
While you can currently remove a temporarily installed keyboard handler on exiting a script, there is no robust way to restore a previously active one - see this GitHub issue.
# Set up a key handler that cancels the prompt on pressing ESC (as Ctrl-C would).
Set-PSReadLineKeyHandler -Key Escape -Function CancelLine
try {
# Prompt the user:
Write-Host -NoNewline 'Enter account name you wish to disable: '
$_Name = PSConsoleHostReadLine
# If ESC was pressed, $_Name will contain the empty string.
# Note that you won't be able to distinguish that from the user
# just pressing ENTER.
$canceled = $_Name -eq ''
# ... act on potential cancellation
} finally {
# Remove the custom Escape key handler.
Remove-PSReadlineKeyHandler -Key Escape
}
I wrote this function that works for me.
# Returns the string "x" when Escape key is pressed, or whatever is indicated with -CancelString
# Pass -MaxLen n to define max string length
function Read-HostPlus()
{
param
(
$CancelString = "x",
$MaxLen = 60
)
$result = ""
$cursor = New-Object System.Management.Automation.Host.Coordinates
while ($true)
{
While (!$host.UI.RawUI.KeyAvailable ){}
$key = $host.UI.RawUI.ReadKey("NoEcho, IncludeKeyDown")
switch ($key.virtualkeycode)
{
27 { While (!$host.UI.RawUI.KeyAvailable ){}; return $CancelString }
13 { While (!$host.UI.RawUI.KeyAvailable ){}; return $result }
8
{
if ($result.length -gt 0)
{
$cursor = $host.UI.RawUI.CursorPosition
$width = $host.UI.RawUI.MaxWindowSize.Width
if ( $cursor.x -gt 0) { $cursor.x-- }
else { $cursor.x = $width -1; $cursor.y-- }
$Host.UI.RawUI.CursorPosition = $cursor ; write-host " " ; $Host.UI.RawUI.CursorPosition = $cursor
$result = $result.substring(0,$result.length - 1 )
}
}
Default
{
$key_char = $key.character
if( [byte][char]$key_char -ne 0 -and [byte][char]$key_char -gt 31 -and ($result + $key_char).Length -le $MaxLen )
{
$result += $key_char
$cursor.x = $host.UI.RawUI.CursorPosition.X
Write-Host $key_char -NoNewline
if ($cursor.X -eq $host.UI.RawUI.MaxWindowSize.Width-1 ) {write-host " `b" -NoNewline }
}
}
}
}
}
Using PowerShell, I would like to read the value of key presses as they occur, without the user having to press enter. For example, if the user presses '1' I would like the PowerShell script to react to the choice immediately without an 'enter'.
My research has turned up ReadKey,
$input = $Host.UI.RawUI.ReadKey('IncludeKeyDown');
But ReadKey returns much more information in the string than I require:
72,h,0,True
While I can parse the key press from this string, I'd prefer a more direct option. Does one exist?
Perhaps you could clarify a bit - but ReadKey returns a KeyInfo object not a string. KeyInfo contains VirtualKeyCode, Character, ControlKeyState and KeyDown members - all fields in your output string in that order. In fact, it looks like PowerShell has just called the .ToString() method in your output example. You will probably want to look at the Character property to find your desired character. Consider the following example where I press 1:
$key = $Host.UI.RawUI.ReadKey()
if ($key.Character -eq '1') {
"Pressed 1"
}
Regarding the use of $Host.UI.RawUI.ReadKey('IncludeKeyDown');
Goyuix answer should be marked as the right answer.
I found the $Host.UI.RawUI.ReadKey rather long and wanted to use [console].
But was a bad decision. This is a warning. Some might know how to use it right.
Key will give you for pressing some keys
1 = D1
1 on the NumPad = NumPad1
a = A (only capitalized letters)
- = OemMinus (yeah)
Additionally the similar looking [console]::ReadKey it will the Key Property.
$key = [console]::ReadKey()
if ($key.Key -eq 'D1') {
"Pressed 1"
}
This will definitely do what you want.
This accepts a single character as input and has validation.
I use a menu system to run programs and utilities using PowerShell.
When I hit a character, it goes right to the option and runs without hitting enter
I have extracted out the essentials of the input and menu system and gave a few example menu items.
Please note: There are 2 modes for this to run in
MODE 1: Uses Read-Host, requires Enter, Runs in ISE. Use this for troubleshooting/building
MODE 2: Uses ReadKey, no Enter required, Does NOT run in ISE... You will need to run this in a PowerShell command line. The code below is currently in Mode 2.
Comment/UnComment the lines as needed to change the mode
##Formatting Variables
$fgc1 = 'cyan'
$fgc2 = 'white'
$indent = ' '
Function MainMenu {
CLS
Write-Host "###############"
Write-Host "## Main Menu ##"
Write-Host "###############"
Write-Host -NoNewLine "$indent" "A " -ForegroundColor 'red'; Write-Host "== Options A" -ForegroundColor $fgc2
Write-Host -NoNewLine "$indent" "B " -ForegroundColor 'red'; Write-Host "== Options B" -ForegroundColor $fgc2
Write-Host -NoNewLine "$indent" "C " -ForegroundColor 'red'; Write-Host "== Options C" -ForegroundColor $fgc2
Write-Host -NoNewLine "$indent" "D " -ForegroundColor 'red'; Write-Host "== Options D" -ForegroundColor $fgc2
Write-Host -NoNewLine "$indent" "E " -ForegroundColor 'red'; Write-Host "== Options E" -ForegroundColor $fgc2
Write-Host -NoNewLine "$indent" "F " -ForegroundColor 'red'; Write-Host "== Options F" -ForegroundColor $fgc2
Write-Host -NoNewLine "$indent" "G " -ForegroundColor 'red'; Write-Host "== Options G" -ForegroundColor $fgc2
Write-Host ""
#This gives you a way to set the current function as a variable. The Script: is there because the variable has to
#be available OUTSIDE the function. This way you can make it back to the menu that you came from as long as all
#of your menus are in functions!
$Script:SourceMenu = $MyInvocation.MyCommand.Name
# Mode 1#
#Use this for troubleshooting so that you can stay in ISE
# Uncomment the 2 lines below to use Read-Host. This will necessitate an ENTER Key. BUT, it WILL work in ISE
#$K = Read-Host - "Which option?"
#MenuActions
# Mode 2#
#Uncomment the line below to use ReadKey. This will NOT necessitate an ENTER Key. BUT, it ## will NOT work ## in ISE
ReadKey
}
Function ReadKey {
Write-Host "Please make your choice..."
Write-Host ""
Write-Host "Press Q to quit"
$KeyPress = [System.Console]::ReadKey()
#This gets the keypress to a common variable so that both modes work (Read-Host and KeyPress)
$K = $KeyPress.Key
#Jumps you down to the MenuActions function to take the keypress and "Switch" to it
MenuActions
}
Function MenuActions {
Switch ($K) {
A {CLS;Write-Host "You Pressed A";Write-Host "Going to pause now... ";&pause}
B {CLS;Write-Host "You pressed B";Write-Host "Going to pause now... ";&pause}
C {CLS;Write-Host "You pressed C";Write-Host "Going to pause now... ";&pause}
D {CLS;Write-Host "You pressed D";Write-Host "Going to pause now... ";&pause}
E {CLS;Write-Host "You pressed E";Write-Host "Going to pause now... ";&pause}
F {CLS;Write-Host "You pressed F";Write-Host "Going to pause now... ";&pause}
G {CLS;Write-Host "You pressed G";Write-Host "Going to pause now... ";&pause}
#This is a little strange of a process to exit out, but I like to use an existing mechanism to exit out
#It sets the $SourceMenu to a process that will exit out.
#I use this same process to jump to a different menu/sub-menu
Q {$SourceMenu = "Exit-PSHostProcess";CLS;Write-Host "Exited Program"}
}
#This next command will loop back to the menu you came from. This, in essence, provides a validation that one of the
#"Switch ($X.key)" options were pressed. This is also a good way to always find your way back to
#the menu you came from. See "$Script:SourceMenu = $MyInvocation.MyCommand.Name" above.
#
#This is also the way that the Menu Item for Q exits out
& $SourceMenu
}
# This runs the MainMenu function. It has to be after all the functions so that they are defined before being called
MainMenu
The main part of the whole thing is:
$KeyPress = [System.Console]::ReadKey()
#This gets the keypress to a common variable so that both modes work (Read-Host and KeyPress)
$K = $KeyPress.Key
#
#Then do something with $K
What about a system that accepts 2 key presses?
Just a little addition here. Since we are talking about a SINGLE key press... how about a double key press? Well, this will work fine. Just stack up the ReadKey commands and assign variables to each, then combine them:
Write-Host "Press the 2 character option you wish"
#Get KeyPress1 Variable
$KeyPress1 = [System.Console]::ReadKey()
#This gets the keypress to a common variable so that both modes work (Read-Host and KeyPress)
$K1 = $KeyPress1.Key
#Get KeyPress1 Variable
$KeyPress2 = [System.Console]::ReadKey()
#This gets the keypress to a common variable so that both modes work (Read-Host and KeyPress)
$K2 = $KeyPress2.Key
#This is just for troubleshooting to prove it works
CLS
Write-Host "This is the state of the variables right now"
Write-Host "Keypress1 is: $K1" -ForegroundColor Green
Write-Host "Keypress1 is: $K2" -ForegroundColor Green
$KEYS = "$K1"+"$K2"
Write-Host "The combined presses are: $KEYS" -ForegroundColor Red
pause
I look forward to questions or comments.
Seems $Host.UI.RawUI.ReadKey() not works in VSCode console.
Here are my ReadKey implementation.
class DConsole {
static [System.ConsoleKeyInfo]ReadKey() {
return [DConsole]::ReadKey($true)
}
static [System.ConsoleKeyInfo]ReadKey([bool]$noEcho = $true) {
$key = [System.Console]::ReadKey()
if ($noEcho) {
$cTop = [System.Console]::CursorTop
[System.Console]::SetCursorPosition(0, $cTop)
}
return $key
}
}
Usage:
$key = [DConsole]::ReadKey()
# or
$key = [DConsole]::ReadKey($false)
Any improvements are welcome.
Back to the question, $key.KeyChar.ToString() may what you want.