How to use WSProxy to work with Automations in server-side JavaScript

How to use WSProxy to work with Automations in server-side JavaScript

This article explains how to use WSProxy to work with Automations in server-side Javascript in Salesforce Marketing Cloud.

Table of contents

Introduction

The current interaction methods with Marketing Cloud Automations in REST API are mostly undocumented and it’s unclear if they are going to be deprecated in the future.

But there is still a number of actions we can perform with SOAP API, using the WSProxy object in server-side JavaScript.

This article explains how to use WSProxy to our advantage to work with Automations and the limits of using this method.

Why use WSProxy?

Compared to the server-side JavaScript Core functions, WSProxy is faster and provides a better support for the error handling.

It also uses JSON objects, instead of arrays when it comes to retrieving records from Data Extensions.

For more information, please have a look at this amazing article by Eliot Harper.

Before we start

In order to properly test the following snippets, please make sure to have an Automation named “My Automation” with several activities that has been running for at least a couple of cycles.

Basics

In order to create, update or delete an Automation, it’s crucial to set the ClientID to verify the rights of the user to perform such actions.

<script runat=server>
    Platform.Load("Core","1");

    var api = new Script.Util.WSProxy();
	
	api.setClientId({
        "ID": Platform.Function.AuthenticatedMemberID(),
        "UserID": Platform.Function.AuthenticatedEmployeeID()
    });
</script>  

Create an Automation

There are currently 2 types of Automations we are able to create: Scheduled (runs based on a schedule) and Triggered (initiated by a file drop).

Please note that in the snippet below I’m willingly dropping the tasks and activities definitions at this time.

<script runat=server>
    Platform.Load("Core","1");

    var api = new Script.Util.WSProxy();

    try {

        var result = createAutomation("My Automation", "Lorem ipsum", 'Scheduled');

        Write(Stringify(result));

    } catch(err) {
        Write(Stringify(err));
    }

    function createAutomation(name, description, type) {

        /*
        AutomationType: 'Scheduled' (Schedule) or 'Triggered' (File Drop)
        */

        api.setClientId({
            "ID": Platform.Function.AuthenticatedMemberID(),
            "UserID": Platform.Function.AuthenticatedEmployeeID()
        });
        
        var config = {
            Name: name,
            CustomerKey: Platform.Function.GUID(),
            Description: description,
            AutomationType: type,
            CategoryID: null
        };

        return api.createItem('Automation', config);

    }
</script>    

Update an Automation

Updating an Automation with WSProxy is a bit tricky, as it doesn’t work like any other object update. Usually, we need to provide an array of objects with Name/Value pairs, but in this case we can provide the object directly.

Note that this method is capable of breaking the appearance of the Automation in the Marketing Cloud UI, when one of the main parameters, such as Name, is not provided in the object.

<script runat=server>
    Platform.Load("Core","1");

    var api = new Script.Util.WSProxy();

    try {

        api.setClientId({
            "ID": Platform.Function.AuthenticatedMemberID(),
            "UserID": Platform.Function.AuthenticatedEmployeeID()
        });

        var options = {
            SaveOptions: [
                {
                    PropertyName: '*',
                    SaveAction: 'UpdateAdd'
                }
            ]
        };
        
        var config = {
            Name: "My Automation",
            CustomerKey: "xxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx",
            Description: "Updated"
        };

        var result = api.updateItem("Automation", config, options);

        Write(Stringify(result));

    } catch(err) {
        Write(Stringify(err));
    }
</script>    

Delete an Automation

Deleting an Automation only requires the CustomerKey.

<script runat=server>
    Platform.Load("Core","1");

    var api = new Script.Util.WSProxy();

    try {

        var del = deleteAutomation("My Automation");

        Write(Stringify(del));

    } catch(err) {
        Write(Stringify(err));
    }

    function deleteAutomation(name) {

        api.setClientId({
            "ID": Platform.Function.AuthenticatedMemberID(),
            "UserID": Platform.Function.AuthenticatedEmployeeID()
        });

        var request = api.retrieve("Automation", ["CustomerKey"], {
            Property: "Name",
            SimpleOperator: "equals",
            Value: name
        });

        var key = request.Results[0].CustomerKey;

        var result = api.deleteItem("Automation", { 
            CustomerKey: key
        });

        return result;

    }
</script>  

Retrieve an Automation

There are a number of fields we can retrieve from the Automation object, but don’t trust the official documentation about which fields are retrievable or not.

<script runat="server">
    Platform.Load("Core","1");

    var api = new Script.Util.WSProxy();

    try {

        var name = "My Automation";

        var cols = [
            "Name",
            "Description",
            "CustomerKey",
            "IsActive",
            "CreatedDate",
            "ModifiedDate",
            "Status",
            "ProgramID",
            "CategoryID",
            "LastRunTime",
            "ScheduledTime",
            "LastSaveDate",
            "ModifiedBy",
            "LastSavedBy",
            "CreatedBy",
            "AutomationType",
            "RecurrenceID"
        ];

        var request = api.retrieve("Automation", cols, {
            Property: "Name",
            SimpleOperator: "equals",
            Value: name
        });

        Write(Stringify(request));

    } catch(error) {
        Write(Stringify(error));
    }
</script>    

Retrieve all Automations

Retrieving all the Automations from a Business Unit is possible when filtering on all possible statuses.

<script runat="server">
    Platform.Load("Core","1");

    var api = new Script.Util.WSProxy();

    try {

        var automations = retrieveAllAutomations();

        Write(Stringify(automations));

    } catch(err) {
        Write(Stringify(err));
    }

    function retrieveAllAutomations() {

        var out = [],
            moreData = true,
            reqID = data = null;

        var cols = [
            "Name",
            "Description",
            "CustomerKey",
            "IsActive",
            "CreatedDate",
            "ModifiedDate",
            "Status",
            "ProgramID",
            "CategoryID",
            "LastRunTime",
            "ScheduledTime",
            "LastSaveDate",
            "ModifiedBy",
            "LastSavedBy",
            "CreatedBy",
            "AutomationType",
            "RecurrenceID"
        ];  

        var filter = {
            Property: 'Status',
            SimpleOperator: 'IN',
            Value: [-1, 0, 1, 2, 3, 4, 5, 6, 7, 8]
        };

        while (moreData) {

            moreData = false;

            if (reqID == null) {
                data = api.retrieve("Automation", cols, filter);
            } else {
                data = api.getNextBatch("Automation", reqID);
            }

            if (data != null) {
                moreData = data.HasMoreRows;
                reqID = data.RequestID;
                for (var i = 0; i < data.Results.length; i++) {
                    out.push(data.Results[i]);
                }
            }
        }

        return out;
    }
</script>    

Start or stop an Automation

We can start or stop an Automation at will when providing the ObjectID parameter and the action name: start or stop.

Please note, that due to some weird API configuration, retrieving the ObjectID is only possible when specifying the ProgramID field.

<script runat="server">
    Platform.Load("Core","1");

    var api = new Script.Util.WSProxy();

    try {

        var name = "My Automation";

        var request = api.retrieve("Automation", ["ProgramID"], {
            Property: "Name",
            SimpleOperator: "equals",
            Value: name
        });

        var objId = request.Results[0].ObjectID;

        var props = {
            ObjectID: objId
        };

        var action = ["start","stop"];

        var opts = {};

        var request = api.performItem("Automation", props, action[1], opts);

        Write(Stringify(request));

    } catch(err) {
        Write(Stringify(err));
    }
</script>    

Filter out the Journey Builder Automations

All automations created by the Journey Builder have an ISO formatted date included in their name. We can easily filter them out by using a regular expression.

function filterOutJourneyBuilderAutomations(arr) {

    var regex = /(?:(\d{1,4}\-\d{1,2}\-\d{1,2}T\d{1,6}\.\d{3}))/g;

    var new_arr = [];

    for(var k in arr) {

        var automation = arr[k];
        var name = automation.Name;
        var hasDate = name.match(regex);

        if (hasDate.length === 0) new_arr.push(automation);

    }

    return new_arr;
}

Automation status

Retrieve the status of the Automation run

This method is only applicable when wishing to check the general status of the Automation run. It will not provide any feedback about the errors which occurred during the run. It will only check if the Automation has run or not.

<script runat="server">
    Platform.Load("Core","1");

    var api = new Script.Util.WSProxy();

    try {

        var name = "My Automation";

        var request = api.retrieve("Automation", ["Status"], {
            Property: "Name",
            SimpleOperator: "equals",
            Value: name
        });

        var status = request.Results[0].Status;

        Write(Stringify(status));

    } catch(err) {
        Write(Stringify(err));
    }
</script>  

Retrieve the Automation status history and details

This method allows us to retrieve the history (or report) on all the runs of the Automation, including the errors.

<script runat="server">
    Platform.Load("Core","1");

    var api = new Script.Util.WSProxy();

    try {

        var name = "My Automation";

        var request = api.retrieve("Automation", ["CustomerKey"], {
            Property: "Name",
            SimpleOperator: "equals",
            Value: name
        });

        var customerKey = request.Results[0].CustomerKey;

        var history = retrieveAutomationHistory(customerKey);

        Write(Stringify(history));

    } catch(err) {
        Write(Stringify(err));
    }

    function retrieveAutomationHistory(key, limit) {

        var limit = limit || null;

        var cols = [
            "Status", 
            "Name", 
            "CustomerKey",
            "CompletedTime",
            "StartTime"
        ];

        var filter = {
            Property: "CustomerKey",
            SimpleOperator: "equals",
            Value: key
        };

        var records = [],
            moreData = true,
            reqID = data = null;
    
        while (moreData) {

            moreData = false;

            if (reqID == null) {
                data = api.retrieve("AutomationInstance", cols, filter);
            } else {
                data = api.getNextBatch("AutomationInstance", reqID);
            }

            if (data != null) {

                moreData = data.HasMoreRows;
                reqID = data.RequestID;
            
                for (var k in data.Results) {

                    var item = data.Results[k];

                    var o = {
                        Status: item.Status,
                        StatusMessage: item.StatusMessage,
                        StartTime: DateTime.SystemDateToLocalDate(item.StartTime),
                        CompletedTime: DateTime.SystemDateToLocalDate(item.CompletedTime)
                    }

                    records.push(o);
                }

            }
        }
    
        records.sort(function (a, b) { 
			return (new Date(a.CompletedTime) < new Date(b.CompletedTime)) ? 1 : -1 
		});
    
        var num = (limit != null && limit <= records.length) ? limit : records.length;
    
        return records.slice(0, num);
    
    }
</script>

Determine the Automation status name

The status of an Automation is returned as a number. Please use the function below to determine what the returned number actually means.

function retrieveAutomationStatusName(num) {

    switch(num) {
        case -1:
            status = 'Error';
        break;
        case 0:
            status = 'BuildingError';
        break;
        case 1:
            status = 'Building';
        break;
        case 2:
            status = 'Ready';
        break;
        case 3:
            status = 'Running';
        break;
        case 4:
            status = 'Paused';
        break;
        case 5:
            status = 'Stopped';
        break;
        case 6:
            status = 'Scheduled';
        break;
        case 7:
            status = 'Awaiting Trigger';
        break;
        case 8:
            status = 'Inactive Trigger';
        break;
    }
	
	return status;

}

Automation activities

Although we are not able to retrieve the configuration or the code from Automation activities, we can still check when and how they have run.

Retrieve Automation activities status

In order to retrieve the information about our Automation activities, we need to use the undocumented Activity object (shocker!).

<script runat="server">
    Platform.Load("Core","1");

    var api = new Script.Util.WSProxy();

    try {

        var name = "My Automation";

        var request = api.retrieve("Automation", ["ProgramID"], {
            Property: "Name",
            SimpleOperator: "equals",
            Value: name
        });

        var objId = request.Results[0].ObjectID;

        var cols = [
            "CustomerKey",
            "Name",
            "Description",
            "ObjectID",
            "IsActive",
            "CreatedDate",
            "ModifiedDate",
            "Definition",
            "Sequence"
        ];

        var filter = {
            Property: "Program.ObjectID",
            SimpleOperator: "equals",
            Value: objId
        };

        var activities = api.retrieve("Activity", cols, filter);

        Write(Stringify(activities));

    } catch(err) {
        Write(Stringify(err));
    }
</script>    

Retrieve the history of Automation activities

Each activity has a history for each of the Automation runs. We can retrieve them and sort them by the time of the last status update.

<script runat="server">
    Platform.Load("Core","1");

    var api = new Script.Util.WSProxy();

    try {

        var name = "My Automation";

        var request = api.retrieve("Automation", ["ProgramID"], {
            Property: "Name",
            SimpleOperator: "equals",
            Value: name
        });

        var objId = request.Results[0].ObjectID;

        var cols = [
            "CustomerKey",
            "Name",
            "Description",
            "ObjectID",
            "IsActive",
            "CreatedDate",
            "ModifiedDate",
            "Definition",
            "Sequence"
        ];

        var filter = {
            Property: "Program.ObjectID",
            SimpleOperator: "equals",
            Value: objId
        };

        var request = api.retrieve("Activity", cols, filter);

        var activities = request.Results;

        var result = [];

        for(var k in activities) {

            var item = activities[k];

            var o = {
                Name: item.Name,
                Sequence: item.Sequence,
                Statuses: retrieveActivityHistory(item.CustomerKey)
            }

            result.push(o);
        }

        result.sort(function (a, b) { 
            return (new Date(a.Statuses[0].StatusLastUpdate) > new Date(b.Statuses[0].StatusLastUpdate)) ? 1 : -1 
        });

        Write(Stringify(result));

    } catch(err) {
        Write(Stringify(err));
    }

    function retrieveActivityHistory(key, limit) {

        var limit = limit || null;

        var cols = [
            "Name",
            "CustomerKey",
            "StatusMessage",
            "Status",
            "CreatedDate",
            "ModifiedDate",
            "StatusLastUpdate"
        ];

        var filter = {
            Property: "CustomerKey",
            SimpleOperator: "equals",
            Value: key
        };

        var records = [],
            moreData = true,
            reqID = data = null;
    
        while (moreData) {

            moreData = false;

            if (reqID == null) {
                data = api.retrieve("ActivityInstance", cols, filter);
            } else {
                data = api.getNextBatch("ActivityInstance", reqID);
            }

            if (data != null) {

                moreData = data.HasMoreRows;
                reqID = data.RequestID;
            
                for (var k in data.Results) {

                    var item = data.Results[k];

                    var o = {
                        Status: item.Status,
                        StatusMessage: item.StatusMessage,
                        StatusLastUpdate: DateTime.SystemDateToLocalDate(item.StatusLastUpdate)
                    }

                    records.push(o);
                }

            }
        }
    
        records.sort(function (a, b) { 
			return (new Date(a.StatusLastUpdate) < new Date(b.StatusLastUpdate)) ? 1 : -1 
		});

        var num = (limit != null && limit <= records.length) ? limit : records.length;
    
        return records.slice(0, num);
    
    }
</script>    

Automation folders

Retrieve an Automation folder

The only difference between a Data Extension folder and an Automation folder is the ContentType property, which is equal to “automations“.

<script runat="server">
    Platform.Load("Core","1");

    var api = new Script.Util.WSProxy();

    try {

        var name = "My Automation";

        var request = api.retrieve("Automation", ["Name", "CategoryID"], {
            Property: "Name",
            SimpleOperator: "equals",
            Value: name
        });

        var catId = request.Results[0].CategoryID;

        var folderName = retrieveFolderName(catId);

        Write(Stringify(folderName));

    } catch(error) {
        Write(Stringify(error));
    }

    function retrieveFolderName(catId) {

        var filter = {
            LeftOperand: {
                Property: "ID",
                SimpleOperator: "equals",
                Value: catId
            },
            LogicalOperator: "AND",
            RightOperand: {
                Property: "ContentType", 
                SimpleOperator: "equals", 
                Value: "automations" 
            }
        }

        var request = api.retrieve("DataFolder", ["Name","ContentType"], filter);

        return request.Results[0].Name;

    }
</script>    

Retrieve all Automation folders

As stated previously, all the Automation folders have the same ContentType.

<script runat="server">
    Platform.Load("Core","1");

    var api = new Script.Util.WSProxy();

    try {

        var folderList = retrieveFoldersList();

        Write(Stringify(folderList));

    } catch(error) {
        Write(Stringify(error));
    }

    function retrieveFoldersList() {

        var request = api.retrieve("DataFolder", ["Name","ContentType"], {
            Property: "ContentType", 
            SimpleOperator: "equals", 
            Value: "automations" 
        });

        var list = [];

        var results = request.Results;

        for (var k in results) {
            list.push(results[k].Name);
        }

        list = list.join(", ");

        return request;

    }
</script>    

Retrieve an Automation folder path

Retrieving the folder path is a matter of looping through each folder retrieve and looking at the ParentFolder ID.

<script runat="server">
    Platform.Load("Core","1");

    var api = new Script.Util.WSProxy();

    try {

        var name = "My Automation";

        var request = api.retrieve("Automation", ["Name", "CategoryID"], {
            Property: "Name",
            SimpleOperator: "equals",
            Value: name
        });

        var catId = request.Results[0].CategoryID;

        var path = retrieveFolderPath(catId);

        Write(Stringify(path));

    } catch(error) {
        Write(Stringify(error));
    }

    function retrieveFolderPath(catid) {

        var list = [];
        var id = catid;

        while (id > 0) {

            var req = api.retrieve("DataFolder", ["Name", "ParentFolder.ID"], {
                Property: "ID",
                SimpleOperator: "equals",
                Value: id
            });

            list.unshift(req.Results[0].Name);
            id = req.Results[0].ParentFolder.ID;
        }

        return list.join(" / ");

    }
</script>    

Conclusion

Working with Automations is hard due to the poor documentation and a risk of the undocumented methods being deprecated in the future.

We can only hope that this situation will change in the next updates.

Have I missed anything?

Please poke me with a sharp comment below or use the contact form.

  1. In the function “retrieveAutomationStatusName” you missed the “return status” at the end.

  2. aha! Thanks for noticing Maciej, fixed it right away 😉

  3. is there any way to know what type of activity is any of the activities returned in (undocumented) Activity object?

    “Sequence” is the order in step but, in which step?

  4. Good questions, but I have no answers, as this is not documented 😉

Leave a Reply

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

Up Next:

Develop faster for Marketing Cloud using One Drive, Google Drive and Dropbox

Develop faster for Marketing Cloud using One Drive, Google Drive and Dropbox