Quick answer: ModbusBB.CLI.exe is a Modbus master/client for the Windows command line. Each command is one-shot, with the connection given inline (--tcp, --udp, --rtu-over-tcp, --rtu, --ascii). For example, ModbusBB.CLI read --tcp 192.168.1.10 --unit 1 --address 0 --count 10. Add --output json or --output csv for machine-readable output and --trace for raw hex frames. Check the exit code: 0 = OK, 1 = runtime error, 2 = usage error, 3 = license, 130 = Ctrl+C. The user manual explains the CLI step by step with examples.

Install & run

The installer puts the CLI in C:\Program Files\ModbusBB\CLI\ModbusBB.CLI.exe. Tick Add CLI to system PATH during setup to call ModbusBB.CLI from any terminal. In the portable ZIP it's CLI\ModbusBB.CLI.exe. It is a self-contained 64-bit executable, so no .NET installation is needed.

  • One-shot - ModbusBB.CLI <command> [options] <connection> connects, runs the command and exits with an exit code
  • Interactive - Run ModbusBB.CLI with no command for a prompt. connect once, then run commands on the open connection
  • Help - ModbusBB.CLI help, help <command>, help connection, help scripting, help exit-codes, and version
  • License - The CLI shares the license of the GUI. In the trial, a session lasts 15 minutes. With --no-prompt or redirected input it never asks for a key. It continues in trial mode, or exits with code 3 when the trial has expired
read with --trace: TX/RX frames go to stderr, data to stdout.

Commands

Addresses are always 0-based protocol addresses (register 40001 = --address 0), in decimal or 0x hex. Unit IDs are 1-247 by default. --allow-broadcast allows 0 and 248-255.

read

Read coils, discrete inputs, holding or input registers (FC01-04).

ModbusBB.CLI read [--holding|--input|--coils|--discrete] --address <addr> [--count <n>]
                  [--format <fmt>] [--byte-order ABCD|DCBA|BADC|CDAB] [--string-length <chars>] <connection>

--holding (FC03, default), --input (FC04), --coils (FC01), --discrete (FC02), or --type / --fc 1-4. --count is registers (1-125) or bits (1-2000). The default is 10, or one value for 32/64-bit formats, and it must be a multiple of the value size. Formats: uint16 int16 hex binary float32 int32 uint32 float64 int64 uint64 string.

ModbusBB.CLI read --tcp 192.168.1.10 --unit 1 --holding --address 0 --count 10
ModbusBB.CLI read --tcp 192.168.1.10 --input --address 0x0C --format float32 --byte-order CDAB --count 4
ModbusBB.CLI read --rtu COM3 --baud 9600 --parity E --unit 5 --coils --address 0 --count 16 --output json

write

Write holding registers or coils (FC05/06/15/16).

ModbusBB.CLI write [--register|--coil] --address <addr> --value <v[,v...]> [--format <fmt>]
                   [--byte-order <order>] [--single|--multiple] [--dry-run] <connection>

Separate values with commas. Integers accept decimal or 0x hex, and coils accept 1/0, true/false and on/off. One 16-bit value uses FC06 (a coil uses FC05), anything else FC16 (FC15). Force the choice with --single / --multiple. --dry-run prints the request frame without connecting.

ModbusBB.CLI write --tcp 192.168.1.10 --address 0x10 --value 1234
ModbusBB.CLI write --tcp 192.168.1.10 --address 100 --format float32 --byte-order CDAB --value 21.5,22.75
ModbusBB.CLI write --tcp 192.168.1.10 --coil --address 0 --value on,off,on
ModbusBB.CLI write --rtu COM3 --unit 2 --address 40 --value 0xFF00 --dry-run --trace

poll

Read repeatedly and print every sample. It runs until Ctrl+C, --samples or --duration, and re-establishes the link automatically if it drops.

ModbusBB.CLI poll [read options] [--interval <ms>] [--samples <n>] [--duration <s>] [--max-errors <n>] [--no-clear] <connection>

--interval 100 ms to 1 h (default 1000). --max-errors stops with exit code 1 after n consecutive errors (0 = never, the default). The exit code is also 1 if no sample succeeded. --no-clear appends table output instead of redrawing it. --output csv writes a header and one row per sample, and --output json writes NDJSON.

ModbusBB.CLI poll --tcp 192.168.1.10 --holding --address 0 --count 4 --interval 1000
ModbusBB.CLI poll --tcp 192.168.1.10 --input --address 0 --count 2 --format float32 --output csv > log.csv
ModbusBB.CLI poll --rtu COM3 --unit 1 --address 0 --count 10 --samples 60 --output json | jq .values

scan

Find responding unit IDs, or readable register ranges.

ModbusBB.CLI scan [--slaves] [--start <id>] [--end <id>] <connection>
ModbusBB.CLI scan --registers [--holding|--input|--coils|--discrete] [--start <addr>] [--end <addr>] [--step <n>] <connection>

A slave scan covers IDs 1-247 (0-255 with --allow-broadcast). A device answering with a Modbus exception counts as found. A register scan probes every --step addresses (default 10, range 1-10000) from --start to --end (default 0-1000). Use a short --timeout such as 200 to speed it up. Ctrl+C prints the partial result and exits with 130.

ModbusBB.CLI scan --rtu COM3 --baud 19200 --timeout 150 --start 1 --end 32
ModbusBB.CLI scan --tcp 192.168.1.10 --registers --input --start 0 --end 2000 --step 1 --output csv

stats

Measure response time and loss. It sends n single-register reads with no retries.

ModbusBB.CLI stats [--samples <n>] [--address <addr>] [--type holding|input|coils|discrete] [--delay <ms>] <connection>

--samples 1-100000 (default 10), --delay between requests 0-60000 ms (default 100). It reports requests sent, valid responses, exception replies, timeouts, other errors, no response (loss %) and avg/min/median/p95/max response time. Loss % = requests without any response ÷ requests sent. Exception replies count as responses.

ModbusBB.CLI stats --tcp 192.168.1.10 --samples 100 --delay 0 --output json

devid

Read Device Identification (FC43 / MEI 14). Multi-part replies are followed automatically.

ModbusBB.CLI devid [--category basic|regular|extended|individual] [--object <id>] <connection>
ModbusBB.CLI devid --tcp 192.168.1.10 --category regular --output json

slaveid

Report Server/Slave ID (FC17).

ModbusBB.CLI slaveid --rtu COM3 --unit 4

diag

Diagnostics (FC08). Sub-function 0 (Return Query Data) must echo the data, and a mismatch exits with code 1.

ModbusBB.CLI diag [--sub <n>] [--data <hex>] <connection>      (default --sub 0 --data "A5 37")
ModbusBB.CLI diag --rtu COM3 --unit 1 --sub 0 --data "12 34"

maskwrite

Mask Write Register (FC22): result = (current AND and) OR (or AND NOT and).

ModbusBB.CLI maskwrite --address <addr> --and <mask> --or <mask> <connection>
ModbusBB.CLI maskwrite --tcp 192.168.1.10 --address 4 --and 0xFFF0 --or 0x0005

rw

Read/Write Multiple Registers (FC23). The write happens first.

ModbusBB.CLI rw --read-address <addr> [--read-count <n>] --write-address <addr> --value <v[,v...]> [--format <fmt>] <connection>
ModbusBB.CLI rw --tcp 192.168.1.10 --read-address 0 --read-count 4 --write-address 10 --value 1,2,3

raw

Send any function code (1-127) with data and print the request/response frames. The data is the PDU after the function code, in hex. The ADU (MBAP header, or address + CRC/LRC) is added for the connection type. An exception reply is printed and exits with code 1. --dry-run shows the frame without sending it.

ModbusBB.CLI raw --tcp 192.168.1.10 --unit 1 --fc 3 --data "00 00 00 02"

Request  (Tcp): 00 01 00 00 00 06 01 03 00 00 00 02
Response (1.3 ms): 00 01 00 00 00 07 01 03 04 00 64 00 00
PDU:      03 04 00 64 00 00

library

Built-in device register maps: eastron-sdm120, eastron-sdm630, schneider-pm5xxx, carlo-gavazzi-em24, sunspec-common, growatt-inverter-v120, sma-inverter, abb-acs580, schneider-altivar. Addresses are 0-based. Always verify a map against your device manual.

ModbusBB.CLI library list
ModbusBB.CLI library show eastron-sdm630
ModbusBB.CLI library export eastron-sdm630 sdm630.csv

workspace

workspace run connects every connection in a .mbws file that has enabled polls, starts those polls and prints every sample until Ctrl+C or --duration. Registers defined in the workspace are decoded with their name, format and scaling. --connection limits it to one connection. CSV output writes one row per value and JSON writes NDJSON. workspace show prints the contents. Old profile .json and .mbcfg files work too.

ModbusBB.CLI workspace run plant.mbws --duration 3600 --output csv > plant.csv
ModbusBB.CLI workspace show plant.mbws

simulate

Run the built-in Modbus slave simulator until Ctrl+C or --duration.

ModbusBB.CLI simulate --tcp <port> | --udp <port> | --rtu <COM> | --ascii <COM> [--baud --parity --databits --stopbits]
                      [--unit 1,2 | 1-4] [--generator <spec>]... [--set type:addr=v,...]... [--delay <ms>] [--jitter <ms>] [--duration <s>]

Generator spec: [unit@]type:address:kind[:min:max[:periodMs[:format[:byteorder]]]]
  type: holding|input|coil|discrete    kind: static|ramp|sine|random|counter|toggle

ModbusBB.CLI simulate --tcp 5020 --unit 1,2 --generator "holding:0:sine:0:100:5000" --set holding:100=1,2,3
ModbusBB.CLI simulate --rtu COM4 --baud 19200 --parity E --generator "2@input:10:ramp:0:1000:10000:float32:CDAB" --verbose

Exception injection is available in the GUI simulator only.

connect, disconnect, status, ports

In interactive mode, connect opens a connection that later commands reuse. --no-reconnect turns off automatic reconnection. As a one-shot command, connect just tests the connection. status shows the connection state, and ports lists the serial ports Windows reports.

Connection options

OptionMeaning
--tcp <host[:port]>Modbus TCP (default port 502)
--udp <host[:port]>Modbus UDP (default port 502)
--rtu-over-tcp <host:port>RTU frames over a TCP socket (serial device servers)
--rtu <COM> / --ascii <COM>Modbus RTU / ASCII on a serial port
--port <n>TCP/UDP port (alternative to host:port)
-b, --baud · --parity N|E|O|M|S · --databits 7|8 · --stopbits 1|1.5|2Serial settings (defaults 9600, N, 8, 1)
-t, --timeout <ms>Response timeout, 10-60000 (default 1000)
--connect-timeout <ms>Connect timeout, 100-60000 (default 5000)
--retries <n>Retries per request, 0-10 (default 3; scans and stats use 0)
--profile <file> · --connection <name>Take the connection from a workspace (.mbws) or old profile (.json/.mbcfg); default is the first connection
-u, --unit <id> (alias --slave)Unit/slave ID, 1-247 (default 1, or from the profile)
--allow-broadcastAllow unit 0 (broadcast) and 248-255

Global options

OptionMeaning
-o, --output table|json|csvOutput format (default table)
--tracePrint raw TX/RX frames (hex) to stderr
--verbosePrint the communication log to stderr
-q, --quietSuppress the banner and informational messages
--no-colorDisable colors (also automatic with NO_COLOR or redirected output)
--no-promptNever prompt (for scripts); trial users continue in trial mode

Output formats

Data goes to stdout. Messages, errors and --trace go to stderr, so redirecting stdout gives clean data.

Table (default)

Address  Value  Hex     Binary
----------------------------------------
0        84     0x0054  0000000001010100
1        0      0x0000  0000000000000000

JSON (--output json)

{
  "timestamp": "2026-09-29T22:37:24.132+03:00",
  "unit": 1, "function": "input", "fc": 4, "address": 0, "count": 2,
  "format": "float32", "byteOrder": "ABCD", "responseMs": 13.34,
  "values": [ { "address": 0, "value": 341.33667, "raw": [17322, 43800] } ]
}

CSV (--output csv)

address,value,raw
10,1,0001
11,2,0002

NDJSON and CSV rows from poll

timestamp,unit,status,response_ms,error,10,11
2026-09-29T22:37:24.779+03:00,1,ok,13.02,,1,2

{"timestamp":"2026-09-29T22:37:25.394+03:00","sample":1,"unit":1,"function":"holding","address":10,"ok":true,"responseMs":14.45,"values":[{"address":10,"value":1,"raw":[1]},{"address":11,"value":2,"raw":[2]}]}

Hex trace (--trace)

[22:37:26.278] TX 00 01 00 00 00 06 01 03 00 00 00 02
[22:37:26.286] RX 00 01 00 00 00 07 01 03 04 00 54 00 00

Exit codes

CodeMeaning
0Success
1Runtime error: connection failed, timeout, Modbus exception reply, I/O error
2Usage error: unknown command/option, missing or invalid value, value out of range
3License error: trial expired or no valid license
130Interrupted with Ctrl+C (poll, simulate and workspace run exit 0 when stopped with Ctrl+C)

Scripting examples

Commands never prompt when stdin is redirected or --no-prompt is given, and colors turn off automatically when output is redirected.

PowerShell: read a float from several devices

foreach ($u in 1..5) {
  $r = ModbusBB.CLI read --rtu COM3 --baud 9600 --unit $u --input --address 0 --format float32 --output json --quiet | ConvertFrom-Json
  if ($LASTEXITCODE -eq 0) { "Unit $u : $($r.values[0].value)" } else { "Unit $u : no answer" }
}

PowerShell: write a setpoint and verify it

$conn = @('--tcp', '192.168.1.10', '--unit', '1', '--quiet', '--no-prompt')
ModbusBB.CLI write @conn --address 100 --value 250
if ($LASTEXITCODE -ne 0) { throw "Write failed (exit $LASTEXITCODE)" }
$r = ModbusBB.CLI read @conn --address 100 --count 1 --output json | ConvertFrom-Json
if ($r.values[0].value -ne 250) { throw "Read-back mismatch: $($r.values[0].value)" }

cmd.exe (batch file)

ModbusBB.CLI write --tcp 192.168.1.10 --address 100 --value 250 --quiet
if errorlevel 1 echo Write failed & exit /b 1

for /L %%u in (1,1,10) do ModbusBB.CLI read --tcp 192.168.1.10 --unit %%u --address 0 --count 1 --output csv --quiet

Bash / WSL

ModbusBB.CLI read --tcp plc --address 0 --count 2 --format uint32 --output json | jq '.values[0].value'

Defaults for many commands

set MODBUSBB_OPTS=--tcp 192.168.1.10 --unit 1          (cmd)
$env:MODBUSBB_OPTS = "--tcp 192.168.1.10 --unit 1"     (PowerShell)
ModbusBB.CLI read --address 0 --count 4

CSV logging from the command line

# One CSV row per sample, every second, for one hour
ModbusBB.CLI poll --tcp 192.168.1.10 --input --address 0 --count 4 --interval 1000 --duration 3600 --output csv --quiet > log.csv

# Float values from an RTU meter, stop after 5 consecutive errors
ModbusBB.CLI poll --rtu COM3 --baud 9600 --parity E --unit 1 --input --address 0 --count 4 --format float32 --max-errors 5 --output csv > meter.csv

# Everything a workspace polls, decoded with names and scaling (one row per value)
ModbusBB.CLI workspace run plant.mbws --duration 3600 --output csv > plant.csv

# NDJSON for log pipelines
ModbusBB.CLI poll --tcp 192.168.1.10 --address 0 --count 10 --duration 3600 --output json > log.ndjson

Each poll row has timestamp, unit, status, response_ms, error and one column per address. Failed samples are logged with their status and error, so gaps are visible. Remember the trial's 15-minute session limit when you log for longer. A licensed copy has no limit.

Environment variables

VariableEffect
MODBUSBB_OPTSDefault connection options used when a command has none, e.g. --tcp 192.168.1.10 --unit 1
MODBUSBB_PROFILEDefault --profile file
MODBUSBB_NO_PROMPT=1Never prompt for a license key (same as --no-prompt)
NO_COLORDisable colors

CLI FAQ

Is there a free Modbus command-line tool for Windows?

ModbusBB CLI comes with ModbusBB. The 30-day trial includes every command, with 15 minutes per session. A $10 lifetime license removes the session limit, and the license covers both the CLI and the GUI.

Can the CLI do everything the GUI does?

No, but it covers the tasks worth scripting: read, write, poll, scan, stats, device identification (FC43), report slave ID (FC17), diagnostics (FC08), mask write (FC22), read/write multiple (FC23), raw requests, the device library, running workspaces and the simulator. The trend chart, watchdog rules and actions, register-map editing, the traffic monitor window (use --trace instead) and simulator exception injection are GUI only.

How do I detect errors in a script?

Check the exit code. 0 means success, 1 a runtime error (connection failed, timeout or Modbus exception reply), 2 a usage error, 3 a license problem and 130 Ctrl+C. In PowerShell use $LASTEXITCODE; in cmd use if errorlevel 1.

Can I see the raw Modbus frames from the CLI?

Yes. Add --trace to any command to print every TX/RX frame in hex to stderr. The raw command prints the request and response frames, and --dry-run on write and raw shows the frame without sending it.

Script your Modbus tests

The CLI is included in the ModbusBB download, next to the GUI.