Skip to content
FreeDOSDeep Dive Published Updated 6 min readViews unavailable

FreeDOS CHOICE: A Precise Input and ERRORLEVEL Contract for Batch Jobs

Build FreeDOS menus around CHOICE's accepted keys, timeout defaults, localization, and numeric ERRORLEVEL results without unsafe fall-through.

CHOICE gives a FreeDOS batch file a constrained keyboard-input operation: wait for one key from an allowed set, then return a numeric result that the script can branch on. It is not a text-input parser, a security boundary, or a transaction. A reliable menu depends on understanding its accepted-key list, one-based choice result, timeout default, special abort/error results, and FreeCOM’s IF ERRORLEVEL comparison semantics.

FreeDOS Help documents both CHOICE and _CHOICE as external programs, unlike built-in FreeCOM commands such as IF and GOTO. The installed package, its language files, the shell version, and environment variables can therefore affect behavior. Confirm the utility is present on the actual boot profile and test its local help before relying on a menu during unattended startup.

Define the input contract before writing branches

The /C switch specifies the accepted characters. Without it, the documented default is YN; the help also says numeric and alphabetic keys may be used. /N hides the rendered key list but does not change which keys are valid. /S makes matching case-sensitive; without it the selected keys are case-insensitive. These are user-interface choices, not substitutes for validation of what a branch is allowed to do.

Use mnemonic choices that are distinct and easy to type. The displayed prompt should describe the action and its consequence, not just list letters. A “restore” option should name the restore target, and a destructive option should explain whether it overwrites data. Keep the default non-destructive when running with a timeout. A timeout is a decision policy: if it defaults to the action that modifies data, a disconnected keyboard or unattended boot can trigger that action without a person choosing it.

The /T syntax takes a default character and a duration in seconds. The documented range is zero through 99; zero means no delay before choosing the default. The default character must be among those declared by /C. This avoids an invalid timer configuration but does not guarantee that the selected operation will succeed. If a boot menu uses /T, document the default in the displayed text and test the actual timeout on the target machine or emulator.

Interpret the numeric result correctly

On a valid keypress, FreeDOS CHOICE sets ERRORLEVEL to the key’s position in the declared choices, not to the character’s ASCII value. The first choice returns 1, the second 2, and so on. The help also documents 0 when aborted and 255 for an error. Scripts must account for those results rather than treating every nonzero value as the same outcome.

FreeCOM’s IF ERRORLEVEL n tests whether the current result is greater than or equal to n. Therefore test larger choice positions before smaller ones. If checks run from 1 upward, a selection of the fourth option will satisfy the first branch and later branches will never execute. Always branch to an explicit error or cancellation path before the ordinary choice handling, and prevent a failure path from falling through into an action label.

Here is a bounded menu where the timeout chooses a non-destructive status view:

@ECHO OFF
ECHO B backs up, R restores, Q quits. Timeout selects Q.
CHOICE /C:BRQ /N /T:Q,20 Select B, R, or Q
IF ERRORLEVEL 255 GOTO CHOICE_ERROR
IF ERRORLEVEL 3 GOTO DONE
IF ERRORLEVEL 2 GOTO RESTORE_CONFIRM
IF ERRORLEVEL 1 GOTO BACKUP
IF ERRORLEVEL 0 GOTO CANCELLED
GOTO CHOICE_ERROR

:BACKUP
ECHO Backup branch selected. Validate source and destination first.
GOTO DONE

:RESTORE_CONFIRM
ECHO Restore branch selected. No restore occurs in this example.
GOTO DONE

:CANCELLED
ECHO Input was aborted. No operation was selected.
GOTO DONE

:CHOICE_ERROR
ECHO CHOICE failed. Stop without changing files.

:DONE

The example is intentionally non-destructive. In a real script, replace the informational branches only after each command’s own syntax, exit codes, and recovery procedure are defined. Check the CHOICE result immediately. A command inserted between CHOICE and IF ERRORLEVEL may change the shell’s current error-level state.

Localization and the external-program boundary

FreeDOS Help says CHOICE obtains messages from %NLSPATH%\CHOICE.%LANG%, with LANG configured through the environment. _CHOICE uses English messages only. CHOICE supports national-language support and requires its program file; it is not internal to COMMAND.COM. A missing binary, wrong PATH, unavailable language file, or stale NLSPATH can produce a failure that should go to the explicit error branch.

Do not rely on the localized message as the machine-readable interface. The choice list, prompt, and result position are the contract your script should own. If you change ordering from /C:BRQ to /C:QBR, the same user input maps to different numeric results. Treat that ordering as an API: update all branches and tests together. For localized environments, preserve a stable mapping between displayed key and branch and test the actual language files.

CHOICE only waits for a key; it does not validate a filename, parse a number, confirm a volume label, or prove the operator is authorized. It cannot safely accept arbitrary path strings. If a later action uses a chosen drive or file, validate it separately and prefer fixed, enumerated targets. Never construct a destructive command from unchecked user text in a batch file.

Timeouts, aborts, and startup conditions

An interactive prompt assumes a usable console. A boot script may run with a redirected input stream, under a secondary shell, or in an emulator where keyboard behavior differs. Verify the key behavior when standard input is redirected, when the timer expires, when Ctrl-C is pressed, and when the CHOICE executable or message file is absent. Treat these as different test conditions, not as proof that a single happy-path test covers the interface.

A timeout selects the specified key after the documented interval; it should not be described as measuring a strict real-time deadline. Timer resolution, emulator scheduling, and system state can affect observed wall-clock timing. If exact deadlines or robust user identity are required, CHOICE is not the right control plane. Use a dedicated application or supervisor with explicit timing and logging.

When a timeout branch may write to disk, make the default action inspect-only or exit. Put the data-changing code behind a deliberate key and a second validation barrier. For rollback-sensitive work, first create a known-good backup, validate free space, and log which path and volume were selected. Then test interruption and error handling on a disposable image rather than on the production volume.

Test matrix for an operational menu

For every menu version, test every allowed key, a lowercase key with and without /S, an invalid key, timeout, Ctrl-C or abort, missing CHOICE binary, and missing NLS data. Confirm each selection reaches exactly one label and that no branch falls through into another. Record the FreeCOM and CHOICE versions, PATH, LANG, NLSPATH, and full invocation.

For numeric handling, make a harmless diagnostic version that prints a unique marker in each branch. Test results in the highest-to-lowest order, then deliberately return or simulate an error to confirm the 255 branch does not trigger an ordinary choice. Verify the cancellation result 0 does not accidentally satisfy any positive action branch. If the shell’s exit-code state is unclear, test the installed build and consult its own help.

After the control-flow tests pass, replace markers with operations one at a time. Keep the safety branch and test all actions against a disposable copy. Review the script as a state machine: every possible result either maps to one explicit branch or exits without changing data. Do not use the absence of a prompt as evidence that an operation was approved; /N hides only the choice display, and /T can select a default automatically.

Related:

Sources:

Comments