Zeitgesteuert arbeitet es sich besser

CRON Scheduler – Aufgabenplanung leichtgemacht [Tutorial]
Kommentare

Programme müssen gelegentlich auf Datum und Uhrzeit reagieren, um Termine anzuzeigen oder um Tätigkeiten zu bestimmten Zeiten durchzuführen. Dieser Artikel stellt die Delphi-Komponente CRON Scheduler vor und zeigt den Umgang mit dem neuen Windows-eigenem Aufgabenplaner.

Neulich am Montag im Büro war ich sehr vertieft in einer Problemstellung – und da passiert es. Pling machte es und es erschien ein Fenster meines E-Mail-Programms auf meinen Monitor, das mich an die nächste Besprechung erinnerte. Das brachte mich auf die Idee, diesen Artikel zu schreiben. Es gibt in vielen Programmen die Notwendigkeit, Tätigkeiten zeitabhängig durchzuführen. Diese Tätigkeiten sind zum Beispiel Erinnerungen anzeigen, Importe und Exporte durchführen, Statistiken durchführen oder Daten zu reorganisieren. Ebenfalls sehr beliebt ist es, in regelmäßigen Abständen auf Updates zu prüfen. Ich möchte in diesen Artikel zwei Methoden vorstellen und zeigen, wie diese Tätigkeiten aufgerufen werden können.
Zuerst möchte ich die Komponente „CRON Scheduler“ von Iztok Kacin vorstellen, die hier kostenlos heruntergeladen werden kann. Die Komponente steht unter der BSD-Lizenz. CRON ist aus der Linux- bzw. Unix-Welt bekannt. Dort gibt es verschiedene Implementierungen, allerdings haben alle etwas gemeinsam – nämlich die Konfiguration der Aktionszeitpunkte. Unter Linux wird vieles über textbasierende Dateien konfiguriert, so auch beim CRON. In der Konfigurationsdatei wird eine Tabelle angelegt, welche die Spalten Minute, Stunde, Tag, Monat und „Tag der Woche“ enthält. Über diese Spalten wird nun der Zeitpunkt konfiguriert, zu der eine Aktion stattfinden soll. Unter Linux würde noch das Programm angegeben, das aufgerufen werden soll. Das entfällt bei der Delphi-Komponente, dafür stellt sie Ereignisse bereit.
Das Symbol * steht für einen Platzhalter, der einem beliebigen Wert entspricht. Würde nun in jeder Spalte ein Stern stehen, wird die Aktion zu jeder Minute ausgelöst. Beispiel:

* * * * *

Wird nun in die Minutenspalte eine „0“ eingetragen, dann wird die Aktion zu jeder vollen Stunde ausgelöst. Also unter anderem um 12:00 Uhr, 13:00 Uhr, usw.

0 * * * *

Soll nun die Aktion immer 12:34 Uhr ausgelöst werden, dann braucht nur in die Minutenspalte „34“ und in die Stundenspalte „12“ eingetragen werden:

34 12 * * *

Dieses Vorgehen lässt sich natürlich auch auf die anderen verbleibenden Spalten anwenden. Werden Werte mit einen Komma voneinander getrennt, dann heißt das, dass zu dem oder zu dem andern angegeben Zeitpunkt reagiert werden soll. Muss eine Aktion um 10, 12 und 15 Minuten nach einer vollen Stunde ausgeführt werden, dann braucht nur Folgendes eingegeben werden:

10,12,15 * * * *

Ebenso einfach lassen sich Intervalle eingeben. Sie werden mit einem Slash eingegeben. Soll etwas alle 5 Minuten durchgeführt werden, wird das wie folgt festgelegt:

*/5 * * * *

Zu guter Letzt lassen sich auch Zeiträume festlegen. Sie werden mit einem Minus festgelegt. Soll nun etwas Montag bis Freitag durchgeführt werden, dann muss nur „1-5“ in die Spalte „Tag der Woche“ eingegeben werden. Der Sonntag entspricht dem Wert 0 oder 7.

* * * * 1-5

Ein letztes Beispiel an dieser Stelle. Soll nun etwas Montag bis Freitag um 1:15 Uhr und 1:45 im Dezember durchgeführt werden, dann müsste Folgendes eingegeben werden:

15,45 1 * 12 1-5

Nach diesem Ausflug kommen wir nun zurück zur Komponente. Sie nutzt genau die beschriebene Konfigurationssyntax von CRON, allerdings mit einem großen Unterschied: die Tabelle der Komponente enthält eine weitere Spalte. Die erste Spalte ist für die Angabe von Sekunden zuständig. Alle anderen bereits beschriebenen Spalten folgen dieser ersten Spalte. Das ist notwendig, da innerhalb von Programmen oft auch kürzere Intervalle als Minuten benötigt werden.

Um mit der Komponente zu arbeiten, wird als Erstes eine Instanz der Klasse TSchEventList benötigt. Diese Klasse ist von TObjectlist abgeleitet und wird später die einzelnen Erinnerungen enthalten. Die Instanz wird in der Delphi-üblichen Art via TSchEventList.Create() erstellt. Über diese Instanz kann nun mit der Methode Add(‚Ereignisname‘) eine Erinnerung angelegt werden. Als Rückgabewert der Methode gibt es dann eine neue Instanz der Klasse TScheduledEvent, die eine Erinnerung repräsentiert. Diese Instanz enthält die Eigenschaft Schedule. An dieser kann über zwei Arten festgelegt werden, wann das Ereignis ausgelöst werden soll. Zum einen gibt es die Eigenschaft EventPlan. Mit ihr können in der bereits beschriebenes CRON-Syntax die Ausführungstermine festgelegt werden. Besonders wenn kompliziertere Erinnerungen definiert werden sollen, sollte diese Eigenschaft genutzt werden. Zum anderen gibt es die Methode SetInterval(Sekunden, Minuten, Stunden, Tage, Monate, Jahre). Über diese Methode lassen sich eher einfache Erinnerungen bzw. Intervalle anlegen. Soll eine Erinnerung nur in einem bestimmten Zeitraum ausgelöst werden, dann können die Eigenschaften ValidFrom und ValidTo gesetzt werden. Die Eigenschaften nehmen jeweils ein TDatetime entgegen, welches den Ausführungszeitraum einschränkt. Ist der Zeitraum und das Intervall konfiguriert, dann braucht nur noch der Eigenschaft OnScheduleEvent die Methode zugewiesen werden, die ausgeführt werden soll, wenn der Zeitpunkt eintritt. Ein ausführliches Manual findet man hier.

Leider erfordert die Nutzung der Komponente, dass die Anwendung gestartet ist. Sollen nun Aufgaben ausgeführt werden, wenn das Programm nicht gestartet ist, dann kann zum Windows-eigenen Task Scheduler bzw. in Deutsch „geplante Aufgaben“ oder „Aufgaben planen“ gegriffen werden. Er ermöglicht das Starten von Programmen zu verschiedenen Ereignissen.

Windows Task Scheduler

Der Windows Task Scheduler wurde mit der Einführung von Windows Vista vollständig überarbeitet. Mit ihm kann man neben den üblichen Zeitpunktereignissen und wiederholenden Ereignissen auch auf Systemereignisse reagieren. Das sind unter anderem das Hochfahren des Systems, Benutzeranmeldungen, Leerlauf des PCs oder bestimmte Einträge im Windows-Ereignisprotokoll. Wer sich einen Überblick verschaffen möchte, öffnet am besten die Verwaltungskonsole und probiert eine Aufgabe anzulegen. Das Konsolen-Plug-in heißt taskschd.msc. Alternativ kann die Konsole natürlich auch über die Systemsteuerung aufgerufen werden.

Der Task Scheduler kann über mehrere Wege genutzt werden. Neben dem bereits erwähnten Management-Plug-in, gibt es ein auf Kommandozeilen basiertes Managementwerkzeug, das über den Namen schtasks.exe  erreicht werden kann. Mit dem Programm können abhängig von Parametern Aufgaben angelegt, angezeigt und gelöscht werden. Zusätzlich können XML-Dateien verarbeitet werden, die Aufgabendefinitionen enthalten. Beide Programme sind eher für das manuelle Bearbeiten geeignet; für die maschinelle Bearbeitung gibt es zwei Möglichkeiten: Zum einen kann der Aufgabenplaner über Windows Scripting angesprochen werden, zum anderen über ein C++-API, das ebenfalls aus Embarcadero Delphi heraus angesprochen werden kann. Leider fehlen in Delphi die Schnittstellendefinitionen, und die Entwickler von Jedi.org haben sie noch nicht in der JWA-Bibliothek umgesetzt. Die Definitionen der Schnittstellen befinden sich für C++ in der Datei Taskschd.h, die dem Windows SDK entnommen werden kann. Doch das Übersetzen ist viel zu aufwendig, weshalb es einen noch einfacheren Weg gibt, um an die Definitionen zu kommen. Über den Menüpunkt KOMPONENTEN | KOMPONENTE IMPORTIEREN kann die Definition mit „Typbibliothek importieren“ importiert werden. In der Liste der registrierten Bibliotheken findet sich ein Eintrag „Task Scheduler 1.1 Type Library“. Dieser Eintrag ist für den Import auszuwählen und der Delphi-eigene Importassistent ist bis zum Ende auszuführen. Nun stehen in Delphi die notwendigen Konstanten und Schnittstellen zur Verfügung.

Über den Aufruf CoTaskScheduler_.Create() wird eine Instanz zum einen ITaskService geliefert. Über dieses Interface wird auf die Task-Scheduler-Methoden zugegriffen. Als nächsten Schritt verbindet man sich mit dem Dienst. Diese Verbindung kann zu dem lokalen PC erfolgen oder zu einen Remote-Dienst auf einen anderen System. Zum Verbinden wird die Connect-Methode verwendet. Die Parameter sind Server, Benutzername, Domain und Passwort. Soll zum lokalen Dienst Verbindung aufgenommen werden, dann reicht es aus, alle Parameter mit EmptyParam zu füllen.
Aufgaben werden in Ordner organisiert, wie Dateien im Dateisystem. Für den Zugriff auf die Ordnerstruktur stellt die ITaskService-Schnittstelle die Methode GetFolder zur Verfügung. Diese nimmt als Parameter einen Pfad entgegen. Um auf die Hauptwurzel zuzugreifen wird einfach ein Backslash () übergeben. Zurück geliefert wird ein ITaskFolder Interface. Dieses stellt seinerseits Methoden zum Erzeugen (CreateFolder), Löschen (DeleteFolder), Anzeigen von Ordnern und Aufgaben (GetFolder, GetFolders, GetTask, GetTasks), sowie zum Registrieren und Erzeugen von Aufgaben (RegisterTask, RegisterTaskDefinition) zur Verfügung. Zu den Methoden gibt es noch zwei Eigenschaften. Name stellt den Namen des Ordners zur Verfügung. Path enthält den vollständigen Pfad, wo der aktuelle Ordner sich befindet. Wichtig mit dem Umgang der Methoden ist: wenn ein Pfad angegeben wird, darf er keinen abschließenden Backslash enthalten.
Doch kommen wir nun erst einmal zurück zu den Aufgaben. Um eine neue Aufgabe zu erstellen, wird an der ITaskService-Schnittstelle die Methode NewTask(0) aufgerufen. Der Parameter 0 ist Pflicht und ist konstant. Microsoft hat den Parameter für spätere Erweiterungen eingeführt. Als Rückgabewerte wird eine Referenz auf eine ITaskDefinition-Schnittstelle zurückgeliefert. Diese enthält alle notwendigen Eigenschaften, um eine Aufgabe zu definieren. Die Eigenschaft RegistrationInfo liefert die Schnittstelle IRegistrationInfo zurück. Dort können Grundinformationen wie Beschreibung (Description) der Aufgabe, der Autor (Author) und einige andere ausgelesen werden.

Abb. 1: Verwaltungskonsole für geplante Aufgaben

Über die Eigenschaft Settings an der Aufgabendefinition können die Aufgabeneigenschaften verändert werden, welche in der Verwaltungskonsole (Abb. 1) in der Karteikarte „Einstellungen“ zu finden sind. Die Beschriftungen in dem Dialog und die Eigenschaftsnamen sind sehr sprechend, daher werde ich an dieser Stelle die Eigenschaften nicht weiter beschreiben.

Aufmacherbild: Businessman with clock in time concept von Shutterstock / Urheberrecht: Elnur

[ header = Seite 2: Auslöser (Trigger) ]

Auslöser (Trigger)

Die wichtigsten Eigenschaften einer Aufgabe sind die Trigger und die Aktionen. Trigger sind die Auslöser einer Aufgabe. Aktionen sind die Tätigkeiten, die ausgeführt werden sollen, wenn ein Trigger ausgelöst wird. Es können mehrere Trigger und mehrere Aktionen angegeben werden, wobei bei den Triggern nur ein Ausgelöster ausreicht, damit alle definierten Aktionen der Aufgabe ausgeführt werden.
An der Aufgabenschnittstelle (ITaskDefinition) gibt es die Eigenschaft Triggers. Über diese erhält man Zugriff auf die Auslöser der Aufgabe. Die Eigenschaft liefert eine ITriggerCollection-Schnittstelle. Sie stellt eine Methode Create bereit, wodurch ein neuer Trigger erstellt wird. Die Trigger-Art kann über den notwenigen Parameter festgelegt werden. Die gültigen Parameter sind in Tabelle 1 zu erkennen. Zurückgeliefert wird eine ITrigger-Schnittstelle; sie kann allerdings jederzeit auf die zum Trigger gehörende Implementierung gecastet werden.

Konstante ID Schnittstelle Funktion
TASK_TRIGGER_EVENT 0 IEventTrigger Wird genutzt, um auf ein Systemereignis zu reagieren.
TASK_TRIGGER_TIME 1 ITimeTrigger Wird genutzt, um auf einen Zeitpunkt zu reagieren.
TASK_TRIGGER_DAILY 2 IDailyTrigger Wird genutzt, um ein tägliches Ereignis zu einer bestimmten Uhrzeit auszulösen.
TASK_TRIGGER_WEEKLY 3 IWeeklyTrigger Wird genutzt, um auf einen wöchentlichen Zeitplan zu reagieren.
TASK_TRIGGER_MONTHLY 4 IMonthlyTrigger Wird genutzt, um auf ein monatliches Ereignis an bestimmten Tagen zu reagieren (z. B. siebten oder letzten Tag)
TASK_TRIGGER_MONTHLYDOW 5 IMonthlyDOWTrigger Wird genutzt, um auf ein monatliches Ereignis an einem bestimmten Tag(en) einer Woche (Day of Week – DOW) zu reagieren.
TASK_TRIGGER_IDLE 6 IIdleTrigger Wird genutzt, um auf einen Systemleerlauf zu reagieren.
TASK_TRIGGER_REGISTRATION 7 IRegistrationTrigger Wird genutzt, um auf Änderungen an den Aufgaben zu reagieren.
TASK_TRIGGER_BOOT 8 IBootTrigger Wird genutzt, um auf den Systemstart zu reagieren.
TASK_TRIGGER_LOGON 9 ILogonTrigger Wird genutzt, um auf Anmeldeereignisse vom System zu reagieren.
TASK_TRIGGER_SESSION_STATE_CHANGE 11 ISessionStateChangeTrigger Wird genutzt, um auf eine Sitzungsänderung zu reagieren. Beispiel: Remote-Anmeldungen, Abmeldungen, Arbeitsstationsperrung und Entsperrungen.

Tabelle 1: Trigger-Konstanten und die implementierenden Schnittstellen

An der Basisschnittstelle ITrigger können allgemeine Dinge eingestellt werden. Die Eigenschaft Enabled legt generell fest, ob ein Trigger aktiviert ist oder nicht, über die Eigenschaften StartBoundary und EndBoundary wird ein Start- bzw. Enddatum festgelegt. Dieses ist als String in dem Format YYYY-MM-DDTHH:MM:SS(+-)HH:MM zu übergeben. Die Eigenschaft Type gibt an, auf welchen Schnittstellentyp die Schnittstelle gecastet werden darf. Die IDs bzw. die Konstanten können ebenfalls in Tabelle 1 und in der Enumeration TASK_TRIGGER_TYPE2 gesehen werden. Das Beispiel in Listing 1 verwendet den Auslösertyp TASK_TRIGGER_LOGON, der eine ILogonTrigger-Schnittstelle implementiert, um auf eine Anmeldung an Windows zu reagieren.

Aktionen

Nachdem wir nun die verschiedenen Auslöser kennengelernt haben, kommen wir zu den Aktionen, die ausgeführt werden sollen, wenn ein Auslöser ausgelöst wird. Um an einer Aufgabe eine Aktion zu erzeugen, wird an der bereits beschriebenen Schnittstelle ITaskDefinition die Eigenschaft Actions bemüht. Diese liefert eine IActionCollection-Schnittstelle. Mithilfe von dieser Schnittstelle können Aktionen erzeugt (Create), einzelne gelöscht (Remove) oder alle (Clear) Aktionen entfernt werden. Die Create-Methode funktioniert ähnlich der Create-Methode der Auslöser: Als Parameter wird die Art der Aktion übergeben. Als Aktionen stehen das Aufrufen von Programmen (TASK_ACTION_EXEC), das Aufrufen eines COM-Objekts (TASK_ACTION_COM_HANDLER), eine E-Mail verschicken (TASK_ACTION_SEND_EMAIL) und eine Nachricht anzeigen (TASK_ACTION_SHOW_MESSAGE) zur Verfügung. Als Rückgabe wird ein universelles IAction geliefert. Auch hier existiert eine Type Eigenschaft, mit der später ermittelt werden kann, welche spezielle Schnittstelle implementiert ist. Diese Schnittstellen sind IComHandlerAction, IExecAction, IEmailAction und IShowMessageAction. Das Beispiel in Listing 1 verwendet der Einfachheit halber die Variante mit „Nachricht anzeigen“. Da das Ausführen von Programmen häufiger vorkommt, stelle ich an dieser Stelle die IExecAction-Schnittstelle vor. Wie bereits angedeutet, wird die Create-Methode an der IActionCollection mit dem Parameter TASK_ACTION_EXEC aufgerufen. Als Rückgabe gibt es die universelle IAction, die aber direkt auf die IExecAction-Schnittstelle gecastet werden kann. Nun stehen direkt die Eigenschaften Arguments, Path und WorkingDirectory zur Verfügung. An diesen kann der Programmaufruf, der ausgeführt werden soll, konfiguriert werden.

var
  service: ITaskService;
  folder: ITaskFolder;

  task: ITaskDefinition;
  regTask: IRegisteredTask;

  msgAction: IShowMessageAction;
  logonTrigger: ILogonTrigger;
begin
  service:=CoTaskScheduler_.Create;
  service.Connect(EmptyParam, EmptyParam, EmptyParam, EmptyParam);
  caption:=IntToHex(service.HighestVersion, 8);
  folder:=service.GetFolder('');

  // Flags immer 0!
  task:=service.NewTask(0);
  task.RegistrationInfo.Author:='Bernd Ott';
  task.RegistrationInfo.Description:='Test-Task';

  task.Settings.StartWhenAvailable:=true;

  logonTrigger:=task.Triggers.Create(TASK_TRIGGER_LOGON) as ILogonTrigger;
  logonTrigger.Enabled:=true;

  msgAction:=task.Actions.Create(TASK_ACTION_SHOW_MESSAGE) as IShowMessageAction;
  msgAction.Title:='Titel';
  msgAction.MessageBody:='Hallo Welt';

  regTask:=folder.RegisterTaskDefinition('taskname', task, TASK_CREATE_OR_UPDATE, EmptyParam, EmptyParam, TASK_LOGON_INTERACTIVE_TOKEN, '');
end;

Fazit

Beide hier vorgestellten Methoden sind leicht in einer eigenen Anwendung zu nutzen. Dabei spielt es keine Rolle, welche Methode genutzt wird, da beide zum Ziel führen: Tätigkeiten zu einem bestimmten Zeitpunkt ausführen. Nur bei der Nutzung des Windows-Aufgabenplaners erhält man erweiterte Möglichkeiten, um auch auf Systemereignisse zu reagieren. Und auch nur hier braucht das Programm nicht gestartet sein.

Unsere Redaktion empfiehlt:

Relevante Beiträge

Meinungen zu diesem Beitrag

X
- Gib Deinen Standort ein -
- or -