1 864-485-9849
support@6thDayInnovations.com
6th Day Innovations

Dynamic Document Creator:
'.Zip' functionality


Due to ServiceNow's security of Scoped applications restricting the use of the "Packages" call into a global scope, we had to improvise.


We made a way to get it to work, however, you will have to create a script includes in the global scope.


  1. Create GLOBAL script includes. (see script includes information below)

  2. Copy the script name into property: 'x_6di_ddc.docCreatorZipScript'



Create a GLOBAL Script Includes


Application: global

Name: docCreatorZipScript

Client Callable: false

Accessible from: All application scopes

Active: true

Script:


  • Script for Application Version 1.2.31+ (click to expand/collapse)

    var docCreatorZipScript = Class.create();

    docCreatorZipScript.prototype = {

     initialize: function (record) {

      this.record = record || current;

      if (this.record) {

      this.table = this.record.getTableName();

      this.sys_id = this.record.getValue('sys_id');

      }

      this.showZipOptions = true; //true: show Zip options, false: do not show options.

     },




     /**

      * The ServiceNow name is allowed to contain certain special characters, however

      * they are not allowed in Windows file names.

      * When trying to zip a file, we need to sanitize the file name by removing the special characters.

      *

      * Given a string, returns a sanitized version of it

      * by replacing all occurrences of special characters

      * with underscores.

      * 

      * Also, If the filename already ends with .zip. will remove it to see if we

      * have any file name that ends with a "."

      * This will prevent file names that are like "filename..zip"

      * You can safely comment out the removing of the zip and replacing the . without issue.

      *

      * @param {string} string - string to be sanitized

      * @return {string} the sanitized string

      */

     sanitizeString: function (string) {


      string = string.replace(/[/\\?%*:|"<>]/g, '_');


      /**

      * We want to check to see if the "string" ends with "."

      * We dont want a filename with a "..zip"

      * If it does, we want to remove it.

      * So, if we have a string that ends with ".zip", we want to remove the ".zip"

      * So that we can check the string to see if it ends with "."

      */

      if (string.endsWith('.zip')) {

      string = string.substring(0, string.length - 4);

      }


      /**

      * remove any trailing "."

      * from: INC0009009 - Unable to access the shared folder.............................zip

      * from: INC0009009 - Unable to access the shared folder..zip

      * 

      * to:   INC0009009 - Unable to access the shared folder.zip

      */

      string = string.replace(/\.+$/, '');


      return string;


     },




     /**

      * This function is used to create a zip file name when creating a scheduled document.

      * The function takes the current record as an argument.

      * The function returns a string that represents the name of the zip file.

      * The name of the zip file is created by combining the record number,

      * the number of rows or files (depending on which one is greater than 0),

      * and the current date and time.

      * The function uses the sanitizeString method to ensure that the file name

      * does not contain any special characters that are not allowed in Windows file names.

      * 

      * @param {object} current - the current gliderecord of the newly created "Scheduled" record

      * @returns {string} the name of the zip file

      */

     scheduledZipName: function (current) {


      /**

      * FEEL FREE TO MODIFY THE ZIPPED FILE NAME IN THE SCRIPT BELOW.

      * Here you will create your file name for the zip file.

      * you have access to the current record of the newly created "Scheduled" record.

      * We have give you the example of a record number and the current date.

      * ex.

      * DOCSCH0001005-files_1-2024-11-29 08_32_16.zip

      *

      * Ensure that your file name is sanitized by calling the sanitizeString method.

      * This will ensure that banned characters in windows file names are removed.

      */

      //######################################

      var num = current.number;

      var fileCount = current.file_count;

      var rowCount = current.row_count;

      var zipName = num + '-';


      if (0 < rowCount) {

      zipName += "rows:" + rowCount;

      } else if (0 < fileCount) {

      zipName += "files:" + fileCount;

      }


      zipName += "-" + new GlideDateTime().getDisplayValue() + ".zip";

      zipName = this.sanitizeString(zipName);

      //######################################

      return zipName;

     },




     /**

      * Creates a zip file name when creating a document.

      * The function takes the current record as an argument.

      * The function returns a string that represents the name of the zip file.

      * When using the zip functionality with the Dynamic Document Creator Scheduled table,

      * use the scheduledZipName method to create the zip name.

      * If you want to change the name, we recommend you change that function/method.

      * If you want to zip attachments from a different table, you can change the field name here.

      * we just threw in the number field as an example.

      * So if you want to zip attachments on Incident INC00302841, you would pass in the current record.

      * the filename would be 'INC00302841.zip'

      * 

      * @param {object} current - the current record of the newly created "Scheduled" record

      * @returns {string} the name of the zip file

      */

     getZipName: function (current) {


      var table = current.getTableName();

      var zipName = '';


      /**

      * Specifically for the table: x_6di_ddc_scheduled.

      * You can change the name of the zip file by modifying the scheduledZipName method

      */

      if (table == 'x_6di_ddc_scheduled') {

       

      zipName = this.scheduledZipName(current);


      } else {


      /**

      * If you want to zip attachments from a different table, you can change the field name here.

      * we just threw in the number field as an example.

      * So if you want to zip attachments on Incident INC00302841, you would pass in the current record.

      * the filename would be 'INC00302841.zip'

      * 

      * If you do it on a custom table where you don't have a number field, you can use the sys_id field.

      * This is where we recommend you create your own zip name.

      */

      var field = 'number';

      if (current.isValidField(field)) {

      zipName = current.getValue(field);

      } else {

      zipName = current.getValue('sys_id');

      }

      }


      return zipName;

     },




     /**

      * Creates a zip file of attachments related to the current record.

      *

      * This function generates a zip file with attachments from the current record. It creates a file name

      * using the provided zip name or derives it from the record details, ensuring it is sanitized to remove

      * special characters not allowed in Windows file names. The function checks for duplicate file names,

      * excludes existing zip files from being added, and logs duplicate file names as errors. The resulting

      * zip file is written as an attachment to the current record.

      *

      * @param {object} current - The current record from which attachments will be zipped.

      * @param {string} zipName - The name of the zip file to be created. If not provided, it will be generated.

      * @return {string} Returns 'complete' if the operation is successful, or a string listing duplicate files

      *                  if duplicates are found.

      */

     createZip: function (current, zipName) {


      /** Check to see if a zipName was provided, if not, derive it from the record details */

      if (zipName == null || zipName == '') {

      zipName = this.getZipName(current);

      }


      /**

      * Ensure that your file name is sanitized by calling the sanitizeString method.

      * This will ensure that banned characters in windows file names are removed.

      */

      zipName = this.sanitizeString(zipName);


      /**

      * If the zip name does not end with .zip, add it

      */

      if (zipName.indexOf('.zip') == -1) {

      zipName += '.zip';

      }



      //Create an array to store the file names and duplicate file names

      var zipArray = [];

      var zipDuplicates = [];


      try {


      var GSA = new GlideSysAttachment();

      var outputStream = new global.Packages.java.io.ByteArrayOutputStream(); //outStream

      var zipOutputStream = new global.Packages.java.util.zip.ZipOutputStream(outputStream); //out


      var attachments = GSA.getAttachments(this.table, this.sys_id);

      while (attachments.next()) {


      var fileName = attachments.getValue("file_name");


      /** 

      * Check if the file name is already in the zipArray 

      * If it is, add it to the zipDuplicates array

      * If added to zipDuplicates, do not add it to the zip file and continue to the next attachment

      */

      if (zipArray.toString().contains(fileName)) {


      if (zipDuplicates.nil()) {

      zipDuplicates.push(fileName);

      } else {

      zipDuplicates.push("\n" + fileName);

      }


      continue;


      } else {


      /**

      * If the file name is not in the zipArray, add it to the zipArray

      * and add it to the zip file

      * However, we do not want to add other zip files to the zip

      */

      if (attachments.content_type == "application/zip") {

      continue;

      }


      /**

      * Add the file name to the zipArray so we can search for duplicates

      * .zip will not be created with duplicate file names.

      */

      zipArray.push(fileName);


      // Get the file stream from the attachment

      var fileStream = GSA.getBytes(attachments);


      // Add the attachment as an entry in the zip file

      zipOutputStream.putNextEntry(new global.Packages.java.util.zip.ZipEntry(this.sanitizeString(fileName)));

      zipOutputStream.write(fileStream, 0, fileStream.length);

      zipOutputStream.closeEntry();


      }


      }



      // Close the streams and the zip file

      zipOutputStream.close();

      outputStream.close();


      //write the zip the record passed into the function

      GSA.write(this.record, zipName, "application/zip", outputStream.toByteArray());


      /**

      * If the zipDuplicates array is not empty, we have duplicate file names

      * and we need to log them as errors

      * 

      * We add the zipDuplicates array to the description of the current record

      */

      if (zipDuplicates.length > 0) {


      var zipString = "\n\nDuplicate files found and Zip will be missing files:\nPlease ensure your naming convention is unique to include all files in the zip. " + zipDuplicates.toString();


      if (current.isValidField('work_notes')) {

      current.work_notes = zipString;

      current.update();

      } else if (current.isValidField('description')) {

      var desc = current.description;

      if (desc.nil()) {

      desc = "";

      }


      desc += zipString

      current.description = desc;

      current.update();

      }


      }


      return 'complete';


      } catch (ex) {

      gs.error("Error when zipping file: " + ex);

      }


     },




     type: 'docCreatorZipScript'

    };



ServiceNow Architect Knowledge (SNAK) Blog

By Steven Young January 14, 2025
What is the benefit of a Script Include, and how to use them!
By Steven Young January 12, 2025
Large code vs. Small code
January 12, 2025
Some common and not so common knowledge!
Show More
Share by: