Создаем средство для автоматического обновления продуктов Symantec на Delphi

За долгое время использования антивирусного ПО Symantec мне ни разу не приходилось сомневаться в качестве его работы. Единственное, что меня всегда огорчало, это невозможность создания локального зеркала обновлений (к Corporate-версиям не относится). Более того, почему-то до сих пор нельзя настроить соединение с сервером обновлений через прокси, и всем, кто не сидит в Интернете через VPN, приходится регулярно скачивать полный архив баз весом в 35 Мб. Сегодня мы наконец немного исправим эту ситуацию.

Введение

Так уж повелось, что у администраторов локальных сетей Интернет бесплатный. Хороший администратор всегда заботится о вирусном климате в сети, поэтому постоянно скачивает обновления для антивирусов. Чтобы облегчить жизнь своим пользователям, администраторы поднимают локальные сервера обновлений и заставляют всех прописывать в своих антивирусах адреса этих серверов. Таким образом, антивирусы сами берут с такого сервера свежие базы, сами обновляются, а пользователи в это время занимаются своими жизненно важными делами. В Сети можно найти рекомендации для создания локальных серверов Kaspersky, NOD32 и некоторых других, но нигде нет рекомендаций для Symantec. Что ж, для нас это не преграда. На странице http://www.symantec.com/business/security_response/definitions.jsp всегда можно взять свежие базы для любого продукта. Скачав базы, их можно выложить в обычный общий доступ, и нам останется только написать программу, которая заставит антивирус пользователя обновляться. К счастью, базы Symantec распространяются в самораспаковывающемся архиве. Наша программа должна будет запускать этот архив, сама отвечать на все вопросы и периодически проверять некоторую общую папку в сети на предмет наличия новых баз. Задача, в общем, совсем не сложная. Мы ее решим в среде Delphi.

Ядро программы

Нашим ядром будет алгоритм, который копирует файл по сети, запускает его и отвечает на все вопросы мастера обновлений. Итак, копировать базы можно как во временные папки, так и в определенное место, например, в папку с антивирусом. Для того, чтобы определить адреса временных (и не только) папок в системе, существует функция SHGetSpecialFolderPath, которая содержится в файле shell32.dll. К счастью, программисты Borland сделали вызов этой функции в юните ShlObj. В параметрах ей нужно передавать идентификационный номер нужной папки (см. таблицу). Для более удобного использования напишем свою функцию:

function GetSpecialPath(CSIDL: word): string;
var s: string;
begin
SetLength(s, 100);
if not SHGetSpecialFolderPath(0, PChar(s), CSIDL, true)
then s := '';
result := PChar(s);
end;

Здесь CSIDL – это идентификатор нужной папки. Мне пришлась по душе папка для Cookies.

Теперь непосредственно копируем файл с базами и запускаем его:

CopyFile('\\Server\upload\Symantec\updates\base.exe',
PChar(GetSpecialPath($0021)+'\base.exe'),true);
WinExec(PChar(GetSpecialPath($0021)+'\base.exe'),0);

Все это поместилось в процедуру nupdate:

procedure nupdate;
begin
with form1 do begin
CopyFile('\\Server\upload \Symantec\updates\ase.exe',
PChar(GetSpecialPath($0021)+'\base.exe'),true);
WinExec(PChar(GetSpecialPath($0021)+'\base.exe'),0);
end; end;

Программа для обновления задает ненужные вопросы, вроде «Вы действительно хотите обновить базы или запустили меня просто так?», поэтому сейчас мы научим наше приложение отвечать на них.

procedure TForm1.Timer1Timer(Sender: TObject);
var
h,j,k,l: hwnd;
b:bool;
begin

h:= findwindow(nil,'SARC Intelligent Updater'); //Ищем окна по заголовкам
l:= findwindow(nil,'SARC Intelligent Updater');
j:=findwindowex(h,0,nil,PChar('&Да'));
k:=findwindowex(l,0,nil,PChar('ОК'));

SendMessage(j,WM_LButtonDown,1,1);
SendMessage(j,WM_LButtonUP,1,1); //Кликаем Да, потому что мы очень хотим обновиться

if k<>0 then begin
SendMessage(k,WM_LButtonDown,1,1); //Кликаем ОК в конце обновления
SendMessage(k,WM_LButtonUP,1,1);
DeleteFile(GetSpecialPath($0021)+'/new_base.exe');
end;

end;

Теперь некоторые комментарии по коду. Во-первых, в переменных h и l хранится два разных хэндла. При внимательном рассмотрении можно заметить, что заголовки окна обновления до начала работы и после отличаются на один пробел после слова SARC. Я довольно долго с этим промучился, пока не запустил Spy++ и не скопировал заголовки оттуда. Функция findwindowex ищет хэндл на контрол с заданным хэндлом родительского окна. В j и k мы запихиваем ссылки на кнопки Да и ОК соответственно. Теперь остается только послать в окошко обновления сообщения о нажатии на эти кнопки. Правильно это делать так, как показано в процедуре: сначала нажимаем кнопку, потом отпускаем.

В принципе наша программа уже работоспособна. Теперь осталось оформить ее в виде утилитки и снабдить настройками.

Дополнительные функции

Как и положено подобной утилите, она должна уметь прописываться в автозагрузку и удаляться из нее. Для этого нам послужит юнит Registry. Привожу код процедур, которые этим занимаются.

procedure putauto;
var reg:tregistry;
begin
reg:=Tregistry.create;
with form1 do begin
Reg.RootKey:=HKEY_LOCAL_MACHINE;
Reg.OpenKey('Software',true);
Reg.OpenKey('Microsoft',true);
Reg.OpenKey('Windows',true);
Reg.OpenKey('CurrentVersion',true);
Reg.OpenKey('Run',true);
Reg.WriteString('Norton Update',Application.ExeName);
Reg.CloseKey;
Reg.Free;
end; end;

procedure deleteauto;
var Reg:TRegistry;
begin
Reg:=TRegistry.Create;
Reg.RootKey:=HKEY_LOCAL_MACHINE;
Reg.OpenKey('Software',true);
Reg.OpenKey('Microsoft',true);
Reg.OpenKey('Windows',true);
Reg.OpenKey('CurrentVersion',true);
Reg.OpenKey('Run',true);
Reg.DeleteValue('Norton Update');
Reg.CloseKey;
Reg.Free;
end;

Здесь все довольно прозрачно. Мы прописываемся в самое видное место в реестре, но наша программа нужная и полезная, поэтому каким образом ее запускать – неважно. Антивирусы, контролирующие автозагрузку, будут молчать, т.к. вирусы уже давно не записывают себя в эту ветку.
Теперь научим нашу программу сворачиваться в трей. Для этого объявим четыре процедуры:

procedure WindowMessage(Var Msg:TMessage); message WM_SYSCOMMAND;
procedure ActionIcon(n:Integer;Icon:TIcon);
procedure OnMinimizeProc(Sender:TObject);
procedure MouseClick(var Msg:TMessage); message WM_USER+1;

и, собственно, вот их код:

Procedure TForm1.WindowMessage (var msg:TMessage);
Begin
if msg.WParam=SC_MINIMIZE then
begin
ActionIcon (1,Application.Icon); //Добавляем значок в трей
ShowWindow(Handle,SW_HIDE); //Скрываем окно приложения
ShowWindow(Application.Handle,SW_HIDE); //Скрываем кнопку с TaskBar'а
maximize1.Enabled:=true;
minimize1.Enabled:=false;
end else inherited;
end;

procedure TForm1.ActionIcon(n:Integer;Icon:TIcon);
var Nim:TNotifyIconData;
begin
with Nim do // Заполняем структуру Nim
begin
cbSize:=SizeOf(Nim); //Размер
Wnd:=Form1.Handle; //Хэндл нашего приложения(окна)
uID:=1;
uFlags:=NIF_ICON or NIF_MESSAGE or NIF_TIP;
hicon:=Icon.Handle; //Хэндл передаваемой в процедуру иконки
uCallbackMessage:=wm_user+1;
szTip:='Обновление Norton';
end;
case n of //Действия выполняемые процедурой
1: Shell_NotifyIcon(Nim_Add,@Nim);
2: Shell_NotifyIcon(Nim_Delete,@Nim);
3: Shell_NotifyIcon(Nim_Modify,@Nim);
end;
end;

procedure TForm1.OnMinimizeProc(Sender:TObject);
begin
PostMessage(Handle,WM_SYSCOMMAND,SC_MINIMIZE,0); //реакция на системное сообщение свертки
end;

procedure TForm1.MouseClick(var msg:TMessage);
var p:tpoint;
Sender: TObject;
begin
GetCursorPos(p); //Запоминаем координаты курсора мыши
Case Msg.LParam OF //Проверяем какая кнопка была нажата
WM_LBUTTONDOWN,WM_LBUTTONDBLCLK: {Действия, выполняемый по одинарному или двойному щелчку левой кнопки мыши на значке}
begin
if maximize1.Enabled then
begin
ShowWindow(Handle,SW_SHOW); //Восстанавливаем окно программы
maximize1.Enabled:=false;
minimize1.Enabled:=true;
SetForegroundWindow(handle);
end
else
begin
//if focused then
OnMinimizeProc(sender);
//else SetForegroundWindow(handle);
end;
end;
WM_RBUTTONUP:
begin
SetForegroundWindow(Handle); //Восстанавливаем программу в качестве переднего окна
PopupMenu1.Popup(p.X,p.Y); //Заставляем всплыть TPopUp, в котором у нас содержатся стандартные действия Свернуть/Развернуть/Закрыть PostMessage(Handle,WM_NULL,0,0);
end; end; end;

Планировщик

Для того чтобы обновления выполнялись регулярно, мы создадим простой планировщик.

procedure TForm1.Timer2Timer(Sender: TObject);
var f:textfile;
s:string;
begin
AssignFile(f,getfiledir(application.ExeName)+'/schedule.bin');
reset(f);
readln(f,s);
closefile(f);
if Date>=StrToDate(s) then
begin
rewrite(f);
writeln(f,DateToStr(Date+delta));
closefile(f);
nupdate; //выполняем обновление непосредственно
end;
end;

В файле schedule.bin будет находиться дата следующего обновления. Таймер будет периодически проверять дату и, если она «просрочена», запускать обновление. Интервал таймера можно поставить на несколько минут. Его постоянная работа совершенно не будет заметна.

Визуальное оформление

В принципе уже все готово. Осталось добавить форму чекбоксов и радиокнопок для создания пользовательского интерфейса. Т.к. программа предназначается для массового использования, то не грех ее и оформить. Для этого можно использовать компоненты с поддержкой скинов. В своей программе я использовал SXSComponents. Из «вкусностей» был добавлен полупрозрачный режим и отдельное диалоговое окошко с настройками. Что в итоге вышло, вы можете посмотреть на скриншотах.

Последним штрихом является упаковка утилиты с помощью какого-нибудь EXE-упаковщика. Например, у меня сырая версия занимает около мегабайта, а упакованная всего 300 КБ. Мелочь, а приятно.

Заключение

Сегодня мы создали средство для автоматического обновления антивирусных продуктов Symantec. Замечу, что с выходом Norton 360 2.0 изменился формат антивирусных баз, поэтому нужно либо заставлять своих пользователей использовать новые версии антивирусов, либо организовать проверку на версию установленного продукта. Таким же образом можно создать утилиту для обновления NOD32, если вы знаете, откуда качать полные версии баз. Приветствуются любые модификации!

Алексей Голованов AlekseyGolovanov@mail.ru


Компьютерная газета. Статья была опубликована в номере 30 за 2009 год в рубрике программирование

©1997-2022 Компьютерная газета