packages/opencode/src/bundled-skills/discovery-patterns/SKILL.md
This skill should be used when the user asks to "discovery", "discover", "probe", "sensor", "identification", "discovery schedule", "MID server", "network scan", or any ServiceNow Discovery development.
npx skillsauth add groeimetai/snow-flow discovery-patternsInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Discovery automatically populates the CMDB by scanning networks and systems.
Discovery Schedule
↓
MID Server
↓
Probes (collect data)
↓
Sensors (process data)
↓
Identification Rules (match/create CIs)
↓
CMDB Population
| Table | Purpose |
| -------------------------- | --------------------- |
| discovery_schedule | Discovery schedules |
| discovery_credentials | Discovery credentials |
| discovery_range | IP ranges to scan |
| cmdb_identification_rule | CI identification |
| ecc_agent | MID Server records |
| discovery_status | Discovery run status |
// Create discovery schedule (ES5 ONLY!)
var schedule = new GlideRecord("discovery_schedule")
schedule.initialize()
// Basic info
schedule.setValue("name", "Data Center Discovery")
schedule.setValue("description", "Weekly discovery of data center infrastructure")
// Type
schedule.setValue("discover", "IP") // IP, CI, Cloud Resources
// Schedule
schedule.setValue("run_type", "weekly")
schedule.setValue("run_dayofweek", "sunday")
schedule.setValue("run_time", "02:00:00")
// MID Server
schedule.setValue("mid_server", getMIDServerSysId("DataCenterMID"))
// Enable
schedule.setValue("active", true)
var scheduleSysId = schedule.insert()
// Add IP range
addDiscoveryRange(scheduleSysId, "10.0.0.0", "10.0.255.255", "Data Center Network")
// Add IP range to discovery schedule (ES5 ONLY!)
function addDiscoveryRange(scheduleSysId, startIP, endIP, name) {
var range = new GlideRecord("discovery_range")
range.initialize()
range.setValue("schedule", scheduleSysId)
range.setValue("name", name)
range.setValue("type", "range")
range.setValue("range_start", startIP)
range.setValue("range_end", endIP)
range.setValue("active", true)
return range.insert()
}
// Add CIDR range
function addDiscoveryCIDR(scheduleSysId, cidr, name) {
var range = new GlideRecord("discovery_range")
range.initialize()
range.setValue("schedule", scheduleSysId)
range.setValue("name", name)
range.setValue("type", "cidr")
range.setValue("range", cidr) // e.g., '10.0.0.0/24'
range.setValue("active", true)
return range.insert()
}
// Create Windows credential (ES5 ONLY!)
var cred = new GlideRecord("discovery_credentials")
cred.initialize()
cred.setValue("name", "Windows Domain Admin")
cred.setValue("type", "windows")
cred.setValue("user_name", "domain\\admin")
cred.setValue("password", "") // Set via secure method
cred.setValue("active", true)
// Credential order
cred.setValue("order", 100)
// Assign to credential affinity
cred.setValue("credential_alias", credentialAliasSysId)
cred.insert()
// Create credential affinity (map credentials to IP ranges) (ES5 ONLY!)
var affinity = new GlideRecord("dscy_credentials_affinity")
affinity.initialize()
affinity.setValue("credential", credentialSysId)
affinity.setValue("range", discoveryRangeSysId)
affinity.setValue("order", 100)
affinity.insert()
// Create custom probe script (ES5 ONLY!)
// Probes run on MID Server to collect data
// Table: discovery_probes_script
var probe = new GlideRecord("discovery_probes_script")
probe.initialize()
probe.setValue("name", "Custom Application Version")
probe.setValue("active", true)
// Probe script (runs on MID Server - ES5 ONLY!)
probe.setValue(
"script",
"var output = {};\n" +
"\n" +
"// Command to run\n" +
'var cmd = "cat /opt/myapp/version.txt";\n' +
"\n" +
"try {\n" +
" var result = Packages.com.service_now.mid.probe.tpcon.OperatingSystemCommand.execute(cmd);\n" +
" output.app_version = result.getOutput().trim();\n" +
" output.success = true;\n" +
"} catch (e) {\n" +
" output.success = false;\n" +
" output.error = e.message;\n" +
"}\n" +
"\n" +
"output;",
)
probe.insert()
// Create custom sensor (ES5 ONLY!)
// Sensors run on ServiceNow instance to process probe results
// Table: discovery_sensors_script
var sensor = new GlideRecord("discovery_sensors_script")
sensor.initialize()
sensor.setValue("name", "Process Custom Application Version")
sensor.setValue("active", true)
sensor.setValue("probe", probeSysId)
// Sensor script (runs on instance - ES5 ONLY!)
sensor.setValue(
"script",
"(function process(result, source) {\n" +
" var output = JSON.parse(result.output);\n" +
" \n" +
" if (!output.success) {\n" +
' gs.warn("Custom app discovery failed: " + output.error);\n' +
" return;\n" +
" }\n" +
" \n" +
" // Find or create CI\n" +
" var ci = source.getDeviceRecord();\n" +
" if (ci) {\n" +
" ci.u_custom_app_version = output.app_version;\n" +
" ci.update();\n" +
' gs.info("Updated CI with app version: " + output.app_version);\n' +
" }\n" +
"})(result, source);",
)
sensor.insert()
// Create identification rule (ES5 ONLY!)
var rule = new GlideRecord("cmdb_identifier")
rule.initialize()
rule.setValue("name", "Server Identification")
rule.setValue("table", "cmdb_ci_server")
rule.setValue("active", true)
// Priority (lower = higher priority)
rule.setValue("order", 100)
// Identification entries (criteria)
rule.insert()
// Add identification criteria
var entry = new GlideRecord("cmdb_identifier_entry")
entry.initialize()
entry.setValue("identifier", rule.getUniqueValue())
entry.setValue("criterion_attributes", "serial_number") // Match by serial
entry.setValue("search_type", "equals")
entry.setValue("active", true)
entry.insert()
// Identification script for complex matching (ES5 ONLY!)
// Table: cmdb_identifier_script
var script = new GlideRecord("cmdb_identifier_script")
script.initialize()
script.setValue("name", "Custom Server Match")
script.setValue("table", "cmdb_ci_server")
script.setValue("active", true)
script.setValue(
"script",
"(function identify(source) {\n" +
' var serial = source.getValue("serial_number");\n' +
' var hostname = source.getValue("name");\n' +
" \n" +
" // Try serial match first\n" +
' var gr = new GlideRecord("cmdb_ci_server");\n' +
" if (serial) {\n" +
' gr.addQuery("serial_number", serial);\n' +
" gr.query();\n" +
" if (gr.next()) {\n" +
" return gr.getUniqueValue();\n" +
" }\n" +
" }\n" +
" \n" +
" // Try hostname + IP match\n" +
' gr = new GlideRecord("cmdb_ci_server");\n' +
' gr.addQuery("name", hostname);\n' +
' gr.addQuery("ip_address", source.getValue("ip_address"));\n' +
" gr.query();\n" +
" if (gr.next()) {\n" +
" return gr.getUniqueValue();\n" +
" }\n" +
" \n" +
" // No match - return null to create new CI\n" +
" return null;\n" +
"})(source);",
)
script.insert()
// Check discovery run status (ES5 ONLY!)
function getDiscoveryStatus(scheduleSysId) {
var status = new GlideRecord("discovery_status")
status.addQuery("dscheduler", scheduleSysId)
status.orderByDesc("sys_created_on")
status.setLimit(1)
status.query()
if (status.next()) {
return {
state: status.state.getDisplayValue(),
started: status.getValue("started"),
completed: status.getValue("completed"),
devices_found: status.getValue("devices_found"),
devices_completed: status.getValue("devices_completed"),
errors: status.getValue("error_count"),
}
}
return null
}
// Get discovered devices from a run (ES5 ONLY!)
function getDiscoveredDevices(statusSysId) {
var devices = []
var device = new GlideRecord("discovery_device_history")
device.addQuery("status", statusSysId)
device.query()
while (device.next()) {
devices.push({
ip_address: device.getValue("source"),
ci: device.cmdb_ci.getDisplayValue(),
ci_class: device.getValue("ci_type"),
state: device.state.getDisplayValue(),
issues: device.getValue("issue_count"),
})
}
return devices
}
| Tool | Purpose |
| --------------------------------- | ---------------------- |
| snow_query_table | Query discovery tables |
| snow_find_artifact | Find discovery configs |
| snow_execute_script_with_output | Test discovery scripts |
| snow_cmdb_search | Search discovered CIs |
// 1. Query active schedules
await snow_query_table({
table: "discovery_schedule",
query: "active=true",
fields: "name,discover,run_type,mid_server",
})
// 2. Check recent discovery status
await snow_execute_script_with_output({
script: `
var status = getDiscoveryStatus('schedule_sys_id');
gs.info(JSON.stringify(status));
`,
})
// 3. Find discovery errors
await snow_query_table({
table: "discovery_log",
query: "level=error^sys_created_on>=javascript:gs.daysAgo(1)",
fields: "message,source,sys_created_on",
})
development
This skill should be used when the user asks to "App Engine Studio", "workspace builder", "custom workspace", "AES", "low code", "app development", "studio", or any ServiceNow App Engine Studio development.
tools
This skill should be used when the user asks to "create a widget", "build a widget", "service portal widget", "sp_widget", "fix widget", "widget not working", "ng-click not working", or any Service Portal widget development.
development
This skill should be used when the user asks to "create chatbot", "virtual agent", "VA topic", "NLU", "conversation", "chat flow", "topic block", or any ServiceNow Virtual Agent development.
development
This skill should be used when the user asks to "vendor", "supplier", "contract", "procurement", "SLA", "vendor risk", "vendor performance", or any ServiceNow Vendor Management development.