Writing Commands
This page covers the standard pattern for defining a shell command — option table, enum, handler, and descriptor — and explains how option parsing works, including error detection via ParseError.
Option Table (X-Macro Pattern)
Define options with an X-macro table so the enum and the WshShellOption_t array stay in sync automatically.
Rules:
- Always start with
WSH_SHELL_OPT_NO(the no-argument default) and end withWSH_SHELL_OPT_END. - Short and long flag strings must be unique within the table — duplicates trigger
WSH_SHELL_ASSERTonWshShellCmd_Attach. ENUM_SIZEsentinel at the end lets you range-check IDs if needed.
Handler
Command Descriptor
Register it at init time:
ParseError — Detecting Unknown Flags
WshShellCmd_ParseOpt returns an optCtx with .Option == NULL in two situations:
| Situation | optCtx.ParseError |
Meaning |
|---|---|---|
| No more tokens to process | false |
Normal end — exit the loop with break |
| Unknown flag token encountered | true |
The flag was not in the table — [WARN] already printed |
Checking ParseError is what separates strict handlers (reject bad flags) from lenient ones (ignore them). The standard pattern is strict:
If you intentionally want lenient behavior — for example, a parent command that ignores unknown tokens it passes down to subcommands — just omit the ParseError check and always break.
Option Types Reference
| Macro | Type constant | Arguments | Example |
|---|---|---|---|
WSH_SHELL_OPT_NO |
WSH_SHELL_OPTION_NO |
0 | default action when no flag given |
WSH_SHELL_OPT_HELP |
WSH_SHELL_OPTION_HELP |
0 | -h / --help |
WSH_SHELL_OPT_INTERACT |
WSH_SHELL_OPTION_INTERACT |
0 | -i / --interactive |
WSH_SHELL_OPT_WO_PARAM |
WSH_SHELL_OPTION_WO_PARAM |
0 | flag without value (--verbose) |
WSH_SHELL_OPT_STR |
WSH_SHELL_OPTION_STR |
1 | flag with any string value (--name foo) |
WSH_SHELL_OPT_INT |
WSH_SHELL_OPTION_INT |
1 | flag with integer value — decimal, 0x hex, or 0 octal (--count 5, --mask 0xFF) |
WSH_SHELL_OPT_FLOAT |
WSH_SHELL_OPTION_FLOAT |
1 | flag with float value (--rate 1.5) |
WSH_SHELL_OPT_ENUM |
WSH_SHELL_OPTION_ENUM |
1 | flag with a closed set of string values — validated and Tab-completed (--format table) |
WSH_SHELL_OPT_MULTI_ARG |
WSH_SHELL_OPTION_MULTI_ARG |
N | flag consuming N tokens |
WSH_SHELL_OPT_WAITS_INPUT |
WSH_SHELL_OPTION_WAITS_INPUT |
0 | handler waits for subsequent input |
WSH_SHELL_OPT_END |
WSH_SHELL_OPTION_END |
— | terminator (required, always last) |
For single-argument options (STR, INT, FLOAT, ENUM), use WshShellCmd_GetOptValue to extract the value — it handles bounds checking and, for ENUM, validates the string against the allowed set before copying.
Enum Options
WSH_SHELL_OPT_ENUM accepts exactly one string argument validated against a fixed list defined at compile time. If the value is not in the list, WshShellCmd_GetOptValue prints a [WARN] and returns ERR_PARAM. Tab completion shows matching values automatically.
Defining the allowed set
WshShellOptionEnum_t fields:
| Field | Type | Description |
|---|---|---|
Values |
const WshShell_Char_t* const* |
NULL-terminated-free array of allowed strings |
Count |
WshShell_Size_t |
Number of entries in Values |
Each string must be shorter than WSH_SHELL_ENUM_VALUE_MAX_LEN (default 16).
Declaring in the option table
Pass a pointer to the enum descriptor as the fourth argument:
Reading the value in the handler
Use WshShellCmd_GetOptValue — it validates the value against the list before copying:
If the value is invalid, GetOptValue already printed a warning and returned ERR_PARAM — you can propagate it or ignore it depending on the use case.
Tab completion behaviour
| What the user typed | Tab result |
|---|---|
mycmd --format <Tab> |
lists all values: [table] [short] [json] |
mycmd --format ta<Tab> |
completes to mycmd --format table |
mycmd --form<Tab> |
completes the flag to mycmd --format |
Access Control
Each option carries an access mask. WshShellCmd_ParseOpt silently skips options the current user lacks rights for — pass pShell->CurrUser->Rights as the access argument. See Permission Rules for the full bitmask reference and group-based command access.
Adding Subcommands
Once a command has its own handler and options defined, it can be a parent by adding .SubCmds and .SubCmdNum:
See Subcommands for the full dispatch and autocomplete details.
Interactive Mode
A command can enter interactive mode: instead of running once and returning, the shell routes every subsequent line of input to a callback until the user exits with Ctrl+D or Ctrl+C.
Attaching a callback
Call WshShellInteract_Attach from inside the command handler:
The PS1 prompt changes to show the interactive command name. The callback receives the raw input buffer (pLine->Buff) on every Enter press; pLine->Len holds the current character count before the line break is appended.
Exiting interactive mode
| Key | Effect |
|---|---|
| Ctrl+D | Clean exit — interactive session ends, normal prompt returns |
| Ctrl+C | Cancel — same as Ctrl+D but prints ^C; also cancels any pending PromptWait |
Both keys are handled by the shell core; your callback does not need to check for them.
Limitations
- Only one interactive command can be active at a time.
- Escape sequences (arrow keys, history navigation) are not available inside interactive mode.
- Double-quoted strings in the interactive buffer are not re-tokenised — the raw line is delivered as-is.
Shell Utilities
WshShellMisc_HexDump
Prints a buffer in hexdump -C style — byte offset, hex bytes in two groups of 8, and printable ASCII on the right (. for non-printable bytes).
| Parameter | Description |
|---|---|
pBuff |
Pointer to the data buffer |
len |
Number of bytes to dump |
offset |
Value printed in the offset column (use 0 for relative, or a real address) |
Row width is controlled by WSH_HEXDUMP_COLS in wsh_shell_cfg_def.h (default 16).
Example output: