GenericPollingScheduledTask
Runs the polling command repeatedly, completes a blocking manual task when polling is done.
- Type
- javascript
- Pack
- CommonScripts
Source
/**
* GenericPollingScheduledTask
* This task is ment to be scheduled by the ScheduleGenericPolling automation
* Logic:
* This task schedules itself to run 'interval' minutes from the current run, and decrease the 'timeout' accordingly.
* This happens until the end condition is met: either the 'timeout' reaches 0, or all IDs are finished running.
* Once the end condition is met, this task will complete the manual task with the given playbookID and tag.
*
* The 'dt' parameter, when applied to the context, should retrieve a list of ids which have not finished running.
* Example:
* dt = "Joe.Analysis(val.Status != 'finished').ID"
* Breakdown:
* Joe - integration name
* Analysis - the object that contains the ID and the status
* Status - can be 'submitted', 'running' or 'finished'
* ID - the key that contains the id for polling
*/
// Constant to verify the minimum build number and XSIAM version for the new polling command (stopScheduleEntry feature).
//const MINIMUM_XSIAM_VERSION = '8.3.0';
//const MINIMUM_BUILD_NUMBER_XSIAM = 313276;
const MINIMUM_XSOAR_VERSION = '8.2.0';
const MINIMUM_BUILD_NUMBER_XSOAR = 309463;
const SANITIZED_ARG_NAMES = ['additionalPollingCommandArgValues', 'additionalPollingCommandArgNames', 'pollingCommandArgName', 'pollingCommand']
function listOfStrings(v) {
if (!Array.isArray(v)) {
v = [v];
}
for (var i = 0; i < v.length; i++) {
v[i] = v[i].toString();
}
return v;
}
// https://stackoverflow.com/questions/16227197/compute-intersection-of-two-arrays-in-javascript#16227294
function intersect(a, b) {
var t;
if (b.length > a.length) t = b, b = a, a = t; // indexOf to loop over shorter
return a.filter(function (e) {
return b.indexOf(e) > -1;
}).filter(function (e, i, c) { // extra step to remove duplicates
return c.indexOf(e) === i;
});
}
function finish(playbookId, tag, err, entryGUID) {
var params = { 'id': tag };
if (err === undefined) {
params.input = 'YES';
} else {
params.input = 'NO';
}
if (playbookId) {
params.parentPlaybookID = playbookId;
}
logDebug('[GenericPollingScheduledTask] finish() completing task. input=' + params.input +
' tag=' + tag + ' entryGUID=' + entryGUID +
(err === undefined ? '' : ' err=' + JSON.stringify('' + err)));
if ((entryGUID !== undefined) && (entryGUID)) {
var res = executeCommand("stopScheduleEntry", {'scheduledEntryGuid': entryGUID});
if (isError(res[0])) {
logError('Failed to stop scheduled entry: ' + res[0]);
}
}
return executeCommand("taskComplete", params);
}
function flatten_cmd_args(cmd_args = {}) {
var ret_value = '';
for(var current_key in cmd_args){
ret_value += current_key + " " + cmd_args[current_key] + " ";
}
return ret_value.trim();
}
//replace all occurences of textToReplace with replaceWith string
String.prototype.replaceAll = function(textToReplace, replaceWith) {
return this.split(textToReplace).join(replaceWith);
};
function checkCommandSanitized(cmd = '', cmd_args = {}) {
var cmd_lower = cmd.toLowerCase() + ' ' + flatten_cmd_args(cmd_args)
for (var i = 0; i < SANITIZED_ARG_NAMES.length; i++) {
var current_arg_name_lower = SANITIZED_ARG_NAMES[i].toLowerCase();
var regex = new RegExp(current_arg_name_lower, "g");
if ((cmd_lower.match(regex) || []).length > 1) {
throw new Error('Error, The value of ' + SANITIZED_ARG_NAMES[i] + ' is malformed.');
}
cmd_lower = cmd_lower.replaceAll(current_arg_name_lower, '')
}
}
function setNextRun(ids, playbookId, pollingCommand, pollingCommandArgName, pendingIds, interval, timeout, tag, additionalArgNames, additionalArgValues, extractMode) {
var idsStr = ids.replace(/"/g, '\\"');
var playbookIdStr = '';
if (playbookId !== undefined) {
playbookIdStr = ' playbookId="' + playbookId + '"';
}
var cmd = '!GenericPollingScheduledTask pollingCommand="' + pollingCommand + '" pollingCommandArgName="' + pollingCommandArgName + '"' + playbookIdStr;
cmd += ' ids="' + idsStr + '" pendingIds="' + pendingIds.replace(/"/g,'\\"') + '" interval="' + interval + '" timeout="' + (parseInt(timeout) - parseInt(interval)) + '" tag="' + tag + '"';
cmd += ' additionalPollingCommandArgNames="' + additionalArgNames.replace(/"/g,'\\"') + '" additionalPollingCommandArgValues="' + additionalArgValues.replace(/"/g,'\\"') + '"';
if (extractMode !== undefined) {
cmd += ' extractMode="' + extractMode + '" auto-extract="' + extractMode + '"';
}
checkCommandSanitized(cmd)
return executeCommand("ScheduleCommand", {
'command': cmd,
'cron': '*/' + interval + ' * * * *',
'times': 1
});
}
function shouldRunWithGuid() {
res = getDemistoVersion();
platform = res.platform;
version = res.version;
buildNumber = res.buildNumber;
// conditions to add when the feature is supported in XSIAM:
// ((platform === "x2") && (compareVersions(version, MINIMUM_XSIAM_VERSION) >= 0) && (parseInt(buildNumber) >= MINIMUM_BUILD_NUMBER_XSIAM))
// Checking if the stopScheduleEntry command is available.
// If not, we are running on an older version of platform and we need to use the old polling mechanism.
// The try/catch mechanism is to support development and to ignore parseInt errors.
try {
var platformMatch = (platform === "xsoar");
var versionMatch = (compareVersions(version, MINIMUM_XSOAR_VERSION) >= 0);
var buildMatch = (parseInt(buildNumber) >= MINIMUM_BUILD_NUMBER_XSOAR);
if (platformMatch && versionMatch && buildMatch) {
logDebug('[GenericPollingScheduledTask] Using GUID flow. platform=' + platform +
' version=' + version + ' buildNumber=' + buildNumber);
return true;
}
}
catch (err) {
logDebug('[GenericPollingScheduledTask] shouldRunWithGuid error, using OLD flow: ' + err);
return false;
}
logDebug('[GenericPollingScheduledTask] Using OLD flow. platform=' + platform +
' version=' + version + ' buildNumber=' + buildNumber);
}
function genericPollingScheduled(){
try {
shouldRunWithGuid = shouldRunWithGuid();
if (shouldRunWithGuid) {
var endTime = stringToDate(args.endTime, "%Y-%m-%d %H:%M:%S");
var currentTime = new Date();
if (currentTime >= endTime) {
logDebug('[GenericPollingScheduledTask] GUID flow: endTime reached, finishing (YES).');
return finish(args.playbookId, args.tag, undefined, args.scheduledEntryGuid);
}
}
else {
if (args.timeout <= 0) {
logDebug('[GenericPollingScheduledTask] OLD flow: timeout reached, finishing (YES).');
return finish(args.playbookId, args.tag, undefined, args.scheduledEntryGuid);
}
}
// Get ids that have not finished yet
var ids = argToList(args.ids);
for (var i = 0; i < ids.length; i++) {
ids[i] = ids[i].replace(/[\\]*"/g, '');
}
// Set the context of the scheduled task to the local playbook context
var idsToPoll = ids;
var pendingPath = args.pendingIds;
if ('playbookId' in args) {
playbookContext = 'subplaybook-' + args.playbookId;
pendingPath = playbookContext + "." + args.pendingIds;
}
var pendings = dq(invContext, pendingPath);
// Compatibility fix: on some platform versions dq() wraps the result in a
// {"result": <value>} envelope instead of returning the bare value/list.
// Unwrap it so the pending-vs-ids intersection behaves consistently across platforms.
if (pendings && (typeof pendings === 'object') && !Array.isArray(pendings) && ('result' in pendings)) {
logDebug('[GenericPollingScheduledTask] dq() returned a {"result":...} envelope - unwrapping.');
pendings = pendings.result;
}
logDebug('[GenericPollingScheduledTask] pendingPath=' + pendingPath +
' | pendings=' + JSON.stringify(pendings) + ' | ids=' + JSON.stringify(ids));
if (pendings === null) {
logDebug('[GenericPollingScheduledTask] No pending ids found in context, finishing (YES).');
return finish(args.playbookId, args.tag, undefined, args.scheduledEntryGuid);
}
var idsStrArr = listOfStrings(ids);
var pendingsStrArr = listOfStrings(pendings);
idsToPoll = intersect(idsStrArr, pendingsStrArr);
if (idsToPoll.length === 0) {
logDebug('[GenericPollingScheduledTask] All ids finished (none still pending), finishing (YES).');
return finish(args.playbookId, args.tag, undefined, args.scheduledEntryGuid);
}
logDebug('[GenericPollingScheduledTask] Still polling ' + idsToPoll.length + ' id(s).');
// Run the polling command for each id
var pollingCommandArgs = {};
var names = argToList(args.additionalPollingCommandArgNames);
var values = argToList(args.additionalPollingCommandArgValues);
for (var index = 0; index < names.length; index++)
pollingCommandArgs[names[index]] = values[index];
pollingCommandArgs[args.pollingCommandArgName] = idsToPoll.join(',');
checkCommandSanitized(args.pollingCommand, pollingCommandArgs);
var res = executeCommand(args.pollingCommand, pollingCommandArgs);
// Change the context output of the polling results to the local playbook context
if ('playbookId' in args) {
for (var i = 0; i < res.length; i++) {
if ('EntryContext' in res[i]) {
for (var k in res[i].EntryContext) {
res[i].EntryContext[playbookContext + "." + k] = res[i].EntryContext[k];
delete res[i].EntryContext[k];
}
}
}
}
if (!shouldRunWithGuid) {
// Schedule the next iteration, old version.
var scheduleTaskRes = setNextRun(args.ids, args.playbookId, args.pollingCommand, args.pollingCommandArgName, args.pendingIds, args.interval, args.timeout, args.tag, args.additionalPollingCommandArgNames, args.additionalPollingCommandArgValues, args.extractMode);
if (isError(scheduleTaskRes[0])) {
logError('[GenericPollingScheduledTask] OLD flow: setNextRun FAILED - polling will NOT continue: ' + scheduleTaskRes[0]);
res.push(scheduleTaskRes);
}
}
return res;
}
catch (err) {
finish(args.playbookId, args.tag, err, args.scheduledEntryGuid);
throw err;
}
}
function main() {
return genericPollingScheduled();
}
return main();
README
Runs the polling command repeatedly, completes a blocking manual task when polling is done.
Script Data
| Name | Description |
|---|---|
| Script Type | javascript |
| Cortex XSOAR Version | 5.0.0 |
Inputs
| Argument Name | Description |
|---|---|
| ids | List of IDs to poll |
| pendingIds | IDs with pending status |
| pollingCommand | Name of the polling command to run |
| pollingCommandArgName | Name of the argument of the polling command |
| interval | Polling frequency - how often the polling command should run (minutes) |
| timeout | How much time to poll before declaring a timeout and resuming the playbook (minutes) |
| playbookId | The ID of the playbook that contains the manual task which will be completed once the polling is done. |
| tag | The tag of the blocking manual task (“Wait For Polling Task To Finish”) |
| additionalPollingCommandArgNames | Names of additional arguments for the polling command (e.g. arg1,arg2,…) |
| additionalPollingCommandArgValues | Commas separated arguments values of the polling command |
| scheduledEntryGuid | The GUID of the scheduled entry that runs the polling command. |
| endTime | The time to end the polling. |
Outputs
There are no outputs for this script.