I have a choice about today

The alarm goes off. Either, You put it on snooze, trying to grab some shut-eye for the last time or wakeup with the first alarm. I chose not to snooze the Alarm; here is my new post about Bulk upload in adobe campaign.

Why do we need Bulk upload utility in the first place? Most of us work in two type of projects either migration project or new development project for Adobe campaign. Either case we deal with lots of file resources, sometimes we have to move that from one instance to another or get new resources from our third party vendors. Does this scenario sound familiar? well, In Adobe campaign we do not have a facility to upload multiple files in one go as it’s console will permit you adding one resource at a time, which is time-consuming.

In addition, it proved to be impossible to transfer images from one instance of Campaign to another – xtk:fileRes records yes, in a package, but absolutely not the associated files. This led us to just accepting for a long time that we had no images in the Dev and QA environment, and so couldn’t properly test anything.

Most of the time our customer demands us to use a library of assets and they update that library in file system and we use assets from that Filesystem library in our instance(DEV, QA, and production alike), with this demand, comes the demand for bulk upload utility so that we can upload multiple assets in one go.

The Solution, Thus facility can be achieved with a custom web application in Adobe Campaign.

file bulk import:Adobe campaign

File bulk import web application workflow

Let’s understand each activity used in this workflow:

Use a query to select all the sources folders for file resources.

Query: template equal to ‘xtkFileRes’ and no this folder is a view

Select all where model is equal to xtkFileRes and it’s  not a view folder(read-only)

JavaScript activity will be used to select folders with write access only.

for each (folder in ctx.folderOptions.folder){
 if( !xtk.folder.HasFolderWriteRight(folder.@id)){
 delete ctx.folderOptions.folder[folder.childIndex()];
//reset the default
ctx.vars.importFolder = <importFolder>{ctx.folderOptions.folder[0].@name}</importFolder>;

Select File page activity will be used to prepare a form for bulk upload.

Javascript Activity will be used to unzip the uploaded file and create the resources and this will consider the os type.

// getContentType
// @param filename
// Determine the content type from the filename
function getContentType(filename) {
  var contentType = "Other";

  if (filename.indexOf(".jpg") > -1) {
    contentType = "image/jpeg";
  if (filename.indexOf(".png") > -1) {
    contentType = "image/png";
  if (filename.indexOf(".gif") > -1) {
    contentType = "image/gif";
  if (filename.indexOf(".html") > -1) {
    contentType = "text/html";
  if (filename.indexOf(".css") > -1) {
    contentType = "text/css";
  if (filename.indexOf(".txt") > -1) {
    contentType = "text/plain";
  return contentType;

function isLinux(){
  if(Application.instanceVarDir.charAt(0) == '/')
    return true;
    return false;

function getExtension(filename){
  return filename.substring(filename.lastIndexOf("."));

ctx.vars.result = "<table id='result'><tr><th><b>Filename</b></th>" +
                  "<th><b>Internal Name</b></th>" +
                  "<th><b>Preview URL</b></th></tr>";

// Get id of xtkFileRes folder

//grab out the ID of the selected folder...
var query = NLWS.xtkQueryDef.create(
  {queryDef: {schema: "xtk:folder", operation: "get", 
    select: {node: [{expr: "@id"}] }, 
    where: {condition: [{expr: "[@name]=" + NL.SQL.escape(ctx.vars.importFolder)}]}

var res = query.ExecuteQuery()
var folderId = res.$id;

// Upload the zip file
var xmlFileRes = serverForm.createFileEntity(ctx.vars.selectedFile);

// Unzip the files to a temp folder
var fileRes = xtk.fileRes.load(xmlFileRes.@id);
var filePath = Application.instanceUploadDir + fileRes.md5 + ".zip";
var file = new File(filePath);

//we'll put the files in a temporary folder under the upload dir
var tempPath = Application.instanceUploadDir + "temp";

var command;
if( isLinux()){
  //linux command
  var command = "unzip -o " + filePath + " -d " + tempPath;
  //windows command 
  var command = "7za e \"" + filePath + "\" -o\"" + tempPath + "\" -y -r";

var result = execCommand(command, true);
logInfo("result: " + result);

// Generate MD5 for each file
// Move files into the upload directory with MD5 name
// Create xtk:fileRes for each file and publish to frontal servers
var tempFolder = new File(tempPath + "/");
for each (var f in tempFolder.list("*")) {
  if( f.isDirectory || f.name == "Thumbs.db" || f.name == ".DS_Store"){
    //skip any directories that may have been unzipped....
  var originalName = f.name;
  var label = f.name;

  //generate md5 from file and move the file to upload folder..
  var buffer = new MemoryBuffer();
  var md5 = buffer.md5();
  var md5FileName = md5 + getExtension(label);  
  //this will be the final published file path, depends on whether MD5 is requested or not.
  var fileName = file.path + f.name;
  var displayNameForResults = f.name;
  if( ctx.vars.md5Hash == 1){  
    fileName = file.path + md5FileName;
  //logInfo("rename to " + Application.instanceUploadDir + md5FileName);
  //the copy stored in the upload directory must always use the MD5 name (otherwise it won't publish)
  f.renameTo(Application.instanceUploadDir + md5FileName);
  var contentType = getContentType(f.name);
  var resXML = 
    <fileRes fileName={fileName} originalName={originalName} label={label} 
      folder-id={folderId} contentType={contentType} loaded="0" 
      storageType="5" useMd5AsFilename={ctx.vars.md5Hash} md5={md5}/>;

  var res = xtk.fileRes.create(resXML);
  ctx.vars.result += "<tr><td>" + label + "</td>" + "<td>" + 
                     res.internalName + "</td>" + "<td><a href='" + 
                     getImageUrl(res.id) + "' target='_blank'>" + 
                     getImageUrl(res.id) + "</a></td></tr>";

ctx.vars.result += "</table>";

// Remove the zip file
    <fileRes xtkschema="xtk:fileRes" id={fileRes.id} _operation="delete" key="@id"/>);
  //do nothing, likely operator does not have delete writes on the folder... no big deal.


Now everything works but we have a limitation that this file will not be available in all frontal servers as mentioned by Vipul in Adobe forms.

to overcome this I dug into the code and found one out of the box method which will solve this.


Once you are able to save the resources you can use this function call which will publish this resource to all your frontal servers.

Any questions, drop in the comments below and I’ll endeavour to answer them as soon as I can – or connect with me over LinkedIn athttps://www.linkedin.com/in/campaigndiscovery/. Happy Uploading! 🙂

Amit Kumar

Results driven Adobe Certified Architect with extensive experience managing and implementing marketing Strategies to drive business growth. Enjoy optimizing the customer experience through the use of data, futuristic thinking + channel mixing – e.g., using creative combinations of traditional methods + shiny new toys like automation platforms.


Ashish Ghai · February 7, 2018 at 12:40 pm

You rock ! thank you so much 😉

Gunjan · February 8, 2018 at 8:25 am

Hi Amit. Great Article!!

Is there any option to parse .gz.gpg file flowing through an SFTP location. Our client is really worried about data security so will be sending .csv zipped with.gz and encrypted through .gpg
So the filename will be something like abc.csv.gz.gpg

Thank you in Advance!!

    marketingcloud · February 8, 2018 at 8:36 pm

    Thanks, Gunjan, yes it is possible. You can configure your environment to use gpg, for hosted environment raise a ticket with Adobe. Now you have to update the server side js to use this code to decrypt

    function decryptUploadedFile(fileObject) {

    var fullName = fileObject.fullName.toString();

    var outputFile = ‘decryptedFile.xxx’;

    var systemCommand = “gpg –output ” + outputFile + ” –decrypt ” + fullName;

    var result = execCommand(cmd, true);


    To handle multiple keys, you will need to import all of them into your gpg and the use one or another to encrypt by using the switch -r or –recipient when calling gpg.

      Gunjan · February 12, 2018 at 1:33 pm

      Thank you for replying Amit. I will try the solution.

Leave a Reply

Avatar placeholder

Your email address will not be published.