A
few months ago I wrote about copying change requests using a UI action. While that method works great, it does require you to specify each and every field and value that you want to populate into the new change request. If you’ve got a lot of fields to copy over then you might end up with a pretty big script and a lot of items to copy over. You also need to be aware of any new fields that get added after you create the script and make sure that they get copied if necessary.
The following method works in much the same way, but it copies by performing an insert against the current record (rather than starting from a brand new change record and supplying each value). Because of this, you’re concerned about overriding any of the values (such as start and end dates) that you DON’T want to be copied over from the record you are copying. This method works better if you know you want to copy over all (or the majority) of the field values from a given change.
Name: Copy change
Form button: True
Table: Change Request
Condition: gs.hasRole(“itil”) && current.isValidRecord()
Script:
function copyChange() {
//Get the current sys_id value for querying
var chgID = current.sys_id.toString();
//Initialize new change for insertion
var newChange = current;
newChange.number = getNextObjNumberPadded(); //Get next change number
newChange.requested_by_date = 'NULL';
newChange.start_date = 'NULL';
newChange.end_date = 'NULL';
newChange.calendar_duration = 'NULL';
newChange.opened_at = current.opened_at;
newChange.opened_by = current.opened_by;
newChange.sys_created_on = current.sys_created_on;
newChange.sys_created_by = current.sys_created_by;
current.insert();
//Copy attachments for this change
if (typeof GlideSysAttachment != 'undefined')
GlideSysAttachment.copy('change_request', chgID, 'change_request', newChange.sys_id);
else
Packages.com.glide.ui.SysAttachment.copy('change_request', chgID, 'change_request', newChange.sys_id);
//Copy associated tasks and CIs
copyTask(chgID);
copyCI(chgID);
gs.addInfoMessage('Change ticket ' + newChange.number + ' created.')
action.setRedirectURL(newChange);
}
function copyTask(chgID) {
//Find the current change tasks and copy them
var tasks = new GlideRecord('change_task');
tasks.addQuery('change_request', chgID);
tasks.query();
while(tasks.next()){
var taskID = tasks.sys_id.toString();
var newTask = tasks;
if (typeof GlideNumberManager != 'undefined')
newTask.number = GlideNumberManager.getNumber('change_task');
else
newTask.number = Packages.com.glide.db.NumberManager.getNumber('change_task'); //Get next change task number
newTask.change_request = current.sys_id;
tasks.insert();
//Copy attachments for this task
if (typeof GlideSysAttachment != 'undefined')
GlideSysAttachment.copy('change_task', taskID, 'change_task', tasks.sys_id);
else
Packages.com.glide.ui.SysAttachment.copy('change_task', taskID, 'change_task', tasks.sys_id);
}
}
function copyCI(chgID) {
//Get the task record being copied
var tskRec = new GlideRecord('task');
if(tskRec.get(chgID)){
//Copy over the affected CI list
var cis = new GlideRecord('task_ci');
cis.addQuery('task', chgID);
cis.addNullQuery('u_ci_group'); //Added to ensure that copying does not duplicate Group CIs
if(gs.getProperty('change.conflict.mode')=='advanced' && tskRec.cmdb_ci!=''){
//Prevent duplicate CI from being added
cis.addQuery('ci_item', '!=', tskRec.cmdb_ci.toString());
}
cis.query();
while(cis.next()){
var newCI = cis;
newCI.task = current.sys_id;
cis.insert();
}
}
}
One other method for copying a change (or any other ticket) is to reference the record to copy and copy from the referenced record. You could use the script below to copy a change that is pulled from a reference field on any form. Just provide the name of the field to grab the change from in line 3.
//Provide the name of the reference field to copy change from
var chgID = current.your_change_reference_field.toString();
var rec = new GlideRecord('change_request');
rec.get(chgID);
copyChange();
function copyChange() {
//Initialize new change for insertion
var newChange = rec;
newChange.number = getNextObjNumberPadded(); //Get next change number
newChange.requested_by_date = 'NULL';
newChange.start_date = 'NULL';
newChange.end_date = 'NULL';
newChange.calendar_duration = 'NULL';
newChange.opened_at = current.opened_at;
newChange.opened_by = current.opened_by;
newChange.sys_created_on = current.sys_created_on;
newChange.sys_created_by = current.sys_created_by;
rec.insert();
//Copy attachments for this change
if (typeof GlideSysAttachment != 'undefined')
GlideSysAttachment.copy('change_request', chgID, 'change_request', newChange.sys_id);
else
Packages.com.glide.ui.SysAttachment.copy('change_request', chgID, 'change_request', newChange.sys_id);
//Copy associated tasks and CIs
copyTask(chgID);
copyCI(chgID);
gs.addInfoMessage('Change ticket ' + newChange.number + ' created.');
action.setRedirectURL(rec);
}
function copyTask(chgID) {
//Find the current change tasks and copy them
var tasks = new GlideRecord('change_task');
tasks.addQuery('change_request', chgID);
tasks.query();
while(tasks.next()){
var taskID = tasks.sys_id.toString();
var newTask = tasks;
if (typeof GlideNumberManager != 'undefined')
newTask.number = GlideNumberManager.getNumber('change_task');
else
newTask.number = Packages.com.glide.db.NumberManager.getNumber('change_task'); //Get next change task number
newTask.change_request = rec.sys_id;
tasks.insert();
//Copy attachments for this task
if (typeof GlideSysAttachment != 'undefined')
GlideSysAttachment.copy('change_task', taskID, 'change_task', tasks.sys_id);
else
Packages.com.glide.ui.SysAttachment.copy('change_task', taskID, 'change_task', tasks.sys_id);
}
}
function copyCI(chgID) {
//Get the task record being copied
var tskRec = new GlideRecord('task');
if(tskRec.get(chgID)){
//Copy over the affected CI list
var cis = new GlideRecord('task_ci');
cis.addQuery('task', chgID);
cis.addNullQuery('u_ci_group'); //Added to ensure that copying does not duplicate Group CIs
if(gs.getProperty('change.conflict.mode')=='advanced' && tskRec.cmdb_ci!=''){
//Prevent duplicate CI from being added
cis.addQuery('ci_item', '!=', tskRec.cmdb_ci.toString());
}
cis.query();
while(cis.next()){
var newCI = cis;
newCI.task =
rec.sys_id;
cis.insert();
}
}
}
Mark,
This is great. We just implemented it for one of our clients, but we ran into a small snag. We were getting duplicate numbers for the copied records. I think the problem is related to the fact that the customer has the check box for “Assign a task number only upon insert (prevents unused numbers)” under “System Properties -> System” selected.
We solved this problem by adding one line of code to the above script:
var newChange = current;
newChange.number = getNextObjNumberPadded(); //new line to fix duplicate number problem
newChange.requested_by_date = ‘NULL’;
Thanks!
Peter
Hey, thanks so much for the feedback. I’ve updated the scripts above to include this fix. Good catch!
Thanks for the article, came in handy with a client. I was a little confused at first when I saw you were making changes to the “newChange” variable but then used “current.insert();” to make the insert of the record. I then realized that “var newChange = current;” actually keeps a live connection between both objects, so any change to one affects the other. I’m no JavaScript expert, but I guess it may not even be worth creating the “newChange” variable at all and just use “current”.
Hi, this works pretty well but when i implement this, the change is copying over with approved as the “approval”. It is also copying over the journal entries in the activity list. Also, is there a way to copy the existing record without adding the CI? thanks
You can override any value on the change request you generate by adding a new ‘newChange’ line in the section of code that copies the change request. For approval you could do something like this…
If you don’t want to copy the associated CIs then just comment out this line in the code…
Mark,
I’m using the first script above and when copying change_tasks it gives me the number prefix defined for the change_request table. I get RFC1234 instead of the desired CTASK1234.
Thanks,
Scott
Good catch Scott. The numbering is controlled by the ‘getNextObjNumberPadded’ line. The issue in this case is that we’re dealing with the change request context and the change tasks…and that function doesn’t understand both of them. The only way I know of to get around this is to use a Packages call in place of the standard call. I’ve updated the scripts above with this fix. Let me know how it works for you.
Mark.. That worked, perfect.
Thanks for the help! (also…nice meeting you and Jacob in San Diego in early December)
I’m glad it worked! It was great to meet you too.
Hi Mark,
I was able to follow your logic to create additional copy modules for the other “Related Lists†that we use on our Change tickets. To ensure appropriate logging, I was careful to manually set all of the table columns like sys_created_by, sys_created_on, sys_updated_by, sys_updated_on, etc.
In your copyCI() script you set the ‘task_group’ table’s “task†column to the current.sys_id. I see that the task column is a reference field, so I’m assuming it is referencing the newly created Change ticket’s sys_id. I guess I’m asking if the current object at this point is the Change ticket or the ‘task_group’ record?
What about the sys_id column within the ‘task_group’ table? Are newly created records getting the same sys_id from the existing record that it is copying from? Do I need to manually set the sys_id within ‘task_group’ table?, or is it getting a unique value at Insert?
It is nice that people like you share your effort gained Knowledge with the rest of us, so we can have a starting point for learning ourselves.
Thanks again!
-Paul
Hey Paul,
I assume you’re talking about ‘task_ci’, not ‘task_group. If so, the ‘current.sys_id’ line refers to the new change record being created. The sys_id of the ‘task_ci’ record will be automatically generated so you shouldn’t need to worry about setting it.
Thanks for the feedback on SNCGuru! Let me know if you’ve got any suggestions.
I am a new user of Service Now with a new job where I have to create a service ticket per Server that I am working on. Many times there are multiple servers with the same action, like for instance back-up “snap-shot” of a server. So I have to open 20 individual Service Now tickets for the exact same duplicate actions. I see here where there is a way for the Service Now administrators/developers to create a button to “clone” a ticket. I assume this is a “Custom” feature you all are speaking of? If so, how do I get this “feature” implemented?
@Jonathan The only way to implement it is to set up a copy action as described above. The action will need to query for, and copy both the parent ticket and any related child tickets. The code above gives an excellent model for accomplishing this. You just need to substitute your table and field names.
I’ve added this script, including the line regarding the Approval status,and it’s working fine, but I’d like to prevent the approval requests from being copied – how would I accomplish that?
There’s nothing in the script that copies approval requests. If they’re being copied or added, it’s probably due to an associated workflow or something else, not this script.
Hi Everyone, is there a way to clone a change request that has custom variables in the template?
Using the following script works fine but not all of the variables get cloned over
Hi All,
How we copy request created from record producer.
Hi Mark,
I’m just testing your ‘Copy Change UI action’ script in our Dev Instance and it works almost perfectly 😉 Unfortunately, every change I copy is duplicating the Affected CIs.
The only things I’ve changed are to comment out the copyTask(chgID); line as I don’t want to copy Change Tasks and I’ve added a couple of our specific user fields into the newchange section at the top.
I’m not much good at scripting, so any pointers would be really appreciated!
First thing you should do is set the UI action up in a ServiceNow demo instance and make sure that it’s not doing the same thing there. That will tell you if the problem is with the UI action or something else unique to your system. Assuming the problem is unique to your system, I would check to see if you’re running multiple updates (using current.update()) in any business rules on your change table. You should also check the ‘task_ci’ table to see if it is using business rules to automatically insert affected CIs.
I always forget about those Demo Instances; sorry I’m fairly new at this ServiceNow stuff…
Doesn’t happen on Demo, so must be something we’ve done. I’ll do some digging around in our Dev Instance to see if I can find the problem.
BTW, I’ve learnt loads from your site, it’s really helped me out a lot. Thanks very much for the quick reply and for pointing me in the right direction!
Thanks for the comment. Good luck finding the issue in your system!
I debugged the Business Rules and found out what’s causing the problem. We have the Conflict Properties Mode set to ‘Advanced’, which fires the ‘Add CI in affected Cis List’ business rule. This is what is duplicating the main Config Item in the Affected CI’s list.
Our Change Manager wants this property left on ‘Advanced’. Not being much of a scripter, would it be possible to add something to the business rule condition or the end of your script to stop the rule firing when we use the ‘copyChange’ function?
Try the updated scripts above and see if that works better for you. I’ve added checks for that property to make sure the UI action doesn’t copy the primary CI if the business rule is going to run.
Genius Mark – works perfectly! Thanks very much for your help, I really appreciate it…