Overview of the procedure:
- Create a MID server script include which saves the file
- Create a custom flow designer action which calls the mid server script include via the ECC queue.
- Create another custom flow designer action to parse the results of the previous script include.
- Create a subflow which puts the two actions together with a pause in between.
On the MID Server, ensure that the service is running with an account that has access to the target directory. If running as Local System account, it may not have permission to save.

MID Server Script Include
On the instance, add the MID Server Script Include from the Script Include module under MID Server

Here’s the code for the MID Server script include (Credit to this community post):
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
Before we create the custom action, we first have to create a script include which will base64 encode files greater than 5MB. Follow this support article https://support.servicenow.com/kb?id=kb_article_view&sysparm_article=KB1002076
The code from this article is here
var BigEncoder64 = Class.create();
BigEncoder64.prototype = {
initialize: function() {
},
GetBase64EncodedString: function(attachment_sys_id) {
var StringUtil = new GlideStringUtil();
var gsis = GlideSysAttachmentInputStream(attachment_sys_id);
var baos = new Packages.java.io.ByteArrayOutputStream();
gsis.writeTo(baos);
baos.close();
var base64Data = StringUtil.base64Encode(baos.toByteArray());
return base64Data;
},
type: 'BigEncoder64'
};
Now we’ll create a Flow Designer custom action to create the ECC Queue record which calls the SaveFile MID server script include. Note that when I tried to do this with a regular create record action I couldn’t get the ecc queue request to process. Here are the steps for the custom action
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\.
Script Step: Create an ecc_queue record
Output: The sys_id of the ecc_queue record
Here are the screenshots of the action
Input

Script step

(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);
//The above did not work in Vancouver. I had to replace with the below two lines. see https://support.servicenow.com/kb?id=kb_article_view&sysparm_article=KB1002076. You have to create a script include as per the article.
var enc = new BigEncoder64();
var encData = enc.GetBase64EncodedString(attGR.getUniqueValue());
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);
Note that you have to create a global script include called BigEncore64 for this to work. See this KB https://support.servicenow.com/kb?id=kb_article_view&sysparm_article=KB1002076
Here’s the output of the Script step

The action outputs the sys_id of the ECC Queue record:

Custom Action for Parsing the ECC Queue Response
This custom action parses the response to the ECC queue request made with the previous custom action.
Input: The payload (this is done by the subflow below)
Script step: Parse the payload
Output: Output the code and the message
Here are the screenshots of the action
Input

Script step

(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);
The output of the script step:

Output

Subflow for Putting It All Together
The subflow calls the request custom action, does a pause, and then calls the parse response custom action. The subflow can be called from anywhere else in the platform (flows, other subflows, UI actions, etc) which makes it very handy.
- 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!).
- Steps
- Call the action which creates the ECC Queue request
- 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).
- 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.
- Pass the payload field from the record in Step 4 to the Parse ECC Queue response action.
- Output the results.
Here are the screenshots
Input

Steps

Step 1

Step 2

Step 3

Step 4

Output

This article was originally written for the community site: