What is GlideScopedEvaluator?
If you come from a JavaScript background, you likely know that you can run a string of text as code using eval()
or by creating a new Function()
using a string.
eval("document.title = 'My awesome title';");
Function("document.title = 'My awesome title';");
You could also pull this script from somewhere else. Let's pretend that there's a text area on a web page called "myScriptField".
eval(document.getElementById("myScriptField").value)
ServiceNow doesn't want you to use eval()
either client-side or server-side, and with good reason. It's a notable security problem to allow any arbitrary code to be executed, and it can be an entry point for malicious users to do bad things.
If you try to use eval()
or Function()
, you might get an error message that looks something like this:
java.lang.SecurityException: eval() not allowed in a custom application, use GlideScopedEvaluator instead
java.lang.SecurityException: function constructor not allowed in a custom application, use GlideScopedEvaluator instead
However, there are times that you will want to call arbitrary script on the server side without creating a new business rule or script include, so ServiceNow has provided the GlideScopedEvaluator class to allow you to run script from script fields. It's pretty straight-forward to use, and it can make your cool project very powerful and flexible.
It works by taking a script from a field on an existing record, and executes it as server-side JavaScript. The field should be the type of "Script" or "Script (plain text)", but it'll accept any string field.
Ever wondered how scripts in one of these below fields goes from plain ol' text to computer code magic?
- The "Script" field on "Business Rules".
- The "Advanced condition" on "Notifications".
- The "Script" field on "Transform Maps".
- The "Source script" field on "Field Maps".
or something very similar that lives behind the scenes but works the same
Why should I use it?
But David, I can just write script in a business rule or a script include, why would I want to write code anywhere else?
That's because you're a talented system administrator, and anything you create in a script include will need to be maintained either by you or someone else just as talented. This is known as technical debt.
Once you've created something large enough and awesome enough, low-code and no-code administrators should be able configure it without modifying scripts and business rules. By using small & simple script fields and GlideScopedEvaluator, you're allowing less-savvy administrators to achieve more with fewer changes and less complexity.
ServiceNow developers should already be using System Properties to allow administrators to configure parts of their creations. This is just the next step, where other administrators can change chunks of code without having to touch any major code or components.
Usage
Using GlideScopedEvaluator is fairly simple. The most important part is the evaluateScript()
function which is used like this:
.evaluateScript(
grObj, /* GlideRecord that contains the script */
field, /* String name of the field that contains the script*/
variables /* Name-value pairs of variables */
);
You can pass in variables for the script to use in 2 different ways: using .putVariable()
or using the variables
parameter in .evaluateScript()
. They both work the same way.
// Variables using .putVariable()
var ge = new GlideScopedEvaluator();
ge.putVariable("current", current);
ge.putVariable("answer", "");
ge.evaluateScript(gr, "script");
// Variables using map object in .evaluateScript()
var ge = new GlideScopedEvaluator();
var vars = {
current: current,
answer: ""
}
ge.evaluateScript(gr, "script", vars);
// You can even use both methods
var ge = new GlideScopedEvaluator();
ge.putVariable("var1", "var1"); // var1
ge.putVariable("var2", "var2"); // var2
var vars = {var3: "var3", var4: "var4"}; // var3 and var4
ge.evaluateScript(gr, "script", vars); // var1, var2, var3, and var4 all work
Example
Let's assume that we've created a new field on the "User group" table called "Assignment condition", and this scripted field will control whether or not an incident can be assigned to this group.
Note: this example is messy but works as an example. Don't actually do this.
To run and check these rules, we'll use a business rule that uses GlideScopedEvaluator to check the assignment condition on the group.
Name: Evaluate group assignment condition
Table: Incident [incident]
Advanced: checked
Insert: checked
Update: checked
Filter conditions: Assignment group CHANGES AND Assignment group IS NOT EMPTY
Script:
(function executeRule(current, previous /*null when async*/) {
var grAG = current.assignment_group.getRefRecord(); // Get the GlideRecord of the assignment group
if (grAG.isValidRecord()) {
var ge = new GlideScopedEvaluator();
ge.putVariable("current", current); // Pass through the "current" variable as "current"
ge.putVariable("group", grAG); // Pass through the "grAG" variable as "group"
ge.putVariable("answer", true); // default "answer" to TRUE
ge.evaluateScript(grAG, "u_assignment_condition"); // Run the script
// Abort the transaction if the "answer" variable was set to FALSE explicitly (undefined doesn't count)
if (ge.getVariable("answer") === false) {
gs.addErrorMessage("Assignment rule did not pass");
current.setAbortAction(true);
}
}
})(current, previous);
What does this script do?
- Gets the GlideRecord of the assignment group
- Starts a new GlideScopedEvaluator
- Puts the "current", "group", and "answer" variables into the script that's going to be run
- Run the script in the group's "u_assignment_condition" field
- If the script set the "answer" variable is FALSE, abort the action and show up an error message about it
Now we can start using some scripted conditions!
This example script in the "Assignment condition" field will always prevent assignment.
answer = (function(current,group) {
return false; // Always block
})(current,group);
This example script in the "Assignment condition" field will only allow assignment if "Category" is "Hardware".
answer = (function(current,group) {
return current.category == "hardware";
})(current,group);
Returning values
In some scenarios, you'll want to use the script to pass back a result of some kind, to drive something else. This usually comes in 2 different flavours: simple results, or variable results.
Simple results
The simple results method works like the "Reference qualifier" field on a dictionary entry, or the "Condition" field on a business rule, where the result is whatever the last statement is.
Here's the "Condition" field on the business rule "Sync System Preference". This script works just like a condition inside an if
condition, that compares stuff together and then either returns true or false.
If you had to write a script that would run this script as a condition, it would look something like this.
var ge = new GlideScopedEvaluator();
ge.putVariable("current", current);
ge.putVariable("previous", previous);
var result = ge.evaluateScript(grBusinessRule, "condition"); // Grab whatever comes out of the .evaluateScript()
// Run the business rule if the last statement evaluates to TRUE
var shouldIRunThisBusinessRule = (result == true);
When the script contains semi-colons or multiple statements, the final statement is what gets returned.
Script: "asdasd"; 1234; 56.78
The result is "56.78".
Script: var gr = new GlideRecord("incident"); gr.get("68abf2a72fba20107087b2e72799b620"); gr.getDisplayValue()
The result is "INC12345689".
Variable results
Variable results is when you pass in a variable (or multiple variables) that you want the user to set in their script. A common example would be the "answer" variable used in many ServiceNow scripted condition fields.
Here's the "Script" field on an ACL record, where the "answer" variable is being set to either true
or false
by the script.
If you had to write something that would run script in that field and retrieve what the user set "answer" to, it would look something like this.
var ge = new GlideScopedEvaluator();
ge.putVariable("current", current);
ge.putVariable("answer", false); // Default the "answer" to FALSE
ge.evaluateScript(grACL, "script");
var answer = ge.getVariable("answer");
// This ACL will grant access if "answer" evaluates as TRUE
var aclGrantsAccess = (answer == true);
Tips and Tricks
Multiple return objects
You can pass in multiple variables that the script to set. However, I'd recommend using a single object that can collect multiple results instead of using multiple objects.
(function(myAnswerObj) {
myAnswerObj.var1 = "abc";
myAnswerObj.var2 = "123";
myAnswerOBj.var3 = "foo";
})();
Self-documenting script fields
A common mistake I cruel developers make is to create their script fields blank by default, and look like this:
What variables are available?
What should this script do?
How should it return a result?
I highly recommend creating a default value for the script field as a function template, similar to the look-and-feel of the default Business Rule and Transform Map script fields. This reduces confusion, and leads to a smoother experience for the user, which means users aren't constantly asking you how to use that field, or having to reverse-engineer your code to see how it works.
In the below screenshot of the default Field Map script field, you can clearly see that the "source" variable is available, and it's a self-running function that's setting the "answer" variable with the result of the function. No confusion there.
Running scripts without needing a record
Normally, GlideScopedEvaluator wants to force you to use a field on a GlideRecord as the source of your script.
However, you can trick GlideScopedEvaluator to run any sort of code you like without needing to save that script back into the database.
Consider the following code:
var gr = new GlideRecord("incident");
gr.setLimit(1); gr.query(); gr.next(); // Get any incident, any at all
gr.setValue("description", "gs.info('Hello there')");
// Note that we haven't saved this record, it's just hanging about in memory
var ge = new GlideScopedEvaluator();
ge.evaluateScript(gr, "description");
// There should now be a message in the system log saying "Hello there"
You can see that it:
- creates a GlideRecord object, then fetches any of those records, but doesn't save it
- puts some code into the "description" field
- runs
evaluateScript
using the temporary GlideRecord object to run the script
ServiceNow has plugged the hole that allowed you to create a new GlideRecord without saving, and running code from it. That approach doesn't work anymore.
However, this is cheating, insecure, and is considered very naughty by ServiceNow. I wouldn't recommend using this approach at all except for testing purposes. If you do this in production, you will be judged.