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