Maik Skoddow
Tera Patron
Tera Patron

background-gbff210677_1280.jpg

 

This article provides an approach to identifying all modifications that have been made to OOTB/Baseline records. Since such modifications lead to increased efforts during an upgrade, it is important to monitor them in order to be able to question them accordingly.

 

 

Data vs. Artifacts

 

In ServiceNow, there are basically two different types of tables: Those that inherit from the sys_metadata table and are commonly referred to as configuration data (e.g. Script Includes) and those that do not and are considered real data (e.g. Incidents). In the first case, I like to call the corresponding records "artifacts" to have a better distinction from the real data.

 

 

Customizations & skipped Records

 

A ServiceNow baseline instance ships with a ton of out-of-the-box (OOTB) artifacts, and each plugin or application adds to that inventory considerably. And in every ServiceNow project, sooner or later you will come to the point where changes to OOTB artifacts become necessary (e.g. modification of a Business Rule or simply deactivation of Email Notifications) to be able of fulfilling customer requirements accordingly. 

There are various definitions of the word "customization", and I define a customization as a modification of an OOTB artifact. Each customization will cause a so-called "skipped record" in the next instance upgrade. That means these records are not overridden by ServiceNow, but marked for reviewing and thus requiring an action. This is fine if only a few records are skipped, but if you have several hundred records or more, then an instance upgrade can be turned into a single small project that consumes the appropriate resources.

 

 

Instance Scans for continuous Monitoring of Customizations

 

Responsible roles such as the platform architect or the platform owner therefore strive to keep the number of customizations as small as possible. For permanent control, Instance Scans are the perfect answer, but finding out what changed OOTB artifacts are is not that easy. I experimented around a bit and by combining two API methods I was able to find a working solution.

 

 

Technical Approach

 

In the following code the method getCustomizedBaselineRecordSysIDs() expects a GlideRecord object with an executed query. The API method new global.UpdateVersionAPI().getCustomerFileIds() will return an array with Sys IDs of all newly created custom artifacts. And after excluding these custom records, API method global.SncAppFiles.hasCustomerUpdate() can be used to check if any changes have been made to an artifact.

 

function getCustomizedBaselineRecordSysIDs(grTableRecords) {
    //determine all new custom records to exclude false positives
    var objUpdateVersionAPI     = new global.UpdateVersionAPI();
    var arrCustomRecordSysIDs   = objUpdateVersionAPI.getCustomerFileIds(grTableRecords);
    var arrChangedBaslineSysIDs = [];

    //move back the pointer to the beginning of the record set as the method
    //"getCustomerFileIds" is iterating the GlideRecord set
    grTableRecords.restoreLocation();

    while (grTableRecords.next()) {
        //was that record changed?
        if (global.SncAppFiles.hasCustomerUpdate(grTableRecords)) {
            var strSysID = grTableRecords.getValue('sys_id');

            //test whether the record is a newly created custom one
            if (arrCustomRecordSysIDs.indexOf(strSysID) == -1) {
                arrChangedBaslineSysIDs.push(strSysID);
            }
        }
    }

    return arrChangedBaslineSysIDs;
}

var grTableRecords = new GlideRecord('sysevent_email_action');

grTableRecords.query();

gs.info(getCustomizedBaselineRecordSysIDs(grTableRecords));

 

For use in an Instance Scan check, you cannot feed the records of all tables derived from sys_metadata, because that would take too long. Instead, you have to focus on the most important ones. For example, it may not make sense to scan the table sysevent_email_action from above code since you always have to adapt a lot of OOTB Email Notifications to the special requirements of the customer.

 

 

Further Readings

 

Configurations vs. Customizations:

 

Upgrades & Skipped Records

 

Instance Scans

 

Comments
Suraj Pagad
Tera Explorer

Not sure if below API gives the correct result,

new global.UpdateVersionAPI().getCustomerFileIds(files);

If you see the script include - UpdateVersionAPI it excludes the artifact if it has a version with source table sys_update_history or sys_store_app, i.e. version created due to app install or instance upgrade(OOTB versions).

But it is quite possibly we can have customization on top of OOTB version, and the API but still doesn't consider as custom.

The related list - Installation files was intended for listing the baseline files that were installed with the app.

Maybe you can modify the above getCustomerFileIds to check the state to verify it current version state is custom version/ update set source table.

 

There is third related list on sys_store_app table i.e Customized files - https://<instance>.service-now.com/nav_to.do?uri=sys_relationship.do?sys_id=20be43c477791010ca93aeca...

Which gives the right code to get the customized artifacts for app

Pass the sys_id of app. 
gs.info(sn_app_customization.AppCustomizationAPI.getAppCustomizationFiles('1da803b9b3b10300f686a72256a8dcbc'));
Maik Skoddow
Tera Patron
Tera Patron

Hi @Suraj Pagad 

I suppose you did not get the purpose of new global.UpdateVersionAPI().getCustomerFileIds(files);

This method returns an Array of Sys IDs for all artifacts which have been created newly by any customer. It does not returns any customizations. ServiceNow often uses this method for instance scan checks to narrow down the findings to user-generated artifacts.

And regarding the second aspect of customized OOTB artifacts: I'm absolutely not interested in these artifacts, as customizations of store apps is part of the development process and the responsible development lead or architect has to verify whether all store app customizations are correct or not.

Daniel A--C
Mega Guru
Mega Guru

This is very helpful Maik thank you. One thing to be aware of is that the 'global.SncAppFiles.hasCustomerUpdate()' method will also return false if an admin has deleted the equivalent Customer Update [sys_update_xml] records for the customised file as it is only checking for the presence of those records. The Delete button is available to admins for those records out of the box, despite it not being a particularly good idea. Therefore this isn't a 100% reliable method of detecting whether a file is currently customised or not. I have found querying the sys_update_version table gives a more reliable picture.

 

Also, your version is only looking for fully custom objects E.g. created from new by the customer and I fully recognise your reasons for that. However, there are plenty of times where you may want to scan both custom and customised objects as these need to meet your in-house standards etc. I have put together the following script which I believe works well for this purpose. It uses UpdateVersionAPI as it's inspiration.

/**
 * Gets whether a file is customised or not
 * 
 * Note 1: Custom objects and customised objects are both treated as customised
 * Note 2: Relies on sys_update_versions as these are not deletable (unlike customer updates)
 * Note 3: This will return true for anything delivered via update set (including 3rd party sets)
 * 
 * @Param sysMetadataName file name e.g. sys_script_include_95f8dc9547072110ef218a88536d43ea
 * @return boolean
 */
function isFileCustomised(sysMetadataName) {

    var versionGR = new GlideRecord('sys_update_version');
    versionGR.addQuery('name', sysMetadataName);
    versionGR.addQuery('state', 'current');
    versionGR.setLimit(1);
    versionGR.query();

    // if a file has 0 versions then not custom or customised
    if(!versionGR.hasNext())
        return false; //never been customised
        
    // if 'current' version is from sys_upgrade_history or sys_store_app, then it is not custom or customised
    versionGR.next()
    var source = versionGR.getValue('source_table');
    if(source === 'sys_upgrade_history' || source === 'sys_store_app')
        return false; //is not currently custom or customised
       
    return 'true'; //is custom or customised
}

Call it like this...

//Note: amend Ids to match your test files
var customFile = 'sys_script_include_95f8dc9547072110ef218a88536d43ea';
var customisedFile = 'sys_script_include_f2b47c0b67100200a5a0f3b457415afd';
var untouchedFile = 'sys_script_include_4226a45953d7101034d1ddeeff7b12e5';

gs.log(isFileCustomised(customFile))
gs.log(isFileCustomised(customisedFile))
gs.log(isFileCustomised(untouchedFile))
Version history
Last update:
‎12-20-2022 07:12 PM
Updated by:
Contributors