S
ervice-now.com provides a really cool Graphical Workflow engine to help manage workflows for different tasks. This graphical workflow engine is particularly useful when working with approvals for Change Requests. As a ServiceNow consultant I’ve found that change approvals usually fall into just a few different types but new administrators and consultants sometimes don’t know the best way to implement approvals. In this post I’ll share some of the common change workflow approval methods and scripts I’ve seen used before. I wouldn’t be surprised to see this list grow over time and I know I haven’t seen all of the common methods. If you have something you’ve used before please comment on this post or use the ‘Contact’ link above to send in your suggestion to share.
Simple Approvers Selection (Select Specific Group)
The simplest (and most common) type of approval is to select specific groups or users to approve the change. The screenshot below shows how you can use the standard reference field lookup to select a group to add as approvers to the change request. This method is commonly used when you want to have a specific group (like the CAB) approve at some point in your workflow.
Simple Approvers Selection (Drill to Related Records)
Another common method is to drill into the change request itself and pull the approvers from a field on the change. This method is commonly used when you want to have something like a manager approval for the person requesting the change (as shown below) or maybe when you want to have the assignment group on the change request approve.
Advanced Approvers Script (Approval Groups for all Change CIs)
The ServiceNow Graphical Workflow mechanism also allows you be even more complex in the selection of approvals for your change requests. You can use script with GlideRecord queries to return any users or groups you want to have approve. The one thing to keep in mind with these scripts is that you need to return group records if your workflow activity is a Group Approval type and user records if your workflow activity is a User Approval type.
One common request I’ve seen before that requires a script is to add approval groups based on the Configuration Items associated to the change request. Chances are you’ll have multiple CIs associated on the ‘Affected CIs’ related list for the change. This script queries for those records and then adds the groups from the ‘Approval Group’ field on each CI.
var answer = [];
//Add the primary CI approval group to the array
answer.push(current.cmdb_ci.change_control);
//Add the Affected CIs list approval groups to the array
var affCIs = new GlideRecord('task_ci');
affCIs.addQuery('task', current.sys_id);
affCIs.query();
while(affCIs.next()){
answer.push(affCIs.ci_item.change_control);
}
Advanced Approvers Script (Approval Groups for all Impacted Change CIs)
There are some rare cases where clients want to pull approval groups from other CIs that will be impacted by the change based on CI relationships. For these cases you can use the CIUtils2 script I created to walk the CI relationship tree and find the impacted CIs that you need to add approvals for. In order to use these scripts you’ll need to add the CIUtils2 script include to your system first. You’ll also want to use this type of approval method sparingly since it involves a sometimes resource-intensive traversal of your CMDB (if you have hundreds of thousands of CIs). It’s also pretty expensive process-wise as well. Do you really want to require approval from every single group in your company when your network team needs to make a change to the core router? Maybe so, maybe not, but either way you’re probably talking about a lot of approvals because that CI impacts so many other CIs in your environment.
–This first example returns Approval Groups for all of the Business Services impacted by the change request–
var ciu = new CIUtils2();
//Initialize an answer array to be returned
var answer = [];
//Add any impacted business services for the change
var allCIs = ciu.cisAffectedByTask(current);
//Query for all CIs and return Approval Groups
for (var i = 0; i < allCIs.length; i++) {
var cis = new GlideRecord('cmdb_ci');
cis.addQuery('sys_id', allCIs[i]);
cis.query();
if (cis.next()) {
answer.push(cis.change_control);
}
}
–This example returns Approval Groups for all of the Business Services impacted by the change request AND Approval Groups for all directly Affected CIs on the change request. It utilizes the ‘CheckDuplicates‘ function I wrote to eliminate some unnecessary processing.–
var ciu = new CIUtils2();
//Initialize an answer array to be returned
var answer = [];
//Initialize an array to gather and store all impacted CIs
var allCIs = [];
//Add the primary CI to the CI array
allCIs.push(current.cmdb_ci);
//Add the Affected CIs list to the CI array
var affCIs = new GlideRecord('task_ci');
affCIs.addQuery('task',current.sys_id);
affCIs.query();
while (affCIs.next()) {
allCIs.push(affCIs.ci_item);
}
//For each directly affected CI on the change, add any impacted business services
for (var i = 0; i < allCIs.length; i++) {
allCIs = allCIs.concat(ciu.cisAffectedByCI(allCIs[i]));
}
//Remove duplicate CIs from the array
allCIs = checkDuplicates(allCIs);
//Query for all CIs and return Approval Groups
for (var j = 0; j < allCIs.length; j++) {
var cis = new GlideRecord('cmdb_ci');
cis.addQuery('sys_id', allCIs[j]);
cis.query();
while (cis.next()) {
answer.push(cis.change_control);
}
}
function checkDuplicates(a) {
//Check all values in the incoming array and eliminate any duplicates
var r = []; //Create a new array to be returned with unique values
//Iterate through all values in the array passed to this function
o:for(var i = 0, n = a.length; i < n; i++){
//Iterate through any values in the array to be returned
for(var x = 0, y = r.length; x < y; x++){
//Compare the current value in the return array with the current value in the incoming array
if(r[x]==a[i]){
//If they match, then the incoming array value is a duplicate and should be skipped
continue o;
}
}
//If the value hasn't already been added to the return array (not a duplicate) then add it
r[r.length] = a[i];
}
//Return the reconstructed array of unique values
return r;
}
Ensuring someone approves
One of the big problems you may encounter is what happens if the approvers don’t exist or aren’t active. The behavior in ServiceNow is to mark the approval activity as ‘Approved’ if no approvers are returned by the activity.
–This script looks to see if an approver is returned, and if not it pulls in the ‘Change admins’ group as approvers for the approval activity.–
//
// For example:
// var answer = [];
// answer.push('id1');
// answer.push('id2');
var answer = [];
//Requested by and assignment group manager approval
if(current.requested_by.manager.active){
answer.push(current.requested_by.manager.sys_id);
}
if(current.assignment_group.manager.active){
answer.push(current.assignment_group.manager.sys_id);
}
//Ensure 'Change Admins' group gets added if no other approvers (optional)
if(answer.length == 0){
var grpName = 'Change Admins';
//Query for change admins group
var cGrp = new GlideRecord('sys_user_group');
cGrp.get('name', grpName);
answer.push(cGrp.sys_id);
}
Making a specific person the key approver
Some approval scenarios may also require that a specific individual has the full approval/rejection responsibility even as a member of a group. Even though others can approve or reject, a single person has the final say. Once they approve or reject, we move on from the activity. The following script can be used in an ‘Approval – User’ workflow activity to facilitate this type of setup.
This script should be placed in the ‘Approval Script’ field in a ‘Approval – User’ workflow activity. The ‘Approval Script’ field displays when the ‘Wait for’ field is set to ‘Condition based on script’ as shown in the following screenshot.

//Set the 'keyApproverID' variable to the sys_id of the user record of the key approver
var keyApproverID = '97000fcc0a0a0a6e0104ca999f619e5b';
if (approvalIDs) {
if (approvalIDs['approved'].indexOf(keyApproverID) > -1) {
answer = 'approved';
}
if (approvalIDs['rejected'].indexOf(keyApproverID) > -1) {
answer = 'rejected';
}
}
This is great script and will work great in our organization. One thing I’m looking to do is build it into a UI Action rather than the workflow so the user can click a button when they’ve added the affected CIs. Is there anything different that needs to be done to push the approver groups from the array to the related list of group approves in the change record?
The scripts above are designed specifically for use in graphical workflow approval activities. Using them in a UI action would require the addition of some code to process the answer array and then create the approval records using a GlideRecord insert. You’re probably better off using your UI action to advance the workflow by setting some value in the change record. Then your workflow woulld have control over the approval process.
So if we added the approvers via GlideRecord insert, the Manual Approval Coordinator of the workflow that is already running against the change record won’t capture and manage the approvals?
It should. I guess the point I was trying to make is that automated anything (in a workflow for example) is usually better than the manual alternative.
Mark,
When I tried putting this into a Fall 2010 level instance. The checkDuplicates function gives me an error on the continue o; statement. Any thoughs?
Thanks.
Ed
Problem at line 66 character 24: Missing ‘;’
continue o;
Problem at line 66 character 25: Identifier ‘o’ already declared as live*
continue o;
Problem at line 66 character 25: Expected an assignment or function call and instead saw an expression.
continue o;
You’ll get the same thing on the Spring 2011 build that’s on demo right now but you can ignore those errors. The script is still fine, but the syntax checker is being a little bit more stringent than it needs to be. It thinks that’s not valid syntax in the script, but the script will save and execute just fine.
is there a way we can select specific group members for a group for approval. I dont want everyone in a group getting a approval request
You can, but you would have to have some indicator on those records so that you could identify them with a script. If you were to do something like that you would need to use a ‘User approval’ activity instead of a ‘Group approval’ activity though.
Ahhhh ok, soo if i do this in a user approval activity….. my script looks like this below
answer = [];
var stream_sys_id = current.u_deal_business_stream;
var list = current.u_selected_stream_approvers.toString();
var gr = new GlideRecord(‘u_deal_business_stream’);
gr.addQuery(‘sys_id’,’=’,stream_sys_id);
gr.query();
if(gr.next())
{
var group = gr.u_business_stream;
answer.push(group); < -----Group ID } var grm2 = new GlideRecord('sys_user_grmember'); grm2.addQuery('group','=', group); grm2.addQuery('user','=' '23rd2e3e23e23e23e23ee32fwewe3') grm2.query(); if(grm2.next()) { answer.push(grm2.user); <-------User ID } But thing is, it does'nt create the group approval..... just create the user approval......
That’s right. If you aren’t pulling all of the members of the group in, it should be treated as a user approval rather than a group approval. There’s no way I know of to do what you want with a group approval activity within a workflow. I suppose you could set up a business rule on the ‘sysapproval_approver’ table to abort the insertion of approval records for users who should not approve, but I’m not sure how that will scale across different workflows.
Thanks for the feedback. Got to think about plan B now
I’m trying to add an advanced approval script in our Change System. The form has a checkbox for each of our 3 regions, AMER, APAC, and EMEA. A change can be submitted for 1, 2 or all 3 regions. There is a Change Manager Group that contains members from each of the regions. I only want approvals to go to users from the regions that are selected on the form. When I run the script in Scripts – Background, I get an array of all the right users. When I run the workflow, I only get the last user from each region. Any ideas?
answer = [];
var amer = current.u_amer;
var apac = current.u_apac;
var emea = current.u_emea;
if (amer == true){
var amercm = new GlideRecord(‘sys_user_grmember’);
amercm.addQuery(‘group.name’,’Change Manager’);
amercm.addQuery(‘user.u_region’,’AMER’);
amercm.query();
while(amercm.next()){
answer.push(‘amercm.user’);
}
}
if (apac == true){
var apaccm = new GlideRecord(‘sys_user_grmember’);
apaccm.addQuery(‘group.name’,’Change Manager’);
apaccm.addQuery(‘user.u_region’,’APAC’);
apaccm.query();
while(apaccm.next()){
answer.push(apaccm.user);
}
}
if (emea == true){
var emeacm = new GlideRecord(‘sys_user_grmember’);
emeacm.addQuery(‘group.name’,’Change Manager’);
emeacm.addQuery(‘user.u_region’,’EMEA’);
emeacm.query();
while(emeacm.next()){
answer.push(emeacm.user);
}
}
There’s a little trick you need to use when pushing items into an array from a GlideRecord query. Add ‘toString()’ to the end of all of the items you’re pushing into the array like this…
answer.push(emeacm.user.toString());
Awesome… Thanks so much… Works Great!!!
Do you have anything that breaks up the approval list? Ie if you have 2 groups (dev and qa) and one person from each group. you see in the approval list Dev and qa grouped separately. That way if someone that isn’t familiar with the people know the QA person signed off on it but the dev person has not, vise versa… thx
Similar to a key person approval script. How can achieve a key group approval script. For example, i want to say that a particular group has the final say so. Any member in that group approves or rejects will override all responses.
Thank you!
Hi Tony,
There are a number of possible methods for accomplishing this, depending on your specific business needs. Examining them is probably beyond the scope of this blog. We’d recommend that you post your question on the ServiceNow Community Forum.
Well done. You did such an amazing job. Keep it up.