Michael Culhan1
Kilo Sage

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).find_real_file.png
  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

find_real_file.png

find_real_file.png

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

find_real_file.pngfind_real_file.png

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

find_real_file.png

find_real_file.png

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

find_real_file.png

find_real_file.png

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.

find_real_file.png

find_real_file.png

find_real_file.png

Comments
BhupeshG
Tera Guru

I have a requirement where I have the output of function to write into a file and just transfer to mid server export directory.

 

Can you please guide how to do the first part. Which take the text output from a sub-flow output and put into a text file and attach as an attachment to a record.

 

Or 

Write that output directly into a new file on midserver (attachment to a record is not compulsory) if we can write directly.

Michael Culhan1
Kilo Sage

Note that I was having trouble getting getContentBase64 to work on a Vancouver instance.  I replaced it with an encoder suggested by this support article 

 

these are the lines that did not work in Vancouver

    // var sa = new GlideSysAttachment();
    // var encData = sa.getContentBase64(attGR);
   
these are the replacement lines
    var enc = new BigEncoder64();
    var encData = enc.GetBase64EncodedString(attGR.getUniqueValue());
Version history
Last update:
‎09-18-2022 06:31 PM
Updated by: