Walking the ServiceNow CMDB Relationship Tree

/, Script includes/Walking the ServiceNow CMDB Relationship Tree

Walking the ServiceNow CMDB Relationship Tree

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();
}
}

About the Author:

46 Comments

  1. Precious April 1, 2010 at 1:24 am - Reply

    We have recently implemented Service now and I have been tasked to put together the configuration management process using the service now tool. Has anyone done this before and are there any points or guidelines apart from the ones mentioned above?

  2. Matt Gaide September 20, 2010 at 5:50 am - Reply

    This was really helpful!

    Thanks!!

  3. Erite October 6, 2011 at 10:05 am - Reply

    Thank you it works very well.
    However what we really want to do is not to return all the impacted Services but only the one immediately linked to the CI.
    How could I do that?
    For instance if a CI is related to Service A and Service A is related to another upstream Service called Service B. Both Service A and Service B are returned. However we just want to return Service A (the one which is immediately connected to the CI).

    Looking at the Script Include I have found this:
    ” initialize : function() {
    this.maxDepth = gs.getProperty(‘glide.relationship.max_depth’,10); // how deep to look
    this.currentDepth = 0;”

    So I believe if I set relationship.max_depth to 1 it will only return the Services immediately linked to the CI?

    • Mark Stanger October 6, 2011 at 2:29 pm - Reply

      Yes, that should work, but it will use that depth for everything else as well. If it were me, I would probably adjust the code to accept another parameter indicating the max depth to go to. That way you have the flexibility to do whatever depth you want for whatever class you want.

  4. Erite October 7, 2011 at 3:55 am - Reply

    Mark,

    What we have done is to create an other Script Includes called CIUtils3 with max depth set to 1 within the Script Includes. It looks like that:

    initialize : function() {
    this.maxDepth = gs.getProperty(‘glide.relationship.max_depth’,1); // how deep to look
    this.currentDepth = 0;

    We have modified our business rule to use CIUtils3 as opposed to CIUtils2.

    Although we haven’t fully tested it, it seems to work as expected. For instance the ‘Refresh Impacted Services’ UI Action on Change gets you all the Business Services where our business rule gets you only the Business Services directly linked to the CI.

    Do you see any issue with that option? We want to make sure that max depth set to 1 is not applied everywhere but only for our business rule.

    Thanks in advance

    • Mark Stanger October 7, 2011 at 6:35 am - Reply

      That looks great. Good idea!

    • Raj June 28, 2017 at 7:21 pm - Reply

      Hi Erite,

      We have a similar requirement. Could you please share the CIUtils3 which you have developed.

      Thanks in advance.

  5. Rob January 12, 2012 at 4:58 am - Reply

    Hi Mark,

    We are trying to implement this script includes, but our customers would like to look upstream and downstream. Can I adjust the script include to look both ways? If so, how would I do that?

    Is there an equivalent to the addParentCIs function and “var al = g.getRelatedRecords(value, null, ‘cmdb_ci’, ‘cmdb_ci’, ‘child’); // returns ArrayList” statement that I can use to search for Child CIs?

    Thanks in advance.

    • Mark Stanger January 12, 2012 at 6:48 am - Reply

      Hey Rob,

      That isn’t built into this script include and, for what it’s worth, I haven’t ever seen a really good reason for doing what you’re asking for so I would reconsider that requirement if I were you. Having said that, you could accomplish it by setting up a new script include that would be identical except for the name of the script include and switching every instance of ‘parent’ and ‘child’ in the script for each other.

      • Rob January 12, 2012 at 8:04 am - Reply

        Our customers want the flexibility to return CIs upstream and downstream. For example, a Server going down may impact an Application with a depends on relationship (upstream), but a switch going out may impact a network circuit with a connects to relationship (downstream). Is there a better way of doing this maybe? Basically our CMDB relationships look like the following for network circuits:

        IP Switch > Network Adapter > Port > Circuit

        Our network team would like to be able to list the Switch as the CI and show the Circuits that will be unavailable due to the switch outage. Is there a way to do this with the code as is?

        If not, would I create a duplicate function called addChildCIs (in addition to the addParentCIs) in the new script includes?

        • Mark Stanger January 13, 2012 at 6:11 am - Reply

          Tough to say what the best solution would be without a full understanding of the environment, but I would look at the CMDB relationships first and see if there’s a way for you to get them all going in the same direction. If possible, you want everything to flow down from the business services. If you can’t do that then you would need to do as I said previously and create a new script include that flips all of the logic by switching ‘parent’ and ‘child’.

      • Ricky March 16, 2015 at 4:28 am - Reply

        Thanks Mark, this worked well for me.

  6. Candi Morris September 21, 2012 at 12:35 pm - Reply

    We are beginning to import business services and their related services (for this case, IT Provider Services, which is a user-defined table based on Business Service). I have the data defined in a spreadsheet…is there any way to build the relationship between the Business Service and the IT Provider Service(s) as they are being imported, rather than having to manually enter these relationships? If there is a code snippet/method that I could use for this case, I would appreciate it!

    • Mark Stanger September 21, 2012 at 1:11 pm - Reply

      Hi Candi,
      The relationships are just a standard parent/child format stored in the ‘cmdb_rel_ci’ table. If you navigate to ‘cmdb_rel_ci.list’ in your left navigation filter you can bring up that table. Then you can right-click the list header for the table and export to CSV to generate a template for your import. You’ll only need to worry about the ‘Parent’, ‘Child’, and ‘Type’ fields for the import. While there’s no script to aid with this, it is pretty simple with a basic import as long as your CI names are unique.

  7. Paintboy April 29, 2013 at 8:39 am - Reply

    I would like to output the reulsts into a table or related list. How do I do that?

    • Mark Stanger April 29, 2013 at 11:35 am - Reply

      Take a look at the ‘Refresh Impacted Services’ UI action on the change request table. This is available out of box and it shows exactly how you could set this up.

      • Paintboy April 29, 2013 at 11:42 am - Reply

        Thanks, I appreciate the help. I’ll give this a try. I am specifically looking to get cis impacting cis in a list view or report. Sort of like a report of what is represented in the BSM map. And, admittedly, my scripting skills are beginner level.

  8. Dan July 15, 2013 at 3:43 pm - Reply

    Have you run into any issues with the script with large relationship structures. We have approx 2.6M relationships in the cmdb_rel_ci table with approx 700k CIs. I have received a couple of notices from service now about this script running long – over a day in some cases. It never seems to return or hit the thresholds which I can certainly limit further but was wondering if there are any known issues you are aware of.

    • Dan September 12, 2013 at 2:39 pm - Reply

      I actually figured out that the issue is if you are only searching for a specific CI Class like cmdb_ci_servers in my issue. When calling addParentCIs recursively there is no guard clause to tell if you have already seen that ci or not. There is a check further below, but it only checks against the CIs that you are interested in. For large relationship structures this can be very inefficient. I basically copied the main CIUtil script and added the parents if check.

      if (this.parents[value])
      return;
      else this.parents[value] = true;

      • Mark Stanger September 12, 2013 at 3:05 pm - Reply

        Thanks Dan. I’d love to update my script above if you can tell me exactly what was modified and where. Did you just update my script include with these lines or did you have to create something new?

        • Dan September 12, 2013 at 3:10 pm - Reply

          I simply modified the script, in the initialize section I added:

          this.parents = new Object();

          Then as the first line of the _addParentCIs function I added the if condition I posted previously. This keeps track of all of the CIs that you have visited so that you don’t traverse the same paths more than once.

          • Mark Stanger September 12, 2013 at 3:53 pm - Reply

            Perfect. I’ve updated the script above. Thanks for the feedback, that’s a great fix to have.

  9. Ron Holton July 25, 2013 at 10:33 am - Reply

    This is exactly what I am looking for… I am just having issues with the exact verbiage in my script…

    I copied the CIUtils Script Includes and replaced the existing text with the content of top script section above and named it CIUtils2. I the copied the UI Action on the Change Table for the “Refreshed Impacted Services” and replaced the text with out success.

    Question: What would I put into the UI Action that would query the entire Affected CI list and return all Upstream Business Services for each without duplicates. Would like to also get the Approvers Group for sending notification and/or approval tasks…

    Thanks in advance…

    Ron

    • Mark Stanger July 25, 2013 at 5:09 pm - Reply

      This is what I use in the Crossfuze change management turnkey solution. Getting the approvers would be done in a completely separate query against the ‘task_cmdb_ci_service’ table after this data has been populated there.

      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();
         }
      }
  10. Scott Simon October 16, 2013 at 1:09 pm - Reply

    Hello, I am working with CMDB database catalog which contains the catalog name and database instance. I want to use this table in the CI Relationship table, but name and database instance needs to be combined to make the row unique. CI Relationship uses parent, class, type, child class (but I want to use multiple fields in the child field). How would you set this up? Thanks for your help.

  11. Amisha Parekh October 28, 2014 at 5:40 am - Reply

    Hi Mark,

    I am trying to use following script lines in my function.
    var list = gc.getRelatedRecords(ciSysId, null, “cmdb_ci”, “cmdb_ci”, relation); // returns ArrayList
    for(var idx = 0; idx < list.size(); idx++) {

    But, it seems like Eureka is not supporting array list function and causes system to throw "java.lang.IndexOutOfBoundsException" Error and run long running query.

    Can you please help me?

    Thanks & Regards,
    Amisha

  12. June Ralph November 21, 2014 at 8:01 am - Reply

    I have a requested from my manager to automatically added any downstream affected CIs to the “Affected CI” list on a change request. Where I am new, I haven’t the faintest idea how to do this. Can someone please help ??? Thanks

  13. Paul Coste December 5, 2014 at 2:12 pm - Reply

    Wondering if anyone has tried anything like this…

    One of the problems I see with ServiceNow (at least out of the box) is that the impact direction is perceived to be fixed, with the parent impacted by the child. Although there is technically a toggle for this in the relationship type (you can simply switch the Parent true/false on each of the relationship pairs to reverse the direction of impact, for example), it is confusing at best. In some cases it is just plain incorrect (for example, in our instance it would seem that if a circuit powers a rack, then the rack going down would impact the circuit, whereas the opposite is true.) In addition, it seems to me that there is nothing native about the calculation of “impact” at all in ServiceNow; rather, it comes down to your implementation. If you use the CIUtils or CIUtils2 scripts and actually do something to calculate the impact, then of course you will follow those rules, but I am not aware of anything that fundamentally requires the parent of a relationship to be impacted by the child outside of these scripts.

    Anyway, I am considering the following ways of fine tuning the concept of “impact”.

    1. Add a boolean like “Reverse direction” on the relationship type table, so that you can easily switch the default direction for that relationship type.

    2. Add a field on the relationship table itself, which would allow you to Reverse the direction of the default direction in a particular case. This one is about semantics. For instance, in some cases, a container is impacted by its contents, but in other cases the opposite may true. Although it is probably best to define separate relationship types for these cases, this flexibility may still be desirable.

    3. Add a field to the Relationship Type table that characterizes the relationship. This could be as simple as another boolean to say whether this relationship type should be used to determine “impact” (allowing for other relationships to be created that are informative but not necessarily impactful). Or, it could be a typed field, or even a script that could be run to determine whether the relationship is impactful, in what ways, and in which direction.

    Seems to me that this would add to the robustness of the relationship model, and would probably indicate some further enhancements to the relationship walker. Not sure whether it would be considered “safe” to add custom fields to the Relationship Types or CI Relationships, or whether it is likely that these would be trampled on or made obsolete by future ServiceNow enhancements. I do know that there is an unused field on the relationship that seems to have the intent of characterizing the relationship but from what I can tell from the forums it has never been implemented.

    Interested to know if anyone else has thought of or attempted anything along these lines.

    Thanks!
    Paul

  14. John Duchock January 8, 2015 at 4:53 am - Reply

    It is interesting how much of the code above is verbatim in the Eureka release….

    • Mark Stanger January 8, 2015 at 11:59 am - Reply

      Yeah, there’s quite a bit of overlap. As I mentioned in the article above, the ‘CIUtils2’ script include is based on the out-of-box ‘CIUtils’. ServiceNow sometimes adopts some of the SNGuru solutions in their core product as well. I always prefer an attribution if someone uses my code, but in this case the waters are pretty muddy all around :). In the end, there’s a lot of people who are benefitting from it!

  15. Jef De Coster February 19, 2015 at 6:02 am - Reply

    Hi all,
    I added some more functions that can be used for task_ci related stuff. Because I based myself on the script as mentioned in this post, I’ll post my new CIUtils_2 extended version here. I’ve tested it on Aspen Release at the customer and it’s working 😉 any feedback, remarks are welcome… (as i’m still quite the newbie in Snow).
    Kind Regards,
    Jef

    //------------------------------------------------------------//
    // **NEW
    //Script Include:
    // Name:            _CIUtils_2
    // Active:          true
    // Client callable: blank/unchecked
    // Description:     Utility class for working with configuration items with additional functions for using Relate CI Method in Change Request and Change Task
    //
    // USE:
    // var ciu = new _CIUtils_2();
    // // 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);
    // var AffectedCIs = ciu.cisAssignedTo(current.requested_for); //Returns an array of CIs where requested_for is assigned_to
    // var AffectedCIs = ciu.cisAssignedTo(current.requested_for, u_relate_ci_class); //Returns an array of CIs where requested_for is assigned_to and only from the CI class specified
    // var AffectedCIs = ciu.cisDepartment(current.u_requested_for_department); //Returns an array of CIs for the specified department
    // var AffectedCIs = ciu.cisDepartment(current.u_requested_for_department, u_relate_ci_class); //Returns an array of CIs for the specified department and only from the CI class specified
    // var AffectedCIs = ciu.cisLocation(current.requested_for); //Returns an array of CIs for the specified location
    // var AffectedCIs = ciu.cisLocation(current.requested_for, u_relate_ci_class); //Returns an array of CIs for the specified location and only from the CI class specified
    // var AffectedCIs = ciu.cisClass(ciClass); //Returns an array of CIs from the CI class specified
    // var AffectedCIs = ciu.cisSupportGroup(assGrp, ciClass); //Returns an array of CIs supported by the assGrp and if specified from the CI class
    // var CHECKaffCIs = ciu.isCISupportedByGroup(id, assGrp, ciClass); // Will return TRUE if the specified Config Item (id) is supported by the Group (assGrp) and if specified is of the CI class
    // var CHECKaffCIs = ciu.isCIFromDepartment:(id, assDep, ciClass); // Will return TRUE if the specified Config Item (id) is from the Department (assDep) and if specified is of the CI class
    // var CHECKaffCIs = ciu.isCIFromLocation(id, assLoc, ciClass); // Will return TRUE if the specified Config Item (id) is at the Location (assLoc) and if specified is of the CI class
    // var AffectedCIs = ciu.cisAllAffectedByTask(TaskID, ciClass); //Returns an array of CIs affected by the Task (TaskID - task_ci record) and if specified from the CI class specified
    // var AffectedCIs = ciu.cisAffectedByTaskSupportGroup(TaskID, assGrp, ciClass); //Returns an array of CIs affected by the Task (TaskID - task_ci record) AND supported by the Group (assGrp) and if specified from the CI class specified
    // var AffectedCIs = ciu.cisAffectedByTaskDepartment(TaskID, assDep, ciClass); //Returns an array of CIs affected by the Task (TaskID - task_ci record) AND at the Department (assDep) and if specified from the CI class specified
    // var AffectedCIs = ciu.cisAffectedByTaskLocation(TaskID, assLoc, ciClass); //Returns an array of CIs affected by the Task (TaskID - task_ci record) AND at the Location (assLoc) and if specified from the CI class specified
    //
    // COMMAND = ciu.deleteTaskAffectedCIs(TaskID) =&gt; command to delete all task_ci records for the referenced Task as specified by TaskID
    // COMMAND = ciu.AddSingleTaskAffectedCI(ConfigItem, TaskID) =&gt; command to add 1 CI in task_ci records for the referenced Task as specified by TaskID
    // COMMAND = ciu.AddMultipleTaskAffectedCIs: function(TaskID, arrAffCIs) =&gt; command to add multiple CIs (array) in task_ci records for the referenced Task as specified by TaskID
    // COMMAND = ciu.CopyTaskAffectedCIs(SourceTaskID, TargetTaskID, arrAffCIs) =&gt; Will copy all Affected Items from SourceTask (table task_ci) to TargetTask, if a list of CI's is given (arrea) then only the matching CI's will be copied.


    // Script:
    gs.include("PrototypeServer");

    var _CIUtils_2 = Class.create();

    _CIUtils_2.prototype = {

        initialize : function() {
            this.maxDepth = gs.getProperty('glide.relationship.max_depth',10); // how deep to look
            this.currentDepth = 0;

            this.CIs = {}; // list of affected CIs
            this.maxSize = gs.getProperty('glide.relationship.threshold',1000); // how many records to return
            this.added = 0; // track how many added, since can't get size() for an Object
            this.parents = {}; // track parents already iterated
        },

        /** Following Script has been made based on
        *   CIUtils2
        *   https://www.servicenowguru.com/scripting/script-includes-scripting/walking-servicenowcom-cmdb-relationship-tree/
        *   I've added some new funtions (such as just getting all CI's from a certain Class, Assigned To, Department, Location)
        */

       
    /*----*/   
        /**
        * 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
        *
        * Returns:
        * an array of sys_id values for cmdb_ci records downstream of
        * (or affected by) the input item
        */

        cisAffectedByCI: function(id, classArr) {
        var ci = new GlideRecord('cmdb_ci');
        ci.get(id);
        //If no class specified then assume business service
        if(classArr == null){
            classArr = ['cmdb_ci_service','service_offering'];
            for (var ciClass in classArr){
                if (ci.sys_class_name == classArr[ciClass]){
                    this._addCI(id, this.CIs);
                }
            }
        }
        //If class = 'ALL' then just add the CI
        else if(classArr[0] == 'ALL'){
            this._addCI(id, this.CIs);
        }
        else{
            for (var ciClass2 in classArr){
                if (ci.sys_class_name == classArr[ciClass2]){
                    this._addCI(id, this.CIs);
                }
            }
        }
        this._addParentCIs(id, this.CIs, this.currentDepth, classArr);

        var ciarr = []; // CIs is an Object, convert to Array for final query
        for (var i in this.CIs)
            ciarr.push(i);

        return ciarr; // 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 downstream of
        * (or affected by) the configuration item referenced by the task's cmdb_ci field and Affected CIs list
        */

        cisAffectedByTask: function(task, classArr) {
            //Find the impacted CIs for the 'cmdb_ci' value
            var id = task.cmdb_ci.toString();
            var allCIArr = [];
            if(id){
                allCIArr = this.cisAffectedByCI(id, classArr);
            }
            //Find the impacted CIs for any Affected CIs listed on the task
            var affCIArr = [];
            var affCI = new GlideRecord('task_ci');
                affCI.addQuery('task', task.sys_id);
                affCI.query();
            while(affCI.next()){
                affCIArr = this.cisAffectedByCI(affCI.ci_item.sys_id, classArr);
                if(allCIArr.length &gt; 0){
                    //Concat the array with the current array
                    allCIArr = allCIArr.concat(affCIArr);
                }
                else{
                    allCIArr = affCIArr;
                }
            }
            //Strip the array of any duplicate values
            return this.unique(allCIArr);
        },
    /*----*/
        /**
        * 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 += '';
                str += ''+gr.child.sys_id+'';
                str += ''+gr.child.name+'';
                str += 'SELF';
            ret = this._recurs(id);
            if(ret){
                str += '';
                str += ret;
                str += '';
            }
            str += '';
            return str;
        },

        _recurs: function(ci) {
            var gr = new GlideRecord('cmdb_rel_ci');
                gr.addQuery('child', ci);
                gr.query();
            var str = '';
            while(gr.next()){
                str += '';
                str += ''+gr.parent.sys_id+'';
                str += ''+gr.parent.name+'';
                str += ''+gr.type.name+'';

                ret = this._recurs(gr.parent.sys_id);
                if (ret) {
                    str += '';
                    str += ret;
                    str += '';
                }

                str += '';
            }
            return str;
        },

        _addParentCIs : function(value, CIs, currentDepth, classArr) {
            if (this.parents[value])
                return;
            else this.parents[value] = true;

            if (this.added &gt;= this.maxSize) {
                // gs.print('reached threshold of ' + this.maxSize);
                return;
            } else {
                currentDepth++;
                var g;
                if (typeof SncEcmdbRelationships != 'undefined')
                    g = SncEcmdbRelationships();
                else
                    g = new Packages.com.glideapp.ecmdb.Relationships();

                var al = g.getRelatedRecords(value, null, 'cmdb_ci', 'cmdb_ci', 'child'); // returns ArrayList

                // first add the unique cis
                var kids = new GlideRecord('cmdb_ci');
                    kids.addQuery('sys_id', al);
                    kids.query();
                while (kids.next()) {
                    var str = kids.sys_id;
                    if (!CIs[str]) {
                        var rec = new GlideRecord('cmdb_ci');
                            rec.get(str);

                        for (var ciClass in classArr){
                            if ((kids.sys_class_name == classArr[ciClass]) || (classArr[0] == 'ALL')){
                                this._addCI(str, CIs);
                            }
                        }
                        if (this.added &gt;= this.maxSize)
                            return;
                        if (currentDepth &lt; this.maxDepth)
                            this._addParentCIs(str, CIs, currentDepth, classArr);
                    }
                }
            }
        },

        _addCI: function(id, CIs) {
            CIs[id] = true;
            this.added++;
        },

        unique: function(a) {
        //Check all values in the incoming array and eliminate any duplicates
            var r = [];
            o:for(var i = 0, n = a.length; i &lt; n; i++){
                for(var x = 0, y = r.length; x &lt; y; x++){
                    if(r[x]==a[i]){
                        continue o;
                    }
                }
                r[r.length] = a[i];
            }
            return r;
        },
    /*----*/
        /**
        * NEW (author JDC)
        * Determine which CIs are assigned to a specific user
        *
        * Inputs:
        * assigned to is the sys_id of a user (caller, requested for, requested by,...)
        *
        * Returns:
        * an array of sys_id values for cmdb_ci records
        * assigned to the input user
        *
        * !!! If Class is specified only the CIs of that Class will be retrieved
        */

        cisAssignedTo: function(assTo, ciClass) {
        //Find all CIs for the assigned to
            var assCI = new GlideRecord(&#039;cmdb_ci&#039;);
                assCI.addQuery(&#039;assigned_to&#039;, assTo);
                assCI.query();
            while(assCI.next()){
                if(ciClass == null){
                    this._addCI(assCI.sys_id, this.CIs);
                } else {
                    if (assCI.sys_class_name == ciClass){
                        this._addCI(assCI.sys_id, this.CIs);
                    }
                }
            }
            var assCIArr = []; // CIs is an Object, convert to Array for final query
            for (var i in this.CIs)
                assCIArr.push(i);
            return assCIArr; // list of affected CIs
        },
    /*----*/
        /**
        * NEW (author JDC)
        * Determine which CIs are from a specific department
        *
        * Inputs:
        * department is the sys_id of a department (agency/department (req by, req for,...)
        *
        * Returns:
        * an array of sys_id values for cmdb_ci records
        * for the input department
        *
        * !!! If Class is specified only the CIs of that Class will be retrieved
        */

        cisDepartment: function(assDep, ciClass) {
        //Find all CIs for the assigned to
            var assCI = new GlideRecord(&#039;cmdb_ci&#039;);
                assCI.addQuery(&#039;department&#039;, assDep);
                assCI.query();
            while(assCI.next()){
                if(ciClass == null){
                    this._addCI(assCI.sys_id, this.CIs);
                } else {
                    if (assCI.sys_class_name == ciClass){
                        this._addCI(assCI.sys_id, this.CIs);
                    }
                }
            }
            var assCIArr = []; // CIs is an Object, convert to Array for final query
            for (var i in this.CIs)
                assCIArr.push(i);
            return assCIArr; // list of affected CIs
        },
    /*----*/
        /**
        * NEW (author JDC)
        * Determine which CIs are from a specific location
        *
        * Inputs:
        * location is the sys_id of a location (task.location, req by, req for,...)
        *
        * Returns:
        * an array of sys_id values for cmdb_ci records
        * for the input location
        *
        * !!! If Class is specified only the CIs of that Class will be retrieved
        */

        cisLocation: function(assLoc, ciClass) {
        //Find all CIs for the assigned to
            var assCI = new GlideRecord(&#039;cmdb_ci&#039;);
                assCI.addQuery(&#039;location&#039;, assLoc);
                assCI.query();
            while(assCI.next()){
                if(ciClass == null){
                    this._addCI(assCI.sys_id, this.CIs);
                } else {
                    if (assCI.sys_class_name == ciClass){
                        this._addCI(assCI.sys_id, this.CIs);
                    }
                }
            }
            var assCIArr = []; // CIs is an Object, convert to Array for final query
            for (var i in this.CIs)
                assCIArr.push(i);
            return assCIArr; // list of affected CIs
        },
    /*----*/
        /**
        * NEW (author JDC)
        * Determine which CIs are from a specific class
        *
        * Inputs:
        * class is the sys_id of a class (as used in CMDB)
        *
        * Returns:
        * an array of sys_id values for cmdb_ci records
        * for the input class
        *
        */

        cisClass: function(ciClass) {
        //Find all CIs for the assigned to
            var assCI = new GlideRecord(&#039;cmdb_ci&#039;);
                assCI.addQuery(&#039;sys_class_name&#039;, ciClass);
                assCI.query();
            while(assCI.next()){
                this._addCI(assCI.sys_id, this.CIs);
            }
            var assCIArr = []; // CIs is an Object, convert to Array for final query
            for (var i in this.CIs)
                assCIArr.push(i);
            return assCIArr; // list of affected CIs
        },
    /*----*/
        /**
        * NEW (author JDC)
        * Determine which CIs are supported by a specific group
        *
        * Inputs:
        * group is the sys_id of a group (assigne_group, owner_group, ...)
        *
        * Returns:
        * an array of sys_id values for cmdb_ci records
        * for the input group
        *
        * !!! If Class is specified only the CIs of that Class will be retrieved
        */

        cisSupportGroup: function(assGrp, ciClass) {
        //Find all CIs for the support group
            var assCI = new GlideRecord(&#039;cmdb_ci&#039;);
                assCI.addQuery(&#039;support_group&#039;, assGrp);
                assCI.query();
            while(assCI.next()){
                if(ciClass == null){
                    this._addCI(assCI.sys_id, this.CIs);
                } else {
                    if (assCI.sys_class_name == ciClass){
                        this._addCI(assCI.sys_id, this.CIs);
                    }
                }
            }
            var assCIArr = []; // CIs is an Object, convert to Array for final query
            for (var i in this.CIs)
                assCIArr.push(i);
            return assCIArr; // list of affected CIs
        },
    /*----*/
        /**
        * NEW (author JDC)
        * Determine if a specific CI is supported by a specific group
        *
        * Inputs:
        * id is the sys_id of the specific CI; group is the sys_id of a group (assigne_group, owner_group, ...)
        *
        * Returns:
        * true if supported by the input group
        * false if not supported by the input group
        *
        * !!! If Class is specified this will be used as a supplementary check condition
        */

        isCISupportedByGroup: function(id, assGrp, ciClass) {
        //Check if CI with input id is assigned to group (supported) as inputted
        //If ciClass is inputted do a supplementary check
            var Result = false;
            var ci = new GlideRecord(&#039;cmdb_ci&#039;);
            ci.get(id);
            if (ci.support_group == assGrp) {
                if(ciClass == null){
                    Result = true;
                } else {
                    if (ci.sys_class_name == ciClass){
                        Result = true;
                    }
                }
            }
            if (Result == true) {
                return true;
            } else {
                return false;
            }
        },
    /*----*/
        /**
        * NEW (author JDC)
        * Determine if a specific CI is owned/used/located by/at a specific department
        *
        * Inputs:
        * id is the sys_id of the specific CI; assDep is the sys_id of a Department
        *
        * Returns:
        * true if owned/used/located by/at the input department
        * false if not owned/used/located by/at the input department
        *
        * !!! If Class is specified this will be used as a supplementary check condition
        */

        isCIFromDepartment: function(id, assDep, ciClass) {
        //Check if CI with input id is owned/used/located by/at the input department
        //If ciClass is inputted do a supplementary check
            var Result = false;
            var ci = new GlideRecord(&#039;cmdb_ci&#039;);
            ci.get(id);
            if (ci.department == assDep) {
                if(ciClass == null){
                    Result = true;
                } else {
                    if (ci.sys_class_name == ciClass){
                        Result = true;
                    }
                }
            }
            if (Result == true) {
                return true;
            } else {
                return false;
            }
        },
    /*----*/
        /**
        * NEW (author JDC)
        * Determine if a specific CI is owned/used/located by/at a specific Location
        *
        * Inputs:
        * id is the sys_id of the specific CI; assLoc is the sys_id of a location (task.location, req by, req for,...)
        *
        * Returns:
        * true if owned/used/located by/at the input Location
        * false if not owned/used/located by/at the input Location
        *
        * !!! If Class is specified this will be used as a supplementary check condition
        */

        isCIFromLocation: function(id, assLoc, ciClass) {
        //Check if CI with input id is owned/used/located by/at the input department
        //If ciClass is inputted do a supplementary check
            var Result = false;
            var ci = new GlideRecord(&#039;cmdb_ci&#039;);
            ci.get(id);
            if (ci.location == assLoc) {
                if(ciClass == null){
                    Result = true;
                } else {
                    if (ci.sys_class_name == ciClass){
                        Result = true;
                    }
                }
            }
            if (Result == true) {
                return true;
            } else {
                return false;
            }
        },
    /*----*/
        /**
        * NEW (author JDC)
        * Will get ALL the CI&#039;s affected by 1 Task (as retrieved from task_ci)
        *
        * Inputs:
        * TaskID is the sys_id of the Task to search for as used in task field on table task_ci
        * Task can be Incident, change_request, change_task, or any other extended task table using task_ci
        *
        * Returns:
        * an array of sys_id values of the retrieved config items (cmdb_ci.sys_id)
        * for the input Task
        *
        * !!! If Class is specified this will be used as a supplementary check condition
        */

        cisAllAffectedByTask: function(TaskID, ciClass) {
            var taskci = new GlideRecord(&#039;task_ci&#039;);
                taskci.addQuery(&#039;task&#039;,TaskID);
                taskci.query();
            while(taskci.next()){
                if(ciClass == null){
                    this._addCI(taskci.ci_item.sys_id, this.CIs);
                } else {
                    if (taskci.ci_item.sys_class_name == ciClass){
                        this._addCI(taskci.ci_item.sys_id, this.CIs);
                    }
                }
                this._addCI(taskci.ci_item.sys_id, this.CIs);
            }
            var assCIArr = []; // CIs is an Object, convert to Array for final query
            for (var i in this.CIs)
                assCIArr.push(i);
            return assCIArr; // list of affected CIs
        },
    /*----*/
        /**
        * NEW (author JDC)
        * Will get ALL the CI&#039;s affected by 1 Task (as retrieved from task_ci)
        * AND Determine which CIs are supported by a specific group
        *
        * Inputs:
        * TaskID is the sys_id of the Task to search for as used in task field on table task_ci
        * Task can be Incident, change_request, change_task, or any other extended task table using task_ci
        * assGrp is the sys_id of a group (assigne_egroup, owner_group, ...)
        *
        * Returns:
        * an array of sys_id values of the retrieved config items (cmdb_ci.sys_id)
        * for the input Task suppoted by the input Group
        *
        * !!! If Class is specified only the CIs of that Class will be retrieved
        */

        cisAffectedByTaskSupportGroup: function(TaskID, assGrp, ciClass) {
        //Find all CIs for the support group
            var arrTaskCi = this.cisAllAffectedByTask(TaskID, ciClass);
            for (var i = 0; i &lt; arrTaskCi.length; i++) {
                if(this.isCISupportedByGroup(arrTaskCi[i], assGrp, ciClass)) {
                    this._addCI(arrTaskCi[i], this.CIs);
                }
            }
            var assCIArr = []; // CIs is an Object, convert to Array for final query
            for (var j in this.CIs)
                assCIArr.push(j);
            return assCIArr; // list of affected CIs
        },
    /*----*/
        /**
        * NEW (author JDC)
        * Will get ALL the CI&#039;s affected by 1 Task (as retrieved from task_ci)
        * AND Determine which CIs are owned/used/located by/at a specific department
        *
        * Inputs:
        * TaskID is the sys_id of the Task to search for as used in task field on table task_ci
        * Task can be Incident, change_request, change_task, or any other extended task table using task_ci
        * assDep is the sys_id of a Department
        *
        * Returns:
        * an array of sys_id values of the retrieved config items (cmdb_ci.sys_id)
        * for the input Task owned/used/located by/at the input department
        *
        * !!! If Class is specified only the CIs of that Class will be retrieved
        */

        cisAffectedByTaskDepartment: function(TaskID, assDep, ciClass) {
        //Find all CIs for the support group
            var arrTaskCi = this.cisAllAffectedByTask(TaskID, ciClass);
            for (var i = 0; i &lt; arrTaskCi.length; i++) {
                if(this.isCIFromDepartment(arrTaskCi[i], assDep, ciClass)) {
                    this._addCI(arrTaskCi[i], this.CIs);
                }
            }
            var assCIArr = []; // CIs is an Object, convert to Array for final query
            for (var j in this.CIs)
                assCIArr.push(j);
            return assCIArr; // list of affected CIs
        },
    /*----*/
        /**
        * NEW (author JDC)
        * Will get ALL the CI&#039;s affected by 1 Task (as retrieved from task_ci)
        * AND Determine which CIs are owned/used/located by/at a specific Location
        *
        * Inputs:
        * TaskID is the sys_id of the Task to search for as used in task field on table task_ci
        * Task can be Incident, change_request, change_task, or any other extended task table using task_ci
        * assLoc is the sys_id of a Location
        *
        * Returns:
        * an array of sys_id values of the retrieved config items (cmdb_ci.sys_id)
        * for the input Task owned/used/located by/at the input Location
        *
        * !!! If Class is specified only the CIs of that Class will be retrieved
        */

        cisAffectedByTaskLocation: function(TaskID, assLoc, ciClass) {
        //Find all CIs for the support group
            var arrTaskCi = this.cisAllAffectedByTask(TaskID, ciClass);
            for (var i = 0; i &lt; arrTaskCi.length; i++) {
                if(this.isCIFromLocation(arrTaskCi[i], assLoc, ciClass)) {
                    this._addCI(arrTaskCi[i], this.CIs);
                }
            }
            var assCIArr = []; // CIs is an Object, convert to Array for final query
            for (var j in this.CIs)
                assCIArr.push(j);
            return assCIArr; // list of affected CIs
        },
    /*----*/
        /**
        * NEW (author JDC)
        * Will DELETE ALL the CI&#039;s affected by list of 1 Task (as retrieved from task_ci)
        */

        deleteTaskAffectedCIs: function(TaskID) {
            //deletes affected CI related records when the Method Changes or any of the methods parameters changes
            var taskci = new GlideRecord(&#039;task_ci&#039;);
                taskci.addQuery(&#039;task&#039;,TaskID);
                taskci.query();
                taskci.deleteMultiple();
        },
    /*----*/
        /**
        * NEW (author JDC)
        * Will ADD 1 CI to the affected by list of 1 Task (to table task_ci)
        */

        AddSingleTaskAffectedCI: function(ConfigItem, TaskID) {
            if(!ChangeCollisionHelper.isCiInAffectedCis(ConfigItem, TaskID)){
                ChangeCollisionHelper.addCiToChangeAffectedCis(ConfigItem, TaskID);
            }
        },
    /*----*/
        /**
        * NEW (author JDC)
        * Will ADD MULTIPLE CIs (array) to the affected by list of 1 Task (to table task_ci)
        */

        AddMultipleTaskAffectedCIs: function(TaskID, arrAffCIs) {
            //Will loop through the Array of Affected CI&#039;s and add the Item to ChangeAffectedCis table
            var AffectedConfigItem = &quot;&quot;;
            for (var i = 0; i &lt; arrAffCIs.length; i++) {
                AffectedConfigItem = arrAffCIs[i];
                AddSingleCI(AffectedConfigItem, TaskID);
            }
        },
    /*----*/
        /**
        * NEW (author JDC)
        * Will COPY MULTIPLE CIs (array) to the affected by list of 1 Task (TargetTaskID)(to table task_ci) from another Task (SourceTaskID)(from table task_ci)
        * If an Array of CI&#039;s is specified only those CI&#039;s will be copied. (arrAffCIs)
        * Input:
        *
        */

        CopyTaskAffectedCIs: function(SourceTaskID, TargetTaskID, arrAffCIs) {
            var SourceConfigItem = &quot;&quot;;
            var TargetConfigItem = &quot;&quot;;
            var AllAffCIs = this.cisAllAffectedByTask(SourceTaskID);
            for (var i = 0; i &lt; AllAffCIs.length; i++) {
                SourceConfigItem = AllAffCIs[i];
                if ((arrAffCIs) &amp;&amp; (arrAffCIs != &quot;&quot;)) {
                    for (var j = 0; j &lt; arrAffCIs.length; i++) {
                        TargetConfigItem = arrAffCIs[j];
                        if (SourceConfigItem == TargetConfigItem) {
                            this._addCI(SourceConfigItem, this.CIs);
                        }
                    }
                } else {
                    this._addCI(SourceConfigItem, this.CIs);
                }
            }
            var assCIArr = []; // CIs is an Object, convert to Array for final query
            for (var k in this.CIs)
                assCIArr.push(k);
            //Now use the new assCIArr to create the new records
            this.AddMultipleTaskCIs(TargetTaskID, assCIArr);
        },

        type: &#039;_CIUtils_2'

    };
    //------------------------------------------------------------//
  16. Ricky March 16, 2015 at 4:32 am - Reply

    Hi,

    Has anyone tried to do this against a list of CIs?
    Basically at the moment using the CiUtils2 we’ve managed to get the downstream CIs of whatever was in the configuration item field. However if changes are being made to multiple CIs in one Change we need that to reflect in the Affected CIs Related list too.

    Thanks
    Ricky

    • Mark Stanger March 16, 2015 at 6:36 am - Reply

      Check out ‘cisAffectedByTask’ in the instructions above. That looks at the entire ‘Affected CIs’ list and walks the tree for each one. If it’s a custom list of CIs, then you’ll probably need to modify the logic a bit to work with your custom list.

  17. Peter S May 11, 2016 at 12:23 am - Reply

    Hi,

    Is there a way to use this to show a list of companies consuming the services of a business service?
    For example if we have a business service producing virtual instances, and these instances are assigned to different companies. How can i make a list of all the companies? For example if we have a problem with one host in the business service called “vmware” and i need a list of all the affected customers?

  18. Joseph Capobianco June 30, 2016 at 9:19 am - Reply

    So it looks looks like this only gets all requested CIs upstream. This wont handle a more complicated situation like:

    Say I want all impacted applications for a chassis CI. If the CI tree looks like
    Rack is parent of Chassis.
    Chassis is parent of server.
    Server is CHILD of application.

    I won’t get the application if my starting CI is a chassis.

    Ill have to check child CIs as well, but i want to get unnecessary child CIs. For instance, in the example above, I don’t want to walk up to the rack and then down to all of the possible applications it affects. I’d want to stop checking children once i get to the topmost level.
    Do you have any suggestions for this? Have you encountered any needs like this?

    • Mark Stanger June 30, 2016 at 10:35 am - Reply

      I think the problem is that your CI relationships aren’t set up in the correct parent/child relationship. They always need to be arranged in the order of the IMPACT flow, not necessarily like a datacenter or network diagram. In a BSM map, applications are always at the top as parents and the physical network/infrastructure components are at the bottom as children like this…

      Application (Runs on)-> Server (Contained by)-> Chassis (Contained by)-> Rack

      • Joseph Capobianco June 30, 2016 at 12:27 pm - Reply

        But “contained by” is a child descriptor, right? So what you wrote is actually what we have

        Also, how would you correctly relate storage volumes/arrays or databases in a proper BSM map?

        • Mark Stanger June 30, 2016 at 12:54 pm - Reply

          Forget about the descriptors, several of those provided out-of-box aren’t correct and they’re just labels anyway. I was just putting those in there as an example and wasn’t intending to prescribe any particular relationship type. The point is that your parent-child relationships need to flow down from the Business Services/Applications. They should be the parent of everything else and your Racks, etc. would generally be at the bottom-most level. You want the impact to flow up to the parent in every case.

          As far as your storage volumes/arrays, you need to look at the impact relationship there as well. What is impacted if your storage array goes down? The thing that is impacted should be a parent to the storage array. In that case you’ll probably have many parents.

          • Joseph Capobianco July 1, 2016 at 8:18 am - Reply

            Intriguing. So, when establishing a relationship using a CI that potentially has an impact upstream, you should never use a “sideways” relationship (i.e. reverse the direction of dependencies). They should always be “vertical,” correct?

  19. Mark Stanger July 1, 2016 at 9:00 am - Reply

    Whatever way you put them in or like to call them, the relationship should always accurately reflect the impact flow. That’s the whole point of the relationship diagram in this case.

  20. Steve Klopfer October 26, 2016 at 11:56 am - Reply

    I tried to utilize the CIUtils2 script on Helsinki, and am getting this error when calling it from a UI Action on a change:

    org.mozilla.javascript.EcmaError: “TaskGlideRecord” is not defined.
    Caused by error in at line 2

    1: var ciu = new CIUtils2();
    ==> 2: var services = ciu.cisAffectedByTask(TaskGlideRecord, [ALL]);

    I had to remove the “” from ALL as the syntax checker didn’t like them. Any ideas what is being don wrong?

    • Mark Stanger October 26, 2016 at 2:58 pm - Reply

      It’s telling you that ‘TaskGlideRecord’ isn’t defined. You need to set that variable somewhere up above (or pass in ‘current’ to the function instead).

  21. Andrés Ramirez March 23, 2017 at 2:06 pm - Reply

    Many Thanks, Mark.
    I have learn so much with your code 😉

  22. Josh October 27, 2017 at 8:38 am - Reply

    Hello Mark – I’m more on the process side of things now, but this is exactly what I’m looking for and would really appreciate a bit more information.

    So I get the Script Include part; what I’m not so good at is the scripting for calling the script include.

    The ‘get impacted services’ UI action is what I’m trying to base a custom one on…but I don’t know what the script should look like or what a couple of lines of code the impacted services one are for.

    I created a CIUtils2 script include, essentially copied and pasted the script you have above – but not sure what to put into UI action to actually call it.

    I want to primarily pull all upstream affected CIs and apply them to the Change record.

    What exactly do I change in this script to do that? Not sure what function should be or the m2m.cmdb_ci_services line should be to call ‘all’ upstream regardless of class. Help? Thanks!

    function addAffectedServices() {
    var ciu = new CIUtils2();
    var services = ciu.cisAffectedByTask(current.cmdb_ci, [“ALL”]);
    var m2m = new GlideRecord(‘task_cmdb_ci’);
    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();
    }

  23. Josh October 27, 2017 at 10:14 am - Reply

    I feel like I’m getting closer, but no cigar. Not sure what the ‘function’ should be?
    I have it calling the script include.

    function addAffectedCis() {
    var ciu = new CIUtils2();
    var affectedcis = ciu.cisAffectedByTask(current.cmdb_ci, ‘ALL’);
    var m2m = new GlideRecord(‘task_ci’);
    for (var i = 0; i < affectedcis.length; i++) {
    m2m.initialize();
    m2m.task = current.sys_id;
    m2m.ci_item = affectedcis[i];
    m2m.insert();
    }
    }

Leave A Comment