Microsoft Plus! for Windows 95: Guidelines for Independent Software Vendors

May 15, 1995
Version 0.8

Introduction

The System Agent feature of Microsoft® Plus! is a general-purpose scheduler application with which users can schedule programs to run at various times. System Agent can launch any Windows®-based or MS-DOS®-based program, without requiring any modification to the program being scheduled. However, there are things independent software vendors (ISVs) can do in their applications to add value for customers using those programs in conjunction with System Agent. Programs that have been modified to exploit System Agent are referred to as "SAGE-aware." (“SAGE” refers to System Agent’s underlying scheduling engine. SAGE.EXE is run whenever a user logs on; it in turn loads SAGE.DLL, which monitors the system and runs programs at the appointed time and which exposes scheduling services to programs. System Agent’s user interface is provided by a separate program, SYSAGENT.EXE, which uses the API provided by SAGE.DLL to edit the SAGE task data base.)

SAGE-aware programs have the following characteristics:

In addition, System Agent exposes an API that applications can use to directly manipulate SAGE's data base of scheduled programs. Using this API, applications can add themselves to SAGE's queue without requiring the user to manually schedule the program using System Agent's user interface. In addition, System Agent exposes APIs that applications can use to detect if System Agent is running, and to suspend System Agent so that it will not start up any programs until the application says so (or until the system is restarted).

How to Make an Application "SAGE-Aware"

SAGE-aware programs declare themselves as such by creating a key in \\HKLM\Software\Microsoft\Plus!\System Agent\SAGE. The name of the key can be anything the program wants, but it should contain the following values:

Program= Name of the program's EXE file. This must be the same name under which the program's PerApp path is registered.
Friendly Name= Display name that System Agent will use in populating the drop-down list in its "Schedule a program" dialog.
Settings= 1-bit binary field indicating whether program has a Settings dialog.
Result Codes Optional key containing a set of value pairs mapping an exit code to a string describing the meaning of that exit code. For example, for SCANDSKW, the Result Codes key may contain a value such as:  0="ScanDisk completed successfully; no errors were found.". This is to allow SAGE to keep a human-comprehensible log of the results of the programs it runs. In addition to the value pairs, this key should also contain a String value named "Success", which indicates the highest value for an exit code that designates that the program completed successfully. The value names should be string values, specified in decimal; the allowable range is 0–32767.

In addition, SAGE-aware applications must register a "Per Application Path", as defined by the Windows 95 Setup guidelines. SAGE will use the PerApp path to locate the program's executable, and it will initialize its environment with the path (if any) specified by the Path value of the app's key in the PerApp path section of the registry.

SAGE-aware applications must also support the /SAGERUN:n command line switch. SAGE will pass this switch on the command line when launching programs that have registered themselves as being SAGE-aware. This is to let the application know that it is being run by SAGE and thus that is should behave in a suitable fashion for unattended operation. The :n suffix allows SAGE to specify a group of application defined settings to be used by this scheduled instance of the program (as described below). If your program does not support multiple saved sets of settings, it may ignore this suffix.

Settings Dialog

SAGE-aware programs also may support a Settings dialog. If so (as indicated by the Settings= value of the program's registry key), they must support the following command line switches:

SAGESET:n This tells the application to display its SAGE settings dialog, rather than just running normally. The application should initialize itself with settings set #n (if it exists). Applications that do not support multiple saved sets of settings may ignore the :n suffix.
SAGERUN:n This tells the application to run using settings set #n. If your program does not support multiple saved sets of settings, it may ignore this suffix.

The application saves its SAGE settings in a subkey of the key in which it registers itself as a SAGE-aware program, as follows:

SetN Key(s) containing a set of settings this program can be run with. The contents and format of this key is defined by each application. The allowable range for N is 0–32767.

When System Agent is scheduling a new instance of a SAGE-aware program, it launches it using /SAGESET:n, with a value of n for which a SetN key does not already exist (for that program, at least). If/when the user confirms his or her choices in the settings dialog, the program should save those choices in the key named SetN. If the user cancels out of the settings dialog, the program simply exits without saving those settings. When SAGE runs the program, it passes it the /SAGERUN:n command line switch. If the program supports one or more sets of Save settings, it runs with setting set #n; otherwise it ignores the :n suffix and uses its default settings.

IMPORTANT   System Agent may launch programs with both the /SAGERUN and /SAGESET command line switches simultaneously. If this happens, the program should display its SAGE Settings dialog—that is, the /SAGESET switch takes precedence over /SAGERUN.

Note also that the /SAGERUN:n command line switch implies that the program is being run by SAGE and thus, the program should behave accordingly and not stop for user input. All the settings required to run the program must be derived from what is stored in the registry—that is, when run with SAGERUN:n, the desired behavior of the program (what drive[s] to run on, and other preferences) must be fully specified by the settings stored in the SetN registry key, or the program must provide reasonable defaults so that it runs unattended, unless an unexpected condition arises that requires user input (regardless of whether or not :n is provided). Although the SAGERUN:n switch is intended to fully specify the behavior of a SAGE-aware program, the user is permitted to provide additional command line parameters to the program through the System Agent UI. If an application supports both a Settings dialog and command line parameters, it is up to the application to resolve any conflicts that may arise between the settings the user specifies in the Settings dialog and any command line switches she or he provides.

Exit Codes

SAGE-aware applications should strive to return a meaningful exit code upon exiting from their WinMain function. In the initial release of Microsoft Plus!, these exit codes will be used by SAGE to display a result string in the System Agent user interface (UI) and write it to its log. The mapping between exit codes and result strings will be provided by each SAGE-aware application in its Result Codes registry key. In future versions of Plus!, SAGE may use the exit code to determine whether a program ran successfully or not, in order to determine whether other programs dependent on its outcome should be allowed to proceed or not. All exit codes indicating that the program completed successfully should be equal to or less than the value indicated by "Success", and numerically lower than those indicating failure conditions. For example, for ScanDisk, error code 0 might be defined to mean "No errors found"; error code 2 "Errors were found but all were fixed"; error code 10 might indicate that "Errors were found and only some were fixed"; and 255 might mean "Errors were found and none were fixed." In this example, a value for "Success" equal to 9 would be reasonable, but 1 would not.

The exit code 0xF9 is distinguished. When a SAGE-aware application is run with /SAGERUN and returns exit code F9 to SAGE, SAGE will try to re-run the application at a later time, possibly repeatedly unless/until its deadline (if any) passes. Essentially, this exit code says to SAGE, "I wasn't able to complete successfully, but try to run me again later." This option could be used, for example, by disk utilities that need to wait for a drive to become unlocked, or backup applets that discover that the destination server is down.

Error Handling

Error handling is handled by each SAGE-aware application. For errors that require user intervention, the application should stop and display a suitable dialog, rather than or in addition to simply exiting with an exit code. Otherwise the user will have no indication that an error occurred, unless she or he checks the SAGE log.

Termination

System Agent provides an option for SAGE to terminate a program if it's not finished by a designated time, or if the user returns to the system and starts using it. SAGE implements this by sending a WM_CLOSE message to the program's top-level window(s). SAGE-aware applications, when run with the SAGERUN:n command line switch, should exit gracefully and silently upon receiving this message. Exceptions to this might occur, however, if the application has unsaved data and must ask for confirmation from the user as to whether to save it, or if the application is in an error state (in which case it may wish to ignore the WM_CLOSE message, so that the user can be made aware of the error).

SAGE API

SAGE.DLL exports the following application programming interfaces (API)s, which applications can use.

Detecting Whether SAGE Is Running

int System_Agent_Detect(void)

If the System Agent thread exists, the System Agent's version number is returned; otherwise zero is returned.

Note   This function is important because the existence of SAGE.DLL does not mean that the System Agent itself has been started. Note also that it is not necessary to call System_Agent_Initialize() before calling this function.

Suspending/Resuming SAGE

This function allows the client to completely enable and disable the System Agent. This feature should be used sparingly, because presumably the user has scheduled programs for a good reason. If the caller crashes while the System Agent is disabled, the System Agent will not be reenabled until the machine is restarted. Note that it is not necessary to call System_Agent_Initialize() before calling this function.

int System_Agent_Enable(int enablefunc)

enablefunc can have 1 of 3 values:

1 = ENABLE_AGENT Enables System Agent scheduling
2 = DISABLE_AGENT Disables System Agent scheduling
3 = GET_AGENT_STATUS Returns the status of the System Agent without affecting the current state. Returns are ENABLE_AGENT or DISABLE_AGENT.

Suspending/resuming SAGE from 16-bit code

16-bit applications can use the following code to suspend System Agent (for example, DisableSystemAgent() remembers the state of the System Agent) and then disable it:

#include <windows.h>
#include "string.h"
static int gSageStatus;
#define SAGE_ENABLE           WM_USER + 6
#define SAGE_DISABLE          WM_USER + 7
#define SAGE_GETSTATUS        WM_USER + 8
#define ENABLE_AGENT 1
#define DISABLE_AGENT 2
#define GET_AGENT_STATUS 3
void DisableSystemAgent(void)
{
   HWND h;
   h = FindWindow("SAGEWINDOWCLASS","SYSTEM AGENT COM WINDOW");
   if(!h)
      return;
   if(h)
   {
      gSageStatus = -1;
      gSageStatus = (int) SendMessage(h,SAGE_GETSTATUS,0,0L);
      if((gSageStatus != ENABLE_AGENT) &&
         (gSageStatus != DISABLE_AGENT))
            return; //something weird is going on
      SendMessage(h,SAGE_DISABLE,0,0L);
   }
}

RestoreSystemAgentState() restores the remembered state:

void RestoreSystemAgentState(void)
{
   HWND h;
   h = FindWindow("SAGEWINDOWCLASS","SYSTEM AGENT COM WINDOW");
   if(!h)
      return;
   if(h)
   {
      switch(gSageStatus)
      {
         case DISABLE_AGENT:
            SendMessage(h,SAGE_DISABLE,0,0L);
            break;
         case ENABLE_AGENT:
            SendMessage(h,SAGE_ENABLE,0,0L);
            break;
         default:
            break; //something weird happened
      }
   }
}

Accessing/Modifying the SAGE Task Database

System Agent exports a rich API through SAGE.DLL that programs can use to directly query or modify the SAGE task data base.

Data structures

#define MAXPATH 267
#define MAXSETTINGS 256
#define MAXCOMMANDLINE (MAXPATH+MAXSETTINGS)
#define MAXCOMMENT 256
#define CB_RESERVED 33
#pragma Pack(1)
typedef struct TaskInfo
{
       unsigned long      StructureSize;        
    unsigned long    Task_Identifier;
    unsigned long     Sub_Task_Identifier;
    unsigned long    Status;
    unsigned long    Result;
    unsigned long    Time_Granularity;
    unsigned long    StopAfterTime;
    unsigned long    ReservedLong;
    unsigned long    User_Idle;
    unsigned long    Powered;
    unsigned long      CreatorId;
       SYSTEMTIME         BeginTime;             
       SYSTEMTIME         EndTime;               
       SYSTEMTIME         LastRunStart;          
       SYSTEMTIME         LastRunEndScheduled;   
       SYSTEMTIME         LastTerminationTime;   
       SYSTEMTIME         ReservedTime1;          
       SYSTEMTIME         LastAlarmTime;
       SYSTEMTIME         ReservedTime2;
       SYSTEMTIME         ReservedTime3;
     STARTUPINFO    StartupInfo;
       DWORD              dwProcessId;
       DWORD              dwThreadId;
       DWORD              LockingProcess;
       unsigned long      LockTime;
       DWORD              fdwCreate;
       DWORD              taskflags;
       char               SystemTask;
       char               TerminateAtRangeEnd;
       char               StartupTask;
       char               AlarmEnabled;
       char               RunNow;  
       char               TerminateNoIdle;
       char               Disabled;
       char               TerminateNow;
       char               RestartNoIdle;
       char               CommandLine[MAXCOMMANDLINE];
       char               Comment[MAXCOMMENT];
       char               WorkingDirectory[MAXPATH];
       char               Reserved[CB_RESERVED];
}TaskInfo;

StructureSize

This is the size of the TaskInfo structure in bytes. This must be correct for API calls. Failure to fill in this field will lead to an ERROR_TASK_INVALID return code. Value is 0x558.

Task_Identifier

This is a unique value identifying this task. This value is passed into some of the APIs to identify the task.

Sub_Task_Identifier

This value is reserved for future use. Clients should leave this value zero.

Status

This field is set only by the System Agent (see System_Agent_Change_Task). It contains a value indicating status for the task. Currently defined values are:

#define STATUS_NOTRUNNING  0L    // Task is not running.
#define STATUS_RUNNING     1L    // Task is currently running.
#define STATUS_COMPLETE    2L    // Task is not running, but has run in the 
                                 // currently scheduled period.
#define STATUS_QUEUEDEXEC  3L    // Task is just about to execute.

Result

This is the result code returned from the last execution of the task.

Time_Granularity

If non-zero, this field indicates that the task should be executed periodically during its scheduled time at an interval of Time_Granularity seconds. If the task is still running when the next Time_Granularity seconds have elapsed, the task is not run; otherwise it is.

StopAfterTime

If non-zero, this field indicates that the task should be terminated after running for StopAfterTime seconds of execution.

ReservedLong

This field is reserved for future use and should be set to zero.

User_Idle

If non-zero, this task indicates that the task should not be executed unless User_Idle seconds have elapsed with no keyboard or mouse activity. Thus, if a task is scheduled to run between 4:00 and 5:00 with User_Idle set to 10 minutes (600 seconds), and the user last moved his mouse at 3:55, the task will execute at 4:05 unless the user interacts with the computer between those times, in which case the time countdown will be restarted at 10 minutes.

Powered

This field indicates that the task should not be run if the computer is currently being run on batteries. This works only on fully APM-compliant Plug and Play machines. Valid values are zero or one (one indicates the task should not run on batteries).

CreatorId

This field is reserved for use by the task creator. The task creator is the application that called System_Agent_Add_Task. This can be any value.

BeginTime

The StartTime and EndTime together define the basic schedule for a task. The StartTime field indicates the beginning of the task's execution period. This can be an exact time (in the case of a run-once task), or it can be a repeating schedule such as Daily, Monthly, Weekly, or Hourly. To specify a repeating schedule, set the units at which you wish to repeat to -1. All fields of higher precedence must also be set to -1. Precedence order is: wYear, wMonth, wDay, wHour, wMinute, wSeconds. The wMillisecond field is not honored when scheduling tasks.

Note   wDayOfWeek does not have precedence and should be set to -1 unless you are specifically scheduling a task to run on a fixed day of the week.

Thus, to run daily at 4:30am, set:

To run every Wednesday at 1:00pm, set:

See EndTime for more details.

EndTime

This field specifies the ending time for the period in which the task is run. It has the same format as the StartTime, and should match its repeating schedule type. This field defines the latest time at which the task will be started if the task runs only when the machine is idle, and it defines the end of the period in which it will be started if it has a Time_Granularity set. Also, this field defines the time that task will be terminated if TerminateAtRangeEnd is set. If no EndTime definition is needed, all fields may be set to -1.

LastRunStart

This field indicates the time at which this task was last executed. This field should not be changed by the client.

LastRunEndScheduled

This field indicates the specific time the task's period is/was scheduled to end according to the schedule defined by StartTime and EndTime. This field does not indicate at what time the task actually terminated (see LastTerminationTime), nor will be terminated, but rather at what time the period for this task is scheduled to end.

LastTerminationTime

This field contains the specific time this task was last terminated.

ReservedTime1

This field is used internally by System Agent, and should not be used or modified by the client.

LastAlarmTime

This field indicates the specific time the last scheduled program notification was displayed for this task. This field should not be used by the client.

ReservedTime2
ReservedTime3

These fields are reserved for future use. They should remain zero and should not be used by the client.

StartupInfo

This field defines the STARTUPINFO structure that should be passed to CreateProcess when the task is started. See CreateProcess.

dwProcessId

This field defines the Process Id for the task, if it is running. See CreateProcess. This field should not be modified by the client.

dwThreadId

This field defines the Thread Id for the task, if it is running. See CreateProcess. This field should not be modified by the client.

LockingProcess

This field indicates the Process ID of the program who owns the lock on this task, if there is one. This field should not be modified by the client.

LockTime

This field records the GetTickCount return value when the task was locked. This field is used for data validation, and should not be used by the client.

fdwCreate

This field indicates the fdwCreate flags passed to CreateProcess. If zero, System Agent uses CREATE_NEW_CONSOLE | CREATE_NEW_PROCESS_GROUP | NORMAL_PRIORITY_CLASS. See CreateProcess.

taskflags

This field is a bitfield defining certain characteristics of the task:

bit 1 = Logging 1 => log execution info to the System Agent log file.
bit 3 = TaskHasRun 1 => the task has run at least once before; 0 means it has never run (read only).
bit 6 = NoIdleTerminated 1 => task was terminated because of loss of idle.
bit 7 = DontRun 1 => don't run during this period after change task (see below).

Other flags are used internally by System Agent and should not be used by the client.

SystemTask

0 means standard task. 1 means task is a system task, and will not show up in the System Agent window.

TerminateAtRangeEnd

If this field is set to 1, the task will be terminated at the EndTime if it is still running.

StartupTask

If this field is set to 1, the task will be run only when System Agent engine is started.

AlarmEnabled

If this field is set to 1, and a task does not run during its scheduled period for any reason, System Agent will put up a dialog box to tell the user. Notification will only occur if SAGE is running. Tasks can fail to run because the machine was off, the system was never idle, or the system was running on batteries during the scheduled time. If the task runs during a subsequent period and the dialog has not been not yet posted, the alarm is cancelled.

RunNow

If this field is set to 1, the task will execute immediately.

TerminateNoIdle

If this field is set to 1, the task will be terminated if it is running and the user uses the keyboard or mouse.

Disabled

If this field is set to 1, the task is disabled, and will not be scheduled to run.

TerminateNow

If this field is set to 1, the task will be terminated if it is currently running.

RestartNoIdle

If this field is set to 1, the task will restart after User_Idle seconds if it was terminated because TerminateNoIdle was set and the user use the mouse or keyboard.

CommandLine

This field defines the full command line for the task. The task may be a .COM, .BAT, .EXE, or associated file type. This is the command line that is passed to CreateProcess. It must be zero-terminated.

Comment

This is the description field shown in the System Agent window. If this field is all zeros, the System Agent will use the CommandLine instead.

WorkingDirectory

This is the working directory as defined by CreateProcess.

Reserved

This field is reserved for use by the System Agent and future API enhancements. It should not be used or modified.

Functions

SAGE.DLL exports the following functions:

int System_Agent_Inititialize(void)

This function should be called once to initialize memory structures. See example code below. Returns zero if successful.

int System_Agent_End(void)

This function frees up memory allocated during API processing. This function should be called once. Returns zero if successful.

int System_Agent_Get_Task_List(TaskInfo **TaskList, BOOL *changed)

This function gets a pointer to an array of tasks, and returns the number of tasks in the system agent's task list. TaskList is a pointer to a pointer to a task list—the memory itself is allocated by System Agent on behalf of the application. The changed parameter receives a Boolean value, with TRUE indicating that the task list has changed since the last time this function was called. A negative return value indicates an error code (see below).

int System_Agent_Add_Task(TaskInfo *Task,unsigned long *Task_Identifier)

Task is a TaskInfo structure that describes the task (see above). Unused fields should be initialized to zero (see example code).

You must at least fill in these fields:

And you may fill in these fields:

Other fields should be zero and not used by the caller.

See the definition for these fields above.

*TaskIdentifier is filled in with the unique ID for this new task. Returns zero if successful, an error code if not.

int System_Agent_Remove_Task(unsigned long Task_Identifier)

This function removes the specified task from the System Agent's task list. Returns zero if successful, otherwise an error code.

int System_Agent_Lock_Task(unsigned long Task_Identifier,DWORD dwProcessId,BOOL volatile)

This function unlocks a task that was locked with System_Agent_Lock_Task.

Error codes

All system agent APIs may return one of the following error codes. Other codes may be defined in the future, so applications should not depend upon certain error codes being returned.

#define ERROR_CANNOT_ADD_TASK     -12    // Unable to add a task, perhaps 
                                         //  because out of memory.
#define ERROR_AGENT_BUSY          -13    // System has timed out the API call.
#define ERROR_TASK_NOT_PRESENT    -14    // Task_Identifer reference not in task
                                         //  list.
#define ERROR_AGENT_NOT_PRESENT   -15    // System Agent is not active.
#define ERROR_CANNOT_LOAD_DLL     -16    // Error loading the DLL.
#define ERROR_CANNOT_LOCK_TASK    -17    // Task cannot be locked at this time.
#define ERROR_TASK_ALREADY_LOCKED -18    // Attempt to lock task failed; another 
                                         //  task owns lock.
#define ERROR_CANNOT_UNLOCK_TASK  -19    // Unable to unlock task, perhaps 
                                         //  because not locked.
#define ERROR_TASK_LOCKED         -20    // Task is locked by a different task, 
                                         //  access denied.
#define ERROR_WRONG_VERSION       -21    // Version of System Agent is not 
                                         //  compatible. 
#define ERROR_TASK_NOT_LOCKED     -22    // Attempt to modify a task that is not 
                                         //  locked.
#define ERROR_TASK_VOLATILE       -23    // Cannot volatile lock because task is 
                                         //  running.
#define ERROR_TASK_INVALID        -24    // Data in task is invalid or corrupt.
#define ERROR_STALE_DATA          -25    // Attempt to modify task with data got 
                                         //  before lock.

Using the System Agent API

The following is the basic pseudo-code sequence for scheduling and displaying SAGE tasks:

{
    if(error = Connect_System_Agent())    // See example source for 
                                          //  this function below.
        goto exit1;
    ...
    if(gTaskList = System_Agent_Get_Task_List())
    {
        error = CANNOT_GET_TASK_LIST;
        goto exit2;
    }
    DisplayTaskStuff(gTaskList);        // User-supplied function for
                                        //  displaying task list.
    
    //...
    
    memset(&gMyTask,0,sizeof(TaskInfo));
    // Fill in info about task..SEE BELOW!
    if(error = System_Agent_Add_Task(&gMyTask,&gMyTaskIdentifer)) 
    // Add a user-defined task
        goto exit2;
    //...
exit2:    
    if(System_Agent_End())
        error("Unable to terminate connection to System Agent");
exit1:
    if(error)
        printf("error = %d",error);
    
}

There are a few things to note about this code sequence. The user-supplied function Connect_System_Agent must call System_Agent_Initialize(). Example code for this is supplied below. If this API was called, System_Agent_End() must also be called to clean up global memory allocations.

The following is example code for initializing the System Agent API:

/*****************  Routine to interface to System Agent ********************/
typedef long (*PFNDLL)();
PFNDLL gpfnSystem_Agent_Initialize = NULL;
PFNDLL gpfnSystem_Agent_End = NULL;
PFNDLL gpfnSystem_Agent_Get_Task_List = NULL;
PFNDLL gpfnSystem_Agent_Add_Task   = NULL;
PFNDLL gpfnSystem_Agent_Remove_Task = NULL;
PFNDLL gpfnSystem_Agent_Lock_Task = NULL;
PFNDLL gpfnSystem_Agent_Unlock_Task = NULL;
PFNDLL gpfnSystem_Agent_Change_Task = NULL;
PFNDLL gpfnSystem_Agent_Detect = NULL;
PFNDLL gpfnSystem_Agent_Enable = NULL;
static HANDLE hLib = 0;
int Connect_System_Agent(void)
{
   int retval;
   if(!hLib)
   {
      if (!(hLib = LoadLibrary ("SAGE.DLL")))
      {
         return(ERROR_CANNOT_LOAD_DLL); 
      }
      if(!(gpfnSystem_Agent_End = 
           (PFNDLL) GetProcAddress (hLib,"System_Agent_End")))
         goto Unable_To_Load;
      if(!(gpfnSystem_Agent_Initialize = 
           (PFNDLL) GetProcAddress (hLib,"System_Agent_Initialize")))
         goto Unable_To_Load;
      if(!(gpfnSystem_Agent_Add_Task   = 
           (PFNDLL) GetProcAddress (hLib,"System_Agent_Add_Task")))
         goto Unable_To_Load;
      if(!(gpfnSystem_Agent_Get_Task_List = 
           (PFNDLL) GetProcAddress (hLib,"System_Agent_Get_Task_List")))
         goto Unable_To_Load;
      if(!(gpfnSystem_Agent_Change_Task = 
           (PFNDLL) GetProcAddress (hLib,"System_Agent_Change_Task")))
         goto Unable_To_Load;
      if(!(gpfnSystem_Agent_Remove_Task = 
           (PFNDLL) GetProcAddress (hLib,"System_Agent_Remove_Task")))
         goto Unable_To_Load;
      if(!(gpfnSystem_Agent_Enable = 
           (PFNDLL) GetProcAddress(hLib,"System_Agent_Enable")))
         goto Unable_To_Load;
      if(!(gpfnSystem_Agent_Lock_Task   = 
           (PFNDLL) GetProcAddress(hLib,"System_Agent_Lock_Task")))
         goto Unable_To_Load;
      if(!(gpfnSystem_Agent_Unlock_Task = 
           (PFNDLL) GetProcAddress(hLib,"System_Agent_Unlock_Task")))
         goto Unable_To_Load;
      if(!(gpfnSystem_Agent_Detect = 
           (PFNDLL) GetProcAddress(hLib,"System_Agent_Detect")))
         goto Unable_To_Load;
   }
  retval = (System_Agent_Detect() == 0); // Zero means agent is not present
  if(!retval)
  {
    retval = (gpfnSystem_Agent_Initialize)();  
    // Agent is present, so initialize interface.
  }
  if(retval)
   {
      if(hLib)
      {
        FreeLibrary(hLib);
      }
      hLib = NULL;
   }
   return(retval);
Unable_To_Load:
   if(hLib)
   {
       FreeLibrary(hLib);
   }
   hLib = NULL;
   return(ERROR_CANNOT_LOAD_DLL);
}
int System_Agent_End(void)
{
   int retval;
   if(!hLib)
   {
    return(0);
   }
   retval = (gpfnSystem_Agent_End)();
   if(hLib)
   {
        FreeLibrary(hLib);
   }
   hLib = NULL;
   return(retval);
}
unsigned long System_Agent_Get_Task_List(TaskInfo **TL,BOOL *changed)
{
    return((gpfnSystem_Agent_Get_Task_List)(TL,changed));
}
int System_Agent_Remove_Task(unsigned long Task_Identifier)
{
   return((gpfnSystem_Agent_Remove_Task)(Task_Identifier));
}
int System_Agent_Add_Task(TaskInfo *task, unsigned long *Task_Identifier)
{
   return((gpfnSystem_Agent_Add_Task)(task,Task_Identifier));
}
int System_Agent_Change_Task(TaskInfo *task, unsigned long Task_Identifier)
{
   return((gpfnSystem_Agent_Change_Task)(task,Task_Identifier));
}
int System_Agent_Enable(BOOL eord)
{
   return((gpfnSystem_Agent_Enable)(eord));
}
int System_Agent_Lock_Task(unsigned long Task_Identifier,DWORD dwProcessId,BOOL volatile)
{
   return((gpfnSystem_Agent_Lock_Task)(Task_Identifier,dwProcessId,volatile));
}
int System_Agent_Unlock_Task(unsigned long Task_Identifier,DWORD dwProcessId,BOOL reenable)
{
   return((gpfnSystem_Agent_Unlock_Task)(Task_Identifier,dwProcessId,reenable));
}
int System_Agent_Detect(void)
{
   return((gpfnSystem_Agent_Detect)());
}
/*****************************************************************************/