Точки восстановления

altПолучаем точки восстановления при помощи WMI.

Начиная с версии XP (а если быть точней, то с Windows ME), системы семейства Windows обзавелись очень полезным механизмом – созданием точек восстановления. Благодаря этой возможности ты можешь не бояться (вру, бояться все равно нужно) последствий установки левого драйвера, который стал вгонять систему в BSOD при загрузке. Создал точку останову (в идеале, ее должен создать установщик приложения/драйвера), установил бажный драйвер. Не работает? Не беда! При появлении стартового меню выбираем загрузку с последней точки восстановления и перед тобой вновь рабочая система. Разве, не замечательно?

Как посмотреть созданные точки восстановления

Для проделывания этой операции не нужно быть гуру Windows. В меню «Пуск» -> «Стандартные» -> «Служебные» есть ярлык на запуск приложения «Восстановления системы». Попробуй запустить его. Перед тобой должен появиться мастер восстановления системных файлов и параметров. Нажав один раз кнопку «Далее», ты увидишь окно, в котором и будут перечислены все имеющиеся в системе точки восстановления.

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

Точки восстановления в Windows

Чтобы перебрать все имеющиеся точки восстановления есть как минимум три способа:

Windows API. В системе есть соответствующие API функции, которые ты без проблем сможешь заюзать в своем приложении. Наверняка эти функции не описаны в стандартных заголовчных файлов, поэтому тебе однозначно придется погрузиться в изучении MSDN и описать все необходимое самостоятельно.

WMI (Windows Management Instrumention). Инструментарий для управления Windows представляет из себя набор интерфейсов для управления операционной системой через специальные компоненты. Одним из главных преимуществ этой технологии – возможность управлять ОС как локально, так и удаленно. WMI уже достаточно старая технология и ее поддерживают абсолютно все версии Windows (для 9x нужно скачивать отдельно). Мне нравится WMI за простоту использования. Сегодня ты в этом убедишься.

PowerShell. Windows PowerShell – это язык сценариев, построенный (и интегрированный) на базе .NET Framework. При работе с этим языком ты взаимодействуешь с командлетами – готовыми приложениями. Таких приложений великое множество и при умелом использовании языка сценариев PW ты можешь творить настоящие чудеса в системе (под чудесами я подразумеваю богатые возможности для автоматизации).

Каждый из этих способ хорош по-своему. Самым универсальным из них конечно же будет первый. Функции API – они и в Африке API. Работают безотказно. Прямо как автомат Калашникова. Несмотря на их универсальность и крутость, сегодня мы их оставим в покое, а решим поставленную задачу при помощи технологии WMI.

Немного занудной теории

Не будет врезаться с разбегу о телегу, сначала разберемся с теорией. Для того, чтобы пробежаться по всем точкам восстановления нужно нам придется воспользоваться WMI классом SystemRestore ( http://msdn.microsoft.com/en-us/library/aa378951%28v=VS.85%29.aspx ). В этом классе реализованы методы для выполнения различных операций над точками восстановления. Среди них:

— Включение/отключение мониторинга точек восстановления
— Получение списка доступных точек восстановления
— Инициирование восстановления на локальной системе.

Сам класс описан следующим образом:

class SystemRestore
{
String Description;
uint32 RestorePointType;
uint32 EventType;
uint32 SequenceNumber;
String CreationTime;
};
Ну а теперь поговорим подробней о методах и свойствах этого класса. Начнем с свойств. Их не так уж и много.

CreationTime (String) – дата создания точки восстановления

Description (String) – описание точки восстановление. Используется для того чтобы можно определить для чего же была эта точка создана.

EventType (uint32) – тип события. Может принимать одно из следующих значений:
BEGIN_NESTED_SYSTEM_CHANGE (102) – Начало изменений в системе. Следующие вложенные вызовы не создают новую точку восстановления. В качестве следующего вызова ты должен использовать END_NESTED_SYSTEM_CHANGE, а не END_SYSTEM_CHANGE.

BEGIN_SYSTEM_CHANGE (100) – Изменения в системе начались
END_NESTED_SYSTEM_CHANGE (103) – Изменения в системе завершены
END_SYSTEM_CHANGE (101) — Изменения в системе завершены

RestorePointType (uint32) — тип точки восстановления. Может принимать одно из следующих значений:

APPLICATION_INSTALL (0) – приложение было установлено.
APPLICATION_UNINSTALL (1) – приложение было деинсталлировано
CANCELLED_OPERATION (13) – приложению необходимо удалить созданную точку восстановление. Такая ситуация может возникнуть, когда пользователь решил отказаться от установки приложения.
DEVICE_DRIVER_INSTALL (10) – драйвер устройства был установлен
MODIFY_SETTINGS (12) – приложение добавило или удалили какие-то свои возможности/компоненты.

SequenceNumber (uin32) – порядковый номер точки восстановления.

Как видишь, все достаточно просто и понятно. Зная теорию, можно состряпать работоспособный пример. Но перед тем как это сделать, взглянем функции, которыми нам придется воспользоваться в Delphi. Можешь не волноваться, их будет совсем немного.

procedure OleCheck(Result:HResult). Данная процедура описана в модуле ComObj и предназначена для создания исключительной ситуации EOleSysError, если в переданном в качестве параметра выражении возникла ошибка. В качестве единственного параметра, процедуре OleCheck можно передавать любые выражения имеющие тип HResult. Благодаря этой процедуре у нас появляется отличная возможность обрабатывать исключительную ситуацию.

Для типа HResult существует предопределенные константы. Некоторые из них я перечислю ниже:

S_OK – операция выполнена успешно
S_FALSE – операция выполнена успешно. Это константа отличается от предыдущей тем, что подразумевает специфическую особенность при выполнении функции. Применение S_FALSE строго не регламентируется.
E_FAIL – просто ошибка. Причина не указана.
E_UNEXPECTED – катастрофическая ошибка. Операция не может быть выполнена из-за непредвиденной ситуации
E_NOTIMPL – описание функции не реализовано.
E_OUTOFMEMORY – недостаточно памяти.
E_INVALIDARG – неверный аргумент функции
E_NOINTERFACE – запрашиваемый интерфейс отсутствует на сервере.
E_POINTER – неверный указатель.
E_HANDLE – ошибочный дескриптор.
E_ABORT – операция прервана
E_ACCESSDENIED – доступ запрещен.

function CreateBindCtx (reserved:LongInt; out bc: IBindCtx): HResult; stdcall;

Функция CreateBindCtx() возвращает указатель на реализацию интерфейса IBindCtx. Этот объект хранит информацию о том, как выполняется процесс связывания. Как только функция завершит свое выполнение, она создаст новый контекст с заполненной структурой BIND_OPS2.

Этой функции требуется передать всего лишь два параметра:

reserved. Параметр зарезервирован и должен быть 0.
ppbc. Указатель на интерфейс IBindCtx.

function MkParseDisplayName(bc: IBindCtx; szUserName: POleStr;
out chEaten: Longint; out mk: IMoniker): HResult; stdcall;
Функция MkParseDisplayName предназначена для разбора строки на моникеры (моникер – это такой com-объект, который реализует интерфейс IMoniker и позволяющий клиенту получить указатель на объект).

Из параметров, эта функция требует:

bc – указатель на интерфейс IBindCtx, реализованный у связывающего объекта.
szUserName [in] – указатель на отображаемое имя для разбора.
pchEaten [out] – указатель на число символов szUserName, которые были использованы.

Если функция выполнится успешно, то здесь будет содержаться длина szUserName, иначе здесь будет число успешно распарсенных символов.

ppmk [out] – адрес указателя на интерфейс IMoniker. Переменная по этому адресу получит указатель на интерфейс построенный из szUserName.

Из возвращаемых значений функция может вернуть: E_OUTOFMEMORY (см. выше), S_OK (см. выше), MK_E_SYNTAX (ошибка в синтаксисе имени файла или ошибка синтаксиса моникера-результата).

Кроме того, данная функция может также вернуть любую из ошибок, возвращаемых IMoniker::BindToObject, IOleItemContainer::GetObject или IParseDisplayName::ParseDisplayName.

На этом с теорией покончено и можно, переходить к разбору реального примера. Если ты что-то не понял из теории, то не отчаивайся. При рассмотрении реального примера все должно будет встать на свои места. На крайний случай, не поленись заглянуть в MSDN. Там все подробненько описано. Правда на английском и все примеры приведены на C++. Но тебя, же не остановит этот маленький барьер?

Делаем пример

Настало время самого интересного – рассмотрение практического примера. Запускай Delphi и создавай новый проект типа Application. Не забудь все это дело сохранить. Как сохранишь, набросай примерно такой же дизайн как и в моем примере.



Компонент много не понадобится. Я всего лишь бросил на форму один компонент TListView, в котором создал несколько колонок (имена колонок равны свойствам рассматриваемого выше класса SystemRestore) и одну кнопку, по нажатию которой и будет выполняться код для получения всех точек останова. Для удобства, я расположил кнопку на предварительно созданной панели инструментов. На этом с дизайном все.

Когда я начинал писать эту статью, то решил, что будет весьма неплохо сделать код получения точек восстановления многоразовым. Сказано сделано. Создай новый Unit и сохрани его под именем RestoreViewer. Сейчас мы в нем опишем новый класс.

Перейди в редактор кода. В нем у тебя должен быть открыт пустой, только что созданный Unit – RestoreViewer. Написание универсального класса мы начнем с подключения необходимых модулей. В секцию Uses допиши имена следующих модулей:

SysUtils, Variants, ComObj, ActiveX.

Для нашего класса их более, чем достаточно. Теперь займемся описанием констант. Ты помнишь, что когда я рассказывал теорию из MSDN, то мне приходилось оперировать символьными названием констант. Если ты просто так начнешь ссылаться на них в коде, то ничего хорошего у тебя не выйдет. Компилятор начнет материться как сапожник, т.к. ни в одном из дельфячих модулей эти константы не описаны. В общем не теряй времени, а скорей объявляй секцию const и опиши в ней константы:

//Константы, характеризующие тип события
BEGIN_NESTED_SYSTEM_CHANGE = 102;
BEGIN_SYSTEM_CHANGE = 100;
END_NESTED_SYSTEM_CHANGE = 103;
END_SYSTEM_CHANGE = 101;
//Константы, характеризующие тип точки восстановления
APPLICATION_INSTALL = 0;
APPLICATION_UNINSTALL = 1;
CANCELLED_OPERATION = 13;
DEVICE_DRIVER_INSTALL = 10;
MODIFY_SETTINGS = 12;
В качестве имен констант я брал те же самые, которые и указаны в MSDN. Значения константы взяты там же. Ok, двинемся к следующему шагу – описанию структуры, для хранения информации о точке восстановления. Можно было конечно для этой цели завести отдельный класс, но зачем забивать гвозди плоскогубцами? Структуру я описал следующим образом:

TRestorePoint = record
Description:string;
EventType:string;
SequenceNumber:string;
CreationTime:string;
RestorePointType:string;
end;
Ее свойства я расписывать не стану, т.к. они идентичны свойствам класса SystemRestore. Для чего я описал структуру, ведь мог обойтись одними переменными? Да, можно было и так, но в нашей ситуации, использование структуры будет более эстетичным, чем объявление нескольких переменных. К тому же, при необходимости, ее свойства будет легко расширить.



Константы объявлены, структура описана – самое время заняться изобретением простенького класса. Свой класс я обозвал как TRestoreViewer. В целом он выглядит так:

TRestoreViewer = class
private
function RestorePointTypeToStr(RestorePointType:Integer):string;
function EventTypeToStr(EventType:Integer):string;
function WMITimeToStr(WMITime:string): string;
function GetWMIObject (const ObjectName:string):IDispatch;
public
function GetRestorePoints():TRestorePoints;
end;
Из public методов я объявил лишь GetRestirePoint(). Именно он будет возвращать все имеющиеся точки восстановления. Сами точки будут возвращаться в виде массива элементов из структур типа TRestorePoint. Чтобы функция возвращала массива, нужно определить новый тип:

TRestorePoints = array of TRestorePoint;

С внешним видом класса все должно быть понятно. Разберем метод GetRestorePoints():

function TRestoreViewer.GetRestorePoints: TRestorePoints;
var
ObjWMIService : OLEVariant;
ColItems : OLEVariant;
ColItem : OLEVariant;
OEnum : IEnumvariant;
IValue : LongWord;
CountOfRestorePoint : Integer;
I: Integer;
RestorePoint : TRestorePoint;
begin
ObjWMIService := GetWMIObject(‘winmgmts:\\localhost\root\default’);
ColItems := objWMIService.ExecQuery(‘SELECT * FROM SystemRestore’,’WQL’,0);
OEnum := IUnknown(colItems._NewEnum) as IEnumVariant;
if (VarIsOrdinal(ColItems.Count)) then
CountOfRestorePoint := StrToInt(VarToStr(ColItems.Count))
else
CountOfRestorePoint := 0;
if (CountOfRestorePoint = 0) then
Exit;
SetLength(Result, CountOfRestorePoint);
I := 0;
while OEnum.Next(1, colItem, IValue) = 0 do
begin
RestorePoint.Description := ColItem.Description;
RestorePoint.EventType := EventTypeToStr(ColItem.EventType);
RestorePoint.SequenceNumber := ColItem.SequenceNumber;
RestorePoint.CreationTime := WMITimeToStr(ColItem.CreationTime);
RestorePoint.RestorePointType := RestorePointTypeToStr(ColItem.RestorePointType);
Result[i] := RestorePoint;
Inc(I);
end;
end;
Самое интересное здесь – получение WMI объекта (winmgmts:\\localhost\root\default) и выборка всех точек восстановления с помощью запроса: SELECT * FROM SystemRestore. Выполнив запрос, необходимо проверить результат выборки на наличие элементов. Если их нет, то дальше нет смысла что-то делать.

В противоположной ситуации (если запрос вернул, хоть один элемент), мы будем руководствоваться следующей стратегией:

Устанавливаем размер массива. Размерность массива определяем количеством элементов, вернувших запросом. Для этого используем метод SetLength(). В первом параметре передаем Result (массив, результат выполнения функции) и переменную CountOfRestorePoint, содержащую количество имеющихся точек восстановления.

Перебираем все точки с помощью метода Next объекта OEnum и сохраняем всю полученную информацию в объявленную ранее нами структуру. Получив свойства очередной точки восстановления, добавляем заполненную структуру в массив.

Дальше, проще

С остальными методами ты разберешься сам. Описание всех используемых API функций я привел, поэтому проблем возникнуть не должно. На крайний случай можешь написать мне письмо с вопросом. Отвечу с удовольствием :). Вот на этой славной ноте я и хочу с тобой попрощаться. Удачи к кодинге!

Понравилась статья? Поделиться с друзьями: