NXSL Cookbook

Practical recipes for common NetXMS scripting tasks. Each recipe states a goal, the script context it applies to, and a complete solution.

Data Collection

Format a UNIX timestamp as human-readable text

Goal

Convert a UNIX timestamp to a formatted date/time string.

Context

Transformation script — receives the raw collected value as $1.

Solution
return DateTime($1).format("%d.%m.%Y %H:%M:%S");

Creates a DateTime object from the timestamp and formats it using standard format specifiers. See DateTime::format() for all available format codes.

Extract a single value from a Table DCI

Goal

Create a scalar DCI from a column value in an existing Table DCI on the same node.

Context

Script DCI — configure with two script arguments: $1 is the table DCI description, $2 is the column name.

Solution
table = GetDCIValueByDescription($node, $1);
if (table != NULL) {
	col = table.getColumnIndex($2);
	if (col >= 0) {
		return table.get(0, col);
	}
}
return 0;

Looks up a Table DCI by description, finds the requested column, and returns the value from the first row.

This script only works when the Table DCI is on the same node as the scalar DCI.

Calculate 95th percentile average for ICMP packet loss

Goal

Aggregate ICMP packet loss values from child nodes, discard the top 5% outliers, and return the average of the remaining values.

Context

Script DCI — runs on a node that is a parent of a container named "all voice".

Solution
trace(1, "Global Ping Loss 95");
pValue = [];
arrayI = 0;

for (parent : $node.parents)
{
	trace(3, "Parent object: name='" .. parent.name .. "' id=" .. parent.id);
	if (parent.name == "all voice")
	{
		for (vNode : parent.children)
		{
			dciName = "ICMP: Packet loss to " .. vNode.name;
			dciId = FindDCIByDescription(vNode, dciName);
			if (dciId > 0)
			{
				tmpValue = GetDCIValue(vNode, dciId);
				if (tmpValue != null)
				{
					pValue[arrayI++] = tmpValue;
				}
			}
		}
	}
}

// Sort the array (arrays are copied when passed as arguments, so use the returned array)
pValue = bubbleSort(pValue);

// Apply the 95 percent rule: keep only the bottom 95% of values
upTo = arrayI * 0.95;
pLoss = 0;
for (ia = 0; ia < upTo; ia++)
{
	pLoss += pValue[ia];
}
if (upTo <= 0)
	return 0;
p95AvgLoss = pLoss / upTo;

trace(1, "Global Ping Loss 95 Summary: arrayI=" .. arrayI .. " upTo=" .. upTo .. " p95AvgLoss=" .. p95AvgLoss);

return p95AvgLoss;

function bubbleSort(arr)
{
	swapped = true;
	while (swapped == true) {
		swapped = false;
		for (ia = 1; arr[ia] != null; ia++)
		{
			ib = ia - 1;
			if (arr[ib] > arr[ia]) {
				t = arr[ib];
				arr[ib] = arr[ia];
				arr[ia] = t;
				swapped = true;
			}
		}
	}
	return arr;
}

The script walks the parent container’s children, collects packet loss DCI values, sorts them, then averages only the bottom 95%. This filters out spike outliers, giving a more representative loss metric.

Note that NXSL arrays are copied when passed as function arguments, so the sort function operates on a copy and must return the sorted array — the caller reassigns it with pValue = bubbleSort(pValue);.

Node and Object Information

Find the primary MAC address of a node

Goal

Print the MAC address of the interface that holds the node’s primary IP address.

Context

Script DCI or Script Library (run from debug console).

Solution
for (i : $node.interfaces)
{
   for (a : i.ipAddressList)
   {
      if (a.address == $node.ipAddress)
         println(i.macAddr);
   }
}

Iterates all interfaces and their IP addresses, matching against the node’s primary IP.

Check if a node belongs to a cluster or container

Goal

Return TRUE if the current node is a child of at least one container or cluster.

Context

Filter script or Transformation script.

Solution
for (p : $node.parents) {
    if (p.type == 5 or p.type == 14) {
        return true;
    }
}
return false;

Object type 5 is Container, type 14 is Cluster. See Constants for all object type values.

Enumerate all nodes in the object tree

Goal

Walk the entire object tree and print each object with its type.

Context

Script Library — run from the server debug console or as an EPP action.

Requires the Objects.Security.CheckTrustedObjects server configuration variable set to 0 (default) to access all nodes.
Solution 1 — recursive tree walk
function EnumerateNodes(obj, level)
{
    for (o : obj.children) {
        for (i = 0; i < level; i++) { print("  "); }
        println("[" .. o.type .. " / " .. classof(o) .. "] " .. o.name);
        EnumerateNodes(o, level + 1);
    }
}

EnumerateNodes(FindObject("Entire Network"), 0);
Solution 2 — flat node list

When you only need nodes (not the full tree structure), use GetAllNodes():

for (n : GetAllNodes()) {
    println(n.name);
}

List all custom attributes on a node

Goal

Print all custom attribute key/value pairs for the current node.

Context

Script Library or Script DCI.

Solution
attributes = $node.customAttributes;
for (a : attributes.keys)
{
    println(a .. "=" .. attributes[a]);
}

Recursively collect custom attribute values from parent objects

Goal

Gather contacts custom attribute values from all parent objects (recursively), concatenate them into a semicolon-separated string, and skip duplicates.

Context

Script Library or EPP action script.

Solution
global contacts = "";
global presence = %{ };

for (o : $node.parents)
{
	add_contacts(o);
}

println("Contacts: " .. contacts);

function add_contacts(curr)
{
	c = curr.getCustomAttribute("contacts");
	if ((c != null) && (presence[c] == null))
	{
		if (contacts.length > 0)
			contacts = contacts .. ";" .. c;
		else
			contacts = c;
		presence[c] = true;
	}

	for (o : curr.parents)
	{
		add_contacts(o);
	}
}

Uses global variables so the recursive function can accumulate results across calls. The presence hash map ensures each value is added only once.

Query asset properties for all nodes

Goal

List all nodes linked to an asset with their serial number, vendor, and model.

Context

Object query (saved query).

Solution using with syntax
with
	objName (order = "asc", name = "Object name") = {
    	return $node.name;
	},

	serial (name = "Serial") = {
		return $node.assetProperties.serial;
	},

	vendorVar (name = "Vendor") = {
		return $node.assetProperties.vendor;
	},

	model (name = "Model") = {
		return $node.assetProperties.model;
	}

(type == NODE) && $node.asset != null
Equivalent solution without with syntax
if (($object.type == NODE) && $node.asset != null)
{
    global objName (order = "asc", name = "Object name") = $node.name;
    global serial (name = "Serial") = $node.assetProperties.serial;
    global vendorVar (name = "Vendor") = $node.assetProperties.vendor;
    global model (name = "Model") = $node.assetProperties.model;
}
return ($object.type == NODE) && $node.asset != null;

Both forms produce the same tabular output. The with syntax is more concise for object queries.

Interface Management

Set all interfaces to "ignore" expected state

Goal

Change the expected state of every interface on every node to "IGNORE", preventing unwanted interface-down alerts.

Context

Script Library — run from the server debug console.

Solution
for (n : GetAllNodes()) {
    println(n.name .. "(" .. n.id .. ")");
    for (i : n.interfaces) {
        println("\t" .. i.name);
        i.setExpectedState("IGNORE");
    }
}

Filter interfaces during instance discovery

Goal

Create DCIs only for eth* and bond* interfaces when using Net.InterfaceNames as an instance discovery source.

Context

Instance discovery filter — $1 contains the instance value.

Prerequisites

Set the instance discovery method to "Agent List" with Net.InterfaceNames as the list name.

Solution
name = $1;
if (name ~= "eth|bond")
{
    return [name];
}
return false;

Returns the instance name wrapped in an array on match, or false to filter it out.

Filter out interfaces from being created

Goal

Prevent NetXMS from creating interface objects for isatap* interfaces.

Context

Hook::CreateInterface — receives the interface prototype as $1.

Solution
if ($1.name ~= "^isatap")
    return false;

return true;

Returning false tells the server to skip creating the interface object.

SNMP

Read an SNMP value from a node

Goal

Query a single SNMP OID from a node specified by name or ID.

Context

Script Library — run from the server debug console with the node name/ID as $1.

Solution
if ($1 == null)
{
   println("Please specify node name as parameter");
   return 3;
}

node = FindObject($1);
if (node == null)
{
    println("Node not found: " .. $1);
    return 3;
}

transport = node.createSNMPTransport();
if (transport == null)
{
    println("Failed to create SNMP transport, exit");
    return 1;
}

value = transport.getValue("1.3.6.1.2.1.1.1.0");
if (value == null)
{
    println("Failed to issue SNMP GET request");
    return 2;
}
else
{
    println("System description: " .. value);
    return 0;
}

Creates an SNMP transport for the target node and retrieves sysDescription. Replace the OID with the value you need.

Read an SNMP octet string as raw bytes

Goal

Retrieve an SNMP value and read it byte-by-byte as a binary stream.

Context

Script DCI or Script Library.

Solution
transport = $node.createSNMPTransport();
if (transport == null) exit;

varbind = transport.get("1.3.6.1.2.1.25.3.5.1.2.1");
if (varbind == null) exit;

bytestream = varbind.getValueAsByteStream();

println(bytestream.pos); // position starts at 0
println("0x" .. d2x(bytestream.readByte(), 2)); // hex value of first byte

The getValueAsByteStream() method returns a ByteStream object for processing binary SNMP data.

Set node geolocation from SNMP values

Goal

Read latitude and longitude from SNMP OIDs and set the node’s geolocation.

Context

Script DCI or Hook::ConfigurationPoll.

Solution
transport = $node.createSNMPTransport();
if (transport == null) {
  return null;
}

lat = transport.getValue("1.2.3.4.1");
lon = transport.getValue("1.2.3.4.2");

if (lat == null || lon == null) {
  return null;
}

geoLoc = new GeoLocation(lat, lon);
$node.setGeoLocation(geoLoc);

return 0;

Replace the OIDs with the actual OIDs for your device’s GPS coordinates.

Event Processing

Add connected device info to interface events

Goal

Enrich interface up/down event notifications with the name, IP address, and MAC address of the connected device.

Context

EPP filter script — runs for each event matching the rule.

Solution
// only for interface up and down events
if (($event.name != "SYS_IF_DOWN") && ($event.name != "SYS_IF_UP"))
	return true;

// get interface object from interface index
iface = $node.getInterface($5);
if (iface == null)
	return true;

// get peer node (node connected to this interface) object
peer = iface.peerNode;
if (peer == null)
	return true;

// get peer interface object (needed to obtain MAC address)
peerIface = iface.peerInterface;
if (peerIface != null)
{
	macAddr = peerIface.macAddr;
}
else
{
	macAddr = "<MAC unknown>";
}

// set event's named parameter
$event.setNamedParameter("additionalInfo",
	"Peer: " .. peer.name .. " " .. peer.ipAddress .. " " .. macAddr);

return true;

After this script runs, use %<additionalInfo> in your notification message template to include the peer device details.

Agent Tables

Read and display a table from an agent

Goal

Retrieve a table from an agent and print it in a formatted layout.

Context

Script Library — run from the server debug console with node name/ID as $1 and table name as $2.

Solution
node = FindObject($1);
if (node == null)
{
	println("ERROR: Node not found");
	return;
}

table = node.readAgentTable($2);
if (table == null)
{
	println("ERROR: Cannot read table from agent");
	return;
}

// Print column headers
for (i = 0; i < table.columnCount; i++)
	print("| " .. table.getColumnName(i).left(20));
println("|");
for (i = 0; i < table.columnCount; i++)
	print("+" .. "-".left(21, "-"));
println("+");

// Print data rows
for (i = 0; i < table.rowCount; i++)
{
	for (j = 0; j < table.columnCount; j++)
	{
		v = table.get(i, j);
		print("| " .. ((v != null) ? v.left(20) : "".left(20)));
	}
	println("|");
}

Sort an array of values

Goal

Sort an array for use in aggregation or reporting scripts.

Context

Any script context. Useful as a helper function within larger scripts.

Alphabetical sort
function BubbleSort(a)
{
   n = a.maxIndex + 1;
   do
   {
      newn = 0;
      for (i = 1; i < n; i++)
      {
         if (a[i - 1].compareTo(a[i]) > 0)
         {
            t = a[i - 1];
            a[i - 1] = a[i];
            a[i] = t;
            newn = i;
         }
      }
      n = newn;
   }
   while(n > 1);
   return a;
}
Numeric sort
function BubbleSort(a)
{
   n = a.maxIndex + 1;
   do
   {
      newn = 0;
      for (i = 1; i < n; i++)
      {
         if (a[i - 1] > a[i])
         {
            t = a[i - 1];
            a[i - 1] = a[i];
            a[i] = t;
            newn = i;
         }
      }
      n = newn;
   }
   while(n > 1);
   return a;
}

Uses the bubble sort algorithm. For small arrays this is sufficient; for large datasets consider using built-in array methods.

NXSL arrays are copied when passed as function arguments, so the function sorts a copy and returns it. Always use the returned array:

a = BubbleSort(a);