Меню сайта
Форма входа
Категории раздела
Общие статьи об INDY [6] INDY IN DEPTH [18]
Учебник созданный авторами INDY
Видеоуроки INDY [3] Основы Delphi [9]
Работа с файлами [6]
Главная » Статьи » Delphi » Основы Delphi

Смена пароля локального администратора

1. Постановка задачи

На компьютерах с операционными системами Windows NT x.x при установке создается учетная запись локального администратора, которая имеет неограниченные права на данном компьютере. Если компьютер предполагается использовать в домене, то, как правило, технический персонал устанавливает один и тот же пароль для данной учетной записи. И как правило он не очень сложный. При наличии физического доступа к рабочей станции пароль администратора может быть легко подобран со всеми вытекающими отсюда последствиями. Задача администратора сети - установить достаточно сложный пароль для данной учетной записи и периодически его менять. Если в домене несколько десятков компьютеров, это может занять много времени. Если же в домене несколько сот компьютеров, а часто они еще и географически разнесены, то без автоматизации данного процесса не обойтись.

Определимся, что должна делать программа - утилита. Т.е. составим простой алгоритм работы:

  1. получить список имен компьютеров в домене (возможно отфильтрованный по заданному критерию);
  2. подключиться к каждому компьютеру из списка и сменить пароль.

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

2. ADSI

После того как определились, что надо сделать (в данном случае это не составляет труда), встает вопрос о реализации. Первой мыслью было использовать технологию WMI, но после краткого исследования проблемы решено было остановиться на ADSI. Далее вольный перевод нескольких предложений из MSDN:

ADSI - Active Directory Service Interfaces. Микрософт создала набор COM-интерфейсов, предназначенных для доступа к различным службам каталогов.

Служба каталогов - это распределенная система, которая предоставляет средства для поиска и использования сетевых ресурсов различных типов.

Объектная модель ADSI базируется на COM - объектах. Программа клиент управляет объектами через интерфейсы. Следующая таблица перечисляет фундаментальные элементы ADSI.

ИнтерфейсыОписание
IADsИспользуется для идентификации объекта. Как фундаментальный интерфейс, поддерживаемый всеми ADSI объектами, позволяет получить доступ к метаданным объекта, включая описание объекта в схеме Active Directory .
IADsContainerИспользуется для извлечения и управления объектом. Все ADSI объекта - контейнеры требуют использование этого интерфейса для доступа к объектам в контейнере и манипулирования ими.
IADsPropertyListИспользуется для работы со свойствами объекта.

Сложные ADSI объекты могут поддерживать дополнительные интерфейсы.

3. VBS

Первая реализация задачи была сделана на VBS. И это понятно. Достаточно зайти на сайт Микрософт и скачать готовые скрипты. И немного их подправить под свои нужды. Кроме того, на VB код получается очень короткий и легкий для восприятия. Вот пример создания списка компьютеров из домена, расположенных в определенном organization unit в Active Directory (AD):

 Set objDictionary = CreateObject("Scripting.Dictionary")
 strDomain = "LDAP://ou=Test, ou=Mine, dc=mydomain, dc=com"
 Set objDomain = GetObject(strDomain)
 objDomain.Filter = Array("computer")
 i = 0
 For Each objComputer In objDomain
 objDictionary.Add i, Mid(objComputer.Name,4)
 i = i + 1
 Next


Рисунок 1

Для получения доступа к пространству имен каталога необходимо связаться с нужным объектом ADSI.

Set objDomain = GetObject(strDomain)

strDomain - строка связывания.

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

Примеры обращения к различным службам

"LDAP://"Служба каталогов, созданная на основе протокола LDAP (Active Directory в том числе)
"WinNt://"Служба каталогов в сети Windows NT 4.0 или на рабочей станции Windows XP/2000

Вторая часть строки связывания определяет положение объекта в каталоге.

В следующих таблицах приводятся примеры строк связывания:

LDAP

LDAP:Связь с корнем пространства имен LDAP
LDAP://server01Связь с конкретным сервером
LDAP://server01:390Связь с конкретным сервером через указанный порт
LDAP://CN=Jeff Smith,CN=users,DC=fabrikam,DC=comСвязь с конкретным объектом
LDAP://server01/CN=Jeff Smith,CN=users,DC=fabrikam,DC=comСвязь с конкретным объектом через указанный сервер

WinNT

WinNT://<domain name>
WinNT://<domain name>/<server>
WinNT://<domain name>/<path>
WinNT://<domain name>/<object name>
WinNT://<domain name>/<object name>,<object class>
WinNT://<server>
WinNT://<server>/<object name>
WinNT://<server>/<object name>,<object class>

Устанавливаем фильтр для выделения объектов - компьютеров.

objDomain.Filter = Array("computer")

И затем перебираем элементы коллекции.

Главный минус данной реализации (на мой взгляд) - это низкая скорость работы. Для перебора ~150 рабочих станция и смены на них пароля понадобилось около часа времени.

Основные задержки приходятся на операцию связывания. Особенно большие таймауты при попытке связывания с выключенным или не существующим компьютером ( или если по какой-то причине отказано в доступе). Решением данной проблемы является организация многопоточности. Поэтому от VBS пришлось отказаться.

4. Реализация на Delphi.

Задача была реализована на Delphi6 sp2. В процессе работы оказалось, что необходимые функции не описаны в библиотеке. Далее в статье будут приведены описания всех необходимых функций.

4.1 Извлечение имен компьютеров домена из AD.

Первым этапом попытаемся установить связь AD. Для этого воспользуемся функцией ADsGetObject. Описание из MSDN:

HRESULT ADsGetObject(
 LPWSTR lpszPathName,
 REFIID riid,
 VOID** ppObject);

lpszPathName - строка связывания;

riid - идентификатор интерфейса;

ppObject - указатель на указатель интерфейса, возвращаемый функцией.

Эта функция эквивалентна функции GetObject из VB (в данном контексте).Она берет строку связывания и возвращает указатель на запрашиваемый интерфейс. Связывание производится в контексте защиты вызывающего потока, используя опции ADS_SECURE_AUTHENTICATION. Если требуется указать конкретного пользователя, необходимо использовать функцию ADsOpenObject (прошу прощения за корявый перевод).

Далее пример использования ADsGetObject для связывания с AD:

interface 
Uses :. , ActiveDs_TLB;
:
function ADsGetObject(lpszPathName: WideString; const riid: TGUID; out ppObject: Pointer): HRESULT; stdcall;


implementation

function ADsGetObject; external 'activeds.dll';

Procedure TForm1.Test
Var hr: HResult;
 objDomain: Pointer;
begin

 hr:= ADsGetObject('LDAP://ou=test, ou=mine, dc=mydomain, dc=com', IID_IADsContainer, 
 objDomain);
 if Failed(hr) then Exit;
end;

Чтобы данный пример мог быть откомпилирован необходимо импортировать библиотеку типов Activeds.tlb, как показано на рисунке 2:


Рисунок 2

Замечание:

При работе с ADsGetObject бывали ситуации, когда при попытке прочитать какое-либо свойство полученного объекта выходила ошибка 'The directory property cannot be found in cache'. К сожалению, это было достаточно давно, и восстановить ситуацию не удалось. Тем не менее ошибка была. Обойти ее удалось при использовании функции ADsOpenObject . Вот пример использования данной функции:

interface 
Uses :. , ActiveDs_TLB;
:
function ADsOpenObject(lpszPathName: WideString; lpszUserName: WideString; lpszPassword: WideString;
 dwReserved: DWORD; const riid: TGUID; out ppObject: Pointer): HRESULT; stdcall;


implementation

function ADsOpenObject; external 'activeds.dll';

Procedure TForm1.Test
Var hr: HResult;
 objDomain: Pointer;
begin

 hr:= ADsOpenObject('LDAP://ou=test, ou=mine, dc=mydomain, dc=com', '', '', 
 DS_SECURE_AUTHENTICATION, IID_IADsContainer, objDomain);}
 if Failed(hr) then Exit;
end;

Далее в статье будет использоваться только ADsGetObject.

В данных примерах мы пытаемся получить ссылку на интерфейс IID_IADsContainer.

IID_IADsContainer используют для получения коллекции ADSI объектов. Полный список интерфейсов, с которыми можно работать при помощи ADsGetObject, и их описание можно найти в MSDN.

После того, как мы получили ссылку на контейнер, осталось перебрать его объекты и считать их имена. Для этого нам понадобятся еще две функции - AdsBuildEnumerator и ADsEnumerateNext.

AdsBuildEnumerator- создает объект Enumerator (перечеслитель) для конкретного объекта контейнера ADSI.

function ADsBuildEnumerator(pADsContainerL: IADsContainer; ppEnumVariant: PIEnumVARIANT): HRESULT; stdcall;
function ADsBuildEnumerator; external 'activeds.dll';

pADsContainerL - указатель на IADsContainer;

ppEnumVariant - указатель на указатель IEnumVariant интерфейс, который связывает создаваемый объект Enumerator с соответствующим объектом контейнером.

Интерфейс IEnumVARIANT описан в модуле ActiveX.

ADsEnumerateNext - позволяет перемещать указатель по элементам коллекции.

function ADsEnumerateNext(pEnumVariant: IEnumVARIANT; cElements: ULONG; pvar: POleVariant;
 pcElementsFetched: PULONG): HRESULT; stdcall;

function ADsEnumerateNext; external 'activeds.dll';

pEnumVariant - получаем после вызова ADsBuildEnumerator;

cElements - количество элементов, которые мы хотим извлечь из коллекции за один раз;

pvar - указатель на массив, в который помещаются извлеченные из коллекции объекты;

pcElementsFetched - указатель на фактическое количество найденных элементов.

Далее, собственно, пример, демонстрирующий как получить список компьютеров домена из AD:

procedure TForm1.Button1Click(Sender: TObject);
var objDomain: Pointer;
 objChild: Pointer;
 hr: HResult;
 s: String;
 i: Integer;
 iArr : OleVariant;
 iEnum: IEnumVARIANT;
 iFetch: ULONG;
 iAPath: String;
begin

 ListBox1.Clear;
 hr:= ADsGetObject('LDAP://ou=test, ou=mine, dc=bogatyr, dc=kz', IID_IADsContainer, objDomain);
 if Failed(hr) then Exit;
 hr:=ADsBuildEnumerator(IADsContainer(objDomain), @iEnum);
 if Failed(hr) then Exit;
 hr := ADsEnumerateNext(iEnum, 1, @iArr, @iFetch);
 while (S_OK = hr) and (1 = iFetch) do

 begin
 hr:=IDispatch(iArr).QueryInterface(IADs,objChild);
 if Failed(hr) then Exit;
 if AnsiLowerCase(IAds(objChild).Class_)='computer' then

 begin
 s:=IAds(objChild).Name;
 System.Delete(s,1,3);
 ListBox1.Items.Add(s);
 end;
 if AnsiLowerCase(IAds(objChild).Class_)='organizationalunit' then

 begin
 Continue;
{ s:=IAds(objChild).Name;
 iAPath:=PAPAth;
 System.Delete(iAPath, 1, 7);
 iAPath:='LDAP:// '+s+','+iAPath;
 if not NextNode_Computer(iAPath) then exit;}
 end;
 if AnsiLowerCase(IAds(objChild).Class_)='container' then

 begin
 Continue;
{ s:=IAds(objChild).Name;
 iAPath:=PAPAth;
 System.Delete(iAPath, 1, 7);
 iAPath:='LDAP:// '+s+','+iAPath;
 if not NextNode_Computer(iAPath) then exit;}
 end;
 iArr:=null;
 hr := ADsEnumerateNext(iEnum, 1, @iArr, @iFetch);
 end;
end;

Часть кода в примере закомментирована. Код взят из рабочей программы и слегка исправлен. В закомментированных частях видно, что подпрограмма вызывается рекурсивно. Это было сделано что бы просканировать всю указанную ветку из AD, включая содержащиеся внутри ветки.

4.2 Смена пароля локального администратора.

Здесь все просто. Формируем строку связывание для доступа к объекту с именем "Администратор". Класс объекта - "user". Объект расположен на рабочей станции "Computer01".

iPath:='WinNT://'+NameWs+'/Администратор,user';

И, собственно, реализация.

procedure ChangePassword;

var objUser: Pointer;
 hr: HResult;
 iPath: String;
 i: Integer;
begin
 iPath:='WinNT://Computer01/Администратор,user';
 hr:= ADsGetObject(iPath, IID_IADsUser, objUser);
 if hr<>S_OK then Exit;
 IADsUser(objUser).SetPassword('anykey');

end;

4.3 Обработка ошибок.

Если вызов ADSI функции завершился неудачей, функция вернет код ошибки стандартным для COM объектов способом. Коды ошибок делятся не четыре группы:

  • Универсальные коды ошибок COM;
  • Универсальные коды ошибок ADSI;
  • Коды ошибок Win32 для ADSI;
  • Коды ошибок LDAP для ADSI;

Кроме того, некоторые интерфейсы предоставляют дополнительные сведения об ошибке, которые могут быть получены при помощи функции ADsGetLastError.

function ADsGetLastError(lpError: LPDWORD; lpErrorBuf: LPWSTR; dwErrorBufLen: DWORD;
 lpNameBuf: LPWSTR; dwNameBufLen: DWORD): HRESULT; stdcall;

lpError - указатель на код ошибки;

lpErrorBuf - указатель на буфер, куда будет передано описание ошибки;

dwErrorBufLen - размер буфера;

lpNameBuf - указатель на буфер, куда будет передано имя провайдера, который возбудил эту ошибку;

dwNameBufLen - размер буфера;

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

Скачать исходный код
Категория: Основы Delphi | Добавил: nazgull (31.03.2013)
Просмотров: 1759 | Теги: пароль администратора, Сброс пароля, программа, Скачать, пример, пароль, код, смена пароля, делфи | Рейтинг: 0.0/0
Всего комментариев: 0
Добавлять комментарии могут только зарегистрированные пользователи.
[ Регистрация | Вход ]
Ссылки