Задача была реализована на 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Чтобы данный пример мог быть откомпилирован необходимо импортировать библиотеку типов 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 объектов способом. Коды ошибок делятся не четыре группы:
Кроме того, некоторые интерфейсы предоставляют дополнительные сведения об ошибке, которые могут быть получены при помощи функции ADsGetLastError.
function ADsGetLastError(lpError: LPDWORD; lpErrorBuf: LPWSTR; dwErrorBufLen: DWORD; lpNameBuf: LPWSTR; dwNameBufLen: DWORD): HRESULT; stdcall;lpError - указатель на код ошибки;
lpErrorBuf - указатель на буфер, куда будет передано описание ошибки;
dwErrorBufLen - размер буфера;
lpNameBuf - указатель на буфер, куда будет передано имя провайдера, который возбудил эту ошибку;
dwNameBufLen - размер буфера;
Простой пример использования этой функции можно будет посмотреть в исходных кодах, прилагаемых к статье. Для получения наиболее полной информации о произошедших ошибках обратитесь к MSDN. В частности, по приведен пример функций (на Си), которые в качестве аргумента принимают код ошибки и возвращают ее описание. Осталось их (эти функции) перевести на Pascal и использовать.
Список литературы
Исходные коды проекта и пример базы данных (22 K)