T
his post comes in response to a request I received from some of my colleagues at ServiceNow to be able to export a catalog item definition to XML and transport it between instances. I’ve written before about how you can quckly export and import data between ServiceNow instances using the XML export/import context menus. While this works great for a lot of situations, it doesn’t work so great for data that resides in multiple tables, but really makes sense to be exported as a single unit. The components that make up a Catalog item definition are actually stored in over 21 separate tables! It is possible to export a catalog item but you have to do at least 21 separate exports to do it. In this post, I’ll show you how you can set up a UI action to export information from all 21+ tables that make up a catalog item definition…in one click!
21+ tables worth of information…in ONE click!
The catalog item export will work for any table extending the ‘sc_cat_item’ table…catalog items, record producers, content items, and order guides! It DOES NOT export associated workflows, delivery plans, or wizard definitions. Workflows can be exported by following the instructions in the Graphical Workflow Export article. The following table information is included.
- Variables (including question choices)
- Variable sets (including associated variables, client scripts, and UI policies
- Client scripts
- UI policies
- Additional categories
- Approved by user and group
- Available for/Not Available for lists
- Order guide rule base
The first step is to set up a ‘Processor’ record to handle the export. ServiceNow actually has some built-in code to handle exports like this. You just have to know how to leverage it. Here’s how you would set up a processor for the catalog item export.
Name: ExportCatalogItem
Type: Script
Path: export_catalog_item
Script:
var sysid = g_request.getParameter('sysparm_sys_id');
gs.log('** Exporting Catalog Item ' + sysid);
//Name all the related lists
var exporter = new ExportWithRelatedLists('sc_cat_item', sysid);
exporter.addRelatedList('item_option_new', 'cat_item');
exporter.addRelatedList('catalog_script_client', 'cat_item');
exporter.addRelatedList('catalog_ui_policy', 'catalog_item');
exporter.addRelatedList('sc_cat_item_category', 'sc_cat_item');
exporter.addRelatedList('sc_cat_item_catalog', 'sc_cat_item');
exporter.addRelatedList('sc_cat_item_user_criteria_mtom', 'sc_cat_item');
exporter.addRelatedList('sc_cat_item_user_criteria_no_mtom', 'sc_cat_item');
exporter.addRelatedList('sc_cat_item_group_mtom', 'sc_cat_item');
exporter.addRelatedList('sc_cat_item_group_no_mtom', 'sc_cat_item');
exporter.addRelatedList('sc_cat_item_company_mtom', 'sc_cat_item');
exporter.addRelatedList('sc_cat_item_company_no_mtom', 'sc_cat_item');
exporter.addRelatedList('sc_cat_item_dept_mtom', 'sc_cat_item');
exporter.addRelatedList('sc_cat_item_dept_no_mtom', 'sc_cat_item');
exporter.addRelatedList('sc_cat_item_location_mtom', 'sc_cat_item');
exporter.addRelatedList('sc_cat_item_location_no_mtom', 'sc_cat_item');
exporter.addRelatedList('sc_cat_item_user_mtom', 'sc_cat_item');
exporter.addRelatedList('sc_cat_item_user_no_mtom', 'sc_cat_item');
exporter.addRelatedList('sc_cat_item_app_group', 'sc_cat_item');
exporter.addRelatedList('sc_cat_item_app_user', 'sc_cat_item');
exporter.addRelatedList('sc_cat_item_guide_items', 'guide');
exporter.addRelatedList('pc_vendor_cat_item', 'product_catalog_item');
var catalogID = '';
var categoryID = '';
var vsrelID = '';
var vsID = '';
var vID = '';
var qcID = '';
var uipID = '';
var actID = '';
var csID = '';
//Query for Catalogs
var catalog = new GlideRecord('sc_cat_item_catalog');
catalog.addQuery('sc_cat_item', sysid);
catalog.query();
while(catalog.next()){
//Get Catalog IDs
catalogID = catalogID + ',' + catalog.sc_catalog.sys_id.toString();
}
//Query for Categories
var category = new GlideRecord('sc_cat_item_category');
category.addQuery('sc_cat_item', sysid);
category.query();
while(category.next()){
//Get Category IDs
categoryID = categoryID + ',' + category.sc_category.sys_id.toString();
}
//Query for variables to get question choices
var item = new GlideRecord('item_option_new');
item.addQuery('cat_item', sysid);
item.query();
while(item.next()){
//Query for question choices
var qc = new GlideRecord('question_choice');
qc.addQuery('question', item.sys_id.toString());
qc.query();
while(qc.next()){
//Add the variable question sys_id to the variable question string
qcID = qcID + ',' + qc.sys_id.toString();
}
}
//Query for ui catalog ui policies to get policy actions
var catpol = new GlideRecord('catalog_ui_policy');
catpol.addQuery('catalog_item', sysid);
catpol.query();
while(catpol.next()){
//Query for ui policy actions
var uipact = new GlideRecord('catalog_ui_policy_action');
uipact.addQuery('ui_policy', catpol.sys_id.toString());
uipact.query();
while(uipact.next()){
//Add the ui policy action sys_id to the ui policy action string
actID = actID + ',' + uipact.sys_id.toString();
}
}
//Query for variable set relationships
var vsrel = new GlideRecord('io_set_item');
vsrel.addQuery('sc_cat_item', sysid);
vsrel.query();
while(vsrel.next()){
//Add the item set relationship sys_id to the item set string
vsrelID = vsrelID + ',' + vsrel.sys_id.toString();
//Get the variable set
var vs = vsrel.variable_set.getRefRecord();
if(vs){
//Add the variable set sys_id to the variable set string
vsID = vsID + ',' + vs.sys_id.toString();
//Query for variables in the set
var v = new GlideRecord('item_option_new');
v.addQuery('variable_set', vs.sys_id);
v.query();
while(v.next()){
//Add the variable sys_id to the variable string
vID = vID + ',' + v.sys_id.toString();
//Query for variable question choices
var vqc = new GlideRecord('question_choice');
vqc.addQuery('question', v.sys_id.toString());
vqc.query();
while(vqc.next()){
//Add the variable question sys_id to the variable question string
qcID = qcID + ',' + vqc.sys_id.toString();
}
}
//Query for ui policies in the set
var uip = new GlideRecord('catalog_ui_policy');
uip.addQuery('variable_set', vs.sys_id.toString());
uip.query();
while(uip.next()){
//Add the ui policy sys_id to the ui policy string
uipID = uipID + ',' + uip.sys_id.toString();
//Query for ui policy actions
var uipa = new GlideRecord('catalog_ui_policy_action');
uipa.addQuery('ui_policy', uip.sys_id.toString());
uipa.query();
while(uipa.next()){
//Add the ui policy action sys_id to the ui policy action string
actID = actID + ',' + uipa.sys_id.toString();
}
}
//Query for client scripts in the set
var cs = new GlideRecord('catalog_script_client');
cs.addQuery('variable_set', vs.sys_id.toString());
cs.query();
while(cs.next()){
//Add the client script sys_id to the client script string
csID = csID + ',' + cs.sys_id.toString();
}
}
}
exporter.addQuerySet('sc_catalog', 'sys_idIN' + catalogID);
exporter.addQuerySet('sc_category', 'sys_idIN' + categoryID);
exporter.addQuerySet('io_set_item', 'sys_idIN' + vsrelID);
exporter.addQuerySet('item_option_new_set', 'sys_idIN' + vsID);
exporter.addQuerySet('item_option_new', 'sys_idIN' + vID);
exporter.addQuerySet('question_choice', 'sys_idIN' + qcID);
exporter.addQuerySet('catalog_ui_policy', 'sys_idIN' + uipID);
exporter.addQuerySet('catalog_ui_policy_action', 'sys_idIN' + actID);
exporter.addQuerySet('catalog_script_client', 'sys_idIN' + csID);
exporter.exportRecords(g_response);
})(g_request, g_response, g_processor);
Once you have your processor set up, you just need to call it. The processor above is called by its path name ‘export_catalog_item’ followed by ‘.do’. It also needs to know what to export so you need to pass it the sys_id of the top-level record that you want to export…in this case, the sys_id of the ‘Catalog item’ record.
Name: Export Catalog Item
Table: Catalog Item (sc_cat_item)
Order: 200
Form link: True
Show insert: False
Show update: True
Client True
Hint: Export catalog item definition to .xml file
Onclick callExportCatalogItem();
Condition: gs.hasRole(“admin”)
Script:
var url = new GlideURL('export_catalog_item.do');
url.addParam('sysparm_sys_id', gel("sys_uniqueValue").value);
var frame = top.gsft_main;
if (!frame){
frame = top;
}
frame.location = url.getURL();
}
Once you’re done with this, you should have an ‘Export Catalog Item’ UI action context menu item on your ‘Catalog Item’ forms that you can use to transport catalog items, record producers, and order guides in the event that they don’t get captured in an update set.
Hi Mark,
This is cool stuff.
Can we do same for form as well. Can we export all fields of form from one environment and import it to other environment. So that we don’t need to create fields again and again for each environment ( Other than Update Set). I tried using existing export/import but it is not working, I can’t see the fields when I try to personalize variable in form at new environment.
Regards,
ND
It’s probably possible to do for forms as well, but it’s best to use update sets to manage that process.
Hi Mark,
Thanks for reply.
Yes, I too agree update sets is best method. But there are some situation when we didn’t enabled the update sets and added the field to form. Now I want to build the application on Dev environment and don’t want to do labor work again.
Could you please guide a bit on how to make same for form I will try once for testing. Also why normal import and explort is not working?
Regards,
ND
The normal import isn’t working because a form is made up of several different pieces. I don’t think there’s a way to export and import the dictionary entries but you might be able to do the form, view, related lists, and form sections by combining the solution in this article with the tables described here.
https://servicenowguru.wpengine.com/system-ui/forms-form-sections-views/
Very good post,of great help.
I’ve been using the Export Workflow function for awhile, and was wondering the following would work to export a UI Policy with its associated Actions:
//Get the sys_id of the UI Policy
var sysID = g_request.getParameter(‘sysparm_sys_id’);
var actID = ”;
//Query for UI Policy Actions
var act = new GlideRecord(‘sys_ui_policy_action’);
act.addQuery(‘ui_policy’, sysID);
act.query();
while(act.next()) { actID = actID + ‘,’ + act.sys_id.toString(); }
//Export UI Policy info to XML
var exporter = new ExportWithRelatedLists(‘sys_ui_policy’, sysID);
exporter.addRelatedList(‘sys_ui_policy_action’, ‘sys_ui_policy’);
if(sysID != ”) { exporter.addQuerySet(‘sys_ui_policy’, ‘sys_id=’ + sysID); }
if(actID != ”) { exporter.addQuerySet(‘sys_variable_value’, ‘document_keyIN’ + actID); }
exporter.exportRecords(g_response);
It looks like it should work. Have you tried it out yet?
No, but I the problem is with the UI Action. I have it set up similar to the old “Export Workflow to XML” UI Action, but I keep getting an “action is undefined” error. If I define action, then I get a “current is undefined error”. Not sure why I get the error with this UI Action, and not the XML one.
I figured out the errors, but I’m still not getting the right results. The resulting XML file has the UI Policy, but all the UI Policy Actions regardless of Policy. I’ll figure this out one way or another!
Thanks, Mark!
I didn’t have a chance to test this yesterday but this should work in your processor record to export a UI policy.
gs.log('** Exporting UI Policy ' + sysid);
var exporter = new ExportWithRelatedLists('sys_ui_policy', sysid);
exporter.addRelatedList('sys_ui_policy_action', 'ui_policy');
exporter.exportRecords(g_response);
Unfortunately, everything I try results in every UI Policy Action being included in the resulting XML file. I’ll keep working on it and post what I figure out.
Thanks!
You’re probably not passing the sys_id in correctly from the UI action then. If you post your UI action code here I can take a look.
Here’s the UI Action I’m using:
function callExportUIPolicy() {
var url = new GlideURL(‘export_ui_policy.do’);
url.addParam(‘sysparm_sys_id’, gel(“sys_uniqueValue”).value);
var frame = top.gsft_main;
if (!frame){ frame = top; }
frame.location = url.getURL();
}
Question … fourth line of code in your processor above, should the last string be “sys_ui_policy” ?
That line of code is correct and the code you’ve given me looks okay. I just set this up in demo07 and it tested out fine. Take a look at the UI action and processor record there.
Will do. Thanks!
That worked perfectly. I was able to take this and produce the same functionality for exporting Form Sections with Section Elements:
var sysid = g_request.getParameter(‘sysparm_sys_id’);
gs.log(‘** Exporting Form Section ‘ + sysid);
var exporter = new ExportWithRelatedLists(‘sys_ui_section’, sysid);
exporter.addRelatedList(‘sys_ui_element’, ‘sys_ui_section.sys_id’);
exporter.exportRecords(g_response);
Cool. Thanks for sharing.
Mark,
I’m trying this out as is on both my instance and a demo instance and I’m noticing that the xml exported includes more items than those related to the specific catalog item. For example on demo16.service-now.com I exported the Install Software catalog item. In the xml I see:
It appears that it’s pulling in *all* questions and not just those defined for the variables of Install Software.
Is this behavior expected?
Thanks,
John
Good catch! It looks like there’s an oddity with the way that the tables are related in this specific case, which causes all of the question choices to be included. I’ve coded a workaround and included it in the Processor script above. Give that a try and let me know how it goes.
Mark,
It’s improved, but I’m still getting items unrelated to the request for these tables:
catalog_script_client
catalog_ui_policy_action
John
I didn’t notice issues with these when I tested on the the Install Software item on demo16. What item can I reproduce these new issues on? Does the item have a variable set?
Mark,
It appears to be when an item includes a variable set. On demo16, I added common_comments vs to Add SAN Storage, and now see the unrelated items in the exported xml. I also created a new catalog item: AAA Export Test that is basically empty expect for a (newly created) variable set and see the same issue.
John
ps. On demo16 I’d tried to personalize the Catalog Items list (and another list) and both times it showed columns from User table instead. I assume someone tweaked something on this demo instance to cause this behavior, but thought I’d point out anyway…
Okay John, sorry for the delay. I’ve been swamped with a bunch of other projects. I think I’ve got this resolved now though. Give the updated processor code a try and let me know how it goes.
I did notice when I was having the same issue, was that how I expected the processor to be scripted was actually wrong, in that the fourth line should not have the “sys_” in the second table name. When I removed the “sys_”, I stopped getting all the actions/options, etc, and only got the ones expected for the record in question.
exporter.addRelatedList(“sys_ui_policy_action”,”ui_policy”);
Mark,
Everything seems to be working well now. Thank you so much for taking the time to fix things.
I’ve learned quite a bit by reviewing your code and the exported XML.
John
Glad to hear it! Thanks for your patience and feedback. I learned a few new things along the way too :).
Hey Mark,
Thanks for digging in, figuring this stuff out, and then putting the cookies on the lower shelf for the rest of us. Keep up the good work!
Thanks,
Justin
Hey Mark,
Thanks for this! This will save me a lot of time.
I am currently working on a project of merging 2 separate instances into one. Have you developed something from transfer the actual data from requests (including variables and current workflow stages etc.)?
Thanks again,
Tony
Thanks Tony. Migrating legacy ticket data is a whole different ballgame unfortunately. As a general rule, I completely discourage it unless it absolutely has to be done. I don’t think that an in-flight migration of service requests is a realistic expectation due to the complexities of the associated workflows. I’d suggest working everything to completion in the legacy system or re-ordering it in the new system rather than attempting to move it over.
Mark,
Is there a way to get this to work from the maintain items list? We do not want to give our catalog developers access to update sets (for multiple reasons) and I wanted to see if this would be a good way for my admins to move multiple catalog items at once.
I don’t think these export options work unless you have the full ‘admin’ role unfortunately.
If you need to export multiple items, you can use this UI Action
Name Export Catalog Items
Active Yes
client Yes
List Choice Yes
onclick callExportCatalogItems();
Condition gs.hasRole(‘admin’)
Script:
selSysIds = gel('dbc95612d701210050f5edcb9e61032f').getAttribute('gsft_allow');
if (selSysIds == null || selSysIds == '')
selSysIds = g_list.getChecked();
var arrIDs = selSysIds.split(',');
if (arrIDs.length > 1)
{
var strMsg = 'You are going to export ' + arrIDs.length + ' Items\n';
strMsg += 'You will see a prompt for each item as it is exported, please click on OK.\n';
strMsg += 'Without this prompt it will only export one record and not all the records you have selected';
alert(strMsg);
}
for (var i=0;i<arrIDs.length;i++)
{
var url = new GlideURL('export_catalog_item.do');
url.addParam('sysparm_sys_id',arrIDs[i]);
alert('Preparing for item : ' + arrIDs[i] + '\nPlease wait until the record has downloaded before you click on OK');
var frame = top.gsft_main;
if (!frame)
frame = top;
frame.location = url.getURL();
}
}
Thanks!
And if you then want to make all these nice XML files into a single importable XML file, then you can use this hastily VBS routine to do the merger and it will create a combined file called “merged.xml”
The VBS needs to be in the same folder as the exported XML files
Const ForReading = 1
Const ForWriting = 2
Set objFSO = CreateObject(“Scripting.FileSystemObject”)
Set outFile = objFSO.CreateTextFile(“merged.xml”, ForWriting)
intCount = 0
For Each objFile In objFSO.GetFolder(“.”).Files
If (UCase(objFile.Name) “MERGED.XML” AND UCase(objFSO.GetExtensionName(objFile.Name)) = “XML”) Then
Set inFile = objFSO.OpenTextFile(objFile, ForReading)
Do Until inFile.AtEndOfStream
strLine = inFile.ReadLine
if (intCount < 2 OR (Left(strLine,5) “<?xml" AND strLine “” AND Left(strLine, 9) “<unload u")) Then
outFile.WriteLine strLine
intCount = intCount + 1
End if
Loop
End If
Next
outFile.WriteLine "”
outFile.Close
Is there a way to pipe this output so that it is a PDF format instead of XML? Thanks. (I’m trying to make a PDF export of all information related to a closed RITM) Thanks for any help.
No way that I’m aware of.
Mark,
Thank you for this amazing aid in transferring catalog items via xml. I am new to Service-Now so my depth of knowledge is growing but not anywhere close to where you are but I did want to ask a question in regards to the scripting. I am testing the processor and UI Action and it appears to work as advertised but I find that certain things do not transfer over and I am wondering if the solution is simply adding additional code to what you already have or am I missing something. My file does not transfer the icon, the picture, or the catalog that it is assigned to in the parent environment.
Any assistance would be greatly appreciated.
Marshall Day
Sorry for the delay in getting back to you on this. It’s been a pretty busy day and I had to make a couple of adjustments to this to fix some things. There isn’t a single, simple answer for how you add additional information because each table can be slightly different. If you’re just dealing with basic information in a related list you can just personalize the list control to get the list ID information necessary to add to the ‘addRelatedList’ section in the script.
I’ve fixed the catalog issue and added a couple of other tables that weren’t being included. Images unfortunately cannot be included because ServiceNow excludes those in XML exports like this. If you have any other tables that you’re noticing should be included please let me know and I’ll get the script here updated again.
Mark,
I reloaded the new code and everything works great…except that image part! j/k I was wondering if you had an opportunity to check out Julian Poyntz’s code for exporting multiple catalog items? I took a look and I did see some issues in the posted copy but wanted to know if you had an opinion on it.
Again, thank you for the great assist in your code from above. You have saved me a lot of time.
Marshall
I just checked Julian’s code out again but it doesn’t seem to be working. Julian, maybe if you’re listening you could check it out and let us know what the issue might be? Here’s the code I’m using. Seems to iterate through and pause at the alert correctly but only initiates a download of the last item.
selSysIds = gel('dbc95612d701210050f5edcb9e61032f').getAttribute('gsft_allow');
if (selSysIds == null || selSysIds == '') {
selSysIds = g_list.getChecked();
}
var arrIDs = selSysIds.split(',');
if (arrIDs.length > 1) {
var strMsg = 'You are going to export ' + arrIDs.length + ' Items\n';
strMsg += 'You will see a prompt for each item as it is exported, please click on OK.\n';
strMsg += 'Without this prompt it will only export one record and not all the records you have selected';
alert(strMsg);
}
for (var i=0; i < arrIDs.length; i++) {
var url = new GlideURL('export_catalog_item.do');
url.addParam('sysparm_sys_id',arrIDs[i]);
alert('Preparing for item : ' + arrIDs[i] + '\nPlease wait until the record has downloaded before you click on OK.');
var frame = top.gsft_main;
if (!frame)
frame = top;
frame.location = url.getURL();
}
}
I created a python based exporter based on this code which can export multiple records and even a whole catalog.
https://github.com/samrocketman/servicenow-servicecatalog-exporter
Hi Mark,
Really helpful article and thanks for sharing this.
Is there any way by which the active flag of the exported catalog item to be made false.
Thank you.
Dilip Kumar
Hi,
Once I have the xml hierarchy as string, can I import it in my instance via script?
E.g.:
var url = new GlideURL(‘upload.do’);
url.addParam(‘sysparm_referring_url’, ….
url.addParam(‘sysparm_target’, g_list.getTableName());
I would like to reuse ServiceNow API (in any list view to import xml file) – but to do via script..
Where my xml record hierarchy stored in a variavle as string …
Any idea as how to do this?
Thanks,
Avi
Hi Mark – I’ve visited your pages many times and have benefited from you code, especially this one. Just thought I’d say thanks!
Dennis