The Forward Networks data model has an extensive collection of normalized vendor agnostic data for each of your devices. There are times though, when you may need a piece of information about a device that you can’t seem to find within the data model, or you need to pull information from a configuration that is not part of the running config or standard collection. This is where custom commands come in. Forward Networks allows you to add additional show commands to each snapshot to tailor the platform to your organization’s specific needs. Knowing how to pull information out of these files gives you flexibility to extract what you need to answer urgent questions, taking your data management to the next level.
The basic formula that will get you started
foreach device in network.devices
foreach command in device.outputs.commands
where command.commandText == "<YOUR COMMAND HERE>"
let parsed_response = parseConfigBlocks(device.platform.os, command.response)
let my_data = blockMatches(parsed_response, pattern)
Almost all NQE’s are going to start with foreach device in network.devices
Then we’re going to look at the outputs of all the commands Forward runs on your device.
For a command that is standard within the data model we have commandType, but for a custom command we need the commandText. All data collected from devices are stored as a text file with type string. We are going to be using the blockMatches function which uses NQE specific data type List<ConfigLine> which adds additional properties to each line of the raw text.
In order to achieve this, we use the parseConfigBlocks function that takes the device OS and the output of the command and converts it to this format.  And then we can use the pattern for the data we want to extract with the blockMatches function to collect the information needed.
The Sample Dataset
The example I’m using will be on getting a list of APs out of an Aruba WLC. The command we want to run on the controller is show ap database and the output is as follows.
Command: show ap database
Name         Group    AP Type  IP Address     Status          Flags  Switch IP    Standby IP
----         -----    -------  ----------     ------          -----  ---------    ----------
AP203R       default  203R     192.168.1.9    Down            2      10.8.36.30   0.0.0.0
AP203RP      default  203RP    192.168.1.10   Up 12h:21m:22s  2f     10.8.36.30   0.0.0.0
AP203RP      default  203RP    192.168.1.8    Down            N2     10.8.36.30   0.0.0.0
AP203RP-new  default  203RP    192.168.1.12   Up 12h:21m:25s  2f     10.8.36.30   0.0.0.0
AP205        default  205      192.168.1.97   Down            N      10.4.183.78  0.0.0.0
AP205        default  205      192.168.1.128  Down                   10.4.183.78  0.0.0.0
AP207        default  207      192.168.1.4    Down            2      10.8.36.30   0.0.0.0
AP207        default  207      192.168.1.115  Down            N2     10.4.183.78  0.0.0.0
AP207        default  207      192.168.1.123  Down            N2     10.4.183.78  0.0.0.0
AP225        default  225      192.168.1.117  Down            2      10.4.183.78  0.0.0.0
AP303H       default  303H     192.168.1.120  Down            N2     10.4.183.78  0.0.0.0
AP303H       default  303H     192.168.1.129  Down            N2     10.4.183.78  0.0.0.0
AP303H       default  303H     192.168.1.2    Up 12h:19m:13s  2      10.8.36.30   0.0.0.0
AP303H-Ash   default  303H     1.1.1.2        Down            Rc2    10.4.183.78  0.0.0.0
AP305        default  305      192.168.1.131  Down            2      10.4.183.78  0.0.0.0
AP315        default  315      192.168.1.100  Down            2      10.4.183.78  0.0.0.0
AP325        default  325      192.168.1.95   Down            N2     10.4.183.78  0.0.0.0
AP325        default  325      192.168.1.10   Down            2      10.8.36.30   0.0.0.0
AP335        default  335      192.168.1.4    Down            2      10.8.36.30   0.0.0.0
mk-ap335     default  335      192.168.1.120  Down            2      10.4.183.78  0.0.0.0
X4           default  335      192.168.1.121  Down            2      10.4.183.78  0.0.0.0The Data Pattern
Once we know what the output looks like, we can determine the pattern we’re going to search for. I like to start creating my block pattern variable by copying one section of the output and replacing each item with a variable. Each AP is its own line, so this one is easy.
ap_config =```
AP203R       default  203R     192.168.1.9    Down            2      10.8.36.30   0.0.0.0
```; After the replacement, the pattern that will catch our data will look like this:
ap_config =```
{name:string} {group:string} {type:string} {ip:ipv4Address} {status: ("Up" string | "Down")} {flag:string} {ipSwitch:ipv4Address} {ipStandby:ipv4Address}
```;Notice that each item has a variable name and type so we can access the information later, but anything you don’t care to collect can be skipped by just adding the data type - {string} is a good catch all.
The Final Query
ap_config =```
{name:string} {group:string} {type:string} {ip:ipv4Address} {status: ("Up" string | "Down")} {flag:string} {ipSwitch:ipv4Address} {ipStandby:ipv4Address}
```;
status(stat) = 
if isPresent(stat) then "Up" else "Down"
;
foreach device in network.devices
foreach command in device.outputs.commands
where command.commandText == "show ap database"
let parsed_response = parseConfigBlocks(OS.ARUBA_WIFI, command.response)
let my_data = blockMatches(parsed_response, ap_config)
foreach ap in my_data
select { 
  name: ap.data.name,
  group: ap.data.group,
  type: ap.data.type,
  ip: ap.data.ip,
  status: status(ap.data.status.left),
  uptime: ap.data.status.left,
  flags: ap.data.flag,
  switch_ip: ap.data.ipSwitch,
  standby_ip: ap.data.ipStandby,
  controller: device.name
}With NQE, every foreach statement takes us to a more granular level.
We want to pull the data for every AP, so the last line foreach ap in my_data and our select statement will then access the pattern variables.
Caveats
These are specific to this query to account for variable length output but will likely not be necessary in other queries.
Pattern matching is based on matching each item after a space. Since the status has 2 variants either with one string Down or 2 strings Up 12h:21m:22s we can use an “or” notated by the “|” to account for both possibilities.
status(stat) = 
if isPresent(stat) then "Up" else "Down"
;This function is then called on by status: status(ap.data.status.left) to determine the output by detecting whether uptime is present. 
The Results

As you can see, we now have a complete list of all APs on the network and the information as it relates to them.
TLDR
This basic formula will provide you with a jumping off point when searching for data in any custom command output.
The example above searches at the data level, to search at the device level, you can create a list for every data point in the select statement.
Searching at the Device Level
pattern =```
<PATTERN YOU WANT TO MATCH - Possible variables: {name:string} {ip:ipv4Address} {other:string}>
```;
foreach device in network.devices
foreach command in device.outputs.commands
where command.commandText == "<YOUR COMMAND HERE>"
let parsed_response = parseConfigBlocks(device.platform.os, command.response)
let my_data = blockMatches(parsed_response, pattern)
select { 
  device: device.name,
  dataName: foreach item in my_data select item.data.name,
  dataIp: foreach item in my_data select item.data.ip,
  dataOther: foreach item in my_data select item.data.other
}I hope this enables you to enhance your network management by expanding the data you are able to collect from Forward Networks.
I’d love to hear if you found this useful and what custom commands you intend to run this against!





