8. User Interaction & Menus¶
What & why¶
Interactive scripts — installers, admin tools, "pick an environment" prompts — need to read choices reliably and re-prompt on bad input. Batch gives you set /p (free text) and choice (single keypress with validation). Building a solid menu loop is a rite of passage.
set /p — a line of text¶
set "name="
set /p "name=Your name: "
if not defined name (
echo Name is required.
exit /b 1
)
echo Hello, %name%.
Remember: on empty Enter, set /p keeps the variable's previous value — so clear it first (set "name=") and then check if defined.
set /p from a file (read one line)¶
set /p is not safe for secrets¶
The typed text is visible on screen. There's no masked input in pure batch. For passwords, call PowerShell:
for /F "usebackq delims=" %%p in (`powershell -NoProfile -Command "$p=Read-Host -AsSecureString; [Runtime.InteropServices.Marshal]::PtrToStringAuto([Runtime.InteropServices.Marshal]::SecureStringToBSTR($p))"`) do set "pw=%%p"
(Even then the value ends up in a plain variable — avoid handling secrets in batch at all where you can.)
choice — one validated keypress¶
choice sets errorlevel to the 1-based position of the chosen letter in /C. Test high to low.
| Switch | Meaning |
|---|---|
/C ABC |
valid keys (default YN) |
/M "text" |
prompt message |
/N |
don't show the [A,B,C]? list |
/CS |
case-sensitive |
/D A |
default key if /T expires |
/T 10 |
timeout in seconds (needs /D) |
choice /C YNC /N /M "Deploy to [Y]es / [N]o / [C]ancel? "
if errorlevel 3 ( echo Cancelled & exit /b 1 )
if errorlevel 2 ( echo Skipped & goto next )
if errorlevel 1 ( echo Deploying & call :deploy )
Note
choice is a real executable (choice.exe), present on Windows 7+. On ancient systems you'd fake it with set /p + validation.
Timeout example (unattended-friendly):
A robust menu loop¶
@echo off
setlocal EnableExtensions
:menu
cls
echo ============================
echo Maintenance Menu
echo ============================
echo 1. Clear temp files
echo 2. Restart service
echo 3. Show disk usage
echo Q. Quit
echo.
choice /C 123Q /N /M "Choose: "
set "sel=%errorlevel%"
if %sel%==4 goto :quit
if %sel%==1 ( call :clear_temp & pause & goto :menu )
if %sel%==2 ( call :restart_svc & pause & goto :menu )
if %sel%==3 ( call :disk_usage & pause & goto :menu )
goto :menu
:quit
echo Bye.
exit /b 0
:clear_temp
echo Clearing %TEMP% ...
rem del /q "%TEMP%\*" 2>nul
goto :eof
:restart_svc
echo (would restart the service)
goto :eof
:disk_usage
wmic logicaldisk get caption,freespace,size 2>nul
goto :eof
Why choice over set /p here: it rejects invalid keys automatically, needs no Enter, and can't be empty. The set "sel=%errorlevel%" snapshot avoids %errorlevel% shifting under you.
The set /p menu variant (when you need multi-char options)¶
:menu
set "sel="
set /p "sel=Choose an action (add / remove / list / quit): "
if /i "%sel%"=="quit" goto :quit
if /i "%sel%"=="add" ( call :add & goto :menu )
if /i "%sel%"=="remove" ( call :remove & goto :menu )
if /i "%sel%"=="list" ( call :list & goto :menu )
echo Unknown option: "%sel%"
goto :menu
Confirmations for dangerous actions¶
:confirm_delete
choice /C YN /N /M "Really delete %count% files? This cannot be undone. [y/n] "
if errorlevel 2 ( echo Aborted. & exit /b 1 )
rem proceed...
For extra friction (type the word):
set "typed="
set /p "typed=Type DELETE to confirm: "
if /i not "%typed%"=="DELETE" ( echo Aborted. & exit /b 1 )
Pausing and "press any key"¶
pause &rem "Press any key to continue . . ."
pause >nul &rem same, but silent
timeout /t 5 &rem wait 5s, or any key
timeout /t 5 /nobreak &rem wait 5s, ignore keypresses
timeout /t 5 /nobreak >nul &rem ...silently
timeout fails if stdin is redirected (e.g. in some CI) — use ping -n 6 127.0.0.1 >nul as a portable "sleep 5."
Common mistakes¶
- Not clearing the variable before
set /p— stale value on empty Enter. - Testing
choiceerrorlevels low-to-high —if errorlevel 1catches everything. Go high-to-low, or snapshot%errorlevel%and use==. choice /C YNthenif %errorlevel%==0—choicenever returns 0 (except on Ctrl+C / error). Valid results start at 1.set /pfor passwords — visible on screen.timeoutin redirected/no-console context — "ERROR: Input redirection is not supported." Use thepingtrick.- Menu loop with no
cls/redraw — output piles up; user loses the menu. goto :menufrom inside aforloop — you can't resume the loop; structure the menu outside loops.- Forgetting
/Nonchoicewhen you've drawn your own menu — you get a duplicate[1,2,3]?.
Exercises¶
- Prompt for a username, rejecting empty input, and loop until something is entered.
- Build a Y/N confirmation subroutine
:ask "question"that returns0for yes,1for no viaexit /b. - Build the maintenance menu above for real: option 3 shows disk usage; others just
echoa placeholder. Usechoice. - Add a 15-second auto-quit to the menu using
choice /D Q /T 15. - Make a "type the environment name to deploy" prompt that only proceeds if the user types
productionexactly (case-insensitive). - Replace a
timeout /t 3with a redirection-safe 3-second wait and verify it works when the script's stdin is redirected fromnul.
Recap¶
set /p "x=prompt: "reads a line; clearxfirst and checkif defined x(empty Enter keeps the old value).choice /C keys /N /M "..."reads one validated keypress;errorlevel= 1-based position; test high-to-low or snapshot and use==.- Menu = a
:label+ draw +choice+ dispatch +goto :label, kept outside anyforloop, withclseach pass. - No masked input in batch — shell out to PowerShell for secrets, or don't.
timeout /t N /nobreak >nulto sleep; fall back toping -n N+1 127.0.0.1 >nulwhen stdin is redirected.