IT Management Suite

 View Only

Altiris Agent for ULM - Working with Client Scheduler 

Sep 19, 2011 12:26 PM

Working with Client Scheduler

Information about how to work with client scheduler.

 

Understanding Client Scheduler

 
Client Scheduler is a plug-in for the Altiris Agent that is responsible for notifying other Agent plug-ins according to certain set of rules (schedules). Most commonly it is used for triggering certain actions (for example, collecting hardware inventory) at specific time. However, Client Scheduler supports other events and restrictions which makes it extremely flexible.

Client Scheduler vs Task Scheduler

 
Altiris Agent (and Agent SDK as well) actually comes with two task scheduling mechanisms. The other one is Task Scheduler, however, it is considered to be deprecated now and all solutions are advised to use the Client Scheduler instead.
 
The key problems with the legacy Task Scheduler mechanism are:
  • it is capable only of triggering tasks, but not able to keep track of activity periods (see the next section)
  • the trigger types support is limited
  • coordination by Server timezone is not available
  • support for modifiers is not available
  • implementation has some flaws which are impossible to fix without complete rewrite
 
Even though the Client Scheduler was designed to be capable of everything that the legacy Task Scheduler mechanism was able to do, there are still some important differences between the two implementations:
  • the schedule XMLs are completely different and incompatible
  • unlike Task Scheduler the new Client Scheduler does not persist tasks on the file-system (see below for exceptions)

Trigger Tasks vs Activity Periods

 
The Client Scheduler mechanism operates with two types of schedules: trigger tasks and activity periods.
 
Trigger tasks are used for notifying plug-ins that something needs to be done. For example, if you want something (e.g. collecting hardware inventory) to happen regularly, you would use the trigger task schedules.
 
Activity periods are used for keeping information whether some special period is currently active or not. For example, Maintenance Windows use this feature to keep track of when a particular window is open or closed.
 
Trigger tasks typically do not have any duration associated with them. However, duration may be used for repetition purposes. The key feature is that at the trigger time the Agent plug-in that owns the schedule will be notified through a special callback interface - Interfaces::IAeXClientSchedulerTaskHandler.
 
Activity periods do not use any callback interface. Instead, the owner of the schedule should subscribe to the appropriate NTracker notifications to learn that a schedule has been (de)activated. The owner can also periodically query the Client Scheduler to find whether a specific schedule is current active or not.

Persistance

 
Client Scheduler does not persist any registered schedules on the file system. This means that when the Agent is restarted information about available schedules is lost. For this reason plug-ins are responsible to re-register their schedules on Agent start-up.
 
There are a couple of things that Client Scheduler does persist though:
  • date and time when the schedule was run (triggered) last time
  • date and time when the schedule was discovered (registered for the first time)
 
This information is needed for making schedules work correctly and not re-trigger in case of Agent restart.
 
Note that if you explicitly unregister a schedule, the persisted information for it will be removed. For this reason plug-ins should not unregister their tasks during Agent shutdown. Instead, the tasks are meant to be explicitly removed only when the schedule is no longer necessary.

Task last run time

 
The last run time is preserved by the Client Scheduler to make sure that schedules are not re-triggered in case Agent is restarted. The information is updated when Client Scheduler determines that a task is about to run, but before the owner plug-in is notified.
Note:
Agent 7.0 used to store the last run time in the local timezone format. The current version of the Agent will convert the migrated last run time to UTC.

Task discovery time

 
The task discovery time is taken as the time when the task (schedule) is registered in the Client Scheduler and there is no persisted information about that task available. Note that if you explicitly remove task registration, all the persisted information about it gets deleted.
 
The task discovery time is important for recurring triggers which need not be triggered in case they were missed before the schedule arrived on client.
Note:
Agent 7.0 used to store the discovery time in the local timezone format. The current version of the Agent will convert the migrated discovery time to UTC.

Schedule structure

 
An individual schedule consists of several parts. Moreover, in order to register a schedule within the Client Scheduler one needs to provide some additional information.

Schedule XML

 
Schedules typically arrive on client through configuration policies. This means that they are in the XML format.
 
Here is a sample schedule XML:
<schedule tz="Server" start="2007-01-01 00:00:00 -1:00" end="2007-01-02 10:15:00 -1:00">
	<trigger type="Daily" exact="true" at="10:00:00 -1:00" duration="00:50:00" repetition="00:30:00" />
	<trigger type="Daily" exact="true" at="15:00:00 -1:00" />
	<modifier type="Network" />
</schedule>

Schedule information

 
The Schedule XML consists of several parts

Valitidy period

 
The schedule node attributes tell when the schedule is valid. Outside of these dates, the schedule cannot be triggered nor can it be active.
 
The start attribute contains the start date. This may be empty if the schedule does not have a fixed start date.
 
The end attribute contains the end date. This may be empty as well meaning that the schedule is valid until it's removed.
Note:
Both start and end dates may or may not contain the time part. For start dates the default time part is 00:00:00 and for end date -- 23:59:59.
 
The timezones are covered in the next section.

Triggers and modifiers

 
Inside the schedule node there are trigger and modifier nodes, which specify when the schedule should be active or when it needs to be triggered. Triggers and modifiers are specifically covered in their own sections.

Schedule coordination timezones

 
For time-based triggers it is important to know which timezone is to be used for checking whether a task is due or active. Moreover, the validity period may be defined in terms of a specific timezone as well.
 
Client Scheduler supports coordination by three timezones:
  • Local - all times are specified in the timezone of the computer that the Agent is running on
  • UTC - all times are specified in UTC (client computers in different locations with different timezones will work synchronously)
  • Server - this is a convinience for infrastructure administrators, the clients will be synchronized by the timezone available on the server that manages these clients
 
Since Agent has no idea which timezone the server is running under, in case of Server timezone synchronization all dates and times are expected to include the timezone shift from UTC. Since both start and end dates for the schedule validity period are allowed to be empty, the at parameter of time-based triggers (this is not allowed to be empty) will contain the timezone shift as well.
Note:
If Server synchronization is selected and no timezone shift is specified, 0 (i.e. UTC) will be assumed.
 
The timezone synchronization may affect not only times, but dates as well. For example, if you specify that something needs to run on Monday and use UTC for synchronization, then Monday is defined as 00:00:00-23:59:59 at UTC, which may not necessarily coincide with Monday on the computer where Agent is running (i.e. in certain timezones the task would actually run on Sunday or Tuesday depending on the shift from UTC).

Tasks

 
The information specified by the schedule XML is not suitable for the Client Scheduler as is. Client Scheduler needs to know some additional stuff and when this stuff is added, one gets a Client Scheduler task. Information about a task is encapsulated inside the ClientScheduler::ATask class.
 
Tasks support having more than one schedule defined, however this feature is not the primary use case. If you do have multiple schedules for one task, then task will trigger if any schedule triggers and it will be active if any schedule is active.

Task ID

 
The most important thing that Client Scheduler needs to know about a schedule is how this schedule is identified. The ID will be included in NTracker notifications and also passed as a parameter when invoking the callback interface. The ID is also used for deleting/updating the task and querying for its status.
 
There are some recommendations and requirements for choosing a good task ID:
  • the IDs should be unique; the unique part could, for example, be the GUID of the policy that contains the schedule
  • when using GUIDs, make them upper-cased for consistency; also use curly braces
  • include information about the owner of the task; for example, use the reverse-dns notation
 
Here is an example of a good task id:
com.altiris.inventorysol.hardware.{ADF4D0F0-C336-488E-AD83-6648E2E46578}

Handler ID

 
If you are dealing with triggering tasks (schedules), you will most likely want to implement the callback interface. For Client Scheduler to invoke your plug-in through that callback interface, it needs to know the plug-ins Class ID. This Class ID is what needs to be supplied as Handler ID for the registered task.

Enabled flag

 
A newly created task is disabled by default. If you want it to be processed by the Client Scheduler, you will have to enable it first.
 
Some plug-ins may find it useful to temporarily disable their scheduled tasks under certain conditions (for example, for maintenance or if only one task is allowed to run at a time).

Trigger types

 
Triggers specify when a specific schedule needs to run. If a schedule has multiple triggers, then it will run if any of the triggers is due. However, a schedule will not trigger if at least one modifier is not satisfied.
 
For activity periods triggers define when the period should become active or inactive. Only time-based triggers currently apply to activity periods. The trigger specifies when period becomes active and duration tells for how long it should remain in that condition. A schedule is considered to be active it should be active according to at least one trigger and modifiers have absolutely no effect on this.

Time-based triggers

 
The most commonly used category of triggers is time-based triggers. These specify that a schedule should be due or should become active at specific time.
 
Time-based triggers are further categorized by type of recurring they support.

Run-Once triggers

 
The simplest trigger type is Run-Once trigger. It does not support any recurring at all and is supposed to run or become active only once.
Note:
Run-Once triggers can actually trigger multiple times if they are defined as repetitive.

Recurring triggers

 
Recurring triggers are supposed to trigger or become active on a periodic basis. The following repetition types are supported:
Repetition Type Description
Daily The trigger will be due or become active every day.
Weekly The trigger will be due or become active only at specific days of week.
Monthly The trigger will be due or become active only at certain dates of month.
Yearly by Month Same as Monthly, but it is possible to limit the months.
Monthly by Week The trigger will be due or become active only at certain weeks of month and at certain days of week.
Yearly by Week Same as Monthly by Week, but it is possible to limit the months.
 
Note that recurring and run-once triggers are handled differently in terms of missed due times before the schedule is registered in the Client Scheduler.

Repetitive triggers

 
For all time-based triggers it is possible to specify a duration. The purpose of this attribute is self-evident for activity-periods. For triggering tasks the duration is used to limit the repetition period during the day.
 
The idea is that it is possible to trigger a schedule multiple times during the duration period starting from the nominal at time. The repetition attribute specifies how often the trigger should be due within the duration.
 
Consider the schedule below:
<schedule tz="Local" start="" end="">
	<trigger type="Daily" exact="true" at="10:00:00" duration="01:00:00" repetition="00:20:00" />
</schedule>
 
The sample schedule will trigger every day at 10:00am and it will be active for 1 hour. During this activity period the task will be triggered every 20 minutes. Thus the schedule will trigger at:
  1. 10:00:00
  2. 10:20:00
  3. 10:40:00
  4. 11:00:00

Frequencies

 
Sometimes it's desirable to specify that a schedule should be active or should trigger not every day, but rather every second or every third day. This is possible through specifying frequency. For example:
<schedule tz="Local" start="2008-01-01" end="">
	<trigger type="Daily" exact="true" at="10:00:00" frequency="2" />
	<trigger type="Weekly" exact="true" at="12:00:00" frequency="4" weekdays="1" />
</schedule>
 
The first trigger will tell to run a task at 10:00:00 every second day (actually, every first day of the two).
 
The second trigger will fire once (on Monday) every 4 weeks (starting from the first week).
 
Numbering days and week starts from the schedule start date. Weeks are considered to start on Sunday. The first day or week will always trigger, after that frequency-1 days/weeks will be skipped and the loop will start over.
Note:
Frequencies are supported only for Daily and Weekly trigger types. The default and minimal frequency is 1.

User logon trigger

 
In a number of scenarios it may be desirable to do something when a user logs on to the system. This can be achieved by using a Logon trigger. Here is a sample of such trigger:
<schedule tz="Local" start="" end="">
	<trigger type="Logon" exact="true" />
</schedule>
 
Logon triggers are supported only on Mac OS X and only for GUI Console user log-ins. Not that switching between already logged on users does not trigger the schedule, but logging to another user while some other people are logged on will.
Warning:
User logons are detected by Agent. If a logon happens while Agent is not running, this will not be considered as a missed logon.
 
Note that Client Scheduler uses AUserLogonManager to get information about user logons.

System boot trigger

 
In a number of scenarios it may be desirable to do something after a computer is rebooted. This can be achieved by using a Startup trigger. Here is a sample of such trigger:
<schedule tz="Local" start="" end="">
	<trigger type="Startup" exact="true" />
</schedule>
 
 
Note that Client Scheduler uses AgentStore::ASystemBootInfoStore to read information about when a last computer reboot took place and when it was detected.

Exact vs Plain triggers

 
All triggers have a pseudo-modifier, which tells whether the trigger is exact or not.
 
Exact triggers are more restricted: if the necessary event is missed, the schedule should not trigger. For example, if a task is scheduled to run at 14:00 and computer is off at that time, then after computer goes back online the task would not trigger. It would trigger only if computer and the Agent are running at that specific time.
 
On the contrast, usual triggers would fire as soon as possible after the scheduled time or the trigger event take place. If in the scenario above the computer would be turned on at 15:00, a non-exact trigger scheduled for 14:00 would fire.
 
On practice Client Scheduler allows trigger 59 seconds to fire (run) after the trigger event. This is especially useful when the Agent starts up after a reboot and also for some other asynchronous events.
Note:
The "exactness" modifier has no effect on activity periods.

Recurring vs Run-Once triggers

 
Of all supported trigger types only Run-Once trigger is not recurring and only if it does not have any repetition associated with it. All other cases are considered to be potentially recurring even if in reality the schedule is allowed to trigger only once or never at all (for example, because of validity start/end date restrictions).
 
There is a fundamental difference in how recurring and non-recurring triggers are handled in terms of missed schedules. A missed schedule is a time when task was supposed to trigger, but did not because the schedule has not been registered in the Client Scheduler by then. This, for example, may happen if something is scheduled to happen at 14:00, but computer is offline at that time and cannot get the configuration. When computer is online again at 15:00 and obtains the task, the schedule has already been missed.
 
For recurring tasks missed schedules (i.e. trigger times before the discovery dates) are ignored. It's as if the validity start date/time of the schedule is set to the discovery date/time.
 
For non-recurring (e.g. Run-Once) triggers the missed schedules are respected. If the exact attribute permits, the schedule will trigger right away. Otherwise, it will never trigger at all (unless the schedule is discovered in the middle of a repetition period).
Note:
Activity periods are not affected in any way. A period is active only within the duration starting from the trigger at time. Missed periods of activity or parts of such periods are ignored.

Modifier types

 
Modifiers are a way to put some restrictions on when a schedule can trigger. In order for a schedule to trigger, all of its modifiers must be satisfied. If at least one is not, the whole schedule will be not allowed to fire, no matter how many triggers are due.

Network connectivity modifier

 
The Network modifier tells that a working network connection should be available on the client machine when the schedule triggers. Client Scheduler uses ANetworkManager::HasNetworkConnection() to find out whether this modifier type can be satisfied.

User logon modifier

 
The Logon modifier can be used to restrict schedules to trigger only if there are any users logged (e.g. for interactive tasks) or if there is noone logged on at all (for maintenance). Client Scheduler uses AUserLogonManager to find out whether there are any logged on users currently available.
Warning:
This modifier works only on Mac OS X and only for GUI console logins. On platforms other than Mac it is always assumed that there are no users logged on to the machine.

Working with Client Scheduler

 
The Client Scheduler functionality is exposed through the Client Scheduler plug-in for Altiris Agent. The plug-in implements the Interfaces::IAeXClientScheduler interface, which is wrapped by the ClientScheduling::AClientScheduler class.
 
Client Scheduler operates on a bunch of helper classes (residing in the ClientScheduling namespace). The most important of these are:

Registering tasks and schedules

 
To register a task in Client Scheduler one needs to have a schedule first. Schedules typically arrive on client as part of configuration policies.
 
Once you have the schedule XML, you need to parse it into an ASchedule object and build up an ATask object using that. The resulting task then needs to be added to the Client Scheduler.
 
Here is a simple example of how to complete these operations:
void MyPlugin::OnAfterPolicyStoreUpdate(
        const AgentSDK::Interfaces::AIpcCallContext &_call_context,
        const BaseSDK::AString &agentClsid,
        const bool &bIsFirstRefresh,
        const AgentSDK::APolicyStatus::Map &policy_status
        )
{
        AError err = E_OK;
        APolicyXmlList policies;

        // Obtain client policies XML
        err = APolicyManager::GetPolicyXml(agentClsid, policies);
        if (IS_FAILURE(err))
        {
                LOG_ERROR(L"Failed to obtain client policies for plug-in %1: %2, %3",
                        agentClsid, err, AGetErrorMsg(err));
                return;
        }

        // Extract schedule from each of the policies
        APolicyXmlList::iterator it = policies.begin();
        const APolicyXmlList::iterator end = policies.end();
        for ( ; end != it; ++it)
        {
                // Get policy GUID; it will be used for task id
                AString policyGuid = (*it)->SelectSingleNode("/Policy/@guid");

                // Extract schedule from the policy
                ASchedule schedule;
                err = ADeserializeFromXml(schedule, (*it)->SelectSingleNode("/Policy/ClientPolicy/scheduleXml/schedule"));
                if (IS_FAILURE(err))
                {
                        LOG_WARNING(L"Failed to deserialize schedule for policy '%1': %2, %3", policyGuid, err, AGetErrorMsg(err));
                        continue;
                }

                // Create the task to register in Client Scheduler
                ATask task(L"com.altiris.sample.clientscheduler.myplugin." + policyGuid);
                task.SetEnabled(true);
                task.SetHandlerId(L"Altiris.Sample.ClientScheduler.MyPlugin");
                task.AddSchedule(schedule);

                // Register the task; if task already exists, it will be updated
                err = AClientScheduler::AddTask(task);
                if (IS_FAILURE(err))
                {
                        LOG_WARNING(L"Failed to add or update task '%1': %2, %3", task.GetId(), err, AGetErrorMsg(err));
                }
        }
        
        return E_OK;
}

Unregistering tasks and schedules

 
When Agent restarts it loses information about all registered tasks. What remains persisted though is the information about task discovery time and last run time. This information is very important and it affects how schedules are processed. For this reason if you no longer need a task, you must remove its registration from the Client Scheduler.
 
The typical scenarios when you should remove task registration are:
  • the schedule is no longer applicable (for example, policy does not apply to the computer)
  • your plug-in is being uninstalled
 
Here is a sample implementation for the case when your plug-in is uninstalled:
AError MyPlugin::OnPluginUninstall(const AgentSDK::Interfaces::AIpcCallContext &_call_context, const BaseSDK::AString &agentClsid)
{
        // Get the list of all registered tasks
        ATaskList tasks;
        AError err = AClientScheduler::GetTasks(tasks);
        if (IS_FAILURE(err))
        {
                LOG_ERROR(L"Failed to obtain the list of tasks to remove: %1, %2", err, AGetErrorMsg(err));
        }
        else
        {
                ATaskList::const_iterator it = tasks.begin();
                const ATaskList::const_iterator end = tasks.end();
                for ( ; end != it; ++it)
                {
                        // Check if this is our task
                        if (!it->GetId().StartsWith(L"com.altiris.sample.clientscheduler.myplugin."))
                                continue;
        
                        // This is our task: remove its registration
                        err = AClientScheduler::RemoveTask(it->GetId());
                        if (IS_FAILURE(err))
                        {
                                LOG_WARNING(L"Failed to remove a task '%1': %2, %3", it->GetId(), err, AGetErrorMsg(err));
                        }
                }
        }

        return E_OK;
}

Handling tasks

 
In order to handle triggering tasks, one needs to implement the AgentSDK::Interfaces::IAeXClientSchedulerTaskHandler interface. This will be invoked by the Client Scheduler when the registered task become due.
 
Here is a sample implementation of the interface:
AError MyPlugin::OnClientScheduleTaskTriggered(
        const AgentSDK::Interfaces::AIpcCallContext &_call_context,
        const AgentSDK::ClientScheduling::ATriggeredTask &task
        )
{
        // First check if this is our task
        if (!task.GetId().StartsWith(L"com.altiris.sample.clientscheduler.myplugin."))
                return E_OK;
        
        // Do some real work.
        // Or, alternatively, spin off/notify a thread to do the work.
        ...
        
        return E_OK;
        
}
 
Note that Client Scheduler invokes plug-ins one after another. For this reason it is critical that the OnClientScheduleTaskTriggered function would return as quickly as possible. If task processing is not trivial, it is recommended that you would implement a separate thread to do the real work. When the scheduled task is due, you can either start this thread or have it running beforehand and just notify it by setting some event.

Handling activity periods

 
For activity periods you need to subscribe to NTracker notifications from the Client Scheduler. Here is a sample on how to do that:
AError MyPlugin::Initialize()
{
        // Subscribe to NTracker notifications
        long lEvents[] =
        {
                NTN_CS_TASK_BECAME_ACTIVE,
                NTN_CS_TASK_BECAME_INACTIVE
        };
        int iSubscriberID = -1;
        AError err = NTracker::Subscribe(L"Altiris.Sample.ClientScheduler.MyPlugin", lEvents, COUNT_OF(lEvents), iSubscriberID);
        if (IS_FAILURE(err))
                        LOG_ERROR(L"APeriodManager::Init(), failed to subscribe to notifications: %1, %2", err, AGetErrorMsg(err));
        
        return E_OK;
}

AError MyPlugin::NotifyObject(
        const AgentSDK::Interfaces::AIpcCallContext &_call_context,
        const long &lNotification,
        const long &lParam,
        const BaseSDK::AString& strParam
        )
{
        // Handle NTracker notifications
        if (NOTIFICATION_NTRACKER == lNotification)
        {
                // A schedule is active
                if (NTN_CS_TASK_BECAME_ACTIVE == lParam)
                {
                        // Is it our schedule?
                        if (strParam.StartsWith(L"com.altiris.sample.clientscheduler.myplugin."))
                        {
                                // Do some work here.
                                ...
                        }
                }
                
                // A schedule became inactive
                else if (NTN_CS_TASK_BECAME_INACTIVE == lParam)
                {
                        // Is it our schedule?
                        if (strParam.StartsWith(L"com.altiris.sample.clientscheduler.myplugin."))
                        {
                                // Do some work here.
                                ...
                        }
                }
        }

        return E_OK;
}
 
Just as in case trigger tasks, handling notifications should take as little time as possible. In case notification processing is not trivial, it is recommended that you would implement a separate thread to do the real work. When the scheduled task is active or inactive, you can either start this thread or have it running beforehand and just notify it by setting some event.

Polling task status

 
Although it is not a very good idea, but it is possible to use polling in order to find out whether a task is active and/or due. The AClientScheduler provides a bunch of functions which you can call periodically to query whether a task needs to be triggered.
 
While with activity periods things are quite easy, treating triggering tasks like that is a little bit tricky.
 
Activity periods do not depend on any previous history: they are either active or not and this can be easily predicted (for example, modifiers are not involved). So it is pretty safe to call functions like IsTaskActive() at any time.
 
With triggering tasks one has to consider that these depend on last run time, which is automatically updated by the Client Scheduler. If a task is due and it is marked as run, the next check for whether the task is due will fail even though nothing has been done (since you do not implement the callback interface). To work around this, you will have to disable the task either when registering it in Client Scheduler or by telling Client Scheduler to disable it. This way Client Scheduler will not process the task, but will let you query its status.

Manual working with tasks

 
In some cases you may find yourself in situation when you cannot use Client Scheduler, but still want to be able to work with its schedules and tasks. The good news is that most of the logic resides in the SDK and is reusable.
 
The Client Scheduler plug-in takes care of the following tasks:
  • persists last run time and discovery dates
  • notifyings other plug-ins about schedules becoming active, inactive and due
  • responding to queries
 
All of this is fairly easy to implement yourself should you need that. However, it is recommended that you stick to the Client Scheduler if possible.

Finding next activation and next due times

 
Once in a while one may need to do some basic processing and querying without registering tasks in Client Scheduler. In some cases a schedule would be available, but Agent would not be running and one would have to figure out how to deal with the schedule. This is especially true for UI applications.
 
The ATask and ASchedule classes provide a bunch of methods and global related functions. Here is a brief list of what you can do:
  • check whether a task/schedule is active or going to be active at specific point of time
  • find the next time a task/schedule is going to be active starting from a given point of time
  • check if a task/schedule is due for execution now
  • find the approximate time a task/schedule is going to be due next time
 
The the next due time is going to be approximate since it is impossible to predict whether the modifiers are going to be satisfied when the task becomes due.

Statistics
0 Favorited
0 Views
0 Files
0 Shares
0 Downloads

Tags and Keywords

Related Entries and Links

No Related Resource entered.