1. Home
  2. Workflow & Automation
  3. Windows Services
  4. Performing Scheduled Tasks Using a Windows Service

Performing Scheduled Tasks Using a Windows Service

This article will describe how to process scheduled tasks using the trellispark generic windows service.

ProcessScheduledTasks

The first step is to retrieve the list of scheduled tasks.

ExecuteSQLInformation mySQL = new()
{
    SessionGUID = userData.SessionGUID,
    UserGUID = userData.UserGUID,
    CurrentInstanceGUID = serviceGUID,
    CommandName = "Get_ZSO_ScheduledTask"
};
MyDB.ExecuteTSQLDataSet(mySQL);

For each task, it attempts to process the task.

foreach (XElement scheduledTask in scheduledTasks.Elements("Table"))
{
    // Process the scheduled task.
    await ProcessScheduledTask(userData, scheduledTask);
}

ProcessScheduledTask

This method determines if the task is due to be run based on the task definition and previous results. If the task is due to be run, it calls the CallScheduledTask method.

First, it reads the details of the scheduled task.

Guid scheduledTaskGUID = scheduledTask.GetElementGUID("InstanceGUID");
InstanceInformation scheduledTaskData = new()
{
    SessionGUID = userData.SessionGUID,
    UserGUID = userData.UserGUID,
    InstanceGUID = scheduledTaskGUID,
    FormGeneration = false,
    RequiredVersion = 0
};
Instance myTask = new(MyDB);
await myTask.ReadInstance(scheduledTaskData, true);

Next the method parses the details of the task to use when determining if the task should be run. First it checks if the task is even enabled.

XElement scheduledTaskXML = XElement.Parse(scheduledTaskData.DataDB);
Guid queueGUID = scheduledTaskXML.GetElementGUID("QueueGUID");
XElement frequencyXML = scheduledTaskXML.GetXElement("Frequency", "ID");

if (scheduledTaskData.Status != "Disabled")
{

Next the service pulls the previous task results.

ExecuteSQLInformation mySQL = new()
{
    SessionGUID = userData.SessionGUID,
    UserGUID = userData.UserGUID,
    CurrentInstanceGUID = queueGUID,
    CommandName = "Get_ZSO_ScheduledTask_Results"
};
MyDB.ExecuteTSQLDataSet(mySQL);

Next the method calls the IsTaskRunnable method to determine if the task is within the assigned start and end date, or below the assigned number of occurrences. If it is runnable, it checks for any previous results. If there are previous results, runs the CheckTaskFrequency method to determine if the task is due to be run. If the task is due to be run, it runs the task. If there are no previous results, the method runs the task.

// If the task is within it's assigned running parameters...
if (IsTaskRunnable(frequencyXML, scheduledTaskResults, scheduledTaskGUID))
{
    // If there are previous results for this task...
    if (scheduledTaskResults.Elements("Table").Any())
    {
        // If the task is due to be run based on past results...
        if (CheckTaskFrequency(scheduledTaskGUID, frequencyXML, scheduledTaskResults))
        {
            // Run the task.
            await CallScheduledTask(scheduledTaskGUID, userData, queueGUID);
        }
    }
    // If there are no previous results for this task...
    else
    {
        // Run the task.
        await CallScheduledTask(scheduledTaskGUID, userData, queueGUID);
    }

IsTaskRunnable

This method determines if the task is within the given date range or number of occurrences for the given task.

First it Parses the given task data to get the frequency information.

XElement frequencyPatternXML = frequencyXML.GetXElement("Pattern", "ID");
XElement frequencyRangeXML = frequencyXML.GetXElement("Range", "ID");
string rangeType = frequencyXML.GetAttribute("Range", "Type", "No end date");
XElement rangeXML = frequencyXML.GetXElement("Range");
DateTime? rangeStart = rangeXML.GetElementDateTimeOrNull("StartDate");

Then based on the range type, it determines if we are in the current date range or below the required number of occurrences.

if (rangeStart <= DateTime.UtcNow)
{
    switch (rangeType)
    {
        case "End by date":
            // If the end date is in the future...
            DateTime? lastRun = frequencyRangeXML.GetElementDateTimeOrNull("Value");
            if (lastRun != null)
            {
                if (lastRun > DateTime.Now)
                {
                    return true;
                }
            }
            break;

        case "End after occurrences":
            // If the task has been run fewer times than it's assigned number...
            long occurrences = frequencyRangeXML.GetElementLong("Value", 1);
            if (occurrences > scheduledTaskResults.Elements("Table").Count())
            {
                return true;
            }
            break;

        case "No end date":
            return true;

        default:
            throw new Exception($"Invalid range type: {rangeType}");
    }
}

CheckTaskFrequency

This method checks the history of the scheduled task execution to determine if the task is due to be run.

First it parses the frequency data to determine if the task is due to run.

 // Parse the frequency data.
 XElement frequencyPatternXML = frequencyXML.GetXElement("Pattern", "ID");
 string patternType = frequencyXML.GetAttribute("Pattern", "Type", "Minutes");
 long frequencyValue = frequencyPatternXML.GetElementLong("Value");
 bool isTaskRunnable = true;

Then for each result, it determines if the task is due based on the execution time of that result and the frequency of the result.

foreach (XElement taskResult in scheduledTaskResults.Elements("Table"))
{
    // When is the task next due to execute based on the last successful execution?
    DateTime? lastExecutedOn = taskResult.GetElementDateTimeOrNull("ExecutedOn");
    if (lastExecutedOn != null)
    {
        DateTime nextDue = (DateTime)lastExecutedOn;
        switch (patternType)
        {
            case "Minutes":
                nextDue = nextDue.AddMinutes(frequencyValue);
                break;
            case "Hourly":
                nextDue = nextDue.AddHours(frequencyValue);
                break;
            case "Daily":
                nextDue = nextDue.AddDays(frequencyValue);
                break;
            case "Weekly":
                nextDue = nextDue.AddDays(frequencyValue * 7);
                break;
            case "Monthly":
                nextDue = nextDue.AddMonths((int)frequencyValue);
                break;
            case "Yearly":
                nextDue = nextDue.AddYears((int)frequencyValue);
                break;
            default:
                throw new Exception($"Invalid Pattern {patternType} Task GUID: {scheduledTaskGUID}");
        }

        // If NextDue is in the future, don't run the task.
        if (nextDue > DateTime.Now)
        {
            isTaskRunnable = false;
        }
    }
}

CallScheduledTask

This method calls the scheduled task and logs the result.

First it parses the task into an ExecuteTargetInformation object. Then it calls the ExecuteTarget class CallTarget method.

XElement AdditionalParameters = new("ID",
    new XElement("FieldName", "TargetInformation"));
ExecuteTarget executeTarget = new(MyDB);
ExecuteTargetInformation myTarget = new()
{
    CurrentInstanceGUID = CommandGUID,
    TargetInstanceGUID = CommandGUID,
    SessionGUID = userData.SessionGUID,
    UserGUID = userData.UserGUID,
    AdditionalParameters = AdditionalParameters.ToString()
};
// Call the Task.
await executeTarget.CallTarget(myTarget);

Then the method logs the result of the call to the ScheduledTaskResults table. If there was an error, it logs that to the event log as well.

// Log the task result.
await MyDB.ScheduledTaskResult(queueGUID, DateTime.Now, myTarget.ErrorMessage);

// If there was an error, log the error.
if (myTarget.ErrorMessage != "")
{
    await AppInsights.Log(1, "ServiceLogic.CallCommand - Failed to run task", $"Failed to run task. Schedule Task: {queueGUID} Error: {myTarget.ErrorMessage}", MyDB);
}

Configuration

To configure a trellispark message queue for processing, first open the Workspace application in the workspace and navigate to the Service tab. Then open the service to configure.

Service Definition

When you are looking at the page above, click the “Add new item” button in the Scheduled Tasks childlist. You will be presented with the following page.

Scheduled task overview

Provide the task with a human readable name in the Name textbox. Then provide the URL for the API that has the code for the task. The Command Name of the command is a reference to an API command. The Rest API Key is any key required to access the API which is recommended for security. If an authorized user is required for this task, select that user in the UserGUID dropdown. If an instance is referenced, include the instance GUID in the CurrentInstanceGUID textbox. If a second instance is referenced, include the instance GUID in the TargetUserGUID textbox. Use the Frequency control to set the frequency of the task. It may be rerun after any number of minutes, hours, days, weeks, months, or years. It may be run immediately, be set to begin after a set date, and it may end after a number of recurrences, a set date, or run indefinitely.

Updated on March 6, 2024

Was this article helpful?

Related Articles

Need Support?
Can’t find the answer you’re looking for? Don’t worry we’re here to help!
Contact Support

Leave a Comment