T
he primary mechanism in Service-now for transporting configuration changes between instances is the System Update Sets functionality. If you’ve worked with Service-now much at all, you’re familiar with this functionality and know that it can be a huge time saver. You also probably know that there are a few gotchas to update sets that can cause problems if you don’t pay attention to them. One of these gotchas is that not all changes you make in your instance get recorded in update sets. Of course, this is by design and it’s usually a good thing that saves you from problems. There are, however, some situations where you need to capture updates to a specific record in a table even though you don’t want all of the records in that table being captured. In this article I’ll show you a solution to this problem.
First, let me give a couple of examples where this might come in handy…
Example 1
You need to modify the ‘Autoclose incidents’ Scheduled job to run once a day instead of once every hour. You can make this modification in your development instance, but it won’t be captured in an update set because the ‘sys_trigger’ table doesn’t have the ‘update_synch’ attribute set. There’s a great reason for this too. The last thing you want is for all of the records in the ‘sys_trigger’ table (SLA jobs, Inactivity monitors, Trend jobs, etc.) recording every update they ever make to an update set. You would end up with literally thousands of garbage updates in your update set in a matter of days just from normal system operations!
Until now, the solution to this problem was to export and import the file between instances as an added, manual step to your code migration process. It works (and in some cases may still be necessary), but it’s one more step that you would probably rather not have to think about.
Example 2
You’ve created a new table and application in your instance. The records in this table need to automatically receive a number when they are created so you create a new record in the ‘Number maintenance’ (‘sys_number’) table. There’s only one problem. Since the number maintenance table doesn’t include the ‘update_synch’ attribute those records won’t be included in your update set. Again, there’s a great reason for this. Number maintenance records get updated every single time you create a new task record, template, or software license. You don’t want all of that extra numbering garbage coming over to your production instance and messing things up there. All you want is to record the creation of a single Number maintenance record for your new table. Again, you’re forced to fall back on the manual export/import process to move that single record between instances.
The Solution:
So here’s the way I came up with to fix this problem. Simply create a new UI action record as shown below on the table(s) that you need to record single-record updates on. Once you add the UI action, you can click the ‘Force to Update Set’ link on any record in that table to add it to your currently-selected update set. The UI action does three things…
- Check to make sure the current table isn’t already recording updates
- Push the current record into the currently-selected update set
- Reload the form and add an info. message indicating the addition
Name: Force to Update Set
Table: Wherever you need it!
Action name: force_update
Form link: True
Condition: gs.hasRole(‘admin’)
Script
current.update();
//Check to make sure the table isn't synchronized already
var tbl = current.getTableName();
if(tbl.startsWith('wf_') || tbl.startsWith('sys_ui_') || tbl == 'sys_choice' || current.getED().getBooleanAttribute('update_synch') || current.getED().getBooleanAttribute('update_synch_custom')){
gs.addErrorMessage('Updates are already being recorded for this table.');
action.setRedirectURL(current);
}
else{
//Push the update into the current update set
var um = new GlideUpdateManager2();
um.saveRecord(current);
//Query for the current update set to display info message
var setID = gs.getPreference('sys_update_set');
var us = new GlideRecord('sys_update_set');
us.get(setID);
//Display info message and reload the form
gs.addInfoMessage('Record included in <a href="sys_update_set.do?sys_id=' + setID + '">' + us.name + '</a> update set.');
action.setRedirectURL(current);
}
Forcing an update from a Background Script
In some cases, it might not be desirable to include a UI action to perform this function. In that case, you can run a script from the ‘Scripts – Background’ module to force the record into an update set. Just replace the table name and sys_id of the record in the script below.
var rec = new GlideRecord('sys_number');
rec.get('973c8e8a9d022000da615b13b3a22f32');
//Push the record into the current update set
var um = new GlideUpdateManager2();
um.saveRecord(rec);
Attachments can also be forced into an update set by including the attachment and attachment doc records. Just pass in the sys_id the same way. Thanks to John Andersen for the idea here. He’s created a script to add a full record and associated attachments all at once that you can find on his blog.
var rec = new GlideRecord('sys_attachment');
rec.get('973c8e8a9d022000da615b13b3a22f32');
addAttachmentToUpdateSet(rec);
function addAttachmentToUpdateSet(attachmentGR) {
var um = new GlideUpdateManager2();
um.saveRecord(attachmentGR);
var attdoc = new GlideRecord('sys_attachment_doc');
attdoc.addQuery('sys_attachment', attachmentGR.sys_id);
attdoc.orderBy('position');
attdoc.query();
while(attdoc.next()){
um.saveRecord(attdoc);
}
}
This is sweet!
Very cool time saver, cheers!
You say “Wherever you need it!”
What if we make it a Global UI Action? Obviously, the admin should not be Forcing INC’s into an Update Set, but since it is “gs.hasRole(‘admin’)”, admin’s should know to play nice… 🙂
What’s your thoughts on this approach to the UI Action? Am I treading on deadly ground?
I considered that approach but decided against it because ‘should know’ is not the same as ‘will know’ :). There are some places in the tool that it’s not completely clear whether or not something is already being handled by update sets…form and list personalizations for example. Adding these to an update set in the wrong way can cause serious problems in an instance. In my experience, there are really only a handful of tables where including this UI action would be useful. My thinking was that I didn’t want to suggest that this is something that could be used anywhere in the system without causing an issue. Even if you or the admin you’re working with understands what’s going on, the odds are that the next person to come along isn’t going to understand. In my opinion it’s not that much work, and it’s A LOT safer to only add this to the specific tables where it’s required…as the issue comes up.
Hi Mark
Slightly off topic, can you remove the chart color entries in the update set and whats the impact if any?
I’m assuming that if you remove them from the update set they are recreated when the report(s) are run for the first time on the recipient environment.
cheers
I haven’t ever tried it, but I would assume you’re correct.
Hi,
Can we capture already created table in update set using sys_id?
Not with this solution, but you should be able to go to the ‘sys_update_xml’ table and find the updates and move them into your update set. You can create a quick test table in the SN demo instance to see what the update XML looks like.
Hey Mark,
Your idea worked for me….Thank you….:)
Similar to Example 1, I’m using this for moving scheduled jobs between instances, but I’m creating new ones. I went with adding this UI Action to the Scheduled Job [sysauto] table, and that actually means that a corresponding [sys_trigger] record doesn’t get carried with the update set, so my jobs don’t run in the destination instance until I manually change them (and create a [sys_trigger] record that way).
What’s the best approach for moving scheduled jobs, then? (Is it just modifying the code to additionally store a [sys_trigger] record?)
That should be possible, but you’ll want to make sure that you don’t end up with duplicate ‘sys_trigger’ entries. I think the best method is just to make an update to the scheduled jobs after migration.
I’m personally fine with making the manual update. If nothing else, I hope this helps those who may not realize that their jobs aren’t running in production. (I only found out when users noticed that scheduled dates had passed without their records being generated!)
As always, thanks for another useful Service-Now trick!
In my case, i needed to always capture data in my custom table.
I added the background script code as a async business rule (insert/update) to a custom table.
One more way in which this script can be useful.
Thanks for posting this! I had 50+ records I wanted to add to an update set but I didn’t want to go through and update them all. So a background script with your code and a while loop worked some magic!
For reference:
rec.addQuery( 'soap_message', 'sysidhere' );
rec.query();
while( rec.next() ) {
gs.log( "Function: " + rec.function_name );
//Push the record into the current update set
if (typeof GlideUpdateManager2 != 'undefined')
var um = new GlideUpdateManager2();
else
var um = new Packages.com.glide.update.UpdateManager2();
um.saveRecord(rec);
}
gs.log( "done" );
Nice! Thanks for sharing your solution!
Hi Mark,
Little observation: If you add some record (let’s say a KB) to the update set, do another update to it (something you remembered), and add it again to the update set using this UI action, you will have both Updates (XMLs) on the Update set.
Problem with that is that you cannot foresee which version will be finally installed on the environment, normally an old version.. This causes a lot of trouble for me.
I’m working something out to remove the previous record before inserting the new one, if we already have something for that particular record on the same update set.
Marcelo,
I’m not sure what’s going on in your system, but I’ve been using this solution for several years now and have never had that problem reported, nor experienced it myself. I just tested again to be sure. You might add this code to a demo environment to confirm. If you can reproduce the error in a system I can get to I’d be happy to look further.
Hi Mark,
You are right. I’m not sure what I did do before, but I cannot reproduce it! Probably it was my mistake,
Sorry for that
We tried to use this (awesome) UI Action today ( which we typically use for capturing updates to groups and roles ) to capture updates to a scheduled job, however it failed to capture the update with the message “~ updates are already being captured for this table”. Problem was traced to a bug in the code:
current.getED().hasAttribute(‘update_synch_custom’)
(sysauto_report has the attribute but it is false.)
After changing the line to:
/true/i.test(current.getED().getAttribute(‘update_synch’))
we were able to capture the update.
Thanks, Mark, for the awesome contribution!
Thanks Trey, it’s great to hear from you! I may be mistaken, but I don’t see that anywhere in my code above. I’m actually testing for the value of those attributes in my code above (which I think is correct and does what your code does). Have you tried the code above to see if that works better?
Thanks Mark, it’s exactly that I looking for !
Hi Mark!
Any idea on how to create something similar in order to “Add to Team Development Local Changes” ?
I was thinking on adding it to sys_sync_changes manually, but I didn’t test it yet.
Thanks for this time-saver!
How can I add to update set deleted record?
I want to store the “DELETE” flag, so the record on target instance will be deleted.
The code below is (of course) not working:
gr.deleteRecord();
var um = new GlideUpdateManager2();
um.saveRecord(gr); // does not work, because gr is deleted….
Any ideas please?