Advanced MID Server Series – Part 4 – File Management

Welcome to part 4 of my advanced MID Server series, this time we’re going to do things a little differently. At the bottom of this post you’ll find a fully prepared MID Server Script Include that will provide file management operations. The rest of this post will explain how to use the script, both directly from with a MID Server script, or via a JavascriptProbe call.

Class: File Manager

The FileManager class can be used to perform various operations on files within a MID Server. The class can be used to achieve movement of files between a ServiceNow instance and other locations such as network folder shares. The class also provides methods to extract zip archives, either directly from an attachment on the instance, or from a file stored on the MID Server.

Methods

downloadAttachmentToFolder(String attachmentSysId, String destinationFolderPath)
downloadAttachmentsToFolder(String attachmentQuery, String destinationFolderPath)
uploadFileToRecord(String srcFilePath, String targetTable, String targetSysID)
uploadFolderToRecord(String srcFolderPath, String targetTable, String targetSysID)
listFiles(String folderPath)
listFilesJSON(String folderPath)
rename(String currentName, String newName)
remove(String strPath)
createFolder(String folderCreationPath)
unzipAttachment(String attachmentId, String targetTable, String targetSysID)
unzipFile(String srcFilePath, String extractFilePath)

downloadAttachmentToFolder(String attachmentSysId, String destinationFolderPath)

Downloads an attachment with the supplied sys_id to a folder on the MID Server file system.

ArgumentDescription
attachmentSysIdSysID of an attachment record in the ServiceNow instance
destinationFolderPathFile location in the MID Servers file system. If the folder does not exist, it will be created. Can be an absolute path, or a relative path. Relative paths are created inside the MID Server’s “agent” folder.
MID Server Script Example
var fm = new FileManager();
var result = fm.downloadAttachmentToFolder('{{SYS_ID}}' 'C:\\MyFolder\\');
ms.log(result);

//Output: true
JavascriptProbe Example
var jsProbe = new JavascriptProbe('{{MID_SERVER_NAME}}');
jsProbe.setName('Download Attachment to MID Server');
jsProbe.addParameter('attachmentSysId', '{{SYS_ID}}');
jsProbe.addParameter('destinationFolderPath', 'C:\\MyFolder\\');
jsProbe.setJavascript('new FileManager().downloadAttachmentToFolder()');
jsProbe.addParameter('skip_sensor', 'true');
jsProbe.create();

downloadAttachmentsToFolder(String attachmentQuery, String destinationFolderPath)

Downloads all attachments matching an encoded query to a folder on the MID Server file system.

ArgumentDescription
attachmentQueryAn encoded query matching the attachments you want to download to the MID Server
destinationFolderPathFile location in the MID Servers file system. If the folder does not exist, it will be created. Can be an absolute path, or a relative path. Relative paths are created inside the MID Server’s “agent” folder.
MID Server Script Example
var fm = new FileManager();
var result = fm.downloadAttachmentsToFolder('table_sys_id={{SYS_ID}}' 'C:\\MyFolder\\');
ms.log(result);

//Output: true
JavascriptProbe Example
var jsProbe = new JavascriptProbe('{{MID_SERVER_NAME}}');
jsProbe.setName('Download Attachments to MID Server');
jsProbe.addParameter('attachmentQuery', 'table_sys_id={{SYS_ID}}');
jsProbe.addParameter('destinationFolderPath', 'C:\\MyFolder\\');
jsProbe.setJavascript('new FileManager().downloadAttachmentsToFolder()');
jsProbe.addParameter('skip_sensor', 'true');
jsProbe.create();

uploadFileToRecord(String srcFilePath, String targetTable, String targetSysID)

Uploads the selected file to a record in the instance.

ArgumentDescription
srcFilePathThe path to an existing file on the MID Server’s file system.
targetTableThe name of a table in the ServiceNow platform
targetSysIDThe SysID of a record residing on the provided targetTable.
MID Server Script Example
var fm = new FileManager();
var result = fm.uploadFileToRecord('C:\\MyFolder\\MyFile.txt' '{{TABLE}}', '{{SYS_ID}}');
ms.log(result);

//Output: true
JavascriptProbe Example
var jsProbe = new JavascriptProbe('{{MID_SERVER_NAME}}');
jsProbe.setName('Upload File to Instance');
jsProbe.addParameter('srcFilePath', 'C:\\MyFolder\\MyFile.txt');
jsProbe.addParameter('targetTable', '{{TABLE}}');
jsProbe.addParameter('targetSysID', '{{SYS_ID}}');
jsProbe.setJavascript('new FileManager().uploadFileToRecord()');
jsProbe.addParameter('skip_sensor', 'true');
jsProbe.create();

uploadFolderToRecord(String srcFolderPath, String targetTable, String targetSysID)

Uploads all files in the selected folder to the instance.

ArgumentDescription
srcFolderPathThe path to an existing folder on the MID Server’s file system.
targetTableThe name of a table in the ServiceNow platform
targetSysIDThe SysID of a record residing on the provided targetTable.
MID Server Script Example
var fm = new FileManager();
var result = fm.srcFolderPath('C:\\MyFolder\\' '{{TABLE}}', '{{SYS_ID}}');
ms.log(result);

//Output: true
JavascriptProbe Example
var jsProbe = new JavascriptProbe('{{MID_SERVER_NAME}}');
jsProbe.setName('Upload Folder to Instance');
jsProbe.addParameter('srcFolderPath', 'C:\\MyFolder\\');
jsProbe.addParameter('targetTable', '{{TABLE}}');
jsProbe.addParameter('targetSysID', '{{SYS_ID}}');
jsProbe.setJavascript('new FileManager().uploadFolderToRecord()');
jsProbe.addParameter('skip_sensor', 'true');
jsProbe.create();

listFiles(String folderPath)

Produces an object describing the contents of a directory on the MID Server’s file system and returns the result as an object.

ArgumentDescription
folderPathThe folder that you’d like to list the contents of.
MID Server Script Example
var fm = new FileManager();
var result = fm.listFiles('C:\\MyFolder\\');
ms.log(result);

/*Output: Object
{
  "success": true,
  "folderPath": "C:\\MyFolder",
  "content": [
    {
      "name": "MyFile.txt",
      "fullPath": "C:\\MyFolder\\MyFile.txt",
      "parent": "C:\\MyFolder",
      "sizeBytes": 0,
      "type": "FILE",
      "canRead": true,
      "canWrite": true,
      "lastModified": 1701784743954
    },
    {
      "name": "Subdirectory",
      "fullPath": "C:\\MyFolder\\Subdirectory",
      "parent": "C:\\MyFolder",
      "sizeBytes": null,
      "type": "DIRECTORY",
      "canRead": true,
      "canWrite": true,
      "lastModified": 1701784751296
    }
  ]
}
*/
JavascriptProbe Example

Should not be used via Javascript Probe, please see listFilesJSON instead.


listFilesJSON(String folderPath)

Produces an object describing the contents of a directory on the MID Server’s file system and returns the result as a JSON string.

ArgumentDescription
folderPathThe folder that you’d like to list the contents of.
MID Server Script Example
var fm = new FileManager();
var result = fm.listFiles('C:\\MyFolder\\');
ms.log(result);

/*Output: JSON String
{
  "success": true,
  "folderPath": "C:\\MyFolder",
  "content": [
    {
      "name": "MyFile.txt",
      "fullPath": "C:\\MyFolder\\MyFile.txt",
      "parent": "C:\\MyFolder",
      "sizeBytes": 0,
      "type": "FILE",
      "canRead": true,
      "canWrite": true,
      "lastModified": 1701784743954
    },
    {
      "name": "Subdirectory",
      "fullPath": "C:\\MyFolder\\Subdirectory",
      "parent": "C:\\MyFolder",
      "sizeBytes": null,
      "type": "DIRECTORY",
      "canRead": true,
      "canWrite": true,
      "lastModified": 1701784751296
    }
  ]
}
*/
JavascriptProbe Example
var jsProbe = new JavascriptProbe('{{MID_SERVER_NAME}}');
jsProbe.setName('List files in folder');
jsProbe.addParameter('folderPath', 'C:\\MyFolder\\');
jsProbe.setJavascript('new FileManager().listFiles()');
jsProbe.addParameter('skip_sensor', 'true');
jsProbe.create();

rename(String currentName, String newName)

Renames a file/folder on the MID Server’s file system. As well as renaming, this can also be seen as a way of “moving” files/folders.

ArgumentDescription
currentNameThe full path to the existing file/folder on the MID Server’s file system
newNamethe full path of the new file/folder name on the MID Server’s file system
MID Server Script Example
var fm = new FileManager();
var result = fm.rename('C:\\MyFolder\\', 'C:\\MyRenamedFolder\\');
ms.log(result);

//Output: true
JavascriptProbe Example
var jsProbe = new JavascriptProbe('{{MID_SERVER_NAME}}');
jsProbe.setName('Rename file/folder');
jsProbe.addParameter('currentName', 'C:\\MyFolder\\');
jsProbe.addParameter('newName', 'C:\\MyRenamedFolder\\');
jsProbe.setJavascript('new FileManager().rename()');
jsProbe.addParameter('skip_sensor', 'true');
jsProbe.create();

remove(String strPath)

Deletes the named file/folder.

ArgumentDescription
strPathThe full path of the file/folder that you want to delete.
MID Server Script Example
var fm = new FileManager();
var result = fm.remove('C:\\MyFolder\\');
ms.log(result);

//Output: true
JavascriptProbe Example
var jsProbe = new JavascriptProbe('{{MID_SERVER_NAME}}');
jsProbe.setName('Remove a file/folder');
jsProbe.addParameter('strPath', 'C:\\MyFolder\\');
jsProbe.setJavascript('new FileManager().remove()');
jsProbe.addParameter('skip_sensor', 'true');
jsProbe.create();

createFolder(String folderCreationPath)

Creates a folder (including the full folder tree if it doesn’t exist) on the MID Server’s file system.

ArgumentDescription
folderCreationPathThe full path of the folder that you want to create. All folders in the path will be created if they do not already exist.
MID Server Script Example
var fm = new FileManager();
var result = fm.createFolder('C:\\MyFolder\\');
ms.log(result);

//Output: true
JavascriptProbe Example
var jsProbe = new JavascriptProbe('{{MID_SERVER_NAME}}');
jsProbe.setName('Create a folder');
jsProbe.addParameter('folderCreationPath', 'C:\\MyFolder\\');
jsProbe.setJavascript('new FileManager().createFolder()');
jsProbe.addParameter('skip_sensor', 'true');
jsProbe.create();

unzipAttachment(String attachmentId, String targetTable, String targetSysID)

Unzips the contents of an attachment and uploads the files to the specified record.

ArgumentDescription
attachmentIdthe SysID of an attachment in the ServiceNow platform.
targetTableThe name of a table in the ServiceNow platform
targetSysIDThe SysID of a record residing on the provided targetTable.
MID Server Script Example
var fm = new FileManager();
var result = fm.unzipAttachment('{{ATTACHMENT_SYS_ID}}', '{{TABLE}}', '{{TABLE_SYS_ID}}');
ms.log(result);

//Output: true
JavascriptProbe Example
var jsProbe = new JavascriptProbe('{{MID_SERVER_NAME}}');
jsProbe.setName('Unzip an attachment');
jsProbe.addParameter('attachmentId', '{{ATTACHMENT_SYS_ID}}');
jsProbe.addParameter('targetTable', '{{TABLE}}');
jsProbe.addParameter('targetSysID', '{{TABLE_SYS_ID}}');
jsProbe.setJavascript('new FileManager().unzipAttachment()');
jsProbe.addParameter('skip_sensor', 'true');
jsProbe.create();

unzipFile(String srcFilePath, String extractFilePath)

Unzips the contents of a file to the specified folder on the MID Server’s file system.

ArgumentDescription
srcFilePathThe full path of a zip file on the MID Server’s file system
extractFilePaththe path of a folder to extract the contents of the ZIP file to.
MID Server Script Example
var fm = new FileManager();
var result = fm.unzipFile('C:\\MyArchive.zip', 'C:\\MyExtractedFiles\\');
ms.log(result);

//Output: true
JavascriptProbe Example
var jsProbe = new JavascriptProbe('{{MID_SERVER_NAME}}');
jsProbe.setName('Unzip a file');
jsProbe.addParameter('srcFilePath', 'C:\\MyArchive.zip');
jsProbe.addParameter('extractFilePath', 'C:\\MyExtractedFiles\\');
jsProbe.setJavascript('new FileManager().unzipFile()');
jsProbe.addParameter('skip_sensor', 'true');
jsProbe.create();

The Script Include

var FileManager = Class.create();
FileManager.prototype = {

    CONSTANTS: {
        SYS_ATTACHMENT: 'sys_attachment'
    },

    initialize: function() {
        this.GetMethod = Packages.org.apache.commons.httpclient.methods.GetMethod;
        this.PostMethod = Packages.org.apache.commons.httpclient.methods.PostMethod;
        this.FileRequestEntity = Packages.org.apache.commons.httpclient.methods.FileRequestEntity;
        this.HttpClient = Packages.org.apache.commons.httpclient.HttpClient;
        this.URIBuilder = Packages.org.apache.http.client.utils.URIBuilder;
        this.GlideStringUtil = Packages.com.glide.util.StringUtil;
        this.Files = Packages.java.nio.file.Files;
        this.File = Packages.java.io.File;
        this.FileInputStream = Packages.java.io.FileInputStream;
        this.FileOutputStream = Packages.java.io.FileOutputStream;
        this.ZipInputStream = Packages.java.util.zip.ZipInputStream;
        this.ReflectedArray = Packages.java.lang.reflect.Array;
        this.Byte = Packages.java.lang.Byte;
        this.arrayUtil = new ArrayUtil();
    },

    downloadAttachmentToFolder: function(attachmentSysId, destinationFolderPath) {
        try {
            attachmentSysId = attachmentSysId || probe.getParameter('attachmentSysId');
            destinationFolderPath = destinationFolderPath || probe.getParameter('destinationFolderPath');
            this._checkRequiredArguments(['attachmentSysId', 'destinationFolderPath'], [attachmentSysId, destinationFolderPath]);
            if (!this.downloadAttachmentsToFolder('sys_id=' + attachmentSysId, destinationFolderPath)) {
                return false;
            }
        } catch (err) {
            this._log(err.message);
            return false;
        }
        return true;
    },

    downloadAttachmentsToFolder: function(attachmentQuery, destinationFolderPath) {
        try {
            attachmentQuery = attachmentQuery || probe.getParameter('attachmentQuery');
            destinationFolderPath = destinationFolderPath || probe.getParameter('destinationFolderPath');
            this._checkRequiredArguments(['attachmentQuery', 'destinationFolderPath'], [attachmentQuery, destinationFolderPath]);
            var lastChar = destinationFolderPath.substr(-1);
            if (lastChar !== this.File.separator) {
                destinationFolderPath += this.File.separator;
            }

            if (!this.createFolder(destinationFolderPath)) {
                return false;
            }

            var grAttachment = new GlideRecord(this.CONSTANTS.SYS_ATTACHMENT);
            grAttachment.addEncodedQuery(attachmentQuery);
            grAttachment.query();

            var connInfo = this._getConnectionInfo();
            var httpClient = new this.HttpClient();
            while (grAttachment.next()) {
                var getMethod = new this.GetMethod(connInfo.baseUrl + 'api/now/attachment/' + grAttachment.getValue('sys_id') + '/file');
                getMethod.addRequestHeader('Authorization', connInfo.auth);
                httpClient.executeMethod(getMethod);
                var attachmentStream = getMethod.getResponseBodyAsStream();
                if (attachmentStream) {
                    var f = new this.File(destinationFolderPath + grAttachment.getValue('file_name'));
                    var out = new this.FileOutputStream(f);
                    var buf = this.ReflectedArray.newInstance(this.Byte.TYPE, 1024);
                    var len;
                    while ((len = attachmentStream.read(buf)) > 0) {
                        out.write(buf, 0, len);
                    }
                    out.close();
                    attachmentStream.close();
                }
                getMethod.releaseConnection();
            }
        } catch (err) {
            this._log(err);
            return false;
        }
        return true;
    },

    uploadFileToRecord: function(srcFilePath, targetTable, targetSysID) {
        try {
            srcFilePath = srcFilePath || probe.getParameter('srcFilePath');
            targetTable = targetTable || probe.getParameter('targetTable');
            targetSysID = targetSysID || probe.getParameter('targetSysID');

            this._checkRequiredArguments(['srcFilePath', 'targetTable', 'targetSysID'], [srcFilePath, targetTable, targetSysID]);

            var file = new this.File(srcFilePath);

            var connInfo = this._getConnectionInfo();
            var httpClient = new this.HttpClient();

            var mimeType = this.Files.probeContentType(file.toPath());

            var uri = new this.URIBuilder(connInfo.baseUrl + '/api/now/attachment/file');
            uri.addParameter('table_name', targetTable);
            uri.addParameter('table_sys_id', targetSysID);
            uri.addParameter('sysparm_payload_type', 'mid_attachment_upload');
            uri.addParameter('file_name', file.getName());

            var postMethod = new this.PostMethod(uri.toString());
            postMethod.addRequestHeader('Authorization', connInfo.auth);
            postMethod.addRequestHeader('Accept', '*/*');
            postMethod.addRequestHeader('Content-Type', mimeType);
            postMethod.setRequestEntity(this.FileRequestEntity(file, mimeType));

            httpClient.executeMethod(postMethod);
            var statusCode = postMethod.getStatusCode();

            if (statusCode != 200) {
                return false;
            }

        } catch (err) {
            this._log(err.message);
            return false;
        }
        return true;
    },

	uploadFolderToRecord: function(srcFolderPath, targetTable, targetSysID) {
		try {
            srcFolderPath = srcFolderPath || probe.getParameter('srcFolderPath');
            targetTable = targetTable || probe.getParameter('targetTable');
            targetSysID = targetSysID || probe.getParameter('targetSysID');

            this._checkRequiredArguments(['srcFolderPath', 'targetTable', 'targetSysID'], [srcFolderPath, targetTable, targetSysID]);
			var folder = this.listFiles(srcFolderPath);
			for(var contentIdx = 0; contentIdx < folder.content.length; contentIdx++) {
				var content = folder.content[contentIdx];
				if(content.type == 'DIRECTORY') {
					this.uploadFolderToRecord(content.fullPath, targetTable, targetSysID);
				} else {
					this.uploadFileToRecord(content.fullPath, targetTable, targetSysID);
				}
				
			}
		} catch (err) {
			this._log(err.message);
            return false;
		}

		return true;
	},

    listFiles: function(folderPath) {
        try {
            folderPath = folderPath || probe.getParameter('folderPath');
            var result = {
                success: false,
                folderPath: folderPath,
                content: []
            };
            this._checkRequiredArguments(['folderPath'], [folderPath]);

            var folder = new this.File(folderPath);

            if (!folder.isDirectory()) {
                throw new Error('Provided path "' + folderPath + '" is not valid.')
            }

            var arrStrFilesAndFolders = folder.list();

            var self = this;
            result.content = arrStrFilesAndFolders.map(function(fileFolderPath) {
                var thisFileFolder = new self.File(folder.getAbsolutePath() + self.File.separator + fileFolderPath);
                return {
                    name: thisFileFolder.getName(),
                    fullPath: thisFileFolder.getCanonicalPath(),
                    parent: thisFileFolder.getParent(),
                    sizeBytes: thisFileFolder.isFile() ? thisFileFolder.length() : null,
                    type: thisFileFolder.isDirectory() ? 'DIRECTORY' : 'FILE',
                    canRead: thisFileFolder.canRead(),
                    canWrite: thisFileFolder.canWrite(),
                    lastModified: thisFileFolder.lastModified()
                };
            });
            result.success = true;
        } catch (err) {
            this._log(err);
        }
        return result;
    },

    listFilesJSON: function(folderPath) {
        return JSON.stringify(this.listFiles(folderPath));
    },

    rename: function(currentName, newName) {
        try {
            currentName = currentName || probe.getParameter('currentName');
            newName = newName || probe.getParameter('newName');
            this._checkRequiredArguments(['currentName', 'newName'], [currentName, newName]);

            var file = new this.File(currentName);
            if (!file.exists()) {
                throw new Error('File/Folder with path "' + currentName + '" could not be found.');
            }
            var newNameFile = new this.File(newName);

            return file.renameTo(newNameFile);

        } catch (err) {
            this._log(err);
            return false;
        }
    },

    remove: function(strPath) {
        try {
            strPath = strPath || probe.getParameter('strPath');
            this._checkRequiredArguments(['strPath'], [strPath]);
            var file = new this.File(strPath);
            if (!file.exists()) {
                throw new Error('File/Folder with path "' + strPath + '" could not be found.');
            }
            if (file.isDirectory()) {
                return this._removeFolder(file);
            } else {
                return file['delete']();
            }
        } catch (err) {
            this._log(err);
            return false;
        }
    },

    createFolder: function(folderCreationPath) {
        try {
            folderCreationPath = folderCreationPath || probe.getParameter('folderCreationPath');
            this._checkRequiredArguments(['folderCreationPath'], [folderCreationPath]);
            var f = new this.File(folderCreationPath);
            if (!f.exists()) {
                if (!f.mkdirs()) {
                    throw new Error('Failed creating folder on MID server: ' + folderCreationPath);
                }
            }
        } catch (err) {
            this._log(err.message);
            return false;
        }
        return true;
    },

    unzipAttachment: function(attachmentId, targetTable, targetSysID) {
        try {
            attachmentId = attachmentId || probe.getParameter('attachmentId');
            targetTable = targetTable || probe.getParameter('targetTable');
            targetSysID = targetSysID || probe.getParameter('targetSysID');

            this._checkRequiredArguments(['attachmentId', 'targetTable', 'targetSysID'], [attachmentId, targetTable, targetSysID]);

            var tempFolderName = attachmentId + '-tempUnzip';
            if (!this.downloadAttachmentToFolder(attachmentId, tempFolderName)) {
                return false;
            }
            var folderContent = this.listFiles(tempFolderName);
            if (!folderContent.success) {
                return false;
            }

            var extractedContentFolder = tempFolderName + this.File.separator + 'extracted-content';
            this.createFolder(extractedContentFolder);

            this.unzipFile(folderContent.content[0].fullPath, extractedContentFolder);
			this.uploadFolderToRecord(extractedContentFolder, targetTable, targetSysID);
            this.remove(tempFolderName);
        } catch (err) {
            this._log(err.message);
            return false;
        }
        return true;
    },

    unzipFile: function(srcFilePath, extractFilePath) {
        try {
            srcFilePath = srcFilePath || probe.getParameter('srcFilePath');
            extractFilePath = extractFilePath || probe.getParameter('extractFilePath');

            this._checkRequiredArguments(['srcFilePath', 'extractFilePath'], [srcFilePath, extractFilePath]);

            var lastChar = extractFilePath.substr(-1);
            if (lastChar !== this.File.separator) {
                extractFilePath += this.File.separator;
            }

            var file = new this.File(srcFilePath);
            var fis = new this.FileInputStream(file);

            var zis = new this.ZipInputStream(fis);

            var zipEntry = zis.getNextEntry();
            while (zipEntry != null) {
                var extractedFile = new this.File(extractFilePath + zipEntry.getName());
                if (zipEntry.isDirectory()) {
                    extractedFile.mkdirs();
                } else {
                    new this.File(extractedFile.getParent()).mkdirs();

                    var buffer = this.ReflectedArray.newInstance(this.Byte.TYPE, 1024);
                    var fos = new this.FileOutputStream(extractedFile);
                    var len;
                    while ((len = zis.read(buffer)) > 0) {
                        fos.write(buffer, 0, len);
                    }
                    fos.flush();
                    fos.close();
                }
                zipEntry = zis.getNextEntry();
            }
        } catch (err) {
            this._log(err.message);
            return false;
        }
        return true;
    },

    /**** PRIVATE METHODS BELOW THIS POINT ****/
    _removeFolder: function(folder) {
        var files = folder.listFiles();
        if (files != null) {
            for (var fileIdx = 0; fileIdx < files.length; fileIdx++) {
                var file = files[fileIdx]
                if (file.isDirectory()) {
                    this._removeFolder(file);
                } else {
                    file['delete']();
                }
            }
        }
        return folder['delete']();
    },

    _getConnectionInfo: function() {
        return {
            baseUrl: ms.getConfigParameter('url'),
            auth: 'Basic ' + this.GlideStringUtil.base64Encode(ms.getConfigParameter('mid.instance.username') + ':' + ms.getConfigParameter('mid.instance.password'))
        };
    },

    _checkRequiredArguments: function(arrParamNames, arrParams) {
        var missingParams = arrParamNames.filter(function(paramName, idx) {
            return JSUtil.nil(arrParams[0]);
        });

        if (missingParams.length > 0) {
            throw new Error('Method called with missing required arguments: ' + missingParams.join(', '));
        }
    },

    _log: function(msg) {
        ms.log('**** FileManager: ' + msg)
    },

    type: 'FileManager'
};
Share this with your network

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Related Posts

Begin typing your search term above and press enter to search. Press ESC to cancel.

Back To Top