Quantcast
Channel: SNBlog – by Logicalis SMC
Viewing all 89 articles
Browse latest View live

Script execution flow

$
0
0


About two years ago I followed the ServiceNow Scripting training. The training provides information about most common places were different types of scripts are located in ServiceNow. As more or less a beginner with ServiceNow at that moment, I heard a lot of different places where I can create or find scripts. Some of these scripts can be ordered with a number in which sequence they have to be executed, for example business rules.
Besides the ‘order’ field, which sorts the execution of the business rule, the type of the business rule is also relevant for the order of executing the multiple scripts. For business rules ServiceNow provides a basis image for which order business rules are executed:

Source: docs.service-now.com

In addition to business rules, we have client scripts with different types (e.g. onLoad, onChange and onSubmit) and we do know the onLoad and onChange UI policy. Not to forget, we also have Server side and client side UI actions, the workflows and data policies.
This was the moment in the training I realized that this amount of scripts must have a specific sequence in which way they are executed and that is what I asked the trainer. Because the trainer understood the added value to this question we had a brain breaking session with the whole class and finally figured out on a whiteboard what this sequence is in ServiceNow. Nowadays this has not changed. After the class I made a Visio drawing of it and shared it between multiple colleagues. Even today when I am debugging, I still use this drawing, because it is very valuable to know in which order all these scripts are executed. Moreover, when colleagues ask me for help I share this image with them. I hope that this will be also helpful for you. Please leave some comments if you have feedback!

.img[at].img

Frequent display issues with (related) lists

$
0
0

I am working on the service desk and on regular base we are getting issues about a particular user having problems with viewing records in a (related) list. In some situations a part of the list is not displayed, but we also get issues where a security constraint error message appears, while the issue is not directly caused by the security rules that were defined on the list. In this blog I will describe some of these issues and of course, I will also provide some steps you could check in case you may face a similar issue.

Issue 1: (Related) List with records is not showing at all, or columns are not displayed as expected.
In this situation most users are trying to clear their browsers cache. That’s a good idea to start with, however unfortunately this will mostly not solve the display issue. We noticed that often the problem is caused by a user preference. These settings are stored in the system table “User Preferences” (tabelname: sys_user_preference).

Check if there are any preferences set for this particular user and for the table where this list of records is not displayed. If you find any records in this table, which you think could be related to the issue, then please delete these entries. We have seen several situations where a user preference record was preventing the list to load fine, so hopefully this will help you here as well.

In case the above did not help a refresh of the server cache could be executed. In this case all list information will be retrieved again from the server. An admin user can clear the system cache by typing “cache.do” in the “filter navigator” field.

Issue 2: Total amount of records in list is showed, however the list with records is not displayed (see picture below)


You are lucky if you get this one to solve, as it is very easy to “fix” the issue. 😉
In fact the user has hided the list themselves here by clicking on the hide/unhide option on the right.

If you click on the + again the list will expand and you will see the records again. Indeed, sometimes it is so easy like this, however as customers are logging this “issue” on regular base it is worth mentioning here.

Issue 3: There a no records visible in the list and there is a “Security constraints” message.
Please have a look at the situation in the picture below:


As there is a “Security constraints” message most people directly think that one of their ACLs are preventing them to see the results in the list. Well, that’s correct for only 19 records as these were removed from the list due to these ACLs. But if you take a closer look at this you will see that in this example there were more results: 80. So the other 61 records should be visible somewhere?

You can easily make them visible by going to the next page(s) by clicking on the blue triangle icons in the lower right. Sometimes changing the order of a column in your list works as well. Ok, not a very difficult issue, however I bet you are not the first person who is verifying their ACL rules for this situation.

Hopefully one of my examples will be helpful for you. Please leave a comment if you have any question!
.img[at].img

The nuts and bolts of JavaScript

$
0
0

Today we will look into a problem that many a Servicenow consultant has lost some hours on solving. To prevent new consultants running into this problem, but also for the ones that solved it but didn’t fully comprehend the solution, here is a detailed explanation of what is going on in the nuts and bolts of JavaScript.

The problem

Suppose you are writing a function that selects the sys_id’s of users and stores them in an array. The array could be used to pass on to another function. In this example, we will query active users:

function getActiveUsers(limit){
var result = [];
var grUser = new GlideRecord('sys_user');
grUser.addQuery('active', true);

if (typeof limit === 'number'){
grUser.setLimit(limit);
}
grUser.query();
while (grUser.next()){
result.push(grUser.sys_id);
}

return result;
}

We will use a test function to show the results, limiting the result set to eight records:

function showUsers(){
var result = getActiveUsers(8);

result.forEach(function(element){
gs.print(element)
})
}

The function showUsers will print eight identical sys_id’s, rather than the eight different ones you might expect. To quote Marvin Gaye: what’s going on?
(Also note the usage of the forEach-method to iterate over the array elements, which is finally available server side from Helsinki onwards!)

Some background

Objects in JavaScript are accessible using variables. These variables do not contain objects themselves (as they do for strings, numbers and booleans), but rather a reference pointer to the memory space where the object is stored.
This can be illustrated running the following code:

var a = [1, 2];
var b = a;
gs.print(b.length);
a.push(3);
gs.print(b.length);

The first time we look at b.length, its value is 2. Without touching b however, the next time we call it, it has changed to 3. This happens because the variables are pointers. After line 1, we can picture the situation like this:

Line 2 sets b to the same value as a:

Notice that b does not point to a itself, but rather to the same object that a is pointing to.
When in line 4 an extra element is added using a.push, the situation looks like this:

Note that a and b still point to the same structure in memory, and will thus behave identical – a.length() and b.length() will yield the same result. The main thing to take away from this example is that the value of a reference variable can change even though the variable itself is untouched.

GlideRecord

In our getActiveUsers-function, we create a new instance of GlideRecord, allowing us access to the records in the sys_user table. The query method runs the query we put in place. Finally, we use next to iterate over the retrieved data.
Calling next on a GlideRecord does a number of things. It retrieves the next record from the dataset, and exposes the field values through the GlideRecord object. What is crucial here, is that even though the value of the field sys_id might give the impression of being a string, it really isn’t. All properties of a GlideRecord instance that represent fields in a table are GlideElement objects. The grUser object created using grUser = new GlideRecord(‘sys_user’) can be represented like this:

The picture shows that grUser.sys_id is a pointer to a GlideElement object. Pushing grUser.sys_id on the array during the first iteration of the while-loop then looks like this:

The first element of the array, result[0], points to the grUser object’s sys_id property, which is a GlideElement object. So far, so good.

Next iteration

The second iteration of the while-loop is started by calling grUser.next(). This loads the data for the next record in the dataset. The grUser object will remain at the same position in memory, as will all of the GlideElement-objects it refers to. Rather than destroying all GlideElements and recreating them, ServiceNow keeps the objects and fills them with the new data.
This means grUser.sys_id will yield the sys_id of the second record from the dataset, even though the GlideElement it points to is still the same. It looks like this:

The first element of the array, result[0], was not changed (just like the array b was not changed in the example above). It still points to the grUser object’s sys_id. But during the grUser.next-call, the contents of this object were changed. It now contains the sys_id of the second record from the dataset. So without actually touching result[0], its value changed nonetheless, and is now equal to result[1].
In the same manner, the third iteration will result in the first three array elements all pointing to (still the same) GlideElement object, which now contains the sys_id of the third record in the dataset.
Ultimately in the example given above, after eight iterations, the array will contain eight elements, all of which refer to the sys_id of the same (eighth) record.

Solution

Now that we can finally explain to Marvin Gaye what’s going on, the solution can’t be far. The trick is to store something else than the reference in the array. If we were to retrieve the sys_id’s string value from the GlideElement object, we would create a new string. This value would not change when calling next() on the GlideRecord, because the grUser object has no knowledge of the new string’s existence. So instead of

result.push(grUser.sys_id);

push the string-converted value on the array. Use whichever of the three options has your preference:

result.push(”+grUser.sys_id);
result.push(grUser.sys_id.toString());
result.push(grUser.getValue(‘sys_id’));

Change the line in the script and run it again; the script will now print eight different user id’s.

I hope this was useful info! Please drop a comment if you would like more information!
.img[at].img

Automated testing to improve software quality

$
0
0

Automated testing is becoming more and more important. There are no human errors, it is easy to perform repetitive tests, it saves time and money and you will see the test results immediately so you can act on errors before the end-user will even notice! Manually testing to find defects or bugs is very time consuming, so why not help the testers? Why not automate the tests that take a lot of time when you test it manually or when you get easily human errors in the test? A quick check in your results will show every possible defect after a change or an upgrade. Test automation will improve the software quality and stability!

A few tips for setting up your test automation environment:

  • Decide which test cases to automate.
  • Test very often.
  • Create good quality test cases.
  • Create tests that are software updates and UI changes independent.

It is also very important to select the best test automation tool for your environment. In this article I will discuss two test automation tools that can be used for ServiceNow:

  • Automated Test Framework (ServiceNow)
  • Test Automation Suite (TrendIC)

Automated Test Framework – ServiceNow

The Automated Test Framework is a new application introduced in the Istanbul release. It is possible to create and run tests on the ServiceNow instance. You can create simple test actions like ‘Open a form’ and ‘Set field values’, but it is also possible to create your own test action by scripting.

How the simple test step works:

  • Add Test Step.
  • Select the action you want to execute, for example ‘Open a form’.
  • Select the next action you want to execute, for example ‘Set field values’.
  • Run Test.

The most commonly used components of the Automated Test Framework:

  • Tests
    • create an automated test with test steps.
  • Test Suites
    • To execute multiple tests in a specified order.
  • Test Results
    • To view the output of the test.

  • Client Test Runner
    • The Execution Frame will show you the exact test steps.

Pros:

  • It is a ServiceNow application and therefor integrated in ServiceNow.
  • Very easy to configure new test steps, just like all the conditions and filters in ServiceNow.
  • View live testing in the Execution Frame.
  • It is possible to set up multiple tests in the queue.
  • It is possible to create a test template to set up several test steps in a specified order which can be reused.
  • When an error occurs, it can easily be found in which test step it occurred with an added printscreen.

Cons:

  • It’s only available for ServiceNow.

In the recorded movie below I want you to show how easy it is to create a simple test case like creating a new incident with several fields set:

TrendIC Test Automation Suite – TrendIC

The Test Automation Suite by TrendIC delivers you several tools to get started with your test automation environment. It is a separate tool, so it can be used for all software. It is possible to create your own test cases and create a test scenario to run the test cases in a specified order. It is also possible to record your mouse and keyboard input and build your test case very fast this way. The Test Automation Suite will use everything that appears on the screen to click on, so you will not have to select a table but you will just create the test step to click on the text ‘Create New’ in the Incident section.

The most commonly used components of the Test Automation Suite:

  • ICDefine
    • To create an automated test case with test steps.
  • ICAssistant
    • To record your mouse or keyboard input and create test steps automatically.
  • ICPortal
    • To view the test results and to create scenarios and schedules.

Pros:

  • Possibility to integrate with other software, for example ServiceNow.
  • There is a possibility to schedule the test scenarios.
  • Everything that is on the screen can be used or clicked on (resolution and position independent).
  • ICAssitant creates very fast test steps with the recording functionality.
  • It is possible to create a global script to set up several test steps in a specified order which can be reused with parameters.
  • While running the test case, it is possible to record the test steps. It can easily be used for example creating manuals.
  • The Test Automation Suite can be used for all software, not restricted to ServiceNow.
  • When an error occurs, it can easily be found in which test step it occurred with an added printscreen.

Cons:

  • Unexpected windows in the test run can block the expected text search or image, for example ‘Remember my logon credentials’ messages. It is necessary to prevent these windows before automated testing, like ‘Remember me’ or ‘Don’t show this message again’.
  • ICAssistant mostly captures images instead of text searches.

In the recorded movie below I want you to show how the ICDefine tool works using everything that appears on the screen using several text searches:

I hope this was useful info! Please drop a comment if you would like more information!
.img[at].img

Importing record producers and variables

$
0
0

At our Facilities Management application, we had to create some record producers, a good amount of them, some with the same groups of basic questions (Who?, When?, Where?), but lots of different specific questions.
We can create the record producers, variable sets and variables manually, but it would take a lot of time to do so.

The Challenge

Create record producers with different categories, questions and order. Must be created quickly and easily from an excel file.

The Solution

– Created the Categories and the variable sets on the instance.
– Created an excel file with two sheets, one for the record producer and variable sets, the second for the variables.

Here is an example of sheet 1:

Note that the requestor, location, time and remarks columns point to existing variable sets.

Below is sheet 2:

On the second sheet, we define the questions, labels, type of variable, order, reference qualifier or choice list.

To create the record producers we import the excel file to our instance. And run the transform map with the following scripts:

(function transformRow(source, target, map, log, isUpdate) {
               
    //Fetch Catalog sys_id
    var catalog = new GlideRecord("sc_catalog");
    catalog.get('title', 'Facilities Management');
   
    var categoryFromData;
   
    //Fetch Category sys_id
    var category = new GlideRecord("sc_category");
    category.addQuery('sc_catalog', catalog.sys_id.toString());
    category.addQuery('title', source.u_category.toString());
    category.query();
    if(category.next()) {
                    categoryFromData = category.sys_id.toString();
    }
   
    //Fetch scope sys_id
    var scope = '';
    var application = new GlideRecord("sys_app");
    application.addQuery('scope', 'x_lsmcb_fm');
    application.query();
    if(application.next()) {
                    scope = application.sys_id.toString();
    }
   
   
    target.sys_scope = scope;
    target.table_name = 'x_lsmcb_fm_facility_order';
    target.active = 1;
    target.category = categoryFromData;
    target.sc_catalogs = catalog.sys_id.toString();
               
})(source, target, map, log, action==="update");

The above script will create the actual Record Producers, the onAfter will assign the existing variable sets and create the new variables. Below is the onAfter script:

(function runTransformScript(source, map, log, target /*undefined onStart*/ ) {
    //Create Questions in Record Producer
    var irpVariables = new GlideRecord("u_imp_irp_variables");
    irpVariables.addQuery("u_record_producer", source.u_name.toString());
    irpVariables.query();
    while (irpVariables.next()) {
        var variables = new GlideRecord("item_option_new");
        var type;
        switch (irpVariables.u_type.toString().trim()) {
            case 'Yes/No':
                type = 1;
                break;
            case 'Multi Line Text':
                type = 2;
                break;
            case 'Multiple Choice':
                type = 3;
                break;
            case 'Numeric Scale':
                type = 4;
                break;
            case 'Select Box':
                type = 5;
                break;
            case 'Single Line Text':
                type = 6;
                break;
            case 'CheckBox':
                type = 7;
                break;
            case 'Reference':
                type = 8;
                break;
            case 'Date':
                type = 9;
                break;
            case 'Date/Time':
                type = 10;
                break;
            case 'Label':
                type = 11;
                break;
            case 'Break':
                type = 12;
                break;
            case 'Macro':
                type = 14;
                break;
            case 'UI Page':
                type = 15;
                break;
            case 'Wide Single Line Text':
                type = 16;
                break;
            case 'Macro with Label':
                type = 17;
                break;
            case 'Lookup Select Box':
                type = 18;
                break;
            case 'Container Start':
                type = 19;
                break;
            case 'Container End':
                type = 20;
                break;
            case 'List Collector':
                type = 21;
                break;
            case 'Lookup Multiple Choice':
                type = 22;
                break;
            case 'HTML':
                type = 23;
                break;
            case 'Container Split':
                type = 24;
                break;
            case 'Masked':
                type = 25;
                break;
            default:
                type = 6;
                break;
        }
        variables.initialize();
        variables.question_text = irpVariables.u_question_text.toString();
        variables.name = irpVariables.u_name.toString();
        variables.type = type;
        variables.sys_scope = target.sys_scope;
        variables.mandatory = irpVariables.u_mandatory;
        variables.active = true;
        variables.order = irpVariables.u_order;
        variables.reference = irpVariables.u_reference.toString();
        variables.use_reference_qualifier = irpVariables.u_reference_qual_type.toString();
        variables.reference_qual = irpVariables.u_reference_qual.toString();
        variables.default_value = irpVariables.u_default_value.toString();
        variables.cat_item = target.sys_id.toString();
        variables.insert();
        //If Multiple Choice type
        if (variables.type == 5) {
            var choices = irpVariables.u_choice_list.toString();
            var choice = choices.split(";");
            for (var i = 0; i < choice.length; i++) {
                var choiceList = new GlideRecord("question_choice");
                choiceList.initialize();
                choiceList.sys_scope = target.sys_scope;
                choiceList.question = variables.sys_id.toString();
                choiceList.text = choice[i];
                choiceList.value = choice[i];
                choiceList.order = i;
                choiceList.insert();
            }
        }
    }
    // Assign the variable sets
    if (source.u_requester.toString() != "") {
        var existingSet = new GlideRecord("item_option_new_set");
        if (existingSet.get("description", source.u_requester.toString())) {
            var setItem = new GlideRecord("io_set_item");
            setItem.initialize();
            setItem.variable_set = existingSet.sys_id;
            setItem.sc_cat_item = target.sys_id.toString();
            setItem.sys_scope = target.sys_scope;
            setItem.insert();
        }
    }
    if (source.u_location.toString() != "") {
        var existingSet = new GlideRecord("item_option_new_set");
        if (existingSet.get("description", source.u_location.toString())) {
            var setItem = new GlideRecord("io_set_item");
            setItem.initialize();
            setItem.variable_set = existingSet.sys_id;
            setItem.sc_cat_item = target.sys_id.toString();
            setItem.sys_scope = target.sys_scope;
            setItem.insert();
        }
    }
    target.update();
})(source, map, log, target);

I hope you are able to reuse this script to quickly import many record producers and variables! Please drop a comment if you would like more information!
.img[at].img

Enhance your forms with icons using addDecoration!

$
0
0


Do you have forms with many mandatory fields? Are your users complaining they can’t save their record after filling just one mandatory field? Then worry no more and addDecorations!

ServiceNow gives you the option to place one of their many icons along with a popup description in front of each field label. Below I will describe some ways you could use this feature:

  • Use icons in front of fields instead of making them mandatory (our use case for this blog)
  • Mark fields with an icon if they are going to be updated by for example an aSync interface
  • Decorate a field to draw the users attention and supply extra information.

For our case we have a form that contains a lot of mandatory fields and users have mentioned they are having trouble filling all the fields in one session and would like to save their entries in between.
In this form we have 4 mandatory fields in the new phase, lets replace them with icons.

First we ensure the fields are no longer mandatory and create an onload UI policy that will apply our icons. The script we will use to add icons is the following:

g_form.addDecoration('field to be decorated', ‘icon you want to be shown’, ‘icon info text’);

An example would be:

g_form.addDecoration('category', 'icon-alert', 'Mandatory for next phase');

Our onLoad UI policy script section will look like this:

Because we are using an onLoad UI Policy combined with an if for each field, the decoration will only load if the fields are empty when loading the form. This gives the user a clear overview of what still needs to be updated to proceed.
The end result of our form will look like this:

The icon-alert is shown and when the user hovers over the icon, the info text you entered in the UI Policy script is shown. Notice how the category field does not show an icon as the user has saved the record after updating the category.
The fields should still be mandatory, so it is up to you to check all fields contain a value before, for example, proceeding to the next phase of the change. If all mandatory fields are not filled in, you can simply refer to the fields showing an icon instead of having to mention all fields that should be filled.
ServiceNow offers a wide range of icons, like the icon-alert we used earlier, that can be used and you can find them in Collaborate -> Administration -> Action Icons.



So what creative ways can you think of to use decorations? Let us know!

.img[at].img

ServiceNow licensing: 7 things to keep in mind…

$
0
0

ServiceNow is still one of the (if not THE) fastest growing software companies out there; really a phenomenal success!
And over the years the platform functionality and pricing changed of course – nothing you wouldn’t expect when dealing with a multinational company that is listed on the stock exchange (NYSE: NOW).

Tons of functionality in and on the platform were added or changed, new solutions and modules got introduced, acquisitions were made – and that is still the case today.
The introduction of new functionalities is sometimes also accompanied by new sales and/or license policies – and that is something worth paying attention to.

Below I’m sharing some hints and tips on this subject:

  1. When signing a new contract, pay close attention to Terms and Conditions. You need to (and want to) understand all conditions;
  2. Pay extra attention when renewing a contract; you want to make sure you don’t lose rights and entitlements you previously negotiated (a.k.a. “grandfathering”);
  3. Ask your ServiceNow sales representative if there are new or changed policies w.r.t. the use of roles in combination with entitlements/rights;
  4. Inform your technical/implementation consultants on changes and updates to policy; configuring functionality can have (big) impact on licenses;
  5. Be aware that activating certain plugins can have impact on roles and entitlements which impact license compliancy;
  6. Make sure you understand your entitlements and perform regular checks for license compliancy;
  7. Ask your ServiceNow sales representative if you are not sure what is allowed and within policy and what is not…;

The items mentioned above are in random order and all of equal importance.
But as a long ServiceNow partner (more than 10 years already!) we’ve learned to pay special attention to items 4 and 5 mentioned above.
These are often overlooked but are of major importance, so inform your customers and consultants on changes in policy, licensing and special terms & conditions and avoid awkward unwanted situations that require a lot of effort to correct.

And for this article the following certainly applies: when not sure, check with your ServiceNow sales representative!

.img[at].img

Prevent data retrieval from growing linear for each result

$
0
0

There are many ServiceNow customers and they all have their own specific demands. Some of them can be fulfilled by customizations using UI actions, but some of them require a fully customized page.
For this ServiceNow has provided a few methods, one of which is a UI page. As the demands often tend to be quite specific, GlideAjax and Script Includes can be used to retrieve and store data following those specific demands.

Data retrieval

The performance tip we talk about in this blog lies in retrieving data. Often you want to retrieve records which also have one or multiple relations. Let’s say you need the following data for your UI page:

  • A list of active incidents created in the last year.
  • For each incident the ‘number’, ‘short description’, ‘priority’, ‘caller first name’, ‘caller last name’ and ‘caller department name’.
  • For each found department the ‘Department head name’.

The code to achieve this could look as follows:

Can we optimize this?
As you can see we use the ‘dot-walk’ method is used to reach the related records and get the information. But what actually happens? We loop through the incident records one by one and for each of them we need information in a related table. This means that when we ‘dot-walk’ the server must make a call to that related table to get the related information. In this example we have 2 related tables from which we need data, one is sys_user that is required twice. Each time when we run the .next() multiple queries are run to retrieve the data from the three tables. The time it takes to request this information will grow linearly for each found incident. So can we optimize this? Yes we can!

How do we optimize?
We have to take the linear growth out of the equation. We can do this by following a simple principle:
Request all data from each individual table in one request.

Below is a code example:

As you can see we are now able to use the ServiceNow prefered .getValue method safely because we do not ‘dot-walk’ to related records. De results object will have the record sys_id as it’s property and the record as its value. In the code example above we still do not have our related fields. For this we need to make this call again only it needs to be to the sys_user table, it requires a different query and requires different fields to be returned. Please note that since we use an object to store our results and the sys_id is the object’s property, ordering on any field (e.g. number) will need to be performed again client-side.

Helper script
To prevent ourselves from repeating a lot of code a helper script is created to do this for us. Click here to open the helper script ‘LSMC’. This script is to be added to your environment as a “Script Include”. This script only asks you for the unique values you require. Now we can write the same request a lot easier and keep the code a lot clearer as well. Feel free to read the code before uploading it to see how it works.

Now we just need to ask the values we require from the sys_user table using the caller_id.

We continue this approach to request the remaining data as well resulting in the following code:

Now we have all the unique data we need and we only made four separate requests to the database. This number will not change based on the amount of incident records we request. So this growth as opposed to our first example is not linear, which will result in a faster data retrieval as the amount records to retrieve gets bigger.

Am I done now?
No your not done! In the first example all relevant data for each record was stored within that record and it was ordered by number. Now everything is related to each other using sys_id’s, but not all data is in the same record. So when this data is send client-side it needs to be linked and possibly ordered by the client. Is this a problem? No, you actually place some of the calculation power the server normally would perform to the client-side, thereby relieving the server from performing calculations the client can also perform! This always tends to be a good thing especially if your application gets bigger.

.img[at].img

Master Data Upload via Catalog Item

$
0
0

Do you want to perform Master Data uploads via a Catalog Item?

ServiceNow gives you the option to make use of the following capabilities:

  • Catalog Items
  • UI Page
  • UI Macro
  • Workflows
  • Script Actions
  • Script Includes
  • Data Sources
  • Import Sets
  • Transform Maps
  • Notifications
  • Reports

In our use case, we make use of these components for the upload of CI’s and Assets.

Preparation per category

  1. Create a template for the upload of CI’s and Assets
  2. Make the import set and transform map
  3. Verify the upload of CI’s and Assets

Create a new catalog item for the mass upload of CI’s/Assets

  1. With the variables: Requested for, Category, Template, Reporting results, attachment
  2. Make sure you add the attachment with the master data to be uploaded in the correct template
    1. UI Page for download the correct template based on the selected category
    2. UI Macro for uploading the attachment
  3. Create a catalog client script OnSubmit
    1. To check if the attachment is added
    2. To check if there is only one attachment with the correct template and extension

Create a new workflow for the Catalog item

Add the following steps:

  1. Create Datasource, Scheduled Import and Run Transformation (Run Script that calls a Script Action)
    dataSourceImport();
    function dataSourceImport(){
        gs.eventQueue('mass.data.uploads.catalog.item', current, current.variables.import_category, current.variables.requested_for.user_name);
    }
  2. Create Exception Report (Run Script that calls a Script Include)
    createExceptionReport();
    function createExceptionReport(){
        var result = new MassDataLoadsUtil().phSendExceptionReport(current.variables.requested_for, current.variables.import_category, current.variables.report_results, current.variables.email_to, current.variables.import_set);
    }

The called script actions will make use of a script include

  1. Create Datasource
    //create the data source record and copy the attachment from the catalog item
    var ds = new GlideRecord('sys_data_source');
    ds.initialize();
    ds.name = dataSource;
    ds.import_set_table_name = importTable;
    ds.import_set_table_label = importLabel;
    ds.type= dsType;
    ds.format = dsFormat;
    ds.file_retrieval_method = dsRetrievalMethod;

    if (dsFormat == 'Excel') {
        ds.sheet_number = dsSheetNr;
        ds.header_row = dsHeaderRow;
    } else if (dsFormat == 'CSV'){
        ds.delimiter = dsDelimiter;
    }

    var NewDataSource = ds.insert();
                           
    //copy attachment from the catalog item to the dataSource
    GlideSysAttachment.copy('sc_req_item',current.sys_id,'sys_data_source',NewDataSource);
  2. Schedule Import and Run Transformation
    //Load the data into an import set and transform this based on the related transform map(s)
    var sourceGr = new GlideRecord('sys_data_source');
    sourceGr.addQuery('sys_id', source);
    sourceGr.query();
                           
    // if we have our data source continue
    if(!sourceGr.next()) {
        return;
    }
                           
    //create the import set and load this with the data from the data source
    var loader = new GlideImportSetLoader();
    var importSetGr = loader.getImportSetGr(sourceGr);
    var ranload = loader.loadImportSetTable(importSetGr, sourceGr);
                           
    if(!ranload) {
        //Failed to load import set
        return;
    }
                           
    //Running Transfom map(s) of the related import set
    var mapName = 'IMP TTM Load Cis and Assets: ' +importCategory;
    var importSetRun = new GlideImportSetRun(importSetGr.sys_id);
    var importLog = new GlideImportLog(importSetRun, mapName);
    var ist = new GlideImportSetTransformer();
    ist.setLogger(importLog);
    ist.setImportSetRun(importSetRun);
    ist.setSyncImport(true);
    ist.transformAllMaps(importSetGr);
  3. Create Exception Report
    //create a list report based on the input variables (all results or only errors) per category
    var grRpt = new GlideRecord('sys_report');
    grRpt.initialize();
    grRpt.title = sTitle;
    grRpt.table = sTable;
    grRpt.type = sType;
    grRpt.filter = sCondition;
    grRpt.field_list = sFields;
    grRpt.orderby_list = sOrder;
    grRpt.user = inRequestedFor;
    grRpt.roles = '';
    var rptID = grRpt.insert();
  4. Send Notification with the Exception Report
    //execute this report direct and send the results to the related users/groups with the report subject and body text
    var userID = sEmails;
                           
    var grSchRpt = new GlideRecord('sysauto_report');
    grSchRpt.initialize();
    grSchRpt.address_list = sEmails;
    grSchRpt.user_list = userID;
    grSchRpt.output_type = sOutputType;
    grSchRpt.report = sRptID;
    grSchRpt.run_type = sRunType;
    grSchRpt.report_title = sSubject;
    grSchRpt.report_body = sBody;
    grSchRpt.name = 'Auto Sending of ' + grSchRpt.report.title;
    var schRptID = grSchRpt.insert(); // since it is a "once" schedule, it will be executed right away

By following above steps you should be able to trigger the upload via a catalog item!
.img[at].img

Creating a pop-up using a GlideDialogWindow

$
0
0
I recently had to create a pop-up window with a reference field where one could select a user to redirect an approval to. A Glide Dialog Window seemed to be the best option to me.
An approver had to be able to open the window by clicking on the ‘Redirect’ button. This button didn’t exist yet, so I created it.

The UI Action

Below you see the settings for the UI Action: It’s a Form Button that is client-callable.

The Redirect button isn’t supposed to be visible for everyone, only users with the role approval_admin should be able to redirect or the current approver. I also do not want the button to be visible if the approval has a different state than ‘requested’ and neither do I want to be able to redirect other than Requested Items approvals, so the source table must be ‘sc_req_item’ which is the table with the requested catalog items.

Once the button is clicked I would like see a window popping up, so I set ‘onClick’ to run the function ‘approvalDialog()’, which is located in the script section of the UI Page:

The function ‘approvalDialog()’ will first get the sys_id of the approval record, we need that later on, in order to find the approval record back on the server side. I do this by creating the variable ‘si’ and populate it with the result of ‘g_form.getUniqueValue()’, that retrieves the sys_id of the current record.
Next, I initialize the Glide Dialog Window. For this I populate the variable ‘gdw’ with a new GlideDialogWindow object. I give the object a fitting title, give it the sys_id which I stored in the variable ‘si’ and then it’s time to have a look at the UI Page that gives the pop-up it’s appearance.
I provide the new UI Page ‘sh_redirect_approver’, because that is what my new Glide Dialog Window object is looking for.

The UI Page

A UI page consists of three sections. The HTML section where the pop-up window is shaped, the client script section, where the code is that is called in the HTML section and finally, the processing script, where the server-side code is.
Below is the code for the HTML section:

<g:ui_form>
    <!-- get the sys_id value that has been stored in 'si' and is sent by the UI Action -->
    <g:evaluate var="jvar_si" expression="RP.getWindowProperties().get('si')" />
    <!-- store the approval sys_id in the (hidden) sid object -->
    <input type="hidden" name="sid" value="${jvar_si}"/>
    <input type="hidden" id="cancelled" name="cancelled" value="false"/>
    <TABLE BORDER="0">
        <TR>
            <TD>
                Please select the user you wish to set as approver.
            </TD>
        </TR>
        <TR>
            <TD>
                <g:ui_reference id="approver_picker" name="approver"table="sys_user" columns="name" columns_search="false" order_by="name" show_popup="true" query="active=true^locked_out=false" />
            </TD>
        </TR>
        <TR>
            <TD>
                <P></P>
            </TD>
        </TR>
        <TR id="dialogbuttons">
            <TD align="center">
                <g:dialog_buttons_ok_cancel ok="return validateApprover();" cancel="onCancel();" />
            </TD>
        </TR>
    </TABLE>
</g:ui_form>

The UI Page above is a Jelly page. With Jelly, logic can be embedded within static content and computed values may be inserted into the static content. ‘g’ tags are extensions to the Jelly syntax, specific to ServiceNow.

Lets have a look at the code above. First it retrieves the sys_id which we stored in the variable ‘si’. This is done with the function ‘RP.getWindowProperties().get(‘si’)’, this obtained value is now stored in the variable ‘jvar_si’. Immediately I create a new, hidden object to which I assign the value of jvar_si and I give it the name ‘sid’. This way, I am sure that I can use the variable on the server-side.
There’s another hidden object that is created: ‘cancelled’ and I use it to tell the server-side processing script whether it should run or not.
Next I call the UI Macro ‘ui_reference’ that I use to make the dropdown list. You can see that it queries the sys_user table for users that are active and are not locked out. Note that we give it the name ‘approver’, this will be used in the processing script.
Finally, I create two buttons. One called ‘OK’, that calls ‘validateApprover()’ and another, ‘Cancel’ that calls ‘onCancel’.
Both functions are in the second section, the client script:

function onCancel() {
    // Set cancelled to 'true' so that the server-side doesn't update the record.
    var c = gel('cancelled');
    c.value = "true";
    GlideDialogWindow.get().destroy();
    return false;
}

function validateApprover() {
    // I don't really want to do stuff on the client side...
    GlideDialogWindow.get().destroy();
    return true;
   
}

In the client script section, you see the two functions for the two buttons in the Jelly page.
‘onCancel()’ gets the ‘cancelled’ object that was created in the Jelly page and sets the value to ‘true’ and this stops the server-side code from running. This is followed by ‘GlideDialogWindow.get().destroy();’ which closes the pop-up.
validateApprover() doesn’t really do anything besides closing the pop-up, the ‘cancelled’ object keeps the value ‘false’, which means that the processing script is executed:

if (cancelled == "false" && trim(approver) != '') {
    // I do want to do things on the server side though...
    var gr = new GlideRecord('sysapproval_approver');
    gr.addQuery('sys_id', sid);
    gr.query();
    while (gr.next()) {  
         //set approver field to the new approver
        gr.approver = approver;
        gr.update();
        }
        response.sendRedirect("sysapproval_approver.do?sys_id=" + sid);
    }

else {
    // Just redirect to the un-updated approval record
    response.sendRedirect("sysapproval_approver.do?sys_id=" + sid);
}

So, If ‘cancelled’ is false and the value in the dropdown list is not empty, then the approval record can be updated.
For this the sysapproval_approver table is queried for the record where the sys_id is the one that we stored in ‘sid’.
After that the approver field is set to the value of the ‘approver’ we selected with the ui_reference and the record is updated.

Last but not least, the UI is redirected to the updated sysapproval_approver record.

This is the result: a basic pop-up Glide Dialog Window with a reference field where one can select the approver of choice:

I hope this article was useful for you. Please leave a comment if you have any questions!
.img[at].img

LDAPS via MID Server

$
0
0

LDAP is a good way to have your user provisioning in ServiceNow. This helps you to maintain user data in one source where it can be accessed by multiple applications. To transfer the user data securely, ServiceNow supports LDAP via the MID Server. LDAPS is also supported if it is directly into the customers’ network.
However, this is not preferred by most customers. They do not want ServiceNow to enter the customer network directly and this is most of the time not even possible. So, they want ServiceNow to do all communication to the customers’ environment via the MID Server because this is secure. However, this does not count for the internal communication. If LDAP is used via the MID Server, an employee who is already in the customers’ network, the data transferred between the LDAP server and the MID Server is not secure. Therefore, a lot of customers want Servicenow to perform LDAPS via the MID Server. Since this is not supported by Service Now I will explain how this can be done.

ServiceNow

Navigate to System LDAP —> LDAP Servers —> Create New

Note that a MID Server should be selected and the URL needs to start with LDAPS and end with the correct port number.
All other related configuration is the same as with a normal LDAP interface.

MID Server

On the MID Server you need to import a certificate from the LDAP Server to make it possible to access the Active directory with ServiceNow. First you need to stop the MID Servers (Do NOT stop them all at once). After this is done you can open the console and enter the following command.

..\MIDServer\agent\jre\bin>keytool -importcert –keystore “..\MIDServer\agent\jre\lib\security\cacerts” -file “..\cert.cer”

If asked for a password, the default password is: changeit. A verification is asked if you trust the certificate and want to import it to the cacerts file. Enter “yes” to import the certificate.

By following above steps you are able to connect ServiceNow through the MID Server to LDAP using LDAPS.

If you have any questions please leave a comment!
.img[at].img

Organizing (a lot of) code with a controller pattern

$
0
0

ServiceNow does a lot of things right. It allows for a great deal of extending and customizing, and compared to most other SaaS solutions I see in practice it does so very well. You write your custom logic, save it as a Script Include and use Business Rules, UI Actions and Scheduled Jobs to invoke your code. For most use cases the Script Include itself is enough to organize your code. But when the amount of functions needed becomes larger, there comes a point when one Script Include is just not enough too properly organize your code. Scrolling through 1500 lines of code is a pain even when it is properly formatted and well written.

One way to solve this issue would be to divide your code over different Script Includes and just call the Script Include that holds the function that you need. A problem with this approach is that you need to remember what Script Include contains what function when you want to call one of the functions. And what if (like the good developer that you are!) made helper functions for common operations. Do you need to put the helper functions in all Script Includes? Do you divide the functions on the basis of what helper functions they need? Obviously these solutions have some drawbacks when it comes to flexibility and maintainability. Since we want to organize our code the notion of using multiple Script Includes seems like a good starting point. Why not have some code that keeps track for us where different functions live? And while we are at it, why not have one place for our generic functions as well?

The solution I found consists of three ingredients: a controller, the functions groups and the generic functions. All of these are actually Script Includes and they are organized as follows:

In this example, there are three function groups, but depending on the amount of functions and the way they are divided this can vary from one to as many as you need. It works as follows:
All functions are registered in the controller, that instantiates the correct Script Include and calls the needed function for you. Every Script Include instantiates the Script Include that contains the generic functions and makes it available for its functions as a variable. All calls are made to the controller Script Include, and all function groups can be made as small or large as is convenient for you. But the functions can also be divided on a logical basis instead of just size, keeping related functionality together where possible or needed. So what does that look like?
In the controller we need a way to register our functions with Script Includes and have it called automatically. That would look something like this:

In the Script Include that acts as a function group 1 there must be a function called processX and the generic functions must be available. That would look something like this:

Finally an example of a generic function Script Include:

So per the above example, if I want to call a function that is in a function group it would look something like the following:

Keep in mind that these are simplified examples, and should not be used in production as-is. There are different ways to achieve this kind of setup and some are more elegant than the other. Think about how you can apply this idea to your own needs for organizing your code on ServiceNow and if the amount of code you are writing is actually needed for the requirements you are fulfilling. Happy coding!
.img[at].img

How to ‘Show XML’– the not so annoying way

$
0
0

The one thing that possibly annoys me the most on Servicenow is the fact that the ‘Show XML’ pop-up is automatically closed when it is no longer in focus. Actually, I am not sure when the window is closed because two hours of investigating on how Servicenow accomplishes this behaviour yielded no results. Chrome developer tools do not show any event listeners on this, window, document, body or any other element for that matter. Especially while developing you might want to keep the XML open to go back and forth between the record (and all data that might not be shown on the form itself) and what other page or program you have open. The first idea that comes to mind is editing the UI Action itself. But it comes as no surprise that there actually is no UI Action called ‘Show XML’ and inspecting the link from the context menu on the form shows that the href property for the div points to ‘xmlView(table, sysId)’. And in true Servicenow fashion, this is undocumented and inaccessible for editing.


Now that changing the built-in ‘UI Action’ is off the table, let’s just build our own UI Action. This can come in very handy on the development instance you are working on. Admittedly it is not the prettiest, but the solution itself is actually quite simple: create a global UI action that uses the built-in XML web service to display the current record. In order to accomplish this do the following:

  • Create a new UI action and give it a sassy name like ‘Show XML – the not so annoying version’ (ok the last bit is not an actual requirement but it makes the UI Action more fun to use of course 😊).
  • Select ‘Global’ as table.
  • Deselect ‘Show Insert’.
  • Select ‘Form context menu’.
  • Select ‘Client’.
  • In the Onclick field, fill in ‘openXML()’.
  • In the script field put:

    function openXML(){
    var url = 'https://' + window.location.host + '/' + g_form.getTableName() + '.do?XML&useUnloadFormat=true&sysparm_query=sys_id=' + g_form.getUniqueValue();
    window.open(url, 'XML', 'height=640,width=960,toolbar=no');
    }
  • For good measure add ‘admin’ as a required role.

The above steps should give you something like the following:

Now save the UI Action and enjoy nuisance-free alt-tabbing back and forth between your XML pop-up and whatever program you are using by opening the context menu of a record and selecting your new UI Action. Have fun!

.img[at].img

Knock, knock who is there?

$
0
0


In ServiceNow out of the box it is possible to restrict access to the instance based on IP address. When access is revoked the following page is visible below.

Under “System Security” a module is available called “IP Address Access Control” to control the IP addresses which can access the instance or the IP addresses which should be denied to access.

Based on the type either Allowed or Denied these entries are distinguished.

For more information a reference is available on the docs of ServiceNow: https://docs.servicenow.com/bundle/helsinki-servicenow-platform/page/administer/login/task/t_AccessControl.html

Denied IP addresses

Out of the box in ServiceNow it is possible to find the denied IP addresses.

  1. Navigate to System Logs > Utilities > Node Log File Browser.
  2. Browse the logs by criteria, such as time period and message.
  3. You can also download log files when you know which log you are looking for, by navigating to System Logs > Utilities > Node Log File Download..

However, this is not a really easy way to find the IP addresses which are denied. Instead is it not more user friendly to see who tried to knock on the door? In this article, it will be described how this can be accomplished, so systems or IP addresses that are accidently revoked access to ServiceNow can be identified.

To make it easier and traceable a table needs to be created. Below are the details with the specification of this table.

Table label Table name Field label Field name Field type
Failed IP Address Access u_failed_ip_address IP address u_ip_address String (80)

Now the table needs to be populated with the IP addresses which were rejected, this will be done using the following scheduled job:
Name: Log failed IP access request
Active: True
Run: Periodically
Repeat interval: 30 min
Script:

logFailedIPAccessAttempts();

function logFailedIPAccessAttempts() {
var logFile = new GlideLogFileReader();
var dtstart = new GlideDateTime();

dtstart.addSeconds(-60*60);
logFile.setStartTime(dtstart.getDisplayValue());
logFile.setEndTime(new GlideDateTime().getDisplayValue());
logFile.setLevel(1);
logFile.setMessage('Security restricted: Access restricted');
logFile.setThread('http');

if(logFile.open()) {
while(logFile.next()) {
var message = logFile.getValueHtmlEscaped('message') + "";
var ipaddress = '';
if(message.indexOf('Security restricted: Access restricted') > -1) {
ipaddress = ''+message.match(/(\d)*[.](\d)*[.](\d)*[.](\d)+/g);
if(!ipaddress.nil()) {
var grfailedip = new GlideRecord('u_failed_ip_access');
if(!grfailedip.get('u_ip_address', ipaddress)) {
grfailedip = new GlideRecord('u_failed_ip_access');
grfailedip.initialize();
grfailedip.u_ip_address = ipaddress;
grfailedip.insert();
}
}
}
}
}
}

For each failed IP address, a new entry will be created. Create a new module, called Failed IP Address Access, so the table can be accessed under System Security.

The module opens a new table with a list of IP addresses which failed to access ServiceNow, based on the Created timestamp it is traceable when the failed attempt was initiated.

Done, not there is a better way of getting insight to the IP addresses which were failed to access your ServiceNow instance.

.img[at].img

Capture the template being used in record chosen from the template bar

$
0
0


Every now and then we get a customer question if it is possible to report on the use of templates. Usage like the number of times templates are used and by whom.

Tracking this has become a bit more challenging since the ServiceNow has locked down the code of the template bar functionality. The following solution is not 100% bulletproof. You probably should check for multiple occasions of the class ‘outputmsg_text’ but it should give you a good idea of what is possible). Also for demonstration purpose I made use of a synchronous GlideRecord query which you should always avoid (so no need to leave comments for that).

1. Create a new reference field ‘u_template’ (reference table = sys_template) on the incident form.

2. Create a new client script with the following code:

function onSubmit() {
//Type appropriate comment here, and begin script below
try {
var template_html = $j(".outputmsg_text").html().split("<\br>")[0];
//alert("template_html [" + template_html + "]");

var patt = new RegExp("(.*?) Template Applied");
var template = patt.exec(template_html);

alert("template [" + template[1] + "]");

var rec = new GlideRecord('sys_template');
rec.addQuery('name', template[1]);
rec.query();
if (rec.next()) {
alert("template name/sys_id [" + rec.name + "] [" + rec.sys_id + "]");
g_form.setValue('u_template',rec.sys_id);
}

} catch(err) {

}
}

.img[at].img

Current in Advanced Reference Qualifiers

$
0
0


The “Current” object in the context of Reference Qualifiers is an interesting object that can assist in delivering the filter you are looking for in a Reference Qualifier. This article describes how to work with this object.

Reference Qualifiers

A Reference Qualifier is used to restrict the amount of records that are returned when a user tries to select a record in a Reference field.
Out of the box there are three types of Reference Qualifiers:

Simple Reference Qualifier

This Reference Qualifier is a basic filter that can be configured just as you would create a filter for any list in ServiceNow.
This means that the Simple Reference Qualifier is not able to make use of the context of the record and only has limited access to the data in the record.

Dynamic Reference Qualifier

For this article I have been playing around with Dynamic Reference qualifiers (in a Kingston release). I ran into some odd behaviour:
When updating the Dynamic Filter Option that is linked to a Reference field, the Reference field changes from being a “Dynamic” reference to an “Advanced” reference, and the old value of the Dynamic Filter is copies to the Reference Qualifier field of the Dictionary record
The Script field in the Dynamic Reference Qualifier expects current.addQuery(…, …); and current.addEncodedQuery(…) doesn´t seem to work.

Because of this, I will not go too much deeper into the dynamic reference qualifiers. I would actually suggest to ignore them for know. The concept is strong, but the execution a bit poor.
Advanced Reference Qualifier
So now let´s have a look at the options we have in the Advanced Reference Qualifiers.
An Advanced Reference Qualifier always results in an Encoded Query being created. This can be done in 2 ways:
1 Write it directly in a string (in some case ServiceNow automatically translates that to a
city=Irvine
2 Write a script that generates the string
javascript:rqCompany();
a
Where rqCompany is a Script Include with the following content:

function rqCompany(){
return 'city=Irvine';
}

Current

People that have been playing with these Advanced Reference Qualifiers may have noticed that they have access to an object named “current”.
Many of you may have assumed that this is the same current that is available to people in a Business Rule, but this is not completely the case.
Investigating the current object will yield the following results:
– All field that are part of the table that you are looking at are available in the object
– Fields that are on the form (either visible or invisible) are in the object, and their value is the value on the form. Even if the record is not saved yet, and even for empty records.
– Fields that are not on the form (visible nor invisible) are also in the object. Their value is the current value in the database. If the record is new, still the initialization values are populated.

The result of this is that the Reference Qualifier adjusts perfectly to the data that is updated by the user.
It is also interesting to mention that dotwalking is possible, even for reference fields that were updated on the form only, and even for reference fields in new records.

.img[at].img

The getRefRecord dilemma

$
0
0


The getRefRecord functionality in ServiceNow is a function that gets a GlideRecord object for a given reference element. But what does it actually do and how should it be used in Istanbul and higher?

Let’s take a look at the following example: From the change task we want to update the ‘counter’ field on the change request by using an After Business Rule. There is a catch, there are Business Rules running on table Change Request that trigger on update.

Table Field Referenced table Referenced field
Change Task Change Request Change Request Counter

 

Business Rule name Table After/Before When to Run Action
Update Counter on Change Request Change Task 250 Update Update the counter field
Update Fields Change Request 100 Insert/Update Update the short description, description, and reason.

In the GlideRecord way that would be. (A get is also applicable):

var changeTask = new GlideRecord(“change_task”);
changeTask.addQuery("sys_id", "xxx");
changeTask.query();

if(changeTask.next()){
var changeRequest = changeTask.change_request.getRefRecord();
changeRequest.counter = 2;
changeRequest.update();
}

Expected Result:
What people expect is that only the counter field on the Change Request is updated. But also the short description, description and reason. This does not happen.

What actually happens:
Because of the processing order and how getRefRecord works the following happens:
1. Business rule “Update Counter on Change Request”: GetRefRecord stores a copy of the Change Request record in the variable “changeRequest”.
2. Business rule “Update Fields”: Update fields on Change Request changes the short description, description, and reason.
3. Business rule “Update Counter on Change Request”: executes the update function.

Result:
The stored object in variable “changeRequest” is set over the Change Request record. This will remove the updates made by Business rule “Update Fields”.

Correct use:
In the Business rule “Update Counter on Change Request” use a new GlideRecord call:

var changeTask = new GlideRecord(“change_task”);
changeTask.addQuery("sys_id", "xxx");
changeTask.query();

if(changeTask.next()){
var changeRequest = new GlideRecord("chnage_request");
changeRequest.addQuery("sys_id", changeTask.change_request);
changeRequest.query();

if(changeRequest.next){
changeRequest.counter = 2;
changeRequest.update();
}
}

Using the new GlideRecord call gets the version that has the updates that have been made by other Business Rule.

Extra:
Because of the way processing and handling of objects in the memory work, more unexpected behaviour can happen. Picture the following:
1. A script include function (a) updates a CI name via GlideRecord.
2. Function a gets aborted by a business rule because of a duplicate CI name.
3. The same script include function (b) stores the record via getRefRecord. This is not for updating but for logging purposes.
4. The name of the CI in function b is the new value used in function a.  This is not what was expected. Here is where processing comes in. Expected was the old name since step 2 aborted it.

Keep in mind to always to get the record again to prevent these unexpected behaviors.
.img[at].img

TOTP (Time based One Time Password)

$
0
0


Make use of one-time passwords for several two-factor authentication systems.

A couple of months ago, I was working for a customer on a ServiceNow implementation where I had to configure an interface to APIGee. This interface consists of an REST and oAUth2.0. This can be done easily within ServiceNow.

For more info about setting up an Outbound REST web service: https://docs.servicenow.com/bundle/jakarta-servicenow-platform/page/integrate/outbound-rest/concept/c_OutboundRESTWebService.html

So, everything was working fine, until the customer contacted me with some panic in his voice… “the interface user has to be connected via a two-factor authentication with Google Authenticator. Like a normal interactive user session will use.” When I heard this, it felt a little overcomplicated for an interface.

Google Authenticator
Is a service that you can use to enable two-factor authentication for your users. When a user logs in with username and password, another extra password should be entered from the Google authenticator application. As you can see for example in the picture below for a WordPress website:

https://camo.envatousercontent.com/071361df7104ca2747ad5ea330f9478dd8fb002d/687474703a2f2f357365632d676f6f676c652d61757468656e74696361746f722e776562666163746f72796c74642e636f6d2f5f707265766965772f64657363312e706e67

The generation of the password is using the Time-based One-time Password Algorithm.
To generate the correct password the algorithm needs a shared/secret key. This shared key is setup between the provided (in this case APIGee) and the device (normal the Google Authenticator app on your phone, but for now the ServiceNow instance). This shared key is known by APIGee and you as user. Based on this shared key you can generate the one-time password via the algorithm. More info: https://en.wikipedia.org/wiki/Time-based_One-time_Password_Algorithm

Example of setting up two-factor authentication with the shared key:

https://camo.githubusercontent.com/8383a293f157bfce290dc3b2f5b38124af18e699/687474703a2f2f7777772e74656368666c656563652e636f6d2f77702d636f6e74656e742f75706c6f6164732f323031332f30342f4d756c7461757468322e6a7067

The solution
Since ServiceNow is not providing such service for outbound messages I had to figure it out for myself and the customer of course. I created a Script Include that can generate the password simply based on the shared key and the current date-time.
The script include has a large script to enable SHA encryption since this was not available OOB in ServiceNow. Once that was setup the rest of the code was not that hard to create. I did not reinvent the wheel and was able to find some basic example code on the internet 😉 So the Script Include looks like this:

var TOTP = Class.create();
TOTP.prototype = {
initialize: function(secret) {
this.secret = secret;
},
dec2hex: function(s) {
return (s < 15.5 ? "0" : "") + Math.round(s).toString(16); }, hex2dec: function(s) { return parseInt(s, 16); }, leftpad: function(s, l, p) { if(l + 1 >= s.length) {
s = Array(l + 1 - s.length).join(p) + s;
}
return s;
},
base32tohex: function(base32) {
var base32chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
var bits = "";
var hex = "";
for(var i = 0; i < base32.length; i++) { var val = base32chars.indexOf(base32.charAt(i).toUpperCase()); bits += this.leftpad(val.toString(2), 5, '0'); } for(var i = 0; i + 4 <= bits.length; i+=4) { var chunk = bits.substr(i, 4); hex = hex + parseInt(chunk, 2).toString(16) ; } return hex; }, getOTP: function() { try { var epoch = Math.round(new Date().getTime() / 1000.0); var time = this.leftpad(this.dec2hex(Math.floor(epoch / 30)), 16, "0"); var hmacObj = new jsSHA(time, "HEX"); var hmac = hmacObj.getHMAC(this.base32tohex(this.secret), "HEX", "SHA-1", "HEX"); var offset = this.hex2dec(hmac.substring(hmac.length - 1)); var otp = (this.hex2dec(hmac.substr(offset * 2, 8)) & this.hex2dec("7fffffff")) + ""; otp = (otp).substr(otp.length - 6, 6); } catch (error) { throw error; } return otp; }, type: 'TOTP' };

In my scenario, I had a script for the outbound message where I call this Script Include to get the TOTP password before the REST call is executed. Example:


// Get the Google Authenticator password
var totpObj = new TOTP("Shared secret");
var otp = totpObj.getOTP();

sm = new sn_ws.RESTMessageV2();
sm.setEndpoint("https://login.apigee.com/oauth/token");
sm.setHttpMethod("POST");
sm.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
sm.setRequestHeader("Accept", "application/json;charset=utf-8");
sm.setRequestHeader("Authorization", "Basic ZWRnZWNsaTplZGdlY2xpc2VjcmV0");
sm.setQueryParameter("mfa_token", otp.toString());
sm.setQueryParameter("username", "username");
sm.setQueryParameter("password", "password");
sm.setQueryParameter("grant_type", "password");
sm.setHttpTimeout(10000);
response = sm.execute();

If you want a copy of my Script Include contact me at .img[at].img
Have nice day!

Migrating knowledge articles from one ServiceNow instance to another

$
0
0

Sounds like a simple export and import XML right?
That’s what I thought too… until we saw that the Knowledge state circles in the list view of kb_knowledge no longer showed the usual green for each completed stage for each migrated article (see screenshots below).

Source instance:

After importing into destination instance:

We found out that besides exporting the article, we needed to export the related record from the stage_state table as well.
So just another export and import XML, right? Well…almost…

When articles are imported, sometimes related stage_state records are generated automatically with empty/invalid values. This typically happened when we viewed the imported articles in a list view before importing the related stage_state records. Consequently, even though we imported the correct stage_state records, the Knowledge state of the articles still did not show green circles unless we deleted the automatically generated stage_state record of these articles.

So before you import the stage_state XML, be sure to remove any existing stage_state records for the articles you imported earlier.

Cheers!
.img[at].img

Creating a pop-up using a GlideDialogWindow

$
0
0
I recently had to create a pop-up window with a reference field where one could select a user to redirect an approval to. A Glide Dialog Window seemed to be the best option to me.
An approver had to be able to open the window by clicking on the ‘Redirect’ button. This button didn’t exist yet, so I created it.

The UI Action

Below you see the settings for the UI Action: It’s a Form Button that is client-callable.

The Redirect button isn’t supposed to be visible for everyone, only users with the role approval_admin should be able to redirect or the current approver. I also do not want the button to be visible if the approval has a different state than ‘requested’ and neither do I want to be able to redirect other than Requested Items approvals, so the source table must be ‘sc_req_item’ which is the table with the requested catalog items.

Once the button is clicked I would like see a window popping up, so I set ‘onClick’ to run the function ‘approvalDialog()’, which is located in the script section of the UI Page:

The function ‘approvalDialog()’ will first get the sys_id of the approval record, we need that later on, in order to find the approval record back on the server side. I do this by creating the variable ‘si’ and populate it with the result of ‘g_form.getUniqueValue()’, that retrieves the sys_id of the current record.
Next, I initialize the Glide Dialog Window. For this I populate the variable ‘gdw’ with a new GlideDialogWindow object. I give the object a fitting title, give it the sys_id which I stored in the variable ‘si’ and then it’s time to have a look at the UI Page that gives the pop-up it’s appearance.
I provide the new UI Page ‘sh_redirect_approver’, because that is what my new Glide Dialog Window object is looking for.

The UI Page

A UI page consists of three sections. The HTML section where the pop-up window is shaped, the client script section, where the code is that is called in the HTML section and finally, the processing script, where the server-side code is.
Below is the code for the HTML section:

The UI Page above is a Jelly page. With Jelly, logic can be embedded within static content and computed values may be inserted into the static content. ‘g’ tags are extensions to the Jelly syntax, specific to ServiceNow.

Lets have a look at the code above. First it retrieves the sys_id which we stored in the variable ‘si’. This is done with the function ‘RP.getWindowProperties().get(‘si’)’, this obtained value is now stored in the variable ‘jvar_si’. Immediately I create a new, hidden object to which I assign the value of jvar_si and I give it the name ‘sid’. This way, I am sure that I can use the variable on the server-side.
There’s another hidden object that is created: ‘cancelled’ and I use it to tell the server-side processing script whether it should run or not.
Next I call the UI Macro ‘ui_reference’ that I use to make the dropdown list. You can see that it queries the sys_user table for users that are active and are not locked out. Note that we give it the name ‘approver’, this will be used in the processing script.
Finally, I create two buttons. One called ‘OK’, that calls ‘validateApprover()’ and another, ‘Cancel’ that calls ‘onCancel’.
Both functions are in the second section, the client script:

function onCancel() {
// Set cancelled to 'true' so that the server-side doesn't update the record.
var c = gel('cancelled');
c.value = "true";
GlideDialogWindow.get().destroy();
return false;
}

function validateApprover() {
// I don't really want to do stuff on the client side...
GlideDialogWindow.get().destroy();
return true;

}

In the client script section, you see the two functions for the two buttons in the Jelly page.
‘onCancel()’ gets the ‘cancelled’ object that was created in the Jelly page and sets the value to ‘true’ and this stops the server-side code from running. This is followed by ‘GlideDialogWindow.get().destroy();’ which closes the pop-up.
validateApprover() doesn’t really do anything besides closing the pop-up, the ‘cancelled’ object keeps the value ‘false’, which means that the processing script is executed:

if (cancelled == "false" && trim(approver) != '') {
// I do want to do things on the server side though...
var gr = new GlideRecord('sysapproval_approver');
gr.addQuery('sys_id', sid);
gr.query();
while (gr.next()) {
//set approver field to the new approver
gr.approver = approver;
gr.update();
}
response.sendRedirect("sysapproval_approver.do?sys_id=" + sid);
}

else {
// Just redirect to the un-updated approval record
response.sendRedirect("sysapproval_approver.do?sys_id=" + sid);
}

So, If ‘cancelled’ is false and the value in the dropdown list is not empty, then the approval record can be updated.
For this the sysapproval_approver table is queried for the record where the sys_id is the one that we stored in ‘sid’.
After that the approver field is set to the value of the ‘approver’ we selected with the ui_reference and the record is updated.

Last but not least, the UI is redirected to the updated sysapproval_approver record.

This is the result: a basic pop-up Glide Dialog Window with a reference field where one can select the approver of choice:

I hope this article was useful for you. Please leave a comment if you have any questions!
.img[at].img

Viewing all 89 articles
Browse latest View live