Iam really enjoying being here at Knowledge 10 and meeting so many of you who read our posts. I have been busy in one-on-one sessions hearing requests and helping come up with answers. One of our readers wanted to know how they would go about downloading all of the attachments on a record as a zip file. Another reader wanted to know how to send attachments as a zip file via a web service. I believe this post should help solve both questions.
The challenge we will solve in this post is this: if a record has any attachments, we will add a button to the form that allows you to download all of the attachments as a single ZIP file.
Name:Save Attachments as ZIP
Condition:
Script:
The only real secret to the above code is the setRedirectURL. This allows us to call a processor in our system that will popup a download dialog to download the zip file. The exportAttachmentsToZip processor does not yet exist in the system and will need to be created. Processors are found under System Definition->Processors.
Create a new processor. Processors are found under System Definition->Processors.
Name: exportAttachmentsToZip
Type: script
Path: exportAttachmentsToZip
Script:
var table = g_request.getParameter('sysparm_table');
var theRecord = new GlideRecord(table);
theRecord.addQuery('sys_id', sysid);
theRecord.query();
theRecord.next();
var zipName = 'attachments.zip';
var StringUtil = GlideStringUtil;
var gr = new GlideRecord('sys_attachment');
gr.addQuery('table_sys_id', theRecord.sys_id);
gr.addQuery('table_name', theRecord.getTableName());
gr.query();
if (gr.hasNext()){
g_response.setHeader('Pragma', 'public');
g_response.addHeader('Cache-Control', 'max-age=0');
g_response.setContentType('application/octet-stream');
g_response.addHeader('Content-Disposition', 'attachment;filename=' + zipName);
var out = new Packages.java.util.zip.ZipOutputStream(g_response.getOutputStream());
var count=0;
while (gr.next()){
var sa = new GlideSysAttachment();
var binData = sa.getBytes(gr);
var file = gr.file_name;
addBytesToZip(out, zipName, file, binData);
count ++;
}
// Complete the ZIP file
out.close();
}
function addBytesToZip (out, dir, file, stream){
// Add ZIP entry to output stream.
out.putNextEntry(new Packages.java.util.zip.ZipEntry(file));
out.write(stream, 0, stream.length);
out.closeEntry();
}
That’s all there is to it. The result will appear like the following screen shot.
Jacob, thanks for the article.
I tried the code but there seems to be aproblem with the zip format. It’s not recognized by pkzip and other zip tools. One of the differences is that it has set the general purpose bit 3 (0x0008)(crc,compressed/uncompressed zero in header) and pkzip uses bit 1 (0x0002). Compression method is both 8 (0x0008) but that should not be a problem.
Forget the previous remark,
It works OK. Because I used the Java Script Editor plugin it messed up the layout.
The final close was missing and that caused an incomplete dictionary entry. I will now try and integrate it in my Webservice.
Jacob,
Currently I use a Business Rule to create the SOAP request. Is it possible to call the processor to retrieve the base64encoded ZIp information and add it to the body ?.
My BR code looks like this:
env = new SOAPEnvelope();
env.createNameSpace(“xmlns:ns1”, gns);
var hdr = env.createHeaderElement(‘ns1:AuthenticationInfo’, null, ‘mustUnderstand’, ‘0’);
hdr.setAttribute(‘actor’, ‘http://www.w3.org/2001/XMLSchema-instance’);
hdr.createElement(env, ‘ns1:userName’, gs.getProperty(‘com.snc.interface.tibco.user_name’));
hdr.createElement(env, ‘ns1:password’, gs.getProperty(‘com.snc.interface.tibco.user_password’));
gs.log( ‘Created Header’ );
// Fill static Tibco fields
var bdy = env.createBodyElement(‘ns1:’+ gs.getProperty(‘com.snc.interface.tibco.soap_operation’));
gs.log( ‘Soap Operation: ‘ + gs.getProperty(‘com.snc.interface.tibco.soap_operation’));
env.createElement(bdy, ‘ns1:messagetype’ , gaction);
env.createElement(bdy, ‘ns1:messagecreated’ , cvtDateTime(gs.daysAgo(0)));
env.createElement(bdy, ‘ns1:application’ , gmessagetype);
env.createElement(bdy, ‘ns1:supplier’ , gsupplier);
env.createElement(bdy, ‘ns1:supplier_id’ , gsupplierid);
env.createElement(bdy, ‘ns1:attachment_filename_1’, ‘attachment.zip’);
env.createElement(bdy, ‘ns1:attachment_content_1’ , gattachcontent);
..etc
Can I use the Processor to fill the variable gattachcontent with the base64encoded query ?
What’s the most simple way to base64encode the produced Zip file ?
Best regards
You’ll want to look at this post:
https://servicenowguru.wpengine.com/integration/sending…
Good luck!
Hi,
i am facing one issue, when i first click the “Save Attachments as ZIP” button it’s working fine but if i perform the same action again it’s not working . i have to reload the page to save the attachments again.
Hi all,
Firstly great solution 🙂
Secondly – with Calgary phasing out the use of Package calls, are updates to the Processor script required? For example:
var StringUtil = Packages.com.glide.util.StringUtil;
will be:
var StringUtil = GlideStringUtil;
Any thoughts on what:
– var out = new Packages.java.util.zip.ZipOutputStream(g_response.getOutputStream());
– out.putNextEntry(new Packages.java.util.zip.ZipEntry(file));
should be updated to? (if they need to be updated)
Cheers!
Kyle,
Yes, the scripts will need to be updated to use the new api function (ie GlideStringUtil). I believe that Packages.java.* may be OK to use, but that may not be the case further on in the future as I’ve heard conflicting reports about that. However, for Calgary, that Packages call should still work.
I am trying to get this to work in Helsinkie but It seems that is where this is now failing;
var sysattachment = “new Packages.com.glide.ui.SysAttachment();”;
var zip = “new Packages.java.util.zip.ZipOutputStream();”;
function getNewMethod(script){
var gcsf = GlideCustomerScriptFixer(script);
gcsf.processScript();
gcsf.convertedScript.toString();
return gcsf.convertedScript;
}
gs.print(‘Attachment: ‘ + getNewMethod(sysattachment));
gs.print(‘zip: ‘ + getNewMethod(zip));
//10:14:40.274: Attachment: new GlideSysAttachment();
//10:14:40.275: zip: new Packages.java.util.zip.ZipOutputStream();
Take a close look at your UI action code and make sure it matches what I’ve got above. There is an ampersand that was encoded incorrectly that may be causing your issue.
Hi
We recently used this for our Finance team who required an easy way to mass download files to a shared directory they use. It works great but like one of the comments above, it can only be used once before having to reload the form. The other issue and the one causing a big problem, is the other UI actions on the form becoming unrepsonsive after this UI action is used. As a workaround we reolad the form, but instead of navigating away we choose stay on page and the ui actions work again.
Is anyone aware of a way to stop the UI actions becoming unresponsive? Our instance is running Calgary.
I think you may have to contact ServiceNow about the UI actions issue. My guess is that it’s something similar to what happens when you try to export an update set to XML. You can reproduce this in any ServiceNow demo instance by completing an update set and then clicking the ‘Export to XML’ UI action link. After the export is complete none of the UI actions work unless you reload the form.
Hi Mark,
Thanks for the advice. This has been raised with support but its the usual case of managing my expectations around non standard SNC code and relating to a Problem ticket so possibly no solution to this any time soon.
The actual requirement from our Finance team was to be able to download all attachments or alternatlively be able to select the attachments in a ticket and download these in a single click. Are you aware or can you advise on a solution that would potentially deliver a similar result, and avoid the issues I’ve experienced above?
Many thanks
I’m not aware of any solution currently that accomplishes that without the UI action issue.
Hi!
I am having the exact same problem. were You able to get a solution from ServiceNow ?
Thanx in advance!
Regards,
Hans
Hi!
I found out, that when using “window.location” instead of “action.setRedirectURL” within the UI Action script, the described problems with the UI Actions becoming unresponsive dont appear.
regards,
Hans
While ‘window.location’ might work around the hanging issue, it also breaks other capability since it is a client-side concept and the rest of the UI action is running server-side code to manipulate and update the record before exporting it.
Hi all!
I’m trying to use this processor in Dublin release but I’m getting the following error on log (even when I’m logged in as admin): “Security restricted: Attempted access to restricted class name com.glide.ui.SysAttachment”. And the resultant zip file is empty.
Does anyone know if there is something to change on this realease to allow this script to run?
Thanks a lot!
Leandro
Leando,
Try changing the line
var sa = new Packages.com.glide.ui.SysAttachment();
to
var sa = new GlideSysAttachment();
The api used in this example has been replaced with GlideSysAttachment.
Thank you Jacob! It worked fine!
Just FYI, the code will not work in Eureka as Eureka will not allow the out.write-call. This Package would need to be whitelisted first :/
If you happen to run on an upgraded instance it might work.
I looked at the code for exporting Update Sets as XML and mimicked the same for this.
Here is the solution for the UI Action:
Name: Save Attachments as ZIP
Table: task
Action Name: export_attachments_to_zip (we had some funny behavior if that one is empty or matches OnClick)
Show insert: un-checked
Show update: checked
Client: checked
Form context menu: checked (works also as a link or button)
Hint: Download all Attachments as one ZIP file
OnClick: callExportAttachmentsToZip();
Condition: current.hasAttachments();
Script:
var url = new GlideURL('exportAttachmentsToZip.do');
url.addParam('sysparm_sys_id', g_form.getUniqueValue()); //gel("sys_uniqueValue").value
url.addParam('sysparm_table', g_form.getTableName());
var frame = top.gsft_main;
if (!frame)
frame = top;
frame.location = url.getURL();
}
Andreas, that is great! I just tried it in both Eureka and Fuji, and it works just fine. Another related question, is there a way to show which tickets that has attachments when viewing them in a list?
//Per
Code works, but sometimes I get the attachment.zip containing the actual attachment file empty. 0 bytes.
Debugging this, at the line var binData = sa.getBytes(gr), binData.length = 0, even if the download of the file directly from the interface works. How can I find what is wrong ?
Did you got solution for this
Code works, but sometimes I get the attachment.zip containing the actual attachment file empty. 0 bytes.
Debugging this, at the line var binData = sa.getBytes(gr), binData.length = 0, even if the download of the file directly from the interface works. How can I find what is wrong ?
am also facing same issue
Update: This is not happening depending on the file. If a file can be downloaded in the archive, it will always work. If a file couldn’t be downloaded, it never will. So it has to be something with the file ?
Jacob,
We are facing issues with more than 5 MB filesize. It works fine for filesize less than 5 mb.
If one of the attachment is more than 5 mb, then it creates the zip but the file in zip will be 0 kb. But other files with less than 5 mb will appear just fine.
Regarding attachments >5mb look at my answer on this Community post: https://community.servicenow.com/message/1111625#1111625
Hi
The getBytes method fails for reading PDF content.
Any alternate for downloading pdf content.
Thanks
Deepak
Hello All,
I am using Export Sets on my DEV instance (Jakarta) in order to export table records as csv files to a specific folder of my MID Server, installed on Windows Server. I am currently struggling to find out if it is possible to export the attached to these records jpg or pdf files. As I know, Export Sets do not export attachments to records. Thus, I am looking for an additional mechanism which to combine with the Export Sets.
Reading this thread, I may say that it sounds really useful! Great Job for posting it!
Here now, I wonder – does anyone of you have any thoughts how the mechanism shared here can be modified to run automatically and cover my scenario? Is there an existing solution at all via which I can export the attached jpg & pdf files to the table records which I am exporting from SNOW as csv files via Export Sets?
I will highly appreciate if you may give me a hand! Thank you!
“Save all attachments” button does not download all the attachments
1. If you select the “Save All Attachments” ui action and unzip the folder, only 2 out of the 6 *.nmf files have downloaded. Four of them have not as they are 0KB. I have done this over 5 times, closing and opening my Chrome browser and it is reproducible each time.
2. However, if you select to download them all one by one – its successful.