我能否在批处理文件中屏蔽输入文本?

106
我正在编写一个批处理文件来执行其他程序。在这种情况下,我需要提示输入密码。有没有办法掩盖输入的文本?我不需要打印*******字符代替输入字符。Linux的密码提示行为(输入时不显示任何内容)已经足够了。

我正在编写批处理文件以执行其他程序。在这种情况下,我需要提示输入密码。是否有一种方法可以掩盖输入文本?我不需要用*******字符代替输入字符。类似Linux的密码提示行为(输入时不显示任何内容)就足够了。

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

这将读取输入,但我无法使用此方法掩盖文本。


19个回答

170

是的 - 我晚了4年。

但我找到了一种方法,在不创建外部脚本的情况下用一行代码调用powershell命令,通过从批处理文件中调用。

感谢TessellatingHeckler - 无需将输出写入文本文件(我将powershell命令设置为一个变量,因为在for循环内使用一长串命令看起来很混乱)。

@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%

最初我将其编写为输出到文本文件,然后从该文本文件读取。但上述方法更好。在一行极长且难以理解的代码中:

@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%

我来为你解析 - 你可以使用插入符号^将它分成几行,这样更加美观...

@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%

本文解释了PowerShell命令的含义。它使用Read-Host -AsSecureString获取输入,然后以下两行将安全字符串转换回纯文本,输出(明文密码)随后使用>.tmp.txt发送到文本文件。该文件随后被读入变量并删除。


37
非常聪明。但是将纯文本密码保存到磁盘的方式让我感到不舒服。可以通过使用以下方法避免这种情况:for /f "usebackq tokens=*" %%p in (powershell -Command "$pword = read-host 'Enter Password' -AsSecureString ; $BSTR=[System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($pword); [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($BSTR)") do set password=%%p,然后使用 echo %password%。注意:我无法正确转义回撤字符 - 在 "powershell" 前面和 ")" 结尾前都应该有一个反引号,但应该只有一个反引号。 - TessellatingHeckler
有没有可能阻止这个方法在每次按键时回显“*”(星号)?也就是说,我不希望观察者知道密码的长度。 - Sam Hasler
我不确定,现在也无法检查。这肯定可以在此脚本的Powershell级别上实现。我确信在powershell中有一些方法可以抑制输出。 - unclemeat
1
@RegieBaguio 不,你需要启用Powershell。看一下npocmaka的纯批处理解决方案。它更加复杂,但应该适合你。 - unclemeat
1
我在批处理文件中使用了enabledelayedexpansion。问题是,当我的密码中有感叹号时,它不起作用,除非我记得用插入符号转义它。你有什么想法吗? - Rod
显示剩余3条评论

51

在XP和Server 2003及以下版本中,您可以利用另一个包含的工具(VBScript)-以下两个脚本可以完成您想要的工作。

首先是getpwd.cmd

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

接下来是getpwd.vbs文件:

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

getpwd.vbs使用密码对象从用户输入密码并输出到标准输出(下一段将解释为什么不会在终端中显示)。

getpwd.cmd命令脚本有点棘手,但基本上是这样工作的。

"<nul: set /p passwd=Password: "命令的效果是输出提示符而没有换行符-这是模拟bash shell中的"echo -n"命令的一个狡猾的方法。它将passwd设置为空字符串作为不相关的副作用,并且不等待输入,因为它正在从nul:设备中获取输入。

"for /f "delims=" %%i in ('cscript /nologo getpwd.vbs') do set passwd=%%i"语句是最棘手的部分。它在没有微软“广告”的情况下运行VBScript,因此输出的唯一一行是密码(来自VBScript的"Wscript.StdOut.WriteLine strPassword")。

将定界符设置为空是捕获具有空格的整个输入行所必需的,否则您只会得到第一个单词。 "for ... do set ..."部分将passwd设置为从VBScript输出的实际密码。

然后我们输出一个空行(以终止"Password: "行),在代码运行后,密码将在passwd环境变量中。


如前所述,scriptpw.dll仅适用于XP/2003。为解决这个问题,您可以从XP/2003系统的Windows\System32文件夹复制scriptpw.dll文件到自己系统的Winnt\System32Windows\System32文件夹中。复制DLL后,您需要通过运行以下命令来注册它:

regsvr32 scriptpw.dll
要想在Vista及更高版本的操作系统上成功注册DLL,你需要管理员权限。我并没有检查这样做的合法性,所以请小心。


如果您不是非常热衷于试图追踪和注册旧的DLL文件(出于方便或合法原因),还有另一种方法。较新版本的Windows(不需要所需的DLL文件)应该为您提供了Powershell。

事实上,您确实应该考虑升级脚本并充分使用它,因为它是比 cmd.exe 更强大的脚本语言。但是,如果您想保持大部分代码作为 cmd.exe 脚本(例如如果您有许多不想转换的代码),您可以使用相同的技巧。

首先,修改 cmd 脚本,使其调用Powershell而不是CScript:

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

PowerShell脚本同样简单:

$password = Read-Host "Enter password" -AsSecureString
$password = [Runtime.InteropServices.Marshal]::SecureStringToBSTR($password)
$password = [Runtime.InteropServices.Marshal]::PtrToStringAuto($password)
echo $password
尽管需要进行一些编组才能获取实际的密码文本,但请记住,要在计算机上运行本地未签名的PowerShell脚本,您可能需要修改执行策略,例如:从(严格但非常安全的)默认设置中修改为以下内容:
set-executionpolicy remotesigned

从PowerShell本身内部执行。


2
哇,那个<nul: set /p 真的很酷。到目前为止我不知道它:-)。但是VBScript对我不起作用:Microsoft VBScript运行时错误:ActiveX组件无法创建对象:'ScriptPW.Password'。我在这里使用的是Windows 7 Beta x64。 - Joey
有趣的是,这个应该从XP开始就可用。如果在那里无法工作(或者你必须做一些其他的技巧才能让它工作),我怀疑Win7还没有完全完成。进行了一些研究后发现原因,详见我的更新。 - paxdiablo
2
不错。还值得注意的是,您可以将VBS文件替换为任何可从命令行调用的等效语言。例如,您可以将cscript部分替换为:'python -c "from getpass import getpass; pwd = getpass(); print pwd;"' - Cerin
1
当这个问题还是新问题时,我曾经使用过这种方法-- 我在下面回答了一种方法链接,可以不需要额外的脚本来实现。 - unclemeat
很遗憾,这个答案已经过时了,因为 ScriptPW.Password 对象只存在于 Windows XP 中。更新的版本没有它。 - Bill_Stewart
1
@Bill:因此,我的部分开始是“现在,不幸的是……” :-) 但是,我已经添加了更多选项到答案中,希望能够使它更好。具体来说,使用PowerShell而不是scriptpw.dll - paxdiablo

26

1.纯批处理方案,滥用XCOPY命令及其/P /L开关,在这里找到(这里可以找到一些改进):

:: Hidden.cmd
::Tom Lavedas, 02/05/2013, 02/20/2013
::Carlos, 02/22/2013
::https://groups.google.com/forum/#!topic/alt.msdos.batch.nt/f7mb_f99lYI


@Echo Off
:HInput
SetLocal EnableExtensions EnableDelayedExpansion
Set "FILE=%Temp%.\T"
Set "FILE=.\T"
Keys List >"%File%"
Set /P "=Hidden text ending with Ctrl-C?: " <Nul
Echo.
Set "HInput="
:HInput_
For /F "tokens=1* delims=?" %%A In (
 '"Xcopy /P /L "%FILE%" "%FILE%" 2>Nul"'
) Do (
  Set "Text=%%B"
  If Defined Text (
    Set "Char=!Text:~1,1!"
    Set "Intro=1"
    For /F delims^=^ eol^= %%Z in ("!Char!") Do Set "Intro=0"
    Rem If press Intro
    If 1 Equ !Intro! Goto :HInput#
    Set "HInput=!HInput!!Char!"
  )
)
Goto :HInput_
:HInput#
Echo(!HInput!
Goto :Eof 

1.2 基于替换命令的另一种方法

@Echo Off
SetLocal EnableExtensions EnableDelayedExpansion

Set /P "=Enter a Password:" < Nul
Call :PasswordInput
Echo(Your input was:!Line!

Goto :Eof

:PasswordInput
::Author: Carlos Montiers Aguilera
::Last updated: 20150401. Created: 20150401.
::Set in variable Line a input password
For /F skip^=1^ delims^=^ eol^= %%# in (
'"Echo(|Replace.exe "%~f0" . /U /W"') Do Set "CR=%%#"
For /F %%# In (
'"Prompt $H &For %%_ In (_) Do Rem"') Do Set "BS=%%#"
Set "Line="
:_PasswordInput_Kbd
Set "CHR=" & For /F skip^=1^ delims^=^ eol^= %%# in (
'Replace.exe "%~f0" . /U /W') Do Set "CHR=%%#"
If !CHR!==!CR! Echo(&Goto :Eof
If !CHR!==!BS! (If Defined Line (Set /P "=!BS! !BS!" <Nul
Set "Line=!Line:~0,-1!"
)
) Else (Set /P "=*" <Nul
If !CHR!==! (Set "Line=!Line!^!"
) Else Set "Line=!Line!!CHR!"
)
Goto :_PasswordInput_Kbd

2. 使用 HTA 弹出窗口的密码提交器 . 这是一个混合 .bat/jscript/mshta 文件,应该保存为 .bat

<!-- :
:: PasswordSubmitter.bat
@echo off
for /f "tokens=* delims=" %%p in ('mshta.exe "%~f0"') do (
    set "pass=%%p"
)

echo your password is %pass%
exit /b
-->

<html>
<head><title>Password submitter</title></head>
<body>

    <script language='javascript' >
        window.resizeTo(300,150);
        function entperPressed(e){
                if (e.keyCode == 13) {
                    pipePass();
                }
        }
        function pipePass() {
            var pass=document.getElementById('pass').value;
            var fso= new ActiveXObject('Scripting.FileSystemObject').GetStandardStream(1);
            close(fso.Write(pass));

        }
    </script>

    <input type='password' name='pass' size='15' onkeypress="return entperPressed(event)" ></input>
    <hr>
    <button onclick='pipePass()'>Submit</button>

</body>
</html>

3.一个自编的 .net 混合程序。同样应该保存为 .bat 文件。与其他解决方案不同的是,它将创建/编译一个小的 .exe 文件,可以调用它(如果你愿意,也可以删除它)。但需要安装 .net framework,不过这并不是问题:

@if (@X)==(@Y) @end /* JScript comment
@echo off
setlocal enableDelayedExpansion

for /f "tokens=* delims=" %%v in ('dir /b /s /a:-d  /o:-n "%SystemRoot%\Microsoft.NET\Framework\*jsc.exe"') do (
   set "jsc=%%v"
)

if not exist "%~n0.exe" (
    "%jsc%" /nologo /out:"%~n0.exe" "%~dpsfnx0"
)

for /f "tokens=* delims=" %%p in ('"%~n0.exe"') do (
    set "pass=%%p"
)

echo your password is !pass!

endlocal & exit /b %errorlevel%

*/



import System;



var pwd = "";
var key;

Console.Error.Write("Enter password: ");

        do {
           key = Console.ReadKey(true);

           if ( (key.KeyChar.ToString().charCodeAt(0)) >= 20 && (key.KeyChar.ToString().charCodeAt(0) <= 126) ) {
              pwd=pwd+(key.KeyChar.ToString());
              Console.Error.Write("*");
           }

           if ( key.Key == ConsoleKey.Backspace && pwd.Length > 0 ) {
               pwd=pwd.Remove(pwd.Length-1);
               Console.Error.Write("\b \b");
           }


        } while (key.Key != ConsoleKey.Enter);
        Console.Error.WriteLine();
        Console.WriteLine(pwd);

6
我的“hacky sense”在发出警报,特别是在混合物上! - Mark K Cowan
2
那个第一个解决方案非常聪明 - 很好 +1。 - unclemeat
对于那些不熟悉批处理脚本的人,请提供一个带有用户名和密码提示的可用示例,特别是带有xcopy版本的示例。谢谢。 - velkoon
1
@npocmaka 1.2方法的最新版本(更新日期:20150405)在此处可找到:http://consolesoft.com/batch/password_input/password_input.cmd - carlos
1
太棒了。喜欢mshta解决方案。 - schumacher574
显示剩余5条评论

17

我可能会这样做:

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

当你输入密码后,屏幕会在输入完成后清空。

请注意,如果批处理文件从命令提示符中执行,则输入的密码将存储在CMD历史记录中(感谢@Mark K Cowan)。

如果这还不够好,我要么会切换到Python,要么会编写一个可执行文件而不是脚本。

我知道这些都不是完美的解决方案,但也许其中一个对你来说已经足够好了 :)


5
请注意,如果批处理文件是从命令提示符中执行的,则输入的密码将存储在CMD历史记录中。虽然你已经说cmd不是做这项工作的正确工具,但不要投反对票。 - Mark K Cowan
如果您不需要控制台中的命令历史记录,可以在 cls 后立即运行 doskey /reinstall 来清除先前的命令缓冲区。 - RuntimeException
1
如果您不想使用doskey /reinstall并清除命令历史记录,另一个选择是在单独的终端窗口中运行上述代码并立即退出。请注意,它仍然不能用星号替换您键入的字符串。如果您不想运行powershell,则上述解决方案是一个选项。 C:\> start“console”cmd /c ireadconsole.bat - RuntimeException
另一个选择是使用类似以下的内容:SET /P pw=C:\^>^ 转义了 >,这样密码提示就会看起来像标准的 cmd 控制台提示。 - nmbell

16

您可以使用ReadFormattedLine子程序进行各种格式化输入。例如,以下命令读取8个字符的密码,在屏幕上显示星号,并自动继续而无需按Enter键:

call :ReadFormattedLine password="********" /M "Enter password (8 chars): "

这个子程序是用纯批处理编写的,因此不需要任何额外的程序,并且它允许进行多种格式化输入操作,如仅读取数字、将字母转换为大写等。您可以从Read a line with specific format下载ReadFormattedLine子程序。

编辑于2018年08月18日输入“隐形”密码的新方法

FINDSTR命令存在一个奇怪的bug,当使用该命令以彩色显示字符并将其输出重定向到CON设备时,就会发生这种情况。有关如何使用FINDSTR命令以彩色显示文本的详细信息,请参见此主题

当以这种方式重定向FINDSTR命令的输出到CON时,在所需颜色中输出文本后,会出现一些奇怪的情况:在输出之后的所有文本都会作为“隐形”字符输出,尽管更精确的描述是该文本会作为黑色文本输出在黑色背景上。如果使用COLOR命令重置整个屏幕的前景和背景颜色,则原始文本将出现。但是,当文本是“隐形”的时候,我们可以执行SET /P命令,这样输入的所有字符都不会显示在屏幕上。

@echo off
setlocal

set /P "=_" < NUL > "Enter password"
findstr /A:1E /V "^$" "Enter password" NUL > CON
del "Enter password"
set /P "password="
cls
color 07
echo The password read is: "%password%"

6

另一种选择是我的EditV32(x86)或EditV64(x64)命令行工具。例如:

editv32 -m -p "Password: " PWD

-m表示“掩码输入”,-p表示提示符。用户的输入存储在PWD环境变量中。您可以在此处获取它:

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


5
这并不是唯一的闭源解决方案(Windows就是闭源的!)。当然,你可以选择不使用它... - Bill_Stewart
如果您对缺少源代码感到困扰,请参阅我的其他答案,有关 ReadLine.exe - Bill_Stewart
不错!许可证允许您使用它。此外,易于集成。只需处理退出代码1。命令行出现错误为2,输入行为空为3,环境变量未更改为4,程序通过Ctrl-C中止。 - James Jithin
注意:editenv32/editenv64和readline实用程序已被开源的editenv实用程序取代。 - Bill_Stewart

6
您可以通过以下方式将前景色设置为黑色:

请注意保留原有的HTML标记。

:: 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

享受吧!!!


仅供参考:只有在 echo off 的情况下才能按预期工作,否则 set 命令已经改变了颜色。当然,如果背景颜色已经是黑色(默认情况下是这样的,但您不能依赖它)。不错的发现(尽管我怀疑它在2009年就能起作用)。 - Stephan

5
如果您已经安装了Python,可以使用以下方法:
for /f "delims=" %%A in ('python -c "import getpass; print(getpass.getpass('Enter the CVS password: '));"') do @set CVSPASS=%%A
echo PASS: %CVSPASS%

输出内容:

Enter the CVS password:
PASS: 123

4
如果你不喜欢没有源代码的情况,我还有另一个选择。
@echo off
for /f "delims=" %%p in ('ReadLine -h -p "Enter password: "') do set PWD=%%p
echo You entered: %PWD%

您可以从https://westmesatech.com/?page_id=49获取它。源代码已包含在内。

注意:editenv32/editenv64和readline实用程序已被开源的editenv实用程序取代。 - Bill_Stewart

2

我使用了Blorgbeard提供的解决方案,我认为它非常好。然后我进行了以下增强:

  1. 搜索ansicon
  2. 下载zip文件和示例文本文件。
  3. 安装(即将2个文件复制到system32中)

像这样使用它:

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

这将在输入密码时将控制台切换到灰色,完成后再切换回来。ESC实际上应该是一个不可打印的字符,您可以从下载的示例文本文件中复制(在记事本中看起来像左箭头),然后粘贴到批处理文件中。您可以使用示例文本文件查找所有颜色组合的代码。

如果您不是计算机管理员,则可能能够将文件安装在非系统目录中,然后在调用程序并使用转义序列之前,在脚本中将目录添加到PATH中。如果您需要一个非管理员可分发的仅包含几个文件的软件包,则这甚至可能是当前目录。


我无法测试这个,但是我希望输入的密码会出现在命令历史记录中。 - Mark K Cowan
@MarkKCowan 这正是发生的事情。 - sjngm

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接