Awhile ago I was asked by a client to help them get set up with the ServiceNow Subscription-based Notification plugin. I had previewed the functionality but really hadn’t worked with it very much. As I dug deeper I realized that the standard setup wasn’t going to work for them. Subscription-based notifications are really not intended to be used on a bulk-subscription basis. The idea is that you might pick a handful of CIs that you are interested in and subscribe to them.

The needs that my client had brought to light the following questions:

1How can I easily subscribe to multiple CIs without having to click through the creation of a single subscription record for each CI? If I want to subscribe to the 50 network devices I’m responsible for I don’t want to have to set each of these up one-by-one.

2How can I use CI subscription notifications and not spam the end-user with a notification for each CI? An example of this would be a system administrator who is responsible for a cluster of web servers. A configuration change on one of them typically means a configuration change on all of them. We don’t want to be sending the admin an email for each of the 100 servers every time an update is made to the change ticket.

Shown below is the solution that we came up with…

Just a warning, this is some fairly advanced stuff so don’t jump in at the deep end with this unless you’ve had a chance to dip your toes in the ServiceNow administration waters already!

Question #1:How can I easily subscribe to multiple CIs without having to click through the creation of a single subscription record for each CI?
I accomplished this by creating a UI action link on the user record that redirects to record producer catalog item. The record producer collects the CI and user information, creates the subscriptions, and redirects the user to his/her subscription page.

CI Subscription Form

Multiple CI Subscription Form

1) Create a Record producer with 3 variables.
-configuration_items (list collector that references the ‘cmdb_ci’ table)
-user (reference variable that references the user table –This will be hidden by a catalog client script)
-email (single line text variable –This will be hidden by a catalog client script)

-The record producer should point to the ‘Notification Messages’ table
-The record producer should have the following in the ‘Script’ field.

var pUser = producer.user.toString();
var pEmail = producer.email.toString();
var pCIs = producer.configuration_items.toString();

//Find the user 'Primary email' notification device
var rec = new GlideRecord('cmn_notif_device');
rec.addQuery('user', pUser);
rec.addQuery('type', 'Email');
rec.addQuery('email_address', pEmail);
rec.query();
rec.next();

//Parse the list of configuration items
var list = pCIs;
var array = list.split(',');
for (var i=0; i < array.length; i++) {
//Query to see if this user has already subscribed to this CI
var nm = new GlideRecord('cmn_notif_message');
nm.addQuery('user', pUser);
nm.addQuery('configuration_item', array[i]);
nm.query();
if(!nm.next()){
//Create the CI notification records
var rec1 = new GlideRecord('cmn_notif_message');
rec1.initialize();
rec1.notification.setDisplayValue('CI affected');
rec1.user = pUser;
rec1.device = rec.sys_id;
rec1.configuration_item = array[i];
rec1.insert();
}
}

//Do not submit this record
current.setAbortAction(true);

//Add a notification message and redirect the user to the subscriptions page
gs.include('FormInfoHeader');
var fi = new FormInfoHeader();

producer.redirect = 'notification_preferences.do?sysparm_user=' + producer.user.toString() + '&sysparm_email=' + producer.email.toString();
var s = 'Subscriptions have been created.<br/>';
s += 'You can modify the subscription settings for individual configuration items below. <br/>';

fi.addMessage(s);

-Create an ‘onLoad’ catalog client script for your record producer with the following script. The purpose of this script is to populate the ‘user’ and ’email’ fields with data passed in from the UI action link on the user form.

function onLoad() {
//Hide the user and email fields
g_form.setDisplay('user', false);
g_form.setDisplay('email', false);

//Get parameters from url and populate them into the user and email fields
var url = document.location.toString();
var userKey = 'sysparm_user=';
var emailKey = 'sysparm_email=';
var userPosition = url.indexOf(userKey);
var emailPosition = url.indexOf(emailKey);
if (userPosition != -1){
var user = url.substr(userPosition+userKey.length, 32);
g_form.setValue('user',user);
}
if (emailPosition != -1){
var email = url.substr(emailPosition + emailKey.length);
g_form.setValue('email',email);
}
}

2) Create a ‘Form link’ UI action link on the ‘sys_user’ table that redirects to your new Record producer
-Condition: gs.getUserID() == current.sys_id || user.hasRole(‘admin’)
-Script: Note–You’ll need to put the sys_id of your record producer in here

gs.setRedirect('com.glideapp.servicecatalog_cat_item_view.do?sysparm_id=YOUR_ITEM_SYSID_HERE&sysparm_user=' + current.sys_id + '&sysparm_email=' + current.email);

Question #2:How can I use CI subscription notifications and not spam the end-user?

This can be accomplished by creating a business rule on the table of your choice (mine was on the ‘change_request’ table) with the following parameters. The business rule triggers an event that sends an email notification.

–The first thing you’ll want to do is de-activate the ‘Affected ci notifications’ business rule. This solution works separately from the trigger in that business rule.

1) Create the business rule
Name: Notify subscribers
Table: change_request (or whatever you choose)
-Runs after insert/update
Condition:

current.approval.changesFrom('not requested') || current.approval.changesFrom('rejected')

-Note: I chose this condition so that the notification would only go out when approval on the change was requested. You should modify this according to your needs.
Script:

var subscribers = getSubscribers();
gs.eventQueue('task.notify.subscribers', current, gs.getUserID(), subscribers);

function getSubscribers(){
// get affected CIs
var affectedCIs = new GlideRecord('task_ci');
affectedCIs.addQuery('task',current.sys_id);
affectedCIs.query();

var allPersons = [];

while(affectedCIs.next()){
var persons = [];
allPersons = allPersons.concat(getRelatedPersons(affectedCIs.ci_item.sys_id, persons));
}

allPersons = eliminateDuplicates(allPersons);
allPersons = allPersons.toString();

return allPersons;
}

function getRelatedPersons(ci_sys_id, persons){
var relPersons = new GlideRecord('cmn_notif_message');
relPersons.addQuery('configuration_item', ci_sys_id);
relPersons.query();

while (relPersons.next()) {
persons.push(relPersons.user.sys_id.toString());
}
var rel = new GlideRecord('cmdb_rel_ci');
rel.addQuery('child', ci_sys_id);
rel.query();
while (rel.next()) {
var parentSysID = rel.parent;
// get Interested Parties for parent CIs
getRelatedPersons(parentSysID, persons);
}
return persons;
}

function eliminateDuplicates(arr) {
// remove duplicates from array
var i, len=arr.length, out=[], obj={};

for (i=0;i < len;i++) {
obj[arr[i]]=0;
}
for (i in obj) {
out.push(i);
}
return out;
}

function notifyParents(table, itemSysID) {
var rel = new GlideRecord('cmdb_rel_ci');
rel.addQuery('child', itemSysID);
rel.query();
while (rel.next()) {
var parentSysID = rel.parent;
var parentName = rel.parent.getDisplayValue();
if (!noNotify[parentSysID]) {
count ++;
gs.log('### Notifying subscribers of ' + parentName);
noNotify[parentSysID] = true;
gs.eventQueue('ci.affected', current, parentSysID, parentName);
notifyParents('cmdb_ci', parentSysID);
}
}
}

2) Register a new event in the event registry called ‘task.notify.subscribers’
3) Create a new email notification that is triggered by the ‘task.notify.subscribers’ event. I used the following parameters for my notification:
-Name: Notify CI subscribers
-Event name: task.notify.subscribers (or whatever you call your event)
-Table: change_request
-User: event.parm2
-Subject:

A CI you have subscribed to is being impacted by change ${number} - ${short_description}

-Message:

${URI}
Short description: ${short_description}

Affected CIs: (Please note that the CIs listed below are only those that are directly impacted by the change request.  The CI you subscribed to may be impacted as a result of impact to one or more of the CIs listed below.

<mail_script>
   var affectedCIs = new GlideRecord('task_ci');
   affectedCIs.addQuery('task',current.sys_id);
   affectedCIs.query();

  while(affectedCIs.next()){
    template.print(affectedCIs.ci_item.getDisplayValue() + '\n');
  }
</mail_script>