Download Attachments as a ZIP File

Home/Scripting, UI actions/Download Attachments as a ZIP File

Download Attachments as a ZIP File

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.

Step 1: Create a UI action on the task table

Name:Save Attachments as ZIP
Condition:

current.hasAttachments();

Script:

action.setRedirectURL('exportAttachmentsToZip.do?sysparm_sys_id=' + current.sys_id + '&sysparm_table=' + current.getTableName());

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.

Step 2: Create an exportAttachmentsToZip processor
Create a new processor. Processors are found under System Definition->Processors.
Name: exportAttachmentsToZip
Type: script
Path: exportAttachmentsToZip
Script:

var sysid = g_request.getParameter('sysparm_sys_id');
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.

This code could be slightly tweaked to send the ZIP file as a web service. Remember, though, that if you do this, you will need to base64 encode the binary data before sending it over.
By | 2017-01-17T11:05:28+00:00 April 20th, 2010|Categories: Scripting, UI actions|Tags: , , |30 Comments

About the Author:

Jacob is the co-founder of Crossfuze, a ServiceNow implementation product and services company. He is one of the foremost ServiceNow integration experts in the world and designed/wrote many of the plugins found in the ServiceNow product today. Prior to co-founding Crossfuze he worked at ServiceNow during its startup years to write the SSO solutions and 3rd-party integrations.

30 Comments

  1. ElPunto April 24, 2010 at 12:51 pm - Reply

    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.

  2. ElPunto April 25, 2010 at 9:40 pm - Reply

    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.

  3. ElPunto May 26, 2010 at 8:11 pm - Reply

    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

  4. Jacob Andersen May 27, 2010 at 2:51 am - Reply

    You’ll want to look at this post:

    https://www.servicenowguru.com/integration/sending

    Good luck!

  5. Raj April 24, 2013 at 12:55 am - Reply

    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.

  6. Kyle October 1, 2013 at 4:40 pm - Reply

    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!

    • Jacob Andersen October 1, 2013 at 5:08 pm - Reply

      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.

      • Jace October 4, 2016 at 12:19 pm - Reply

        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();

        • Mark Stanger October 4, 2016 at 2:37 pm - Reply

          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.

  7. Ash Bari November 22, 2013 at 5:25 pm - Reply

    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.

    • Mark Stanger November 22, 2013 at 5:31 pm - Reply

      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.

      • Ash November 26, 2013 at 4:04 pm - Reply

        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

        • Mark Stanger November 27, 2013 at 12:06 am - Reply

          I’m not aware of any solution currently that accomplishes that without the UI action issue.

        • Hans January 21, 2014 at 6:37 am - Reply

          Hi!

          I am having the exact same problem. were You able to get a solution from ServiceNow ?
          Thanx in advance!

          Regards,
          Hans

  8. Hans February 17, 2014 at 1:25 am - Reply

    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

    • Mark Stanger February 17, 2014 at 5:48 am - Reply

      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.

  9. Leandro February 20, 2014 at 7:41 am - Reply

    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

    • Jacob Andersen February 20, 2014 at 7:48 am - Reply

      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.

      • Leandro February 20, 2014 at 10:06 am - Reply

        Thank you Jacob! It worked fine!

  10. Daniel November 3, 2014 at 5:00 am - Reply

    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.

  11. Andreas June 4, 2015 at 1:05 pm - Reply

    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:

    function callExportAttachmentsToZip() {
                    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();
    }
    • Per July 15, 2015 at 1:11 am - Reply

      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

  12. Laurentiu Latu August 5, 2015 at 6:29 am - Reply

    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 ?

    • MadanKumar Nachukuru October 3, 2017 at 3:57 am - Reply

      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

  13. Laurentiu Latu August 5, 2015 at 6:54 am - Reply

    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 ?

  14. Nitin Shukl October 28, 2016 at 6:53 am - Reply

    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.

  15. Ben Collyer May 16, 2017 at 9:23 pm - Reply

    Regarding attachments >5mb look at my answer on this Community post: https://community.servicenow.com/message/1111625#1111625

  16. Deepak September 18, 2017 at 11:05 pm - Reply

    Hi

    The getBytes method fails for reading PDF content.

    Any alternate for downloading pdf content.

    Thanks
    Deepak

  17. Georgi October 3, 2017 at 1:06 am - Reply

    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!

  18. MadanKumar Nachukuru October 4, 2017 at 12:34 am - Reply

    “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.

Leave A Comment