Skip to main content

Updating Store Apps Sucks

· 5 min read
Eric Riemer
ServiceNow Wizard

First posted August 13, 2023

Updated May 15, 2024

The Pain

Every quarter or so, ServiceNow released updates to Store apps. This is usually a good thing. The bad part is that you also have to update them in your instance.

One...

At...

A...

Time...

In...

Each...

Instance...

It's painful.

Tools to make it better

However there is a CI/CD API that lets you update or install apps in any instance. Unfortunately, it is a REST API so you need to deal with authentication and passwords etc. You also need to figure out what the payload should be.

So I came up with a script that takes most of the pain out of this (use at your own risk, always review code some rando on the internet publishes and make sure you understand it before running it in your instance, follow your companies processes for deploying changes, particularly to Production).

Feel free to skip down and just grab the code.

There is a single setting at the top to decide if you want to "check" the box for demo data, which just sets a flag in the payload.

In order to build the payload, we need to figure out which apps need to be updated. To do this we look at the sys_store_app table, where they are stored. Sort order is really important here because the table may have many entries for the same app (one for each version that is compatible with your family release). so we check to see if the name is the same as the previous records name (because we only care about the highest version of each app) and skip it if it matches. Then we check to see if the installed version is the same as the latest version (which is conveniently stored on the record). From there the code is adding JSON objects to an Array and then wrapping that into a JSON object to make up the payload that the CI/CD API requires.

Next problem is dealing with needing to authenticate for the REST API. I didn't like any of my options at first. I didn't want to put my password in the script, I didn't want to deal with a credential record (hard to use across multiple instances or share with others), and then I realized that I just needed to make the human do a small amount of work. The REST API Explorer allows you to interact with REST endpoints without needing to authenticate separately from the current browser session. The code gives you instructions to open a URL in a new tab that will take you to the REST Explorer with the correct API already selected (assuming that you are using a service-now.com URL and aren't using a custom URL or GCC instance, if you are, its not hard to figure out what you need to change), and then to just paste in a payload (that also gets printed out for easy selection) into the Raw section.

Once you hit Send, the API takes over. It can take a few minutes to get started, but your instance will figure out any dependancies and the appropriate order to install all the Apps and then proceed to install them starting the next one as soon as one completes.

You can see what is happening and track the progress of the install on the sys_batch_install_plan table.

The Code

/*----------------------------------------------------*/
/* */
/* Have a bunch of apps that need to be updated? */
/* Run this and follow the directions in the output */
/* It will build a payload and use the CI/CD API to */
/* run a batch install of all of the needed updates. */
/* */
/*----------------------------------------------------*/

//Want Demo Data with the app?
var loadDemoData = true;
var updateCheck = false //this can take some time to run and adds a LOT of stuff to the lod making the important bit harder to find

if (updateCheck)
new sn_appclient.UpdateChecker().checkAvailableUpdates();

var prevName;
var appsArray = [];
var grSSA = new GlideRecord('sys_store_app');
grSSA.addEncodedQuery('install_dateISNOTEMPTY^hide_on_ui=false^vendor=ServiceNow^ORvendorISEMPTY');
grSSA.orderBy('name');
grSSA.orderBy('version');
grSSA.query();
while (grSSA.next()) {
var curName = grSSA.getValue('name');
var latestVersion = updateAvailable(grSSA);
if (curName == prevName) {
continue;
}
if (latestVersion) {
prevName = curName;
var appObject = {
displayName: curName,
id: grSSA.getUniqueValue(),
load_demo_data: loadDemoData,
type: "application",
requested_version: grSSA.getValue('latest_version')
};
appsArray.push(appObject);
}
}
function updateAvailable(grSSA) {
var installedVersion = grSSA.getValue('version');
var latestVersion = grSSA.getValue('latest_version');
var installedArray = installedVersion.split('.');
var latestArray = latestVersion.split('.');
var len = Math.max(installedArray.length, latestArray.length);
for (var i = 0; i < len; i++) {
var installed = installedArray[i] ? parseInt(installedArray[i]) : 0;
var latest = latestArray[i] ? parseInt(latestArray[i]) : 0;
if (installed < latest) {
return true;
} else if (installed > latest) {
return false;
}
}
return false;
}
if (appsArray.length > 0) {
var appsPackages = {};
appsPackages.packages = appsArray;
appsPackages.name = 'Update Apps';
var data = new global.JSON().encode(appsPackages);

var url = 'https://' + gs.getProperty('instance_name') + '.service-now.com/$restapi.do?ns=sn_cicd&service=CICD%20Batch%20Install%20API&version=latest';
gs.info('Open the following URL in a new tab:\n\n' + url + '\n\ncopy/paste the following JSON into the "Raw" Request body\n\n' + data);
} else {
gs.info("\n\nNo apps to update found. \n\nIf you think this is incorrect please try running this script again with `updateCheck` set to `true`. This will check the store for any new updates.\n(sometimes there are apps in the Application Manager that say that there are updates but you can't actually update them)\n\n");
}```