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: