Blog

Learn about ServiceNow Scripting

Why use scripting in ServiceNow and how does it work?

If you are new to ServiceNow, or want to know more about scripting, this article may just help you out!

PROGRAMMING STYLES

There are two "styles" of programming in ServiceNow:

Declarative

In computer science, declarative programming is a programming paradigm—a style of building the structure and elements of computer programs—that expresses the logic of a computation without describing its control flow.

In short, declarative is the easy "low-code" or "no code" type of programming.  ServiceNow Express uses all declarative programming.  I believe vendors like Cherwell use declarative primarily as well.

Most of ServiceNow uses declarative programming.  You don't have use scripting, and you shouldn't if you don't have to.

This "layer of abstraction" has been going to replace me since 2000.  

Workflow

Declarative Business Rule

Matching Rule

Programmatic

When Declarative doesn’t work, meaning you can't get it done with a condition builder or workflow, you can resort to scripting.  The programmatic, technical part of ServiceNow.  Often is just referred to as a generic "scripting".

Most programming in ServiceNow is in Javascript.  However there are branches to other types at times:

  • Javascript - main programming language used in ServiceNow
  • Angular JS - Used in Service Portal and other places
  • JQuery, node.js, and Bootstrap, ECMAScript - Scripting libraries used at times 
  • HTML/CSS - Used throughout ServiceNow and the Service Portal
  • Apache Jelly - Used in UI Pages.  Being replaced by Angular JS.  
  • Groovy (Removed?) - Used in Event Management.  May be replaced now, not sure

In the "Test your skills" section below, that is all about programmatic Javascript.

Script Includes

UI Action

Programmatic Business Rule

TEST YOUR SCRIPTING SKILLS

Editor's note: sorry if the website strips out some of the code indents and formatting.

1. EXPLAIN THIS SCRIPT

function onChange(control, oldValue, newValue, isLoading) {
   //If the page isn't loading
   if (!isLoading) {
      //If the new value isn't blank
      if(newValue != '') {
         //Type appropriate comment here, and begin script below
            if (newValue == '2'){
               var isItil = g_user.hasRole('itil');
                  if (isItil){
                     g_form.setValue('assigned_to', g_user.userID);
                  }  
            }
      }
   }
}

Answer

This is a Client Script.  On an Incident, if a user sets the state to 2, and the user has the itil role, sets the assigned to field to the logged in user.

2. Explain this script

(function() {
var grIncident = new GlideRecord('incident');
grIncident.addQuery('priority','5');
grIncident.addQuery('category','inquiry');
grIncident.query();
if (grIncident.next()) {
gs.print('Incident Found: '+grIncident.number);
}
})();

Answer

This could be used as Background Script. It uses the GlideRecord library to query for incidents with the priority of 5 and category of "inquiry".  If the query finds incident, it will print the Incident number to the console.  The if statement will limit that to 1 printed.

[0:00:00.028] Script completed in scope global: script
*** Script: Incident Found: INC0000020

3. EXPLAIN THIS SCRIPT

(function() {
var grIncident = new GlideRecord('incident');
grIncident.addQuery('priority','5');
grIncident.addQuery('category','inquiry');
grIncident.setLimit(1);
grIncident.query();
if (grIncident.next()) {
gs.print('Incident Found: '+grIncident.number);
}
})();

Answer

This could be used as Background Script. This is the same as the script in #2.  However there is a setLimit statement to limit returned values from the query to 1.  

[0:00:00.028] Script completed in scope global: script
*** Script: Incident Found: INC0000020

3. EXPLAIN THIS SCRIPT

(function() { 
var grIncident = new GlideRecord('incident');
grIncident.addEncodedQuery('priority=5^category=inquiry');
grIncident.setLimit(1); 
grIncident.query(); 
if (grIncident.next()) {
gs.print('Incident Found: '+grIncident.number);
}
})();

Answer

This could be used as Background Script. This is the same as the other scripts above, except it uses an encoded query.  Encoded queries are a similar way to write query statements, but not always applicable in all situations.

[0:00:00.028] Script completed in scope global: script
*** Script: Incident Found: INC0000020

4. EXPLAIN THIS SCRIPT

(function() { 
var grIncident = new GlideRecord('incident');
grIncident.addEncodedQuery('priority=5^category=inquiry'); 
grIncident.query(); 
while (grIncident.next()) {
gs.print('Incident Found: '+grIncident.number);
}
})();

Answer

This could be used as Background Script. This is like all the scripts above, except uses a while statement to return multiple values.

[0:00:00.008] Script completed in scope global: script
*** Script: Incident Found: INC0000020
*** Script: Incident Found: INC0000021
*** Script: Incident Found: INC0000024
*** Script: Incident Found: INC0000028
*** Script: Incident Found: INC0000029
*** Script: Incident Found: INC0000032
*** Script: Incident Found: INC0000035
*** Script: Incident Found: INC0000036

5. EXPLAIN THIS SCRIPT

(function() {
 var grIncident = new GlideRecord('incident');
 grIncident.get('priority','5');
 gs.print('Incident Found: '+grIncident.number);
})();

Answer

This could be used as Background Script. This script uses a get method to return a value.  

6. WHAT IS WRONG WITH THIS SCRIPT?

(function() {
  var count = new GlideAggregate('cmdb_ci');
  count.addAggregate('COUNT', 'sys_class_name');
  count.query();
  var ciClass = count.sys_class_name;
  var classCount = count.getAggregate('COUNT', 'sys_class_name');
  gs.log("The are currently " + classCount + " configuration items with a class of " + ciClass);
})();

Answer

Missing while (count.next()) { statement. 

7. EXPLAIN THIS SCRIPT

Solution Proposed Info Message
Table: Incident
When: display
Condition: current.incident_state == 6 && (gs.hasRole("itil_admin") || gs.hasRole("admin") || gs.getUserID() == current.caller_id
Script:
function onDisplay(current, g_scratchpad) {
gs.addInfoMessage('This incident has a Proposed Solution.Click Accept Solution to accept this solution, or Reject Solution to reject this solution.This Incident will close automatically in 2 business days if no action is taken.');
}

Answer

Business Rule. This script adds an information message to the form to Accept a Solution.  

8. EXPLAIN THIS SCRIPT

Name: Related cases
Applies to table: <case>
Queries to table: <case>
Query with:
var qc = current.addQuery("parent", parent.sys_id);
qc.addOrCondition("sys_id", parent.parent);

Answer

Relationship.  Adds M2M relationship between cases.

9. What is wrong with this script?

var grIncident = new GlideRecord('incident'); grIncident.addQuery('short_description', ''); 
while (grIncident.next()) {
grIncident.short_description = "Priority "+ grIncident.priority + " Caller: "+ grIncident.caller_id.name; 
grIncident.autoSysFields(false); 
grIncident.setWorkflow(false); 
grIncident.update();
}

Answer

Missing the grIncident.query(); line.  Without that, nothing will happen.  

9. EXPLAIN THIS SCRIPT

doit("incident");
doit("sc_request");
doit("sc_req_item");
doit("sc_task");
doit("problem");
doit("problem_task");
doit("change_request");
doit("change_task");
doit("wf_context");
doit("sys_email");
doit("task_ci"); doit("sysapproval_approver");
doit("sysapproval_group");
function doit(table) {
var gr = new GlideRecord(table);
gr.query();
while (gr.next()) {
gr.deleteRecord();
}
}

ANSWER

Background script. This was a common script back in the day. Questionable usage of the "doit" function name. Deletes all records from table specified.

10. EXPLAIN THIS SCRIPT

Table: Incident [Incident]
Active: true
When: after
Insert: true
Update: true
Order: 100
Condition: current.isValidRecord() && (!current.cmdb_ci.nil() && previous.cmdb_ci.nil()) || (current.cmdb_ci != previous.cmdb_ci)
Script:
(function executeRule(current, previous /*null when async*/) {
   var ciOutage = new GlideRecord('cmdb_ci_outage');
   ciOutage.initialize();
   ciOutage.begin = current.opened_at;
   ciOutage.end = current.closed_at;
   ciOutage.cmdb_ci = current.cmdb_ci;
   ciOutage.u_related_incident = current.sys_id;
   ciOutage.type = "Outage";
   ciOutage.insert();
})(current, previous);

ANSWER

Business Rule.  Creates an outage record from an incident when CI added to Incident.

11. EXPLAIN THIS SCRIPT

(function() {
var gr = new GlideRecord('sys_user');
gr.addQuery('roles','!=','admin');
gr.query();
while (gr.next()) {
gr.locked_out= true;
gr.update();
}
})();

ANSWER

This could be used as Background script. Creates an outage record from an incident when CI added to Incident.

11. EXPLAIN THIS SCRIPT

Type: onChange
Field: u_awesome_check
Script:
function onChange(control, oldValue, newValue, isLoading) {
if (isLoading || newValue == '') {
return;
}
if (newValue == 'mike_awesome') {
alert('Yes this is true'); }
}

ANSWER

Client Script. If u_awesome_check equals mike_awesome, it sends an alert to the user

12. EXPLAIN THIS SCRIPT

Name: Assignment Group Check
When: before
Insert/Update: true
Table: Incident [incident]
Condition: current.assigned_to.changes() && !current.assignment_group.nil() && !gs.hasRole('admin') && !current.assigned_to.nil()
Script:
(function() {
  if (!gs.getUser().isMemberOf(current.assignment_group)) {
    current.assignment_group = previous.assignment_group;
    current.assigned_to = previous.assigned_to;
    //current.setAbortAction(true);
    gs.addErrorMessage('Can not set assigned to unless you are a member of that Assignment group ');
  }
})();

ANSWER

Business Rule.  Doesn't allow setting assigned to unless member of that group.  I tried this business rule, I didn't like it, but it is kind of interesting.

13. EXPLAIN THIS SCRIPT

When: before
Insert/Update: true
Table: Incident [incident]
Condition: current.state.changesTo(6) && current.priority == 1
Script:
if (current.reopen_count == 0) {
var prob = new GlideRecord('problem');
prob.initialize();
prob.u_requested_by = current.caller_id;
prob.u_phone = current.u_phone;
prob.short_description = current.short_description;
prob.description = current.description;
prob.company = current.company;
prob.location = current.location;
prob.approval = 'Approved';
prob.state = 5;
var sysID = prob.insert();
current.problem_id = sysID;
current.work_notes = ("Problem " + prob.number + " created");
gs.addInfoMessage("Problem " + prob.number + " created");
}
current.update();

ANSWER

Business Rule.  Runs when state changes to resolved, priority is 1. Creates a Problem record if this is a Priority incident and user has problem role

14. EXPLAIN THIS SCRIPT

Form Context Menu: true
Condition: gs.hasRole("itil")
Script:
createStory();
function createStory() {
var grStory = new GlideRecord("rm_story");
grStory.initialize();
grStory.short_description = current.short_description;
grStory.u_requestor = current.caller_id;
grStory.comments = current.comments.getJournalEntry(1);
grStory.work_notes = "Generated from Incident " + current.number;
var sysID = grStory.insert();
GlideSysAttachment.copy('rm_story', current.sys_id, 'grStory', sysID);
gs.addInfoMessage(gs.getMessage("Story {0} converted from Incident {1}", [grStory.number, current.number]));
gs.addInfoMessage("Story " + grStory.number + " created");
action.setRedirectURL(grStory);
action.setReturnURL(current);

current.close_code = 'Moved to Story';
current.close_notes = "Moved to Story "+grStory.number;
current.state = 7;
current.active = 'false';
current.update();
}

ANSWER

UI Action.  Converts an Incident into a story

15. EXPLAIN THIS SCRIPT

Table: Change Task [change_task]
When: display
insert: true
Advanced: true
Condition: current.isNewRecord() && !current.change_request.nil()
cript:
(function executeRule(current, previous /*null when async*/) {
current.cmdb_ci = current.change_request.cmdb_ci;
current.due_date = current.change_request.due_date;
})(current, previous);

ANSWER

Business Rule.  When you create a change task, it takes the CI and Due Date from the related Change Request.

16. What DOES this TRANSFORM SCRIPT DO?

if (action == 'insert') {
ignore = true;
}

ANSWER

Transform "Run Script".  Ignores inserts and only updates records.

17. EXPLAIN THIS SCRIPT

Copy Contract
Table: Contract
Order: 100
Form Context Menu: true
Form link: true
Active: true
Show insert: true
Show update: true
Script:
copyContract();
function copyContract() {
var grContract = new GlideRecord("ast_contract");
grContract.initialize();
grContract.vendor = current.vendor;
grContract.contract_model = current.contract_model;
grContract.location = current.location;
grContract.u_product = current.u_product;
grContract.u_client = current.u_client;
grContract.cost_center = current.cost_center;
grContract.po_number = current.po_number;
grContract.u_purchase_request = current.u_purchase_request;
grContract.short_description = current.short_description;
grContract.description = current.description;
grContract.approver = current.approver;
grContract.contract_administrator = current.contract_administrator;
grContract.vendor_contact = current.vendor_contact;
grContract.starts = current.starts;
grContract.ends = current.ends;
grContract.payment_amount = current.payment_amount;
var sysid = grContract.insert();

action.setRedirectURL(grContract);
action.setReturnURL(current);
}

ANSWER

UI Action. Copies and creates a new contract.

18. EXPLAIN THIS WORKFLOW SCRIPT

answer = checkIfSame();
function checkIfSame(){
 if (current.variables.u_requested_for.manager == current.opened_by){
 return 'yes';
 }
 return 'no';
}

ANSWER

Checks if manager is same as opened by in a IF Activity in a workflow

19. EXPLAIN THIS SCRIPT

<?xml version="1.0" encoding="utf-8" ?>
<j:jelly trim="false" xmlns:j="jelly:core" xmlns:g="glide" xmlns:j2="null" xmlns:g2="null">
<g:evaluate var="jvar_guid" expression="gs.generateGUID(this);" />
<j:set var="jvar_n" value="show_incidents_${jvar_guid}:${ref}"/>
<g:reference_decoration id="${jvar_n}" field="${ref}" 
onclick="showRelatedList('${ref}'); "
title="${gs.getMessage('Show related incidents')}" image="images/icons/tasks.gifx"/>

<script>
// show related list 
// todo: should be part of the PopupWindow class
// todo: needs new stack name
function showRelatedList(reference) {
var s = reference.split('.');
// Get the field name which is always last
var referenceField = s[s.length - 1];
var v = g_form.getValue(reference);
var w = new GlideDialogWindow('show_list');
w.setTitle('Related incidents');
w.setPreference('table', 'incident_list');
w.setPreference('sysparm_view', 'default');
w.setPreference('sysparm_query', referenceField + '=' + v);
w.render();
}

</script>
</j:jelly> 

ANSWER

UI Macro.  Adds button next field to show related incidents

20. EXPLAIN THIS SCRIPT

gs.print(getDuplicates('cmdb_ci_server','ip_address'));
function getDuplicates(tablename,val) {
 var dupRecords = [];
 var gaDupCheck = new GlideAggregate(tablename);
 gaDupCheck.addQuery('active','true');
 gaDupCheck.addAggregate('COUNT',val);
 gaDupCheck.addNotNullQuery(val);
 gaDupCheck.groupBy(val);
 gaDupCheck.addHaving('COUNT', '>', 1);
 gaDupCheck.query();
 while (gaDupCheck.next()) {
 dupRecords.push(gaDupCheck[val].toString());
 }
 return dupRecords;
}

ANSWER

This could be used as Background Script. This script finds all duplicate servers by IP Address.  It uses the GlideAggregate library to find them.  GlideAggregate is faster than GlideRecord for these types of queries.

MORE RESOURCES

Helpful sites to help with scripting: