logo

NJP

Subflow and actions for saving file to on-premise network using MID server

Import · Sep 18, 2022 · article

I recently had a requirement to generate a text file in SN, attach it to a record and then save it to an on-premise network drive. While generating the text file was not a problem, I wasn't sure about saving it to a local network drive. Fortunately, others have already trodden this path before me, so there was some good information on the community site, notably this question. This article is about how I put the logic from that question into a subflow such that it can be easily called from elsewhere.

First, a look at the main parts:

  • A MID server script include which saves the file
  • A custom flow designer action which calls the mid server script include via the ECC queue.
  • Another custom flow designer action to parse the results of the previous script include.
  • A subflow which puts the two actions together with a pause in between.

MID Server Script Include

This was my first encounter with this exotic animal. I lifted the code directly from the above mentioned question. Two important points of note:

  1. The user that your MID Server service is running under has to have permissions to write to your target destination. You can see which user the service is running under by going to Services and clicking on the ServiceNow MID Server service (or whatever your service happens to be named).image
  2. I read somewhere that this will not work for saving files over 5mb to network drives. The MID Server script include uses the Java FileOutputStream which will timeout when writing large files over the network. If you need to save large files it's best to first save them locally to the MID server and then copy them with a powershell script.

Here's the MID Server Script Include (again, credit to this question)

var SaveFile = Class.create();
SaveFile.prototype = {
    initialize: function () {
        /* Set up the Packages references */
        this.File = Packages.java.io.File;
        this.FileOutputStream = Packages.java.io.FileOutputStream;
        this.HttpClient = Packages.org.apache.commons.httpclient.HttpClient;
        this.UsernamePasswordCredentials = Packages.org.apache.commons.httpclient.UsernamePasswordCredentials;
        this.AuthScope = Packages.org.apache.commons.httpclient.auth.AuthScope;
        this.GetMethod = Packages.org.apache.commons.httpclient.methods.GetMethod;

        /* Set up the parameters */
        this.verbose = probe.getParameter("verbose");
        this.filepath = probe.getParameter("filepath");
        this.filename = probe.getParameter("filename");
        this.encodedData = probe.getParameter('encodedData');
        this.debug("verbose -- filepath -- filename : "+this.verbose + ' -- ' + this.filepath + ' -- ' + this.filename);
    },

    /* Function to create new file and write data in target path*/
    saveToFile: function(targetPath)
    {
        this.debug("Initiating file save function");
        var f = new this.File(targetPath); // create new file
        var inputStream = this.encodedData;
        var fout = new this.FileOutputStream(f);
        this.StringUtil = Packages.com.glide.util.StringUtil;
        var data = this.StringUtil.base64DecodeAsBytes(inputStream); // convert base64 to original format
        fout.write(data); // write data to newly created file
        fout.close();
        inputStream.close();
        result = "File successfully created : "+this.filepath+this.filename;
        this.debug(result);
    },

    /* Function to debug in mid-server log*/
    debug: function (m)
    {
        if (this.verbose == "true") {
            ms.log("Attachment: " + m);
        }
    },

    /* Execute the Probe*/
    execute: function()
    {
        var saveRes = this.saveToFile(this.filepath+this.filename);
        return result;
    },
    type : "SaveFile"
};

Custom Action for Creating an ECC Queue Request

Next we need to create a record in the ECC Queue table that requests that the MID Server run the above MID server script include. I created the below custom flow designer action to do this (note that when I tried to do this with a regular create record action I couldn't get the ecc queue request to process).

  1. Input the sys_id of the attachment to be copied and the path to copy it to. All paths should have a trailing \, for example C:\temp\.
  2. Create an ecc_queue record
  3. Output the sys_id of the ecc_queue record

image

image

(function execute(inputs, outputs) {
  //from https://community.servicenow.com/community?id=community_question&sys_id=0679b3b0dbfeeb00f21f5583ca961916
  //sends base64 encoded attachment to MID server and triggers the SaveFile MID server script include to write the attachment to a network (or local) location
  var attGR = new GlideRecord('sys_attachment');
  attGR.addQuery('sys_id', inputs.attachment_sysid);
  attGR.query();
  if (attGR.next())
  {
    var sa = new GlideSysAttachment();
    var encData = sa.getContentBase64(attGR);
    var jspr = new global.JavascriptProbe('[MID SERVER NAME]');
    jspr.setName('Flow Action: Save File to on-prem network'); // This can be any name 
    jspr.setJavascript("var ddr = new SaveFile(); res= ddr.execute();");
    jspr.addParameter("verbose","true");
    jspr.addParameter("filepath", inputs.path); // eg, D://ServiceNow//JiraAttachment// This is optional, if not added, file will be created at rool i.e midserver's agent folder
    jspr.addParameter("filename",attGR.file_name);
    jspr.addParameter("encodedData",encData);
    var eccSysId = jspr.create(); 
    outputs.ecc_sys_id = eccSysId;
  }
})(inputs, outputs);

imageimage

Custom Action for Parsing the ECC Queue Response

We need another custom action to parse the response to the ECC queue request made with the previous custom action.

  1. Input the payload (this is done by the subflow below)
  2. Parse the payload
  3. Output the code and the message

image

image

(function execute(inputs, outputs) {

 var xmlDoc = new XMLDocument2();
xmlDoc.parseXML(inputs.payload);

//get the value of the result_code attribute
var node = xmlDoc.getNode("//results");
var nodeStr = node.toString()
var start = nodeStr.indexOf('result_code=') + 13
outputs.code = nodeStr.substring(start,start+1);

//get the output 
outputs.outmessage = xmlDoc.getNodeText("//output");


})(inputs, outputs);

image

image

Subflow for Putting It All Together

The subflow puts the request and response actions together with a pause in between. The subflow can be called from anywhere else in the platform (flows, other subflows, UI actions, etc) which makes it very handy.

  1. Input the sys_id of the attachment to be saved and the path to which to save it (don't forget the \ at the end!).
  2. Call the action which creates the ECC Queue request
  3. Wait for 1 minute (for my purposes this was more than enough, but you might want to make it longer if your files are bigger. I didn't do any tests to see how long bigger files might take).
  4. Use a look up record action on the ecc_queue table where the Response to is the sys_id of the record created in Step 2.
  5. Pass the payload field from the record in Step 4 to the Parse ECC Queue response action.
  6. Output the results.

image

image

image

View original source

https://www.servicenow.com/community/now-platform-articles/subflow-and-actions-for-saving-file-to-on-premise-network-using/ta-p/2322773