- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
11-10-2024 01:44 PM - edited 11-10-2024 01:47 PM
Hi all,
Just have a question about what process people are doing to update plugins.
Upgrading plugins is easily the worse part of my ServiceNow experience, scheduling the update doesn't work when you have hundreds to deal with.
I've just updated to Xanadu and after making all the initial plugin updates due to the family instance change, I was shocked and frustrated a week later, I had another 250+ updates required (which needs to be completed in 3 separate instances) it's a very time consuming process.
I'm interested in hearing how other people are managing high volume plugin updates, maybe there's a better way that I'm missing.
Ideally, I would like the the ability to opt in to auto update these plugins, overnight between the hours I define and if they don't complete, continue the next night.
Solved! Go to Solution.
- Labels:
-
plugins
- 8,052 Views
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
11-20-2024 02:43 AM
Big thanks to @Eric Riemer
/*----------------------------------------------------*/
/* */
/* 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 prevName;
var appsArray = [];
var grSSA = new GlideRecord('sys_store_app');
grSSA.addEncodedQuery('install_dateISNOTEMPTY^hide_on_ui=false');
grSSA.orderBy('name');
grSSA.orderBy('version');
grSSA.query();
while (grSSA.next()) {
var curName = grSSA.getValue('name');
if (curName == prevName) {
continue;
}
var installedVersion = grSSA.getValue('version');
var latestVersion = grSSA.getValue('latest_version');
if (latestVersion != installedVersion) {
prevName = curName;
var appObject = {
displayName: curName,
id: grSSA.getUniqueValue(),
load_demo_data: loadDemoData,
type: "application",
requested_version: latestVersion
};
appsArray.push(appObject);
}
}
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);
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
10-17-2025 01:38 PM
If I ever meet you or Eric in the wild the first beer is on me!! this worked great
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
3 weeks ago
Trying it out in my PDI right now and hoping for success 🙂
Thanks for sharing @Kieran Anson and thanks for asking @SeanM1 !
I was really looking for a solution to update some (desired) SN-applications by setting the "auto_update" to true on th "sys_store_app" table, but I never got to modify that field 😞
Kind regards
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
a month ago - last edited 3 weeks ago
While the above script works fine, I stumbled upon some limitations/issues:
- Simply comparing that "latestVersion != installedVersion" can lead to downgrades instead of upgrades in some situations. This is happening because the "grSSA.orderBy('version');" is not 100% reliable when version number is weird. (e.g. comparing 6.1.2 to 6.1.100)
- There is one dedicated filed to check if upgrade is available or not
- Taking the last available version can lead to incompatibilities. For example, the customer has version 6.3 and is running Yokohama. The latest version is 7.0, and it requires Zurich, while the previous version, 6.9, remains compatible with Yokohama.
- Upgrading some system applications (mainly the ones beginning with @) can also lead to errors in the results
- Creating one big batch can lead to issues and can create some overhead while managing related skipped records
Based on these limitations, I developed my own version of the script over time, which I am sharing here. Have a look at the variables on top to modify the behavior.
/*----------------------------------------------------*/
/* Batch upgrade Store apps via CI/CD Batch Install */
/* Background Script – optimized & safer version */
/*----------------------------------------------------*/
// Configuration
// default when preserveDemoData=false
var LOAD_DEMO_DATA_DEFAULT = false;
// if true, mirror current demo_data state per app
var PRESERVE_DEMO_DATA = true;
// Maximum number of plugins to update
var APP_LIMIT = 50;
// Should the update also include system applications?
// Set to false in case of issues. Plugins starting with "@" will be ignored
var INCLUDE_SYSTEM_APPS = true;
// Log prefix of the pluin. This is printed at the beginning of the message to ease log search.
var PREFIX = "[BATUCH PLUGIN UPGRADE SCRIPT]";
// System Properties
// Instance family (e.g., "Washington", etc.)
var BUILD_NAME = gs.getProperty('glide.buildname', '');
if (!BUILD_NAME) {
gs.warn(PREFIX + " glide.buildname property is empty. Compatibility filtering may fail.");
}
// Safer base URL
// trim trailing slash
var BASE_URI = (gs.getProperty('glide.servlet.uri') || '').replace(/\/+$/, '');
// State
var prevName = null;
var appsArray = [];
var limitReached = false;
var updateCnt = 0;
// --- utilities ---
/**
* Compare dotted version strings with optional lexicographical and zeroExtend options.
* Returns: -1 if v1 < v2, 0 if equal, 1 if v1 > v2, NaN if invalid input per options.
*
* Options:
* - lexicographical: when true, compare parts as strings (e.g., "1a" allowed)
* - zeroExtend: when true, pad the shorter version with "0" parts before comparing
*/
function versionCompare(v1, v2, options) {
options = options || {};
var lexicographical = !!options.lexicographical;
var zeroExtend = !!options.zeroExtend;
// Fast path: identical strings
if (v1 === v2) {
return 0
};
// Precompile appropriate validator for this call
var re = lexicographical ? /^\d+[A-Za-z]*$/ : /^\d+$/;
var a = v1.split('.');
var b = v2.split('.');
// Validate parts (simple loop is faster than Array.every w/ closures)
var i;
for (i = 0; i < a.length; i++) {
if (!re.test(a[i])) {
return NaN;
}
}
for (i = 0; i < b.length; i++) {
if (!re.test(b[i])) {
return NaN;
}
}
// Zero-extend to same length if requested
if (zeroExtend) {
var diff = a.length - b.length;
if (diff > 0) {
for (i = 0; i < diff; i++) {
b.push('0')
};
}
else if (diff < 0) {
for (i = 0; i < -diff; i++) {
a.push('0');
}
}
}
// Compare part-by-part
var len = Math.max(a.length, b.length);
for (i = 0; i < len; i++) {
var ai = a[i];
var bi = b[i];
// If one version ran out of parts, the longer one is greater
if (ai === undefined) {
return -1;
}
if (bi === undefined) {
return 1;
}
if (lexicographical) {
if (ai === bi) {
continue;
}
// ASCII string comparison (matches original semantics)
return ai > bi ? 1 : -1;
}
else {
// Numeric comparison; parse lazily (unary + is fast)
var na = +ai;
var nb = +bi;
if (na === nb) {
continue;
}
return na > nb ? 1 : -1;
}
}
return 0;
}
/**
* Decide whether to load demo data for an app based on the current app state
* and the global flags.
*/
function shouldLoadDemoData(storeRec) {
if (!PRESERVE_DEMO_DATA) {
return LOAD_DEMO_DATA_DEFAULT;
}
var demoData = String(storeRec.getValue('demo_data') || '');
return demoData === 'demo_data_loaded';
}
// --- main ---
// Filter Store apps that are installed, visible, and have updates
var storeQuery = "install_dateISNOTEMPTY^hide_on_ui=false^update_available=true";
if (!INCLUDE_SYSTEM_APPS) storeQuery += "^nameNOT LIKE@";
var grStore = new GlideRecord('sys_store_app');
grStore.addEncodedQuery(storeQuery);
// name+version ordering to keep duplicates contiguous
grStore.orderBy('name');
grStore.orderBy('version');
grStore.query();
var t0 = new Date().getTime();
while (grStore.next()) {
var curName = grStore.getValue('name');
var installedVersion = grStore.getValue('version');
var storeSysId = grStore.getUniqueValue();
// Deduplicate by name (process only the first row of each name group)
if (prevName !== null && curName === prevName) {
continue;
}
// gs.print("Retriving versions for " + curName);
// Build version query: same app id, compatible with this family, and not the installed version
var versionQuery = "source_app_id=" + storeSysId + "^compatibilitiesLIKE" + BUILD_NAME + "^version!=" +
installedVersion;
var grVer = new GlideRecord('sys_app_version');
grVer.addEncodedQuery(versionQuery);
// lexical+numeric sort, still validate with versionCompare
grVer.orderByDesc('version');
// gs.print(grVer.getEncodedQuery())
grVer.query();
// Find the highest strictly greater compatible version
var bestVersion = installedVersion;
var foundHigher = false;
while (grVer.next()) {
var candidate = grVer.getValue('version');
if (versionCompare(candidate, bestVersion) === 1) {
// gs.print(curName + " version " )
bestVersion = candidate;
foundHigher = true;
// Optional micro-optimization: break early if your ordering is reliable
// break;
}
}
if (foundHigher) {
var appObj = {
id: storeSysId,
load_demo_data: shouldLoadDemoData(grStore),
displayName: curName,
type: "application",
requested_version: bestVersion,
current_version: installedVersion
};
if (appsArray.length < APP_LIMIT) {
appsArray.push(appObj);
updateCnt++;
}
else {
limitReached = true;
break;
}
}
// advance the name guard
prevName = curName;
}
var t1 = new Date().getTime();
var elapsedMs = t1 - t0;
if (appsArray.length > 0) {
var time = new GlideDateTime();
var payload = {
packages: appsArray,
name: 'Batch Applications Update via CI/CD - ' + time.getDisplayValue()
};
var url = BASE_URI + "/$restapi.do?ns=sn_cicd&service=CICD%20Batch%20Install%20API&version=latest";
var report = PREFIX;
if (limitReached) {
report += "\n\n!!!!!!!!!!!! ATTENTION - LIMIT OF " + APP_LIMIT + " HAS BEEN REACHED !!!!!!!!!!";
}
report += "\n\nA total of " + updateCnt + " plugins will be upgraded";
report += "\nElapsed time: " + elapsedMs + " ms";
var json = JSON.stringify(payload, null, 2);
// Background Script console-friendly output
gs.print(report + "\n\nOpen the following URL in a new tab:\n\n" + url +
"\n\nCopy/paste the following JSON into the \"Raw\" Request body:\n\n" + json);
// Optional audit log
// gs.info(report + "\nRequest URL: " + url + "\nPayload:\n" + json);
}
else {
gs.print(PREFIX + " No application has been found (elapsed " + elapsedMs + " ms)");
}
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
3 weeks ago - last edited 3 weeks ago
and just to confirm....we are running this as a Background Script after hours correct? Is this safe to run against PROD? I just noticed we had over 200 updates after I had got that down to 3 like a month ago.
My only fear with a batch update like this is sometimes updates have warnings or notes such as this:
Or Agent Client Collector is updated, but we aren't ready to update it so it's excluded right now...
Things like that are my only concerns really.
Please mark this response as correct and/or helpful if it assisted you with your question.
Steven
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
3 weeks ago
You run it as Backgroud script and use the output JSON to run the CI/CD APIs. After you run it, instructions are shown at monitor.
You should never run this in PROD directly. You want to run this as part of a more structured Instance Upgrade project (typically with a new family release) and verify outcomes in non-production instances.
This type of batch update, based on the customizations you made, can also generate skipped items that you need to review before moving to PROD.
