Skip to content
This repository has been archived by the owner on Mar 25, 2024. It is now read-only.

Client Commands

Aluisio Amaral edited this page Mar 9, 2018 · 6 revisions

Summary

With the client returned after connecting, we are able to do a handfull of commands. Let's check them out.

Menu Navigation

Once we have connected to the routerboard and a RosApiMenu client was returned, we then use it to navigate through the menus so we can do our commands. To do that, we use the menu() function and giving it the menu we want to navigate to, it will return a RosApiCommands object so we are able to do some commands in that said menu. Let's see an example:

menu(path: string): RosApiCommands

routeros.connect().then((client) => {
    // Connected successfully

    const addressMenu = client.menu("/ip address");

    // We can either use the "/ip address" syntax or
    // the usual API syntax like "/ip/address"
    const accessMenu = client.menu("/ip/proxy/access");

    console.log(addressMenu); // is at /ip address
    console.log(accessMenu); // is at /ip proxy access

}).catch((err) => {
    // Error when trying to connect
    console.log(err);
});

Note: the menu() function will always return a new RosApiCommands object, so you don't need to worry about having to change back to a previous menu, you can reuse it anywhere you want once created.

Printing, selecting data

Once we navigate to the menu we want, and having the RosApiCommands object, we can then start doing commands with it. Let's start by printing some data using the get() command:

get(data?: object): Promise<object[]>

routeros.connect().then((client) => {
    // Connected successfully

    const addressMenu = client.menu("/ip address");

    // This would translate to something like
    // '/ip address print'
    addressMenu.get().then((addresses) => {

        // Addresses is an array of objects of 
        // all addresses from the '/ip address' menu
        console.log(addresses);

    }).catch((err) => {
        // Error getting data
        console.log(err);
    });

}).catch((err) => {
    // Error when trying to connect
    console.log(err);
});

There are other aliases you can use:

  • get(data?: object)
  • getAll(data?: object)
  • print(data?: object)

The array returned when getting the entries from a menu will be formated to be used in a way that we won't need to get the object properties by name, we can do it by index. Example:

{
    address: "192.168.88.1/24",
    network: "192.168.88.0",
    interface: "ether5",
    actualInterface: "ether2-master"
}

Note that the actualInterface was formatted from routeros's actual-interface. All dashed properties are converted to camelCase, so you don't need to call it like address['actual-interface'], you can call it by index like address.actualInterface. Other considerations:

  • All properties that would start with a dot . gets the dot removed, which means that .id, .nextid and .dead are converted to id, nextid and dead respectively.
  • Values that are all numbers are parsed to float, allowing you to do calculations directly, otherwise it would be considered a string and concatenated in case you would make a sum.
  • true or false strings are parsed to boolean.

Note: if your workflow uses snake_case instead of camelCase, use this tip.

Filtering

We can query when printing data using the where() function. Example:

const addressMenu = client.menu("/ip address");

addressMenu.where("interface", "ether5").get().then((addresses) => {

    // Addresses will be all addresses from interface ether5
    console.log(addresses);

}).catch((err) => {
    // Error getting data
    console.log(err);
});

You can also pass an object when querying:

addressMenu.where("interface", "ether5").get();
// Or
addressMenu.where({interface: "ether5"}).get();

Where function is chainable, so you can do either:

addressMenu.where("interface", "ether5").where("network", "192.168.88.0").get();
// Or
addressMenu.where({interface: "ether5", network: "192.168.88.0"}).get();

You are able to get a bit more literal:

addressMenu
    .where("interface", "ether5")
    .andWhere("network", "192.168.88.0")
    .get();

It is possible to use operations like AND, OR, NOT and etc. Examples:

// OR
addressMenu
    .where("interface", "ether5")
    .orWhere("interface", "ether2")
    .get();

// NOT
addressMenu
    .whereNot("interface", "ether5")
    .get();

// AND + NOT
addressMenu
    .where("interface", "ether5")
    .andWhereNot("network", "192.168.88.0")
    .get();

// OR + NOT
addressMenu
    .where("interface", "ether5")
    .orWhereNot("interface" "ether2")
    .get();

// HIGHER THAN
const interfaceMenu = client.menu("/interface");
interfaceMenu
    .whereHigher("mtu", 1500)
    .get();

// LOWER THAN
interfaceMenu
    .whereLower("l2mtu", 1600)
    .get();

// Property exists and has a value
interfaceMenu
    .whereExists("comment") // or whereNotEmpty("comment")
    .get();

// Property doesn't exists or has no value
interfaceMenu
    .whereEmpty("comment") // or whereNotExists("comment")
    .get();

Note: when querying using objects, all parameters are compared with the AND operation.

Filtering non-list menus

The RouterOS has menus that are not a list of entries, like "/system routerboard". In thoses cases, if you use the get() method, it would always return an array with length equal to 1. You can save time by using the getOne() function:

const routerboardMenu = client.menu("/system routerboard");
routerboard.getOne().then((rbProps) => {

    console.log(rbProps);
    // rbProps {
    //     routerboard: "yes",
    //     model: "1100AHx2",
    //     serialNumber: "341102190E12",
    //     firmwareType: "p2020",
    //     factoryFirmware: 2.39,
    //     currentFirmware: 3.02,
    //     upgradeFirmware: 3.24
    // }

}).catch((err) => {
    // Error getting data
})

You can also use one of the aliases:

  • find(data?: object)
  • getOne(data?: object)
  • getOnly(data?: object)

Filtering using raw API string arrays

You can pass your own filter using the API syntax using the whereRaw() method. Example:

addressMenu.whereRaw([
    "?interface=ether1",
    "?type=ether",
    "?#|"
]).get();

For more on the raw syntax, you can find it here.

Restricting retrieved parameters

We can filter the parameters that we want to retrieve by using the select() function. Example:

select(fields: string | string[]): RosApiCommands

addressMenu.select("id", "address", "interface").get().then((addresses) => {

    console.log(addresses);
    // addresses[0] {
    //     id: "*A1",
    //     address: "192.168.88.1",
    //     interface: "ether5"
    // }

}).catch((err) => {
    // Error getting data
});

This will omit all other properties, retrieving only the ones you asked for. Depending on the usage, opting to use select() can increase the performance of the query. You can also pass an array of properties:

const props = ["id", "address", "interface"];
addressMenu.select(props).get();

You can also use the aliases:

  • select(fields: string | string[])
  • only(fields: string | string[])
  • proplist(fields: string | string[])

Handling menu options

There are some menus on the routerOS that accepts some options when printing data. Imagine you want to print all the interfaces and you also want to pass the detail to get all properties of the menu, to do that we use the options() method. It follows the same principle of the select() function, example:

options(opts: string | string[], ...args: string[]): RosApiCommands

interfaceMenu.options("detail").get();
// You can use multiple parameters
interfaceMenu.options("detail", "stats", "oid").get();
// Or pass them all as an array
interfaceMenu.options(["detail", "stats", "oid"]).get();

Note: careful when using the interval or follow and etc. option this way, when you are expecting continuous flow of data, use the stream instead.

Keep in mind that the options is used only for parameters that don't accept values. If you do need to pass an options that needs a value, use the query() method instead. Example:

query(key: object | string, value?: string): RosApiCommands

When using query() or filter(), the api will not add the question mark when writing over the socket, even when printing.

interfaceMenu.query("file", "afiletosavetheprint.txt").get();

This is not very beautiful, but in this specific case you can use the exec() method like so:

interfaceMenu.exec("print", {
    file: "afiletosavetheprint.txt"
}).then((data) => {
    // Command executed successfully
    console.log(data); // any data that your command should return, if any
}).catch((err) => {
    // Error executing the command
});

You can see more of the exec() method here.

Other aliases for query():

  • query(key: object | string, value?: string)
  • filter(key: object | string, value?: string)

Adding entries

Once inside a menu, we can add new items by using the add() function, giving an object of parameters to add:

add(data: object): Promise<object>

var vlanMenu = client.menu("/interface vlan");
vlanMenu.add({
    name: "vlan40",
    mtu: 1500,
    interface: "ether1",
    vlanId: 40
}).then((response) => {
    console.log(response); 
    /* { 
        '$$path': "/interface/vlan",
        id: "*EA",
        name: "vlan40",
        mtu: 1500,
        interface: "ether1",
        vlanId: 40,
        ...
    } */
}).catch((err) => {
    // Error adding
    console.log(err); 
});

Other aliases:

  • add(data: object)
  • create(data: object)

Note: remember that all dashed properties of the routerOS, like the vlanId used here, can be represented in 3 ways: vlanId which is the one we used, vlan_id if you prefer snake_case or vlan-id which is routerOS's default.

Note 2: when adding an entry, it will always return the ID (or ret) of the just added item, if no error was given. Changed in v0.10.0. When adding an entry, the item added will be returned with it's internal id.

Editing entries

When editing, we have to consider two types of menus:

  1. List menus
  2. Non list menus

In list menus, we need to inform the id of the item we want to update. In non-list menus we just give the parameters that needs to be updated.

Some menus or items have parameters that can't be updated to an empty value, to do that we need to unset the property, which is covered below.

Updating properties

To update properties, you guessed it, we use the update() method:

update(data: object, ids?: string | string[]): Promise<any[]>

addressMenu.update({
    address: "192.168.50.1/24",
    comment: "WIFI network on VLAN 5"
}, item.id).then((response) => {
    // Updated!
    // The response will be the updated item
}).catch((err) => {
    // Error updating
    console.log(err);
});

Note that we passed the item id in the second parameter, we can omit it and use the where() method to define the id if you want:

addressMenu.where("id", item.id).update({
    address: "192.168.50.1/24",
    comment: "WIFI network on VLAN 5"
}).then((response) => {
    // Updated!
    // The response will be the updated item
}).catch((err) => {
    // Error updating
    console.log(err);
});

We can also query for an item to update:

addressMenu.where("interface", "ether4").update({
    address: "192.168.50.1/24",
    comment: "WIFI network on VLAN 5"
}).then((response) => {
    // Updated!
    // The response will be the updated item
}).catch((err) => {
    // Error updating
    console.log(err);
});

If it is a non-list menu:

var snmpMenu = client.menu("/ip snmp");
snmpMenu.update({
    enabled: true
}).then((response) => {
    // Updated!
    // The response will be the updated info
}).catch((err) => {
    // Error updating
    console.log(err);
});

Other aliases:

  • update(data: object, ids?: string | string[])
  • set(data: object, ids?: string | string[])
  • edit(data: object, ids?: string | string[])

Unsetting properties

Like stated before, some properties can't be set to an empty value, that's a routerOS behavior. To deal with that, we need to unset the property by using the unset() method:

unset(properties: string | string[], ids?: string | string[]): Promise<any[]>

var filterMenu = client.menu("/ip firewall filter");
filterMenu.unset([
    "srcAddress",
    "inInterface",
    "outInterface"
], item.id).then((response) => {
    // All properties was unset!
    // The item(s) changed will be returned with the updated info
}).catch((err) => {
    // Error unsetting
    console.log(err);
});

You can also query unset:

var filterMenu = client.menu("/ip firewall filter");
filterMenu.where("dstPort", 8080).orWhere("chain", "output").unset([
    "srcAddress",
    "inInterface",
    "outInterface"
]).then((response) => {
    // All properties was unset!
    // The item(s) changed will be returned with the updated info
}).catch((err) => {
    // Error unsetting
    console.log(err);
});

Note: you can give either an array of properties to unset or just a string with one property. When giving an array of properties, the API will unset them randomly in parallel. If trying to unset the second property, for example, and that property doesn't exist, the API will throw an error but won't stop unsetting the other properties. Keep that in mind if you want to unset the property only if another property needs to be unset first.

Note 2: unsetting properties that are already unset shouldn't throw any errors. But unsetting properties that can't be unset (ones that needs to be updated as empty string instead of unsetting) will throw an error.

Enabling and disabling entries

Enabling and disabling only works with list menus, which is, that contains a list of entries (or items). To enable or disable an entry, you can do it in more than one way:

enable(ids?: string | string[]): Promise<any[]>

disable(ids?: string | string[]): Promise<any[]>

var filterMenu = client.menu("/ip firewall filter");
filterMenu.get().then((list) => {
    var firstItem = list[0];

    // DISABLING
    filterMenu.disable(firstItem.id);
    filterMenu.where("id", firstItem.id).disable();
    filterMenu.where("id", firstItem.id).update({
        disabled: true
    });

    // ENABLING
    filterMenu.enable(firstItem.id);
    filterMenu.where("id", firstItem.id).enable();
    filterMenu.where("id", firstItem.id).update({
        disabled: false
    });

}).catch((err) => {
    // Error getting the list of firewall filter entries
    console.log(err);
})

Keep in mind that, whichever method you choose, it will always return a Promise:

filterMenu.disable(firstItem.id).then(() => {
    // Disabled!
}).catch((err) => {
    // Error disabling
    console.log(err);
});

// ----------------------- //

filterMenu.enable(firstItem.id).then(() => {
    // Enabled!
}).catch((err) => {
    // Error enabling
    console.log(err);
});

Moving entries

Like enabling and disabling, moving is just for list menus. To move an item we have to keep in mind that:

  • The item moved will always be above the destination.
  • You can move by either the item id or the number that you see when using winbox.
  • To move to the bottom as the last item, just omit the destination.
  • You can move one or multiples items above a single destination. Let's see an example:

move(from: string | string[], to?: string | number): Promise<any[]>

filterMenu.move(thirdItem.id, firstItem.id).then((response) => {
    // Third item moved above first item!
}).catch((err) => {
    // Error moving
    console.log(err);
});

// Moving multiple
filterMenu.move([
    thirdItem.id,   // *A1
    fourthItem.id,  // *A2
    fifthItem.id,   // *A3
    sixthItem.id    // *A4
], firstItem.id /* *A0 */).then((response) => {
    // All items moved above first item! At once!
}).catch((err) => {
    // Error moving
    console.log(err);
});

Note: the move() method doesn't work without informing the ids you want to move. If you want to query-move, you need to use the moveAbove() method:

moveAbove(to?: string | number): Promise<any[]>

filterMenu.where("chain", "input").moveAbove(firstFowardRule.id).then((response) => {
    // response will be an array of all items moved
}).catch((err) => {
    // error moving
});

Removing entries

Once again, removing only works in list menus. You can remove an entry using the remove() method:

remove(ids?: string | string[]): Promise<any[]>

filterMenu.remove(firstItem.id);
// OR
filterMenu.where("id", firstItem.id).remove();

You can remove multiple items at once:

var filterMenu = client.menu("/ip firewall filter");
filterMenu.get().then((filterList) => {

    var ids = filterList.map((entry) => {
        return entry.id;
    });

    filterMenu.remove(ids).then((response) => {
        // Items removed!
    }).catch((err) => {
        // Error removing
        console.log(err);
    });

}).catch((err) => {
    // Error getting the list of firewall filter entries
    console.log(err);
})

You can also do it by querying:

var filterMenu = client.menu("/ip firewall filter");
filterMenu.where("chain", "output").remove().then((response) => {
    // response is an array of all items removed
}).catch((err) => {
    // Error getting the list of firewall filter entries
    console.log(err);
})

Other aliases:

  • remove(ids?: string | string[])
  • delete(ids?: string | string[])

Removing all entries of the menu

CAREFUL. You can use the purge() method:

filterMenu.purge().then((response) => {
    // Menu cleaned!
}).catch((err) => {
    // Error purging entries
    console.log(err);
})

Other commands

In routerOS we have commands other than add, set, remove, enable and etc. To use those commands we can invoke the exec() function. Let's say we want to generate and export of our routerboard and generate a backup:

exec(command: string, data?: object): Promise<any[]>

var rootMenu = client.menu("/");

var now = new Date();
var year = now.getYear();
var month = now.getMonth() + 1;

rootMenu.exec("export", {
    file: `export-${ year }-${ month }.rsc`
}).then((response) => {
    // Export done, let's backup
    return client.menu("/system backup").exec("save", {
        name: `backup-${ year }-${ month }`,
        dontEncrypt: true
    });
}).then((response) => {
    // Backup done!
    console.log(response);
}).catch((err) => {
    // Error exporting or backing up
    console.log(err);
});

Next: Streaming Content