Can I mask an input text in a bat file?

Viewed 237502

I am writing a batch file to execute some other programs. In this case I need to prompt for a password. Do I have any way to mask the input text? I don't need to print ******* characters instead of input characters. Linux's Password prompt behavior (Print nothing while typing) is enough.

@echo off
SET /P variable=Password : 
echo %variable%
Pause

This will read the input but I can't mask the text using this approach.

18 Answers

Yes - I am 4 years late.

But I found a way to do this in one line without having to create an external script; by calling powershell commands from a batch file.

Thanks to TessellatingHeckler - without outputting to a text file (I set the powershell command in a variable, because it's pretty messy in one long line inside a for loop).

@echo off
set "psCommand=powershell -Command "$pword = read-host 'Enter Password' -AsSecureString ; ^
    $BSTR=[System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($pword); ^
        [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($BSTR)""
for /f "usebackq delims=" %%p in (`%psCommand%`) do set password=%%p
echo %password%

Originally I wrote it to output to a text file, then read from that text file. But the above method is better. In one extremely long, near incomprehensible line:

@echo off
powershell -Command $pword = read-host "Enter password" -AsSecureString ; $BSTR=[System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($pword) ; [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($BSTR) > .tmp.txt & set /p password=<.tmp.txt & del .tmp.txt
echo %password%

I'll break this down - you can split it up over a few lines using caret ^, which is much nicer...

@echo off
powershell -Command $pword = read-host "Enter password" -AsSecureString ; ^
    $BSTR=[System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($pword) ; ^
        [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($BSTR) > .tmp.txt 
set /p password=<.tmp.txt & del .tmp.txt
echo %password%

This article explains what the powershell commands are doing; essentially it gets input using Read-Host -AsSecureString - the following two lines convert that secure string back into plain text, the output (plaintext password) is then sent to a text file using >.tmp.txt. That file is then read into a variable and deleted.

Up to XP and Server 2003, you can make use of another included tool (VBScript) - the following two scripts do the job you want.

First, getpwd.cmd:

@echo off
<nul: set /p passwd=Password: 
for /f "delims=" %%i in ('cscript /nologo getpwd.vbs') do set passwd=%%i
echo.

Then, getpwd.vbs:

Set oScriptPW = CreateObject("ScriptPW.Password")
strPassword = oScriptPW.GetPassword()
Wscript.StdOut.WriteLine strPassword

The getpwd.vbs simply uses the password object to input the password from the user and then print it to standard output (the next paragraph will explain why that doesn't show up in the terminal).

The getpwd.cmd command script is a bit trickier but it basically works as follows.

The effect of the "<nul: set /p passwd=Password: " command is to output the prompt with no trailing newline character - it's a sneaky way to emulate the "echo -n" command from the bash shell. It sets passwd to an empty string as an irrelevant side effect and doesn't wait for input since it's taking its input from the nul: device.

The "for /f "delims=" %%i in ('cscript /nologo getpwd.vbs') do set passwd=%%i" statement is the trickiest bit. It runs the VBScript with no Microsoft "advertising", so that the only line output is the password (from the VBscript "Wscript.StdOut.WriteLine strPassword".

Setting the delimiters to nothing is required to capture an entire input line with spaces, otherwise you just get the first word. The "for ... do set ..." bit sets passwd to be the actual password output from the VBScript.

Then we echo a blank line (to terminate the "Password: " line) and the password will be in the passwd environment variable after the code has run.


Now, as mentioned, scriptpw.dll is available only up to XP/2003. In order to rectify this, you can simply copy the scriptpw.dll file from the Windows\System32 folder of an XP/2003 system to the Winnt\System32 or Windows\System32 folder on your own system. Once the DLL has been copied, you will need to register it by running:

regsvr32 scriptpw.dll

To successfully register the DLL on Vista and later, you will need administrator privileges. I haven't examined the legality of such a move so cave lector.


If you're not overly keen on trying to track down and register older DLL files (for convenience or legal reasons), there is another way. Later versions of Windows (the ones that don't have the required DLL) should have Powershell available to you.

And, in fact, you really should consider upgrading your scripts to use it fully since it's a much more capable scripting language than cmd.exe. However, if you want to keep the bulk of your code as cmd.exe scripts (such as if you have a lot of code that you don't want to convert), you can use the same trick.

First, modify the cmd script so it calls Powershell rather than CScript:

@echo off
for /f "delims=" %%i in ('powershell -file getpwd.ps1') do set passwd=%%i

The Powershell script is equally simple:

$password = Read-Host "Enter password" -AsSecureString
$password = [Runtime.InteropServices.Marshal]::SecureStringToBSTR($password)
$password = [Runtime.InteropServices.Marshal]::PtrToStringAuto($password)
echo $password

although with some marshalling to get the actual password text.

Remember that, to run local unsigned Powershell scripts on your machine, you may need to modify the execution policy from the (draconian, though very safe) default, with something like:

set-executionpolicy remotesigned

from within Powershell itself.

I would probably just do:

..
echo Before you enter your password, make sure no-one is looking!
set /P password=Password:  
cls
echo Thanks, got that.
.. 

So you get a prompt, then the screen clears after it's entered.

Note that the entered password will be stored in the CMD history if the batch file is executed from a command prompt (Thanks @Mark K Cowan).

If that wasn't good enough, I would either switch to python, or write an executable instead of a script.

I know none of these are perfect soutions, but maybe one is good enough for you :)

another alternative is my EditV32 (x86) or EditV64 (x64) command-line tools. For example:

editv32 -m -p "Password: " PWD

-m means "masked input" and -p is the prompt. The user's input is stored in the PWD environment variable. You can get it here:

https://westmesatech.com/?page_id=19

You can set the forground color to black such:

:: see https://stackoverflow.com/questions/33293220/what-is-the-fastest-color-function-in-batch
@echo off
for /F %%a in ('echo prompt $E ^| cmd') do set "ESC=%%a"
set color_white=%ESC%[37m
set color_black=%ESC%[30m
echo(
set /p user=Username:
set /p pass=password:%color_black%
echo %color_white%
echo blablablba

Enjoy!!! C.

I used Blorgbeard's above solution which is actually great in my opinion. Then I enhanced it as follows:

  1. Google for ansicon
  2. Download zip file and sample text file.
  3. Install (that means copy 2 files into system32)

Use it like this:

@echo off
ansicon -p
set /p pwd=Password:ESC[0;37;47m
echo ESC[0m

This switches the console to gray on gray for your password entry and switches back when you are done. The ESC should actually be an unprintable character, which you can copy over from the downloaded sample text file (appears like a left-arrow in Notepad) into your batch file. You can use the sample text file to find the codes for all color combinations.

If you are not admin of the machine, you will probably be able to install the files in a non-system directory, then you have to append the directory to the PATH in your script before calling the program and using the escape sequences. This could even be the current directory probably, if you need a non-admin distributable package of just a few files.

I wrote an open source program called editenv that replaces my older editv32/editv64 utilities:

https://github.com/Bill-Stewart/editenv

The --maskinput (-m) option[*] lets you hide (mask) the typed input and has a configurable character (default character is *); e.g.:

editenv -m -p "Password: " PWD

The --prompt (-p) option lets you specify an input prompt. The above will display a Password: prompt and wait for you to enter something. Typed characters will appear as *. Pressing Ctrl+C will end the program with an exit code of 1223.

Download is here:

https://github.com/Bill-Stewart/editenv/releases

[*] Note that the --maskinput (-m) option is not secure -- the entered string is entered as plain-text in the environment. This feature is for convenience only.

Ok so I just found a really decent work around to this issue. CMDKEY works on windows 7+ and allows you to enter details into the windows cred manager from the cmd prompt, and if you use the /pass switch it provides its own password entry without exposing the password. It does not present ***** as you type but the password is hidden.

The script below requests the username as normal and creates a var called "%username%" it then checks to see if the cred manager already has an entry for the target server. If it does it just maps the drive to the usename and does not request a password. If there is no entry it then uses the CMDKEY to prompt for password and stores it in cred manager.

    set /p username="Enter School Username: "
    CMDKEY /list:SERVERNAME | FIND "NONE" > nul 2>&1
    IF %ERRORLEVEL% NEQ 0 GOTO MAPDRIVES
    CMDKEY /ADD:SERVERNAME /username:%username% /pass
    :MAPDRIVES
    NET USE Y: \\SERVERNAME\SHARE\%username%

I couldn't get @unclemeat's answer to work on Windows 10 21H1 systems unfortunately. I just got: [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($BSTR)"" was unexpected at this time.

But he set me on the right track.

So here is my variant:

Configuration at top of script:

@ECHO OFF
:: Don't leak variables to command shell
SETLOCAL

    REM service user for backend services - comment out to default to LOCALSYSTEM
SET WINDOWS_SVC_USERNAME=svcadmin

Somewhere at the bottom after checking for admin privileges and after creating the service:

SETLOCAL ENABLEDELAYEDEXPANSION

:: Read password for service user
IF DEFINED WINDOWS_SVC_USERNAME (
    SET PWR=powershell -Command "$secureString = Read-Host 'Enter password for service user \".\%WINDOWS_SVC_USERNAME%\"' -AsSecureString -ErrorAction Stop; $ptr = [System.Runtime.InteropServices.Marshal]::SecureStringToGlobalAllocUnicode($secureString); $plain = [System.Runtime.InteropServices.Marshal]::PtrToStringUni($ptr); [System.Runtime.InteropServices.Marshal]::ZeroFreeGlobalAllocUnicode($ptr); Write-Host $plain;"
    FOR /F "usebackq delims=" %%P IN (`!PWR!`) DO SET WINDOWS_SVC_PASSWORD=%%P
    IF DEFINED WINDOWS_SVC_PASSWORD (
        IF ["!WINDOWS_SVC_PASSWORD!"] NEQ [""] (
            IF ["!WINDOWS_SVC_PASSWORD!"] NEQ [" "] (
                :: The password is now set correctly
                ECHO "Using ".\%WINDOWS_SVC_USERNAME%" as service user..."
                sc config  prod!WINDOWS_SVC_INFIX!rest!INDEX! obj= ".\!WINDOWS_SVC_USERNAME!" password= "!WINDOWS_SVC_PASSWORD!"
            ) ELSE (
                ECHO "ERROR -- Password possibly empty or PowerShell threw an exception."
                SET "WINDOWS_SVC_USERNAME="
            )
        ) ELSE (
            ECHO "ERROR -- Could not retrieve password! Password is possibly empty."
            SET "WINDOWS_SVC_USERNAME="
        )
    ) ELSE (
        ECHO "ERROR -- No password given or PowerShell is not available."
        SET "WINDOWS_SVC_USERNAME="
    )
) ELSE (
    ECHO "No service user specified - keeping default "LOCALSYSTEM" user..."
)

Of course the sc config prod!WINDOWS_SVC_INFIX!rest!INDEX! is just copied from my for-loop further down and needs adaption to your specific case. Use ECHO password read: "!WINDOWS_SVC_PASSWORD!" for debugging instead.

This is not yet tested in the wild, so please handle with care.

Related