Task scheduler vel. harmonogram zadań

kubryk

Witam!

Jak dodać zadanie do schedulera?
Jak napisać program, żeby się sam uruchamiał?
Jak z poziomu Delphi utworzyć plik .job?

Widzę, że dość dużo pytań tego typu przewija się (najczęściej bez odpowiedzi niestety) przez grupy i różne forum. Tak więc wychodząc na przeciw przyszłym problemom, opisze jak z tym sobie poradzić.

Po pierwsze musimy zassać z sieci bibliotekę MsTask, która zawiera wszystkie potrzebne nam rzeczy. Znajdziemy ją pod adresem:
http://codecentral.borland.com/codecentral/ccweb.exe/listing?id=16007

Później utworzymy unit, zawierający potrzebne nam procedurki. Aby było szybko, łatwo i przyjemnie, cały kod zamieszczam poniżej:

unit SchedulerUnit;

interface

Uses MsTask, Windows, ActiveX, SysUtils;

function StrToWide(SourceString : String): LPCWSTR;
procedure AddToScheduler(ApplicationPath : String; Hour : String);

implementation

var
  wsBuffer : PWideChar;
  SchedulingAgent : ITaskScheduler;
  Task : ITask;

function StrToWide(SourceString : String): LPCWSTR;
begin
  Result := StringToWideChar(SourceString, wsBuffer, Length(SourceString) + 1);
end;

procedure AddToScheduler(ApplicationPath : String; Hour : String);
var
  pIPersistFile : IPersistFile;
  TaskName : String;
  WorkItem : IUnknown;
  piNewTrigger : Word;
  ITTrigger : ITaskTrigger;
  TaskTrig : TTaskTrigger;
  uname, passwd : PWideChar;
begin
  GetMem(wsBuffer, 1024);

//  Inicjowanie schedulera;
  if not assigned(SchedulingAgent) then
  begin
    ActiveX.CoInitialize(nil);
    ActiveX.CoCreateInstance(CLSID_CSchedulingAgent, nil, CLSCTX_INPROC_SERVER, IID_ITaskScheduler, SchedulingAgent);
  end;

  TaskName := 'MojeZadanie'; //nazwa zadania
  SchedulingAgent.Delete(StrToWide(TaskName)); //kasowanie, żeby zabezpieczyć się przed tworzeniem istniejącego zadania

  SchedulingAgent.NewWorkItem(StrToWide(TaskName), CLSID_CTask, IID_IScheduledWorkItem, WorkItem); //utworzenie zadania
  Task := ITask(WorkItem);
  Task.SetApplicationName(StrToWide(ApplicationPath));
  Task.SetWorkingDirectory(StrToWide(ExtractFilePath(ApplicationPath));

  GetMem(uname, 255);
  GetMem(passw, 255);
  StringToWideChar(User, uname, Length(User) + 1);
  StringToWideChar(Pass, passw, Length(Pass) + 1);
  Task.SetAccountInformation(uname, passw);  //(User, Password) Ustawnienie konta usera

  Task.CreateTrigger(piNewTrigger, ITTrigger); //utworzenie triggera
  ZeroMemory(@TaskTrig, sizeof(TASK_TRIGGER));
  TaskTrig.cbTriggerSize := sizeof(TASK_TRIGGER);
  TaskTrig.wBeginYear := 2004; //
  TaskTrig.wBeginMonth := 7;    // data rozpoczęcia - dowolna
  TaskTrig.wBeginDay := 25;     //
  TaskTrig.wStartHour := StrToInt(Copy(Hour, 1, 2)); //godzina wykonania
  TaskTrig.wStartMinute := StrToInt(Copy(Hour, 4, 2)); //minuty wykonania
  TaskTrig.TriggerType := TASK_TIME_TRIGGER_DAILY; //typ triggera (dni)
  TaskTrig.Type_.Daily.DaysInterval := 1; //ustawienie typu - codziennie
  ITTrigger.SetTrigger(@TaskTrig); //zapisanie triggera

//  Zapisywanie .job;
  Task.QueryInterface(IID_IPersistFile, pIPersistFile);
  if pIPersistFile <> nil then
    pIPersistFile.Save(nil, true);

  FreeMem(wsBuffer);
  FreeMem(uname);
  FreeMem(passw);
end;

end.

W tym konkretnym przykładzie zadanie będzie wykonywane codziennie o godzinie podanej w parametrze procedury (w postaci 'hh:mm').
Podobnie ścieżkę do programu podajemy w parametrze procedury. Więc przykładowe wywołanie będzie wyglądało nastęoująco:

AddToScheduler('c:\katalog\mojprogram.exe', '14:31');

W tym konkretnym przykładzie zadanie będzie wykonywane codziennie, jednak nic nie stoi na przeszkodze, aby robić to co tydzień, albo co dwa dni. Należy jedynie zmienić parametry TaskTrig.TriggerType.
Więcej informacji o tym znajdziecie na stronie MS pod adresem:
http://msdn.microsoft.com/library/en-us/taskschd/taskschd/trigger_type_union.asp

Wszystkie zadania w postaci plików *.job przechowywanie są w %WINDIR%\Tasks. W ten sposób też możemy sprawdzać, czy jakieś interesujące nas zadanie istnieje (za pomocą FileExists).

Tak po krótce przedstawia się sprawa planowania zadań w Windows. Oczywiście przedstawiłem tu tylko nieznaczną część możliwości (jednak tą najbardziej potrzebną) Schedulera. A po resztę odsyłam na wspomnianą wcześniej stronę MSDN.

Pozdrawiam i życzę owocnej pracy, Kuba Kubryński

4 komentarzy

co to znaczy "vel." :D

Ja poproszę następną porcję :) Ponadto proponowałbym umieszczenie w procedurze AddToScheduler() po procedurze GetMem() sekcji try, a wywołanie FreeMem() do sekcji finally. Nie wiem, czy się zgodzicie (mogą być ważne powody, dla których chcemy stracić parę bajtów :) ), ale takie jest moje skromne zdanie.

troche krótki ten artykuł. ale fajny

Na początku procedury "AddToScheduler" jest instrukcja:

// Inicjowanie schedulera;
if not assigned(SchedulingAgent) then

To sprawdzenie ma sens pod warunkiem, że zmienna "SchedulingAgent" jest zmienną globalną. Zmienne globalne są inicjowane pustymi wartościami. Natomiast zmienne lokalne mają wartości przypadkowe do czasu podstawienia pod nie pierwszej wartości. Poza tym wartości zmiennych lokalnych nie są zachowywane pomiędzy wywołaniami podprogramów.