Delphi programming blog
Источник: http://teran.karelia.pro/articles/item_4443.html
 

Планировщик задач (taskScheduler)

Опубликовано 15.10.2010 г. 00:28

Наверное, все знают такую службу windows, как планировщик задач или назначенные задания. Недавно решил отключить надоедавшее обновление продуктов Adobe, поискал его в автозагрузке и службах, но не нашел. Потом откопал его в назначенных заданиях. И здесь возник вопрос, а как разработчики добавляют задачи обновления своего ПО в планировщик?

Вобще по старой памяти со времени WinXP планировщик был не очень богат функционалом, так что я вообще редко когда им пользовался, да и вообще обычно данная служба была отключена. Если вы запустите планировщик в Vista или Windows 7 то наверняка заметите, что функционал заметно увеличился. Без долгих раздумий и поисков в MSDN можно найти TaskScheduler API для управления назначенными задачами. Скажу сразу, что в использовании данный API весьма прост. И почему то у меня сложилось впечатление, что человек его разрабатывавший когда то любил delphi :) Ибо нумерация всех элементов в коллекциях начинает с единицы. Вообще, если вы особо не знакомы с планировщиком (как я), то читая документацию по API вы узнаете, много нового о нем, о функциональных возможностях. Т.е весь тот функционал, который используется в оснастке планировщика (taskschd.msc) доступен с помощью COM-интерфейсов. Существует API двух версий. 1.0 - в win2000 & winXP, 2.0 - Vista & win7. Первый мы рассматривать не будем. В принципе, вы можете сами описать все необходимые интерфейсы, а можете импортировать библиотеку типов - taskschd.dll. А можете вообще ничего не делать, и скачать файл описания интерфейсов, который будет прицеплен к статье (: Исходный код опубликованный далее привязан к опубликованному файлу описания интерфейсов, т.к. я переименовал все перечисляемые типы, и некоторые свойства интерфейсов. Далее будет приведен простой пример использования API2.0 для решения такой задачи: заполнить ListView списком заданий, в котором вывести название заданий, время последнего запуска, время следующего запуска, статус задания. При выборе задания вывести его описание, производителя, версию. Если задача имеет исполняемый файл, указать его путь. Т.е если вам уже не интересно, то дальше читать не следует (: Картинка результата:
 
Итак, мы знаем что есть такая папка "Назначенные задания", которая раньше была доступна из панели управления (если не изменяет память), в windows 7 вы можете найти все ваши задачи в windows/system32/tasks. Каждая задача описывается xml-файлом. Т.е с ними можно работать как с xml файлами вручную. Весь список заданий организован в древовидную структуру. Для тех кто работать с xml не хочет, и реализованы COM интерфейсы (: Первое, что нам потребуется сделать - подключиться к службе планировщика. Для этого следует использовать интерфейс ITaskService и его метод Connect. Экземпляр класса можно получить как обычно используя CreateComObject либо воспользовашись вспомогательным классом CoTaskScheduler. Подключаться можно не только к локальной службе, но и к удаленной, поэтому метод Connect имеет соответствующие параметры. Для подключения к локальной, параметры должны быть пусты. После того как мы подключились к службе, нам следует выбрать папку заданий. Это именно те папки, которые приведены в win32/tasks. Т.е выбрать папку из дерева с помощью метода getFolder. Корневая папка обозначается как '\', выбрав папку мы получим объект интерфейса ITaskFolder. В нашем тестовом приложении мы разместим taskListView : TListView в котором создадим 4 колонки, добавим descriptionMemo для описания задания. В секцию private формы мы добавим список наших задач вида taskList : TList<IRegisteredTask>. Данный интерфейс описывает задачи в наших папках. Т.е любое задание планировщика. При создании формы мы выполним первую часть работы: заполним список. Итак:
procedure TMainForm.FormCreate(Sender: TObject);
var ts : ITaskService;
    tf : ITaskFolder;
    tc : IRegisteredTaskCollection;
    task : IRegisteredTask;
begin
    taskList := TList.create();

    ts := CoTaskScheduler.Create();
    ts.Connect('', '', '', '');

    tf := ts.GetFolder('\');
    getTasks(tf);
Мы создали экземпляр списка для хранения задач, Подключились к к службе планировщика с помощью ts : ITaskService. Выбрали корневую директорию tf : ITaskFolder. Далее мы хотим перебрать все задачи, которые имеются, для этого нам потребуется реализовать обход дерева, т.е обойти все вложенные папки, и получить для них задачи. Для этого мы реализовали вложенный метод getTasks. Какова его суть: для заданной папки получаем список ее заданий, и добавляем в нашу коллекцию. Далее получаем список вложенных папок. Для каждой из них запускаем процедуру рекурсивно. Задачи для папки мы можем получить используя метод getTasks, параметр принимает значения 1 или 0 (отображать скрытые или нет).
        procedure getTasks(folder : ITaskFolder);
        var  i : integer;
             tfc : ITaskFolderCollection;
        begin
            tc := folder.GetTasks(1);
            for i := 1 to tc.Count do taskList.Add(tc.Item[i]);

            tfc := folder.GetFolders(0);
            for i:=1 to tfc.Count do  getTasks(tfc.Item[i]);
        end;
методы getTasks & getFolders возвращают соответствующие коллекции папок и задач, элементы которых доступны через свойство items. [с названиями немного сумбурно вышло, следует различать getTasks(folder) и folder.getTasks() (:] На этом этапе мы имеем сформированный список задач. Теперь нам требуется на его основе заполнить listView. Немного забегая вперед, опишем еще одну функцию форматирования времени. Для каждой задачи мы можем посмотреть время последнего и следующего запуска, но когда они не определены время равно нулю, чтобы в коде не проверять это равенство, то просто добавим еще одну вложенную функцию
        function getDate(date:TDate):string; inline;
        begin
            if date = 0 then result := ''
            else result := DateToStr(date);
        end;
Вернемся теперь обратно в обработчик события создания формы, мы построили список задач и теперь обновим ListView:
    taskListView.Items.BeginUpdate();
    for task in taskList do begin
        with taskListView.Items.Add() do begin
            caption := task.Name;
            subItems.Add(getDate(task.LastRunTime));
            subItems.add(getDate(task.NextRunTime));
            subItems.Add(TaskStateNames[task.State]);
        end;
    end;
    taskListView.Items.EndUpdate();
Как видно мы используем следующие свойства задания: имя-Name, время последнего и следующего запуска - LastRunTime/NextRunTime, и состояние задачи. Для названий состояния заданий TTaskState мы подготовили следующий массив констант:
const TaskStateNames : array[TTaskState] of string = ('неизвестно', 'отключено', 'в очереди', 'готово', 'выполняется');
На этом наша форма запущена, и мы видим список заданий. Теперь вторая часть - отображения описания задачи и т.п. Каждое задание состоит из нескольких частей - выполняемые действия, вызывающие события(триггеры), настройки безопасности, регистрационная информация (содержит описание), доп. информация для сторонних разработчиков, и настройки выполнения.
Представление задания
Итак, для выбранного из списка задания нам требуется получить ее описание (т.е представление, то что на картинке) в виде интерфейса ITaskDefinition воспользовавшись свойством definition. Из данного описания мы можем получить регистрационную информацию в виде IRegistrationInfo. Ну а далее получим обычные текстовые описания, разработчика, версию, воспользовавшись одноименными свойствами. Следующий этап, если задача имеет действие, в виде исполняемого файла, то выведем к нему путь. Для этого нам потребуется коллекция IActionCollection, для каждого IAction из коллекции проверим его тип ActionType на равенство taExec. Если условие истина, то для данного действия мы запрашиваем интерфейс IExecAction и используем его свойство path. Теперь представим это в виде кода события смены пункта в listView:
procedure TMainForm.taskListViewSelectItem(Sender: TObject; Item: TListItem; Selected: Boolean);
var td : ITaskDefinition;
    regInfo : IRegistrationInfo;
    ac : IActionCollection;
    execAction : IExecAction;
    i:integer;
begin
    if not selected then exit;

    td := taskList[taskListView.ItemIndex].Definition;
    regInfo := td.RegistrationInfo;
    with descriptionMemo.lines do begin
        text := regInfo.Description;
        add( regInfo.Author + '/' + regInfo.Version );
    end;

    ac := td.Actions;
    for i := 1 to ac.Count do begin
        if ac.Item[i].ActionType = taExec then begin
            execAction := ac.item[i] as IExecAction;
            descriptionMemo.Lines.Add('Путь:  ' + execAction.Path );
        end;
    end;
end;
Вот и все. Вообще, мне весьма понравилась работа с этими интерфейсами, все как то очень просто получается. А напоследок вопрос к знатокам: в описанных интерфейсах часто используются строки. При импорте они все дружно назвались WideString. При переименовании в string все перестает работать. чем вызвано?

 

Метки:  windows 7  |  COM  |  task scheduler 

Комментарии

GunSmoker
15.10.2010 в 13:16
Ссылка в тему - советы по обновлялкам от сотрудника Microsoft. Начало серии Ответить
Знаток
18.10.2010 в 17:42
Ну так WideString это Unicode-строка, а String это Ansi-строка.
В первой размер символа 2 байта и именно с этой строкой работает вся юникодовая часть WinAPI.
Во второй же строке размер символа 1 байт, если только это не D2009 и выше, там перешли по дефолту
на UTF-8.
Практически все библиотеки типов, ActiveX, Com & Ole серверы используют WideString.
Попытки использовать AnsiString приведут к непониманию =)
ter
18.10.2010 в 21:10
речь идет о д2010 (:
ter
18.10.2010 в 23:10
Видимо различие в том что string = UnicodeString, что есть не совсем WideString
planificateur de tache
04.10.2011 в 04:42
Спасибо, очень хорошо объяснить!
ter
04.10.2011 в 10:24
thanx :)
Сергей
18.10.2013 в 09:11
А можно обновить ссылку на исходники примера ?
teran
19.10.2013 в 14:06
да вроде рабочая была ссылка,
тем не менее обновил, в конце статьи.
tosha
17.06.2014 в 12:28
Здрасьте, а подскажите начинающему, что значит объявление в Private секции taskList : TList В таком виде хмм, не компилирует, а если оставить TList просто, то затем требует в строке taskList.Add(tc.Item) Pointer, а там IRegisteredTask ?
ter
02.07.2014 в 10:03
чую опоздал я с ответом.
TList - обобщенные коллекции из generics.collections. Если у вас Delphi старых версий, то компилироваться действительно не будет. Обобщения (generics) появились только с 2009й версии.

в целом TList это индексированный список. В угловых скобках указывается тип элементов, которые он будет содержать. Более подробно можно узнать в соответствующей главе документации к Delphi.
tosha
04.08.2014 в 16:53
Да, спасибо всё равно, что ответили) Во всём к тому времени уже разобрался, дельфи да, старовато, но такие правила. Ваше описание интерфейсов использовал, в свою прогу интегрировал, задачи создаются, удаляются, апдейтятся, выполняются. Пример использования взял отсюда: http://code.msdn.microsoft.com/CSTaskScheduler-2f70d723/sourcecode?fileId=64961&pathId=545079553
Зашёл поблагодарить: сэкономили минимум неделю работы, ещё раз огромное спасибо!
tosha
17.06.2014 в 12:44
секции taskList: TLISTменьшеIregisteredTaskбольше а то съел знаки форум
- Имя
- e-mail*
- Сайт
вы можете использовать теги [i],[b],[code],[quote]
Дополнительно