Repository navigation
CLI raw commands
tik4net lets you drive a CLI connection in either of two dialects:
-
the API dialect, translated for you —
/ip/address/printwith?interface=ether1, the same command and parameters you would send over the binary API. tik4net turns it into a RouterOS command line, runs it and parses the answer. This is the standard way: the code is portable across all eleven transports, and the ADO.NET-like API and the O/R mapper work this way. What each call becomes on the wire is described in Command translation on non-API transports. -
the CLI's own native dialect, sent unchanged —
:put [/ip address print as-value where interface=ether1], exactly what you would type at the router's terminal. This page is about this one.
The native dialect works on all five CLI transports — Telnet, Ssh, MacTelnet, WinboxCli and
WinboxCliMac — because all five end at the same RouterOS console. A line that works on one works on the
other four, so only the Create…Connection() call differs.
Stay with the API dialect for CRUD — it is less work, portable, and its error handling knows which verb it sent. Go native when you want something the API dialect cannot say:
-
scripting —
:foreach,:len,:local, arithmetic,findwith any condition the console accepts; -
free text —
/export, or anything else that is not a table; - a modifier or a menu tik4net does not model;
- a command copied from a MikroTik forum post or from your own terminal history, used as it is.
The program keeps an address list of offenders. It adds entries, reads them back, extends the timeout of
a whole subnet in one script line, counts the result, prints the list as a person would see it, and cleans
up. Pick the transport
on the command line; nothing below Open knows which one it got.
// ==========================================================================
// TASK maintain a firewall address list ("blocklist") in native CLI syntax:
// add → read → bulk update by script → count → print as text → clean up
// LEVEL Raw — CallCommandSync (sentences) and CreateRawCommand (ITikCommand)
// TRANSPORT telnet | ssh | mactelnet | winboxcli | winboxclimac (args[0])
// ==========================================================================
// ON THE ROUTER: the service for the transport you pick, and a user with
// read,write plus the transport's policy (telnet / ssh / winbox).
// Only rows with list=tik4net-blocklist are touched, and they are removed at the end.
//
// OUTPUT (RouterOS 7.24.5 — identical on all five transports, apart from the ids and times)
// added 192.0.2.10 as *58
// added 192.0.2.11 as *59
// added 198.51.100.7 as *5A
// *58 192.0.2.10 timeout=23h59m59s comment=ssh brute force
// *59 192.0.2.11 timeout=23h59m59s comment=ssh brute force
// *5A 198.51.100.7 timeout=23h59m59s comment=ssh brute force
// entries extended to 7d: 2
// Flags: D - DYNAMIC
// Columns: LIST, ADDRESS, CREATION-TIME, TIMEOUT
// # LIST ADDRESS CREATION-TIME TIMEOUT
// ;;; ssh brute force
// 0 D tik4net-blocklist 192.0.2.10 2026-10-10 13:47:20 6d23h59m59s
// ...
// 2 D tik4net-blocklist 198.51.100.7 2026-10-10 13:47:21 23h59m59s
// refused as expected: no such item (4)
//
// WATCH OUT An entry with a timeout is DYNAMIC (the D flag), and /export leaves dynamic
// entries out — which is why step 5 prints the list instead of exporting it.
// The two MAC-layer transports find the router's MAC by MNDP. Where that fails, pass
// it: setup.CreateMacTelnetConnection("AA:BB:CC:DD:EE:FF").
// ==========================================================================
using System;
using System.Linq;
using tik4net;
using tik4net.Telnet;
using tik4net.Ssh;
using tik4net.MacTelnet;
using tik4net.WinboxCli;
using tik4net.WinboxCliMac;
class Program
{
const string Host = "192.168.88.1";
const string User = "admin";
const string Password = "";
const string List = "tik4net-blocklist";
static ITikCliConnection Open(TikConnectionSetup setup, string transport) => transport switch
{
"telnet" => setup.CreateTelnetConnection(),
"ssh" => setup.CreateSshConnection(),
"mactelnet" => setup.CreateMacTelnetConnection(), // router MAC found by MNDP
"winboxcli" => setup.CreateWinboxCliConnection(),
"winboxclimac" => setup.CreateWinboxCliMacConnection(),
_ => throw new ArgumentException("unknown transport: " + transport),
};
static void Main(string[] args)
{
var setup = new TikConnectionSetup(Host, User, Password);
using (ITikCliConnection cli = Open(setup, args.Length > 0 ? args[0] : "telnet"))
{
// 1. ADD — ':put [ … ]' is what makes the console print the new row's .id.
// Without it, add prints nothing at all.
foreach (string address in new[] { "192.0.2.10", "192.0.2.11", "198.51.100.7" })
{
var reply = cli.CallCommandSync(
":put [/ip firewall address-list add list=" + List + " address=" + address
+ " timeout=1d comment=\"ssh brute force\"]");
string id = reply.OfType<ITikDoneSentence>().Single().GetResponseWord();
Console.WriteLine("added " + address + " as " + id);
}
// 2. READ — as-value output becomes one ITikReSentence per row.
// 'detail' asks for every field, not just the summary columns.
foreach (var row in cli.CallCommandSync(
":put [/ip firewall address-list print detail as-value where list=" + List + "]")
.OfType<ITikReSentence>())
Console.WriteLine(row.GetId() + " " + row.GetResponseField("address")
+ " timeout=" + row.GetResponseFieldOrDefault("timeout", "-")
+ " comment=" + row.GetResponseFieldOrDefault("comment", "<unset>"));
// 3. BULK UPDATE BY SCRIPT — one line, one round trip, any condition the console
// understands ('in' tests subnet membership). The API dialect has no such sentence.
cli.CreateRawCommand(
":foreach i in=[/ip firewall address-list find where list=" + List
+ " and address in 192.0.2.0/24] do={ /ip firewall address-list set $i timeout=7d }")
.ExecuteNonQuery();
// 4. A COMPUTED VALUE — ExecuteScalar returns what the line printed.
string count = cli.CreateRawCommand(
":put [:len [/ip firewall address-list find where list=" + List + " and timeout>3d]]")
.ExecuteScalar();
Console.WriteLine("entries extended to 7d: " + count);
// 5. FREE TEXT — a print without as-value is the table a person would see, and comes
// back whole, as text. (/export is read the same way.)
Console.WriteLine(cli.CreateRawCommand("/ip firewall address-list print where list=" + List)
.ExecuteScalar());
// 6. A REFUSAL — raised as the same exception type the portable levels use.
try
{
cli.CreateRawCommand("/ip firewall address-list remove numbers=*FFFFFF").ExecuteNonQuery();
}
catch (TikNoSuchItemException ex)
{
Console.WriteLine("refused as expected: " + ex.Message);
}
// 7. CLEAN UP — one line for the whole list.
cli.CreateRawCommand("/ip firewall address-list remove [find where list=" + List + "]")
.ExecuteNonQuery();
}
}
}The program uses both, on purpose. They send the same line; they differ in what they hand back.
CallCommandSync / CallCommandAsync
|
CreateRawCommand(line) |
|
|---|---|---|
| Where it is |
ITikRawSentenceConnection — which ITikCliConnection is |
an extension on ITikConnection
|
| What comes back | sentences: one ITikReSentence per as-value row, then an ITikDoneSentence; any other output is the ret word of the !done
|
an ITikCommand: ExecuteList · ExecuteScalar · ExecuteNonQuery
|
| Read rows | write the :put [ … as-value] yourself |
the same, or pass wrapAsValue: true
|
| A router error | throws | throws |
| Best for | reading rows; code shared with the Low-level API | scalars, free text, writes |
Several rows given to CallCommandSync are joined with one space, so CallCommandSync(":put [/interface print", "as-value]") and the one-piece line are the same command.
On a connection whose transport is chosen at run time, check connection.Supports(TikConnectionCapability.RawCommand).
The binary API (Api / ApiSsl) has the same two entry points, but its native dialect is API sentence
words, not CLI text — see Low-level API. REST and WinBox native have no command language,
so raw CLI is not offered there; on the WinBox channel use WinBox CLI, or address
WinBox native with WinBox's own names.
Raw means the line goes out as you wrote it — including its mistakes. These are the ones that do not fail: the command is accepted and the answer is wrong or empty. The API dialect avoids every one of them for you.
| Symptom | Cause | Fix |
|---|---|---|
| a read returns no rows | a bare print as-value — the console prints as-value output only inside a script |
wrap it in :put [ … ], or wrapAsValue: true
|
| a filter matches nothing | an unquoted /, : or . in a where value; ="true" on a boolean |
quote values; booleans are yes / no
|
| a filter matches everything | a field name the menu does not have, such as the other RouterOS version's name | check the name — CLI tips and tricks |
| a field is missing | the summary columns; a secret without show-sensitive; counters without stats
|
detail, show-sensitive, a separate stats read |
| a value reads back changed |
$ or \ inside double quotes — substituted and unescaped by the router |
escape them: \$, \\
|
a set or remove "succeeds" on a row that is gone |
[find …] that matches nothing is not an error |
address by numbers=*N, which answers no such item (4)
|
The full list, with the measurements behind it, is CLI tips and tricks.
- CLI tips and tricks — the cookbook: reads, writes, actions, monitors, scripting
- Command translation on non-API transports — the API dialect, and what it becomes on a terminal
- Low-level API — the sentence model, and raw on the binary API
- WinBox native — GUI-like syntax — the WinBox counterpart
- Communication debugging — see the exact line that reached the router
- Transport pages: Telnet · SSH · MAC-Telnet · WinBox CLI · WinBox CLI over MAC
Start
Connecting
- Connecting to a router — setup, options, connection string
- Transports & capabilities — the matrix
- Threading & concurrency
- API · API-SSL
- REST
-
CLI transports
- Telnet · SSH · WinBox CLI
- MAC-layer transports — no IP needed
- WinBox native · GUI-like syntax
- RoMON — through a neighbouring router
- MNDP discovery
Working with data
- Low-level API — raw sentences
- ADO.NET-like API — commands, parameters
- CLI raw commands · tips & tricks
- Command translation
- Async & cancellation
- Monitors & streaming — ping, torch, listen
O/R mapper
-
High-level API
- Reading data · Create, update, delete
- Change tracking · List merge
- TikField · Value types · Helpers
- Entity reference — every covered menu
- Custom entities
Safety
- Safe Mode — rollback protection
- Exception handling
RouterOS
Testing & diagnostics
Extending & tools
Project