How do i prevent a StackOverflow error in the following code? - c#

I have the follwing code that provides an auto refresh feature to a WCF Console Hosted Application.
When the Console.ReadKey accepts an invalid character, it restarts the ReadKey method. If the user mashes the keyboard long enough on this code it will go into a StackOverflowException.
Does anyone have a way to re-write this code so that it doesn't cause the stack to blow?
[STAThread]
static void Main(string[] args)
{
bool restart = true;
while(restart)
{
using (var myWcfHost = new MyWcfHost())
{
myWcfHost.start();
Console.WriteLine("Press Enter to quit or Ctrl+R to restart");
restart = WaitForRestart();
}
}
}
private static bool WaitForRestart()
{
// clear users input
Console.CursorLeft = 0;
Console.Write(' ');
Console.CursorLeft = 0;
// read users input
var key = Console.ReadKey();
if ((key.Modifiers & ConsoleModifiers.Control) != 0
&& key.Key == ConsoleKey.R)
{
// refersh the settings
ConfigurationManager.RefreshSection("appSettings");
return true;
}
if (key.Key == ConsoleKey.Enter || key.Key == ConsoleKey.Escape)
{
return false;
}
return WaitForRestart();
}

Replace recursion with a loop:
private static bool WaitForRestart()
{
while (true)
{
// clear users input
Console.CursorLeft = 0;
Console.Write(' ');
Console.CursorLeft = 0;
// read users input
var key = Console.ReadKey();
if ((key.Modifiers & ConsoleModifiers.Control) != 0
&& key.Key == ConsoleKey.R)
{
// refersh the settings
ConfigurationManager.RefreshSection("appSettings");
return true;
}
if (key.Key == ConsoleKey.Enter || key.Key == ConsoleKey.Escape)
{
return false;
}
}
}

It looks like each time there's an invalid key pressed, you push another WaitForRestart onto the stack, eventually resulting in an overflow exception. I think this would fix:
private static bool WaitForRestart()
{
// clear users input
Console.CursorLeft = 0;
Console.Write(' ');
Console.CursorLeft = 0;
while (true)
{
// read users input
var key = Console.ReadKey();
if ((key.Modifiers & ConsoleModifiers.Control) != 0
&& key.Key == ConsoleKey.R)
{
// refersh the settings
ConfigurationManager.RefreshSection("appSettings");
return true;
}
if (key.Key == ConsoleKey.Enter || key.Key == ConsoleKey.Escape)
{
return false;
}
}
}

Related

how to stop my code from looping at the end?

i'm new at coding C# pls help me fix this simple dice game, it keeps on looping at the end of the game
** apparently how i think is that in the end of this code something makes the NO commend on looping and getting re runed**
using System;
namespace first_game
{
class Program
{
static void Main(string[] args)
{
string userName;
userName = Console.ReadLine();
#region
{
int userPoint = 0;
int cpuPoint = 0;
int pDice;
bool closeApp;
while (true)
{
while (cpuPoint < 10 && userPoint < 10)
{
Random rd = new Random();
pDice = rd.Next(1, 6);
#endregion
int P2Dice;
Random scondRd = new Random();
P2Dice = scondRd.Next(1, 6);
Console.ReadLine();
Console.WriteLine("-------------------");
Console.Write("playe dice:");
Console.WriteLine(pDice);
Console.Write("CPU dice:");
Console.WriteLine(P2Dice);
Console.ReadLine();
if (pDice > P2Dice)
{
userPoint = userPoint + 1;
}
else if (pDice < P2Dice)
{
cpuPoint = cpuPoint + 1;
}
Console.Clear();
Console.WriteLine(userName);
Console.Write("player point:");
Console.WriteLine(userPoint);
Console.Write("CPU point:");
Console.Write(cpuPoint);
Console.ReadLine();
}
if (userPoint == 10)
{
Console.WriteLine("-----------------\nYOU WIN!!!");
Console.ReadLine();
}
else
{
Console.WriteLine("-----------------\nYOU lost!!! LOL");
Console.ReadLine();
}
Console.WriteLine("wanna continue?\n press Y for yes press N for NO");
ConsoleKeyInfo sw;
sw = Console.ReadKey();
Console.Clear();
if (sw.Key == ConsoleKey.Y)
{
userPoint = 0;
cpuPoint = 0;
continue;
}
else if (sw.Key == ConsoleKey.N)
{
}
else
Console.WriteLine("ERROR!");
}
}
}
}
}
You seem to have the wrong idea about continue. A while loop always (given a true condition) reruns if it reaches the end, a continue just starts the next iteration early.
else if (sw.Key == ConsoleKey.N) { }
else Console.WriteLine("ERROR!");
This is where you should exit the loop, for example using a break
else if (sw.Key == ConsoleKey.N) {
break;
} else Console.WriteLine("ERROR!");
Add a break to exit the while loop:
if (userPoint == 10)
{
Console.WriteLine("-----------------\nYOU WIN!!!");
break;
}
else
{
Console.WriteLine("-----------------\nYOU lost!!! LOL");
break;
}
Then add this after the while loop, so that the console doesn't close:
Console.ReadLine();

How do I tell my console app not to continue until i enter a number?

static int beverageSelection()
{
Console.WriteLine();
int brand;
string _val = "";
Console.Write("Enter number: ");
ConsoleKeyInfo key;
do
{
key = Console.ReadKey(true);
if (key.Key != ConsoleKey.Backspace)
{
double val = 0;
bool _x = double.TryParse(key.KeyChar.ToString(), out val);
if (_x)
{
_val += key.KeyChar;
Console.Write(key.KeyChar);
}
}
else
{
if (key.Key == ConsoleKey.Backspace && _val.Length > 0)
{
_val = _val.Substring(0, (_val.Length - 1));
Console.Write("\b \b");
}
}
}
while (key.Key != ConsoleKey.Enter);
brand = Convert.ToInt32(Console.ReadLine());
return brand;
}
The method above is giving me a headache. I cannot figure out how to tell the console app that it shouldn't let me input any character or even the enter button until I have typed a number into the console. Then and only then should I be able to press enter.
In any case this program is a vending machine I created for fun and I do not fully understand the do while loop in this code just to be clear.
Use Console.ReadLine instead of Console.ReadKey:
int brand = 0;
while (true)
{
string val = Console.ReadLine();
if (int.TryParse(val, out brand)) break;
}
Improved your original code to display and accept only numbers
static void Main(string[] args)
{
Console.WriteLine();
int brand;
string _val = "";
Console.Write("Enter number: ");
while(true)
{
var key = Console.ReadKey(true);
if (key.Key == ConsoleKey.Enter && int.TryParse(_val, out brand))
{
Console.WriteLine();
break;
}
if (key.Key != ConsoleKey.Backspace)
{
int val;
if (int.TryParse(key.KeyChar.ToString(), out val))
{
_val += key.KeyChar;
Console.Write(key.KeyChar);
}
}
else
{
if (_val.Length > 0)
{
_val = _val.Substring(0, _val.Length - 1);
Console.Write("\b \b");
}
}
}
Console.WriteLine("Brand: {0}", brand);
Console.ReadKey();
}
For the sake of completeness, if anyone wants the console to prevent non-numeric characters until a numeric value is chosen, you can simply use this unerring code to accomplish your task:
int inputBoundary = Console.CursorLeft;
string consoleInput = String.Empty;
ConsoleKeyInfo inputKey;
while((inputKey = Console.ReadKey(true)).Key != ConsoleKey.Enter || consoleInput == String.Empty)
{
if (inputKey.Key == ConsoleKey.Backspace && Console.CursorLeft != inputBoundary)
{
Console.Write("\b \b");
consoleInput = consoleInput.Remove(consoleInput.Length - 1);
continue;
}
if (Char.IsNumber(inputKey.KeyChar))
{
Console.Write(inputKey.KeyChar);
consoleInput += inputKey.KeyChar;
}
}
int selection = Int32.Parse(consoleInput);

Bug when reading password with confirmation and retries in a loop

EDIT: The problem I am facing is that I enter an incorrect password to confirm previous password and it works since it is the first try. The max is 3 tries. During 2nd and 3rd try even if password is correct it will say invalid password.
I do not want that
I know I am missing something very trivial but I cannot manage to find it. This is the code:
class Program
{
static void Main(string[] args)
{
string username = "";
string pass = "";
string confirmPass = "";
Console.Write("Enter username: ");
username = Console.ReadLine();
Console.WriteLine();
Console.Write("Enter your password: ");
ConsoleKeyInfo key;
do
{
key = Console.ReadKey(true);
// Backspace Should Not Work
if (key.Key != ConsoleKey.Backspace && key.Key != ConsoleKey.Enter)
{
pass += key.KeyChar;
Console.Write("*");
}
else if (key.Key == ConsoleKey.Backspace && pass.Length > 0)
{
pass = pass.Substring(0, (pass.Length - 1));
Console.Write("\b \b");
}
// Stops Receving Keys Once Enter is Pressed
} while (key.Key != ConsoleKey.Enter);
Console.WriteLine("");
Console.WriteLine("Confirm your password: ");
ConsoleKeyInfo confirmKey;
int retryCount = 3;
string finalPass = "";
do{
confirmKey = Console.ReadKey(true);
// Backspace Should Not Work
if (confirmKey.Key != ConsoleKey.Backspace && confirmKey.Key != ConsoleKey.Enter)
{
confirmPass += confirmKey.KeyChar;
Console.Write("*");
}
else if (confirmKey.Key == ConsoleKey.Backspace && pass.Length > 0)
{
confirmPass = confirmPass.Substring(0, (confirmPass.Length - 1));
Console.Write("\b \b");
}
//
// Stops Receving Keys Once Enter is Pressed
} while ((confirmKey.Key != ConsoleKey.Enter));
//Console.WriteLine("");
//string confirmPass = "";
do
{
if (confirmPass != pass)
{
Console.WriteLine("Re-enter Password: (" + retryCount + " tries remaining)");
do
{
confirmKey = Console.ReadKey(true);
// Backspace Should Not Work
if (confirmKey.Key != ConsoleKey.Backspace && confirmKey.Key != ConsoleKey.Enter)
{
confirmPass += confirmKey.KeyChar;
Console.Write("*");
}
else if (confirmKey.Key == ConsoleKey.Backspace && pass.Length > 0)
{
confirmPass = confirmPass.Substring(0, (confirmPass.Length - 1));
Console.Write("\b \b");
}
//
// Stops Receving Keys Once Enter is Pressed
} while ((confirmKey.Key != ConsoleKey.Enter));
retryCount--;
}
else if (confirmPass == pass)
{
Console.WriteLine("Enter password to log in :");
finalPass = Console.ReadLine();
if (finalPass == pass)
{
Console.WriteLine("Login Successful. Welcome " + username + "!");
Console.WriteLine();
Console.WriteLine("Test Successful. Press Enter to quit.");
}
}
if (retryCount == 0)
{
Console.WriteLine("Exceeded number of tries!!");
Console.ReadLine();
}
} while (confirmPass != pass && retryCount != 0);
}
}
Based on your code, this is what I came up with:
public static void Main(string[] args)
{
string username = "";
string pass = "";
string confirmPass = "";
Console.Write("Enter username: ");
username = Console.ReadLine();
Console.WriteLine();
Console.Write("Enter your password: ");
ConsoleKeyInfo key;
do
{
key = Console.ReadKey(true);
// Backspace Should Not Work
if (key.Key != ConsoleKey.Backspace && key.Key != ConsoleKey.Enter)
{
pass += key.KeyChar;
Console.Write("*");
}
else if (key.Key == ConsoleKey.Backspace && pass.Length > 0)
{
pass = pass.Substring(0, (pass.Length - 1));
Console.Write("\b \b");
}
// Stops Receving Keys Once Enter is Pressed
} while (key.Key != ConsoleKey.Enter);
Console.WriteLine("");
Console.WriteLine("Confirm your password: ");
ConsoleKeyInfo confirmKey;
int retryCount = 3;
string finalPass = "";
do
{
confirmKey = Console.ReadKey(true);
// Backspace Should Not Work
if (confirmKey.Key != ConsoleKey.Backspace && confirmKey.Key != ConsoleKey.Enter)
{
confirmPass += confirmKey.KeyChar;
Console.Write("*");
}
else if (confirmKey.Key == ConsoleKey.Backspace && pass.Length > 0)
{
confirmPass = confirmPass.Substring(0, (confirmPass.Length - 1));
Console.Write("\b \b");
}
// Stops Receving Keys Once Enter is Pressed
} while ((confirmKey.Key != ConsoleKey.Enter));
do
{
if (confirmPass != pass)
{
confirmPass = "";
Console.WriteLine("Re-enter Password: (" + retryCount + " tries remaining)");
do
{
confirmKey = Console.ReadKey(true);
// Backspace Should Not Work
if (confirmKey.Key != ConsoleKey.Backspace && confirmKey.Key != ConsoleKey.Enter)
{
confirmPass += confirmKey.KeyChar;
Console.Write("*");
}
else if (confirmKey.Key == ConsoleKey.Backspace && pass.Length > 0)
{
confirmPass = confirmPass.Substring(0, (confirmPass.Length - 1));
Console.Write("\b \b");
}
// Stops Receving Keys Once Enter is Pressed
} while ((confirmKey.Key != ConsoleKey.Enter));
retryCount--;
}
else if (confirmPass == pass)
{
Console.WriteLine("Enter password to log in :");
finalPass = Console.ReadLine();
if (finalPass == pass)
{
Console.WriteLine("Login Successful. Welcome " + username + "!");
Console.WriteLine();
Console.WriteLine("Test Successful. Press Enter to quit.");
Console.ReadLine();
break;
}
}
if (retryCount == 0)
{
Console.WriteLine("Exceeded number of tries!!");
Console.ReadLine();
}
} while (retryCount != 0);
}
Basically I made the following changes:
When you enter that last do loop, and enter the first if statement, I set confirmPass = "";. This is the reason your code did not do that part initially, because you were appending their new confirmPass to the original. Thus, if they typed password, then password2, then to retry they typed password, confirmPass was password2password.
After changing this, the flow did not work correctly, so I removed the confirmPass != pass && bit from that while. This allowed you to get to the elseif portion in that loop.
Still, I assume this was a bit wrong as once you hit that elseif it would infinite loop. So I told it to break inside that elseif which kills that do/while loop.
If this solution is not correct, please clarify your requirements so that we may better assist you.
Your program is rather convoluted, which is why it will be much harder to spot any errors. You should really learn how to use sub routines (i.e. functions), so that you don't have to repeat blocks of code that do the same thing over and over again (like reading a password).
If we were to refactor your code a little bit, by moving those repeated parts out into a method named ReadPassword, like this:
public static string ReadPassword()
{
var pass = "";
while (true)
{
var key = Console.ReadKey(true);
if (key.Key == ConsoleKey.Enter)
// Stop Receving Keys Once Enter is Pressed
break;
// Backspace Should Not Work
if (key.Key != ConsoleKey.Backspace)
{
pass += key.KeyChar;
Console.Write("*");
}
else if (pass.Length > 0)
{
pass = pass.Substring(0, (pass.Length - 1));
Console.Write("\b \b");
}
}
return pass;
}
And used it to replace all those do ... while loops used for reading a password, without changing anything else in your code (so the bugs are still there):
public static void Main(params string[] args)
{
string username = "";
string pass = "";
string confirmPass = "";
Console.Write("Enter username: ");
username = Console.ReadLine();
Console.WriteLine();
Console.Write("Enter your password: ");
pass = ReadPassword(); // changed to function call
Console.WriteLine("");
Console.WriteLine("Confirm your password: ");
int retryCount = 3;
string finalPass = "";
confirmPass = ReadPassword(); // changed to function call
do
{
if (confirmPass != pass) // <==== this is performed before the "else"
{
Console.WriteLine("Re-enter Password: (" + retryCount + " tries remaining)");
confirmPass = ReadPassword(); // changed to function call
retryCount--;
}
else if (confirmPass == pass) // <==== "else": bug
{
// Bug: it will only get here, if the confirmed password
// was entered correctly the first time
Console.WriteLine("Enter password to log in :");
finalPass = Console.ReadLine();
if (finalPass == pass)
{
Console.WriteLine("Login Successful. Welcome " + username + "!");
Console.WriteLine();
Console.WriteLine("Test Successful. Press Enter to quit.");
}
// else <==== now what?
}
if (retryCount == 0)
{
Console.WriteLine("Exceeded number of tries!!");
Console.ReadLine();
}
} while (confirmPass != pass && retryCount != 0);
}
Now the bugs become much easier to spot. I highlighted them using <=== in the comments. I will leave it to you to figure out what is wrong, and how you can fix it.

How to remove char '*' from Console Window? Command Console.Write("\b"); didn't work

This is contain from method, which writes hidden letters. When I hit a backspace, I need somehow to delete char * from console.
private static string Password()
{
bool enter=true;
string pass="";
do
{
char letter = Console.ReadKey(true).KeyChar;
if (letter == (char)13)
{ enter = false; }
else if (letter == (char)8 && pass.Length >= 1)
{
pass = pass.Remove(pass.Length - 1);
Console.Write("\b");
}
else
{
pass += letter;
Console.Write("*");
}
} while (enter);
Console.WriteLine();
return pass;
}
I searched for an answer but the command Console.Write("\b"); but it doesn't seem to work correctly.
You can set the cursor's position back one, write a null character, then move it back again. Like this:
private static string Password()
{
bool enter = true;
string pass = "";
do
{
char letter = Console.ReadKey(true).KeyChar;
if (letter == (char)13)
{ enter = false; }
else if (letter == (char)8 && pass.Length >= 1)
{
pass = pass.Remove(pass.Length - 1);
Console.CursorLeft--;
Console.Write('\0');
Console.CursorLeft--;
}
//Additionally, don't print backspaces if it's the first character.
else if (letter != (char)8)
{
pass += letter;
Console.Write("*");
}
} while (enter);
Console.WriteLine();
return pass;
}
Try this:
Console.Write("\b \b");

Password masking console application

I tried the following code...
string pass = "";
Console.Write("Enter your password: ");
ConsoleKeyInfo key;
do
{
key = Console.ReadKey(true);
// Backspace Should Not Work
if (key.Key != ConsoleKey.Backspace)
{
pass += key.KeyChar;
Console.Write("*");
}
else
{
Console.Write("\b");
}
}
// Stops Receving Keys Once Enter is Pressed
while (key.Key != ConsoleKey.Enter);
Console.WriteLine();
Console.WriteLine("The Password You entered is : " + pass);
But this way the backspace functionality doesn't work while typing the password.
Any suggestion?
Console.Write("\b \b"); will delete the asterisk character from the screen, but you do not have any code within your else block that removes the previously entered character from your pass string variable.
Here's the relevant working code that should do what you require:
var pass = string.Empty;
ConsoleKey key;
do
{
var keyInfo = Console.ReadKey(intercept: true);
key = keyInfo.Key;
if (key == ConsoleKey.Backspace && pass.Length > 0)
{
Console.Write("\b \b");
pass = pass[0..^1];
}
else if (!char.IsControl(keyInfo.KeyChar))
{
Console.Write("*");
pass += keyInfo.KeyChar;
}
} while (key != ConsoleKey.Enter);
For this you should use the System.Security.SecureString
public SecureString GetPassword()
{
var pwd = new SecureString();
while (true)
{
ConsoleKeyInfo i = Console.ReadKey(true);
if (i.Key == ConsoleKey.Enter)
{
break;
}
else if (i.Key == ConsoleKey.Backspace)
{
if (pwd.Length > 0)
{
pwd.RemoveAt(pwd.Length - 1);
Console.Write("\b \b");
}
}
else if (i.KeyChar != '\u0000' ) // KeyChar == '\u0000' if the key pressed does not correspond to a printable character, e.g. F1, Pause-Break, etc
{
pwd.AppendChar(i.KeyChar);
Console.Write("*");
}
}
return pwd;
}
Complete solution, vanilla C# .net 3.5+
Cut & Paste
:)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace ConsoleReadPasswords
{
class Program
{
static void Main(string[] args)
{
Console.Write("Password:");
string password = Orb.App.Console.ReadPassword();
Console.WriteLine("Sorry - I just can't keep a secret!");
Console.WriteLine("Your password was:\n<Password>{0}</Password>", password);
Console.ReadLine();
}
}
}
namespace Orb.App
{
/// <summary>
/// Adds some nice help to the console. Static extension methods don't exist (probably for a good reason) so the next best thing is congruent naming.
/// </summary>
static public class Console
{
/// <summary>
/// Like System.Console.ReadLine(), only with a mask.
/// </summary>
/// <param name="mask">a <c>char</c> representing your choice of console mask</param>
/// <returns>the string the user typed in </returns>
public static string ReadPassword(char mask)
{
const int ENTER = 13, BACKSP = 8, CTRLBACKSP = 127;
int[] FILTERED = { 0, 27, 9, 10 /*, 32 space, if you care */ }; // const
var pass = new Stack<char>();
char chr = (char)0;
while ((chr = System.Console.ReadKey(true).KeyChar) != ENTER)
{
if (chr == BACKSP)
{
if (pass.Count > 0)
{
System.Console.Write("\b \b");
pass.Pop();
}
}
else if (chr == CTRLBACKSP)
{
while (pass.Count > 0)
{
System.Console.Write("\b \b");
pass.Pop();
}
}
else if (FILTERED.Count(x => chr == x) > 0) { }
else
{
pass.Push((char)chr);
System.Console.Write(mask);
}
}
System.Console.WriteLine();
return new string(pass.Reverse().ToArray());
}
/// <summary>
/// Like System.Console.ReadLine(), only with a mask.
/// </summary>
/// <returns>the string the user typed in </returns>
public static string ReadPassword()
{
return Orb.App.Console.ReadPassword('*');
}
}
}
Taking the top answer, as well as the suggestions from its comments, and modifying it to use SecureString instead of String, test for all control keys, and not error or write an extra "*" to the screen when the password length is 0, my solution is:
public static SecureString getPasswordFromConsole(String displayMessage) {
SecureString pass = new SecureString();
Console.Write(displayMessage);
ConsoleKeyInfo key;
do {
key = Console.ReadKey(true);
// Backspace Should Not Work
if (!char.IsControl(key.KeyChar)) {
pass.AppendChar(key.KeyChar);
Console.Write("*");
} else {
if (key.Key == ConsoleKey.Backspace && pass.Length > 0) {
pass.RemoveAt(pass.Length - 1);
Console.Write("\b \b");
}
}
}
// Stops Receving Keys Once Enter is Pressed
while (key.Key != ConsoleKey.Enter);
return pass;
}
Mine ignores control characters and handles line wrapping:
public static string ReadLineMasked(char mask = '*')
{
var sb = new StringBuilder();
ConsoleKeyInfo keyInfo;
while ((keyInfo = Console.ReadKey(true)).Key != ConsoleKey.Enter)
{
if (!char.IsControl(keyInfo.KeyChar))
{
sb.Append(keyInfo.KeyChar);
Console.Write(mask);
}
else if (keyInfo.Key == ConsoleKey.Backspace && sb.Length > 0)
{
sb.Remove(sb.Length - 1, 1);
if (Console.CursorLeft == 0)
{
Console.SetCursorPosition(Console.BufferWidth - 1, Console.CursorTop - 1);
Console.Write(' ');
Console.SetCursorPosition(Console.BufferWidth - 1, Console.CursorTop - 1);
}
else Console.Write("\b \b");
}
}
Console.WriteLine();
return sb.ToString();
}
This masks the password with a red square, then reverts back to the original colours once the password has been entered.
It doesn't stop the user from using copy/paste to get the password, but if it's more just about stopping someone looking over your shoulder, this is a good quick solution.
Console.Write("Password ");
ConsoleColor origBG = Console.BackgroundColor; // Store original values
ConsoleColor origFG = Console.ForegroundColor;
Console.BackgroundColor = ConsoleColor.Red; // Set the block colour (could be anything)
Console.ForegroundColor = ConsoleColor.Red;
string Password = Console.ReadLine(); // read the password
Console.BackgroundColor= origBG; // revert back to original
Console.ForegroundColor= origFG;
Reading console input is hard, you need to handle special keys like Ctrl, Alt, also cursor keys and Backspace/Delete. On some keyboard layouts, like Swedish Ctrl is even needed to enter keys that exist directly on US keyboard. I believe that trying to handle this using the "low-level" Console.ReadKey(true) is just very hard, so the easiest and most robust way is to just to disable "console input echo" during entering password using a bit of WINAPI.
The sample below is based on answer to Read a password from std::cin question.
private enum StdHandle
{
Input = -10,
Output = -11,
Error = -12,
}
private enum ConsoleMode
{
ENABLE_ECHO_INPUT = 4
}
[DllImport("kernel32.dll", SetLastError = true)]
private static extern IntPtr GetStdHandle(StdHandle nStdHandle);
[DllImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool GetConsoleMode(IntPtr hConsoleHandle, out int lpMode);
[DllImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool SetConsoleMode(IntPtr hConsoleHandle, int dwMode);
public static string ReadPassword()
{
IntPtr stdInputHandle = GetStdHandle(StdHandle.Input);
if (stdInputHandle == IntPtr.Zero)
{
throw new InvalidOperationException("No console input");
}
int previousConsoleMode;
if (!GetConsoleMode(stdInputHandle , out previousConsoleMode))
{
throw new Win32Exception(Marshal.GetLastWin32Error(), "Could not get console mode.");
}
// disable console input echo
if (!SetConsoleMode(stdInputHandle , previousConsoleMode & ~(int)ConsoleMode.ENABLE_ECHO_INPUT))
{
throw new Win32Exception(Marshal.GetLastWin32Error(), "Could not disable console input echo.");
}
// just read the password using standard Console.ReadLine()
string password = Console.ReadLine();
// reset console mode to previous
if (!SetConsoleMode(stdInputHandle , previousConsoleMode))
{
throw new Win32Exception(Marshal.GetLastWin32Error(), "Could not reset console mode.");
}
return password;
}
I found a bug in shermy's vanilla C# 3.5 .NET solution which otherwise works a charm. I have also incorporated Damian LeszczyƄski - Vash's SecureString idea here but you can use an ordinary string if you prefer.
THE BUG: If you press backspace during the password prompt and the current length of the password is 0 then an asterisk is incorrectly inserted in the password mask. To fix this bug modify the following method.
public static string ReadPassword(char mask)
{
const int ENTER = 13, BACKSP = 8, CTRLBACKSP = 127;
int[] FILTERED = { 0, 27, 9, 10 /*, 32 space, if you care */ }; // const
SecureString securePass = new SecureString();
char chr = (char)0;
while ((chr = System.Console.ReadKey(true).KeyChar) != ENTER)
{
if (((chr == BACKSP) || (chr == CTRLBACKSP))
&& (securePass.Length > 0))
{
System.Console.Write("\b \b");
securePass.RemoveAt(securePass.Length - 1);
}
// Don't append * when length is 0 and backspace is selected
else if (((chr == BACKSP) || (chr == CTRLBACKSP)) && (securePass.Length == 0))
{
}
// Don't append when a filtered char is detected
else if (FILTERED.Count(x => chr == x) > 0)
{
}
// Append and write * mask
else
{
securePass.AppendChar(chr);
System.Console.Write(mask);
}
}
System.Console.WriteLine();
IntPtr ptr = new IntPtr();
ptr = Marshal.SecureStringToBSTR(securePass);
string plainPass = Marshal.PtrToStringBSTR(ptr);
Marshal.ZeroFreeBSTR(ptr);
return plainPass;
}
(My) nuget package to do this, based on the top answer:
install-package PanoramicData.ConsoleExtensions
Usage:
using PanoramicData.ConsoleExtensions;
...
Console.Write("Password: ");
var password = ConsolePlus.ReadPassword();
Console.WriteLine();
Project URL: https://github.com/panoramicdata/PanoramicData.ConsoleExtensions
Pull requests welcome.
Here's a version that adds support for the Escape key (which returns a null string)
public static string ReadPassword()
{
string password = "";
while (true)
{
ConsoleKeyInfo key = Console.ReadKey(true);
switch (key.Key)
{
case ConsoleKey.Escape:
return null;
case ConsoleKey.Enter:
return password;
case ConsoleKey.Backspace:
if (password.Length > 0)
{
password = password.Substring(0, (password.Length - 1));
Console.Write("\b \b");
}
break;
default:
password += key.KeyChar;
Console.Write("*");
break;
}
}
}
Jeez guys
static string ReadPasswordLine()
{
string pass = "";
ConsoleKeyInfo key;
do
{
key = Console.ReadKey(true);
if (key.Key != ConsoleKey.Enter)
{
if (!(key.KeyChar < ' '))
{
pass += key.KeyChar;
Console.Write("*");
}
else if (key.Key == ConsoleKey.Backspace && pass.Length > 0)
{
Console.Write(Convert.ToChar(ConsoleKey.Backspace));
pass = pass.Remove(pass.Length - 1);
Console.Write(" ");
Console.Write(Convert.ToChar(ConsoleKey.Backspace));
}
}
} while (key.Key != ConsoleKey.Enter);
return pass;
}
You could append your keys to an accumulating linked list.
When a backspace key is received, remove the last key from the list.
When you receive the enter key, collapse your list into a string and do the rest of your work.
I made some changes for backspace
string pass = "";
Console.Write("Enter your password: ");
ConsoleKeyInfo key;
do
{
key = Console.ReadKey(true);
// Backspace Should Not Work
if (key.Key != ConsoleKey.Backspace)
{
pass += key.KeyChar;
Console.Write("*");
}
else
{
pass = pass.Remove(pass.Length - 1);
Console.Write("\b \b");
}
}
// Stops Receving Keys Once Enter is Pressed
while (key.Key != ConsoleKey.Enter);
Console.WriteLine();
Console.WriteLine("The Password You entered is : " + pass);
I have updated Ronnie's version after spending way too much time trying to enter a password only to find out that I had my CAPS LOCK on!
With this version what ever the message is in _CapsLockMessage will "float" at the end of the typing area and will be displayed in red.
This version takes a bit more code and does require a polling loop. On my computer CPU usage about 3% to 4%, but one could always add a small Sleep() value to decrease CPU usage if needed.
private const string _CapsLockMessage = " CAPS LOCK";
/// <summary>
/// Like System.Console.ReadLine(), only with a mask.
/// </summary>
/// <param name="mask">a <c>char</c> representing your choice of console mask</param>
/// <returns>the string the user typed in</returns>
public static string ReadLineMasked(char mask = '*')
{
// Taken from http://stackoverflow.com/a/19770778/486660
var consoleLine = new StringBuilder();
ConsoleKeyInfo keyInfo;
bool isDone;
bool isAlreadyLocked;
bool isCapsLockOn;
int cursorLeft;
int cursorTop;
ConsoleColor originalForegroundColor;
isDone = false;
isAlreadyLocked = Console.CapsLock;
while (isDone == false)
{
isCapsLockOn = Console.CapsLock;
if (isCapsLockOn != isAlreadyLocked)
{
if (isCapsLockOn)
{
cursorLeft = Console.CursorLeft;
cursorTop = Console.CursorTop;
originalForegroundColor = Console.ForegroundColor;
Console.ForegroundColor = ConsoleColor.Red;
Console.Write("{0}", _CapsLockMessage);
Console.SetCursorPosition(cursorLeft, cursorTop);
Console.ForegroundColor = originalForegroundColor;
}
else
{
cursorLeft = Console.CursorLeft;
cursorTop = Console.CursorTop;
Console.Write("{0}", string.Empty.PadRight(_CapsLockMessage.Length));
Console.SetCursorPosition(cursorLeft, cursorTop);
}
isAlreadyLocked = isCapsLockOn;
}
if (Console.KeyAvailable)
{
keyInfo = Console.ReadKey(intercept: true);
if (keyInfo.Key == ConsoleKey.Enter)
{
isDone = true;
continue;
}
if (!char.IsControl(keyInfo.KeyChar))
{
consoleLine.Append(keyInfo.KeyChar);
Console.Write(mask);
}
else if (keyInfo.Key == ConsoleKey.Backspace && consoleLine.Length > 0)
{
consoleLine.Remove(consoleLine.Length - 1, 1);
if (Console.CursorLeft == 0)
{
Console.SetCursorPosition(Console.BufferWidth - 1, Console.CursorTop - 1);
Console.Write(' ');
Console.SetCursorPosition(Console.BufferWidth - 1, Console.CursorTop - 1);
}
else
{
Console.Write("\b \b");
}
}
if (isCapsLockOn)
{
cursorLeft = Console.CursorLeft;
cursorTop = Console.CursorTop;
originalForegroundColor = Console.ForegroundColor;
Console.ForegroundColor = ConsoleColor.Red;
Console.Write("{0}", _CapsLockMessage);
Console.CursorLeft = cursorLeft;
Console.CursorTop = cursorTop;
Console.ForegroundColor = originalForegroundColor;
}
}
}
Console.WriteLine();
return consoleLine.ToString();
}
Here is my simple version.
Every time you hit a key, delete all from console and draw as many '*' as the length of password string is.
int chr = 0;
string pass = "";
const int ENTER = 13;
const int BS = 8;
do
{
chr = Console.ReadKey().KeyChar;
Console.Clear(); //imediately clear the char you printed
//if the char is not 'return' or 'backspace' add it to pass string
if (chr != ENTER && chr != BS) pass += (char)chr;
//if you hit backspace remove last char from pass string
if (chr == BS) pass = pass.Remove(pass.Length-1, 1);
for (int i = 0; i < pass.Length; i++)
{
Console.Write('*');
}
}
while (chr != ENTER);
Console.Write("\n");
Console.Write(pass);
Console.Read(); //just to see the pass
If I understand this correctly, you're trying to make backspace delete both the visible * character on screen and the cached character in your pass variable?
If so, then just change your else block to this:
else
{
Console.Write("\b");
pass = pass.Remove(pass.Length -1);
}
I just improve code from ask I simple and just work
string pass = ""; //create empty password string
Console.Write("Enter your password: ");
ConsoleKeyInfo key;
int passLen = 0; // base password length
do
{
key = Console.ReadKey(true); //reading keyboard key
if (key.Key == ConsoleKey.Escape) Environment.Exit(0); // If key is escape console will close (optional)
if (key.Key != ConsoleKey.Backspace && key.Key != ConsoleKey.Enter) // on key with is not bacspase and enter
{
pass += key.KeyChar; //password string add key value
Console.Write("*"); // and print star as masked char
passLen++; // upgrading password length
}
else if (passLen > 0 && key.Key == ConsoleKey.Backspace) //if password have a any symbol and u press Backspace
{
Console.Write("\b \b"); //Backspace delete star symbol and coursor back in line
passLen--; // password length is one less
pass = pass[0..^1]; // new string passowrd is string without last charter
}
}
while (key.Key != ConsoleKey.Enter); // if you press enter this stop execute with your password
Console.WriteLine();
Console.WriteLine("The Password You entered is : " + pass);
string pass = "";
Console.WriteLine("Enter your password: ");
ConsoleKeyInfo key;
do {
key = Console.ReadKey(true);
if (key.Key != ConsoleKey.Backspace) {
pass += key.KeyChar;
Console.Write("*");
} else {
Console.Write("\b \b");
char[] pas = pass.ToCharArray();
string temp = "";
for (int i = 0; i < pass.Length - 1; i++) {
temp += pas[i];
}
pass = temp;
}
}
// Stops Receving Keys Once Enter is Pressed
while (key.Key != ConsoleKey.Enter);
Console.WriteLine();
Console.WriteLine("The Password You entered is : " + pass);

Categories

Resources