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.
In the function “retrieveAutomationStatusName” you missed the “return status” at the end.
aha! Thanks for noticing Maciej, fixed it right away 😉
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?
Good questions, but I have no answers, as this is not documented 😉
How to populate/update DE with output
from “Retrieve the Automation status history and details” script.
Please have a look at the following article: https://ampscript.xyz/how-tos/how-to-use-wsproxy-to-work-with-data-extensions-in-ssjs/
Hi,
Can we add the already created activities into the new automation with “Create an Automation”. If yes, could you help with script for adding the existing activities into the new automation.
Hi Ivan,
I want to create a new automation but with the activities that are existing i.e. already created ones. I have couple of SQL queries and SCRIPT Activity that are already created and I want to create new automation with these activities. Could you help me with the code for the same.
Many thanks!
Is it possible to copy an existing automation with any SSJS or WSProxy script? I’m trying to copy automation and remove certain steps from it and automate it to run once.
I think you can! But I personally never tried it.
Hello there. I’m not writing code for other people, I rather try to give them enough examples and use cases to figure it out on their own 😉
Hey Ivan, thank you! This blog has been super useful 🙂
When I tried “start” action on a paused (ready state) automation, it would activate the automation and also start running it immediately.
Is there a way to just activate the automation (from Ready to Scheduled state) without running it?
Thanks again 😊
That’s a good question! Haven’t tried it, but I would try to update the automation’s properties with IsActive = true or Status = 2, see if that works. Don’t forget that to update an automation you need to pass your user id 😉
Hi Ivan,
Is it possible to retrieve all automations and their associated activities?
Thanks!
Yes but separately, please check https://www.ssjsdocs.xyz/automation-studio/automations/retrieve/ and https://www.ssjsdocs.xyz/automation-studio/activities/scripts/retrieve/