Special thanks to Lyle Taylor for identifying…and fixing…a performance issue with this script that may occur with VERY large data sets! Most environments won’t be dealing with a CMDB large enough to have this be an issue, but it’s probably a good idea to update your CIUtils2 script include with the code below anyway.

One of the great features of ServiceNow is its CMDB and relational mapping. You can easily set up relationships between any CIs in the system. Once the relationships are defined, it becomes very simple to pull up a visual representation of a CI and its dependencies by using ServiceNow BSM maps.  Using this feature allows an end user to look at that CI and identify what else in the environment is impacted by an outage or a change to that CI.

While this works fine for most situations, it may be necessary to be able to use this relationship information outside of the map view.  What if you wanted to determine all of the Business Services impacted by a particular change request? What if you wanted to pull an approval group from each impacted Business Service to approve a specific step in your change workflow?

The ‘CIUtils’ Script Include is designed to help you get some of this information. You can find it by navigating to ‘System Definition -> Script Includes’.  To use it, you could do something like this in a Business rule, UI action, or Scheduled job…

var ciu = new CIUtils();
//Return all of the Business services affected by the current CI on this ticket
var services = ciu.servicesAffectedByCI(current.cmdb_ci);
//Do something with the array of Business Services returned

You can also use the ‘servicesAffectedByTask’ function to use a task record as your input parameter

var ciu = new CIUtils();
//Return all of the Business services affected by the current CI on this ticket
var services = ciu.servicesAffectedByTask(current);
//Do something with the array of Business Services returned

There are a couple of limitations with the CIUtils Script include though. To fill in some of those gaps, I recently created an enhancement to CIUtils called ‘CIUtils2’. It is used in pretty much the same way as the CIUtils Script include, but it gives you these added benefits…

  • Return all impacted Business Services based on the ‘cmdb_ci’ field AND the ‘Affected CIs’ related list
  • Return all impacted CIs based on the CI classes you specify
  • Return all impacted CIs throughout the entire CMDB

To use it, simply create a new Script include record with the script below WITH A NAME OF ‘CIUtils2’. You can then call the functions like this…

Usage:
var ciu = new CIUtils2();
//cisAffectedByTask takes a Task glideRecord as input and returns an array of CI sys_ids
var services = ciu.cisAffectedByTask(TaskGlideRecord); //Returns an array of impacted Business Service CIs
var services = ciu.cisAffectedByTask(TaskGlideRecord, [“cmdb_ci_service”, “cmdb_ci_web_server”]); //Returns an array of impacted CIs from the CI classes specified
var services = ciu.cisAffectedByTask(TaskGlideRecord, [“ALL”]); //Returns an array of ALL impacted CIs//cisAffectedByCI takes a CI glideRecord as input and returns an array of CI sys_ids
var services = ciu.cisAffectedByCI(current.cmdb_ci); //Returns an array of impacted Business Service CIs
var services = ciu.cisAffectedByCI(current.cmdb_ci, [“cmdb_ci_service”, “cmdb_ci_web_server”]); //Returns an array of impacted CIs from the CI classes specified
var services = ciu.cisAffectedByCI(current.cmdb_ci, [“ALL”]); //Returns an array of ALL impacted CIs. Use this with caution!!!//getCIXML takes a CI sys_id as input and returns an XML-formatted string showing all CIs impacted by an outage to the CI given
var myXML = ciu.getCIXML(current.cmdb_ci);Script:

gs.include('PrototypeServer');

var CIUtils2 = Class.create();

CIUtils2.prototype = {
    initialize: function () {
        this.maxDepth = gs.getProperty('glide.relationship.max_depth', 10); // how deep to look
        this.maxAffectedCIs = gs.getProperty('glide.relationship.threshold', 1000); // max records to return
        this.defaultClasses = ['cmdb_ci_service', 'service_offering'];
        this.arutil = new ArrayUtil();
    },
   
    /**
    * Determine which CIs are affected by a specific CI
    *
    * Inputs:
    * id is the sys_id of a configuration item (cmdb_ci)
    * classArr is an array of CI class names that should be returned
    * infoObj is an object created by _getInfoObj used to track data accross multiple
    *     calls to the function made by cisAffectedByTask. It is not needed when calling
    *     the function directly.
    *
    * Returns:
    * an array of sys_id values for cmdb_ci records upstream of
    * (or affected by) the input item
    */

   
    cisAffectedByCI: function (id, classArr, infoObj /*optional*/) {
        if (!infoObj) {
            infoObj = this._getInfoObj();
        }
        if (infoObj.visitedCIs[id]) {
            // We've already processed this CI
            return [];
        }
        infoObj.visitedCIs[id] = true;
        if (!classArr || classArr.length == 0) {
            classArr = this.defaultClasses;
        }
       
        // This is to keep track of affected CIs from this CI only.
        // CIs that are already listed in infoObj.affectedCIs from prior
        // calls to the function will not be included.
        var affectedCIs = [];
        var ci = new GlideRecord('cmdb_ci');
        if (ci.get(id)) {
            //If class = 'ALL' then just add the CI
            if (classArr[0] == 'ALL' || this.arutil.contains(classArr, ci.sys_class_name.toString())) {
                affectedCIs.push(id);
                this._addCI(id, infoObj);
            }
            this._addParentCIs(id, infoObj, affectedCIs, 1, classArr);
        }
        return this._unique(affectedCIs); // list of affected CIs
    },
   
    /**
    * Determine which CIs are affected by a task
    *
    * Inputs:
    * task is a task GlideRecord (e.g., incident, change_request, problem)
    * classArr is an array of CI class names that should be returned
    *
    * Returns:
    * an array of sys_id values for cmdb_ci records upstream from
    * (or affected by) the configuration item referenced by the task's cmdb_ci field and Affected CIs list
    */

   
    cisAffectedByTask: function (task, classArr) {
        var infoObj = this._getInfoObj();
        //Find the impacted CIs for the 'cmdb_ci' value
        var id = task.cmdb_ci.toString();
        if (id) {
            this.cisAffectedByCI(id, classArr, infoObj);
        }
       
        //Find the impacted CIs for any Affected CIs listed on the task
        var affCI = new GlideRecord('task_ci');
        affCI.addQuery('task', task.sys_id);
        affCI.query();
        while (affCI.next()) {
            this.cisAffectedByCI(affCI.ci_item.sys_id.toString(), classArr, infoObj);
        }
        return this._objToArray(infoObj.affectedCIs);
    },
   
    /**
    * Returns an XML-formatted string showing all CIs impacted by an outage to the CI given
    *
    * Inputs:
    * id is the sys_id of the root CI
    *
    * Returns:
    * an XML-formatted string containing cmdb_ci records downstream of
    * (or affected by) the configuration item provided as input
    */

   
    getCIXML: function (id) {
        var gr = new GlideRecord('cmdb_rel_ci');
        gr.addQuery('child', id);
        gr.query();
        gr.next();
        var str = '';
        str += '<CI>';
        str += '<sys_id>' + gr.child.sys_id + '</sys_id>';
        str += '<name>' + gr.child.name + '</name>';
        str += '<relType>SELF</relType>';
        ret = this._recurs(id);
        if (ret) {
            str += '<children>';
            str += ret;
            str += '</children>';
        }
        str += '</CI>';
        return str;
    },
   
    _recurs: function (ci) {
        var gr = new GlideRecord('cmdb_rel_ci');
        gr.addQuery('child', ci);
        gr.query();
        var str = '';
        while (gr.next()) {
            str += '<CI>';
            str += '<sys_id>' + gr.parent.sys_id + '</sys_id>';
            str += '<name>' + gr.parent.name + '</name>';
            str += '<relType>' + gr.type.name + '</relType>';
            ret = this._recurs(gr.parent.sys_id);
            if (ret) {
                str += '<children>';
                str += ret;
                str += '</children>';
            }
            str += '</CI>';
        }
        return str;
    },
   
    _addParentCIs: function (id, infoObj, affectedCIs, currentDepth, classArr) {
        if (infoObj.affectedCIsCount >= this.maxAffectedCIs)
            return;
       
        var rel = new GlideRecord('cmdb_rel_ci');
        rel.addQuery('child', id);
        rel.query();
       
        var parents = [];
        while (rel.next()) {
            parents.push(rel.parent.toString());
        }
        if (parents.length) {
            var parent = new GlideRecord('cmdb_ci');
            parent.addQuery('sys_id', parents);
            parent.query();
           
            while (parent.next() && infoObj.affectedCIsCount < this.maxAffectedCIs) {
                var pid = parent.sys_id.toString();
                if (!infoObj.visitedCIs[pid]) {
                    infoObj.visitedCIs[pid] = true;
                    if (classArr[0] == 'ALL' || this.arutil.contains(classArr, parent.sys_class_name.toString())) {
                        affectedCIs.push(pid);
                        this._addCI(pid, infoObj);
                    }
                    if (currentDepth < this.maxDepth)
                        this._addParentCIs(pid, infoObj, affectedCIs, currentDepth + 1, classArr);
                }
            }
        }
    },
   
    _addCI: function (id, infoObj) {
        infoObj.affectedCIs[id] = true;
        infoObj.affectedCIsCount++;
    },
   
    _getInfoObj: function () {
        return {
            affectedCIsCount: 0, // track how many added, since can't get size() for an Object
            affectedCIs: {},     // full list of affected CIs for specified classes
            visitedCIs: {}       // track CIs already iterated over
        };
    },
   
    _objToArray: function (obj) {
        var ar = [];
        for (var id in obj) {
            ar.push(id);
        }
        return ar;
    },
   
    _unique: function (a) {
        var obj = {};
        for (var idx in a) {
            obj[a[idx]] = true;
        }
        return this._objToArray(obj);
    },
   
    type: 'CIUtils2'
};

 

Another practical example for Change Management

Here’s an example UI action that I use for almost all of my clients. In change management, you’re often interested in the business services that will be impacted by a given change. While you can see this in a BSM map, it’s often useful to see this in a related list directly on your change form as well. By adding the ‘Impacted Services’ related list to your change form, you can populate this data. This UI action gathers all of the information about the CIs on your change request and adds the impacted services to the list.

‘Refresh Impacted Services’ UI Action
Name: Refresh Impacted Services
Table: Change request
Action name: refresh_impacted_services
Form context menu: True
Condition: gs.hasRole(‘itil’)
Script:

current.update();
action.setRedirectURL(current);
removeAffectedServices();
addAffectedServices();function removeAffectedServices() {
var m2m = new GlideRecord('task_cmdb_ci_service');
m2m.addQuery('task',current.sys_id);
m2m.addQuery('manually_added','false');
m2m.query();
m2m.deleteMultiple();
}function addAffectedServices() {
var ciu = new CIUtils2();
//Find all impacted business services
var services = ciu.cisAffectedByTask(current);
//var services = ciu.cisAffectedByTask(current, ["cmdb_ci_service", "cmdb_ci_windows_server"]);
//var services = ciu.cisAffectedByTask(current, ["ALL"]);
var m2m = new GlideRecord('task_cmdb_ci_service');
for (var i = 0; i < services.length; i++) {
m2m.initialize();
m2m.task = current.sys_id;
m2m.cmdb_ci_service = services[i];
m2m.manually_added = 'false';
m2m.insert();
}
}