GenericPollingScheduledTask

Runs the polling command repeatedly, completes a blocking manual task when polling is done.

javascript · Common Scripts

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.