Получение информации о пользователях и группах домена

////////////////////////////////////////////////////////////////////////////////
//
// ****************************************************************************
// * Project : DomainInfo
// * Unit Name : uMain
// * Purpose : Демо получения информации о пользователях и группах домена
// * Author : Александр (Rouse_) Багель
// * Version : 1.00
// ****************************************************************************
//
// Спасибо милой девушке Ане и группе "Машина Времени" за моральную поддержку...
//
unit uMain;
interface
uses
 Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
 Dialogs, StdCtrls, ExtCtrls, ComCtrls
 {$IFDEF VER150}
 , XPMan
 {$ENDIF};
const
 netapi32lib = 'netapi32.dll';
 NERR_Success = NO_ERROR;
type
 // Структура для получения информации о рабочей станции
 PWkstaInfo100 = ^TWkstaInfo100;
 TWkstaInfo100 = record
 wki100_platform_id : DWORD;
 wki100_computername : PWideChar;
 wki100_langroup : PWideChar;
 wki100_ver_major : DWORD;
 wki100_ver_minor : DWORD;
 end;
 // Структура для определения DNS имени контролера домена
 TDomainControllerInfoA = record
 DomainControllerName: LPSTR;
 DomainControllerAddress: LPSTR;
 DomainControllerAddressType: ULONG;
 DomainGuid: TGUID;
 DomainName: LPSTR;
 DnsForestName: LPSTR;
 Flags: ULONG;
 DcSiteName: LPSTR;
 ClientSiteName: LPSTR;
 end;
 PDomainControllerInfoA = ^TDomainControllerInfoA;
 // Структура для отображения пользователей
 PNetDisplayUser = ^TNetDisplayUser;
 TNetDisplayUser = record
 usri1_name: LPWSTR;
 usri1_comment: LPWSTR;
 usri1_flags: DWORD;
 usri1_full_name: LPWSTR;
 usri1_user_id: DWORD;
 usri1_next_index: DWORD;
 end;
 // Структура для отображения рабочих станций
 PNetDisplayMachine = ^TNetDisplayMachine;
 TNetDisplayMachine = record
 usri2_name: LPWSTR;
 usri2_comment: LPWSTR;
 usri2_flags: DWORD;
 usri2_user_id: DWORD;
 usri2_next_index: DWORD;
 end;
 // Структура для отображения групп
 PNetDisplayGroup = ^TNetDisplayGroup;
 TNetDisplayGroup = record
 grpi3_name: LPWSTR;
 grpi3_comment: LPWSTR;
 grpi3_group_id: DWORD;
 grpi3_attributes: DWORD;
 grpi3_next_index: DWORD;
 end;
 // Структура для отображения пользователей принадлежащих группе
 // или групп в которые входит пользователь
 PGroupUsersInfo0 = ^TGroupUsersInfo0;
 TGroupUsersInfo0 = record
 grui0_name: LPWSTR;
 end;
 TfrmDomainInfo = class(TForm)
 Button1: TButton;
 gbCurrent: TGroupBox;
 gbDomainResList: TGroupBox;
 ledCompName: TLabeledEdit;
 ledUserName: TLabeledEdit;
 ledDomainName: TLabeledEdit;
 ledControllerName: TLabeledEdit;
 lvUsers: TListView;
 gbInfo: TGroupBox;
 lbInfo: TListBox;
 VSplitter: TSplitter;
 pcRes: TPageControl;
 TabSheet1: TTabSheet;
 TabSheet2: TTabSheet;
 TabSheet3: TTabSheet;
 lvWorkStation: TListView;
 lvGroups: TListView;
 Label1: TLabel;
 memTrustedDomains: TMemo;
 ledDNSName: TLabeledEdit;
 procedure FormCreate(Sender: TObject);
 procedure lvGroupsClick(Sender: TObject);
 private
 CurrentDomainName: String;
 function GetCurrentUserName: String;
 function GetCurrentComputerName: String;
 function GetDomainController(const DomainName: String): String;
 function GetDNSDomainName(const DomainName: String): String;
 function EnumAllTrustedDomains: Boolean;
 function EnumAllUsers: Boolean;
 function EnumAllGroups: Boolean;
 function EnumAllWorkStation: Boolean;
 function GetSID(const SecureObject: String): String;
 function GetAllGroupUsers(const GroupName: String): Boolean;
 function GetAllUserGroups(const UserName: String): Boolean;
 end;
 // Функции которые предоставят нам возможность получения информации
 function NetApiBufferFree(Buffer: Pointer): DWORD; stdcall;
 external netapi32lib;
 function NetWkstaGetInfo(ServerName: PWideChar; Level: DWORD;
 Bufptr: Pointer): DWORD; stdcall; external netapi32lib;
 function NetGetDCName(ServerName: PWideChar; DomainName: PWideChar;
 var Bufptr: PWideChar): DWORD; stdcall; external netapi32lib;
 function DsGetDcName(ComputerName, DomainName: PChar; DomainGuid: PGUID;
 SiteName: PChar; Flags: ULONG;
 var DomainControllerInfo: PDomainControllerInfoA): DWORD; stdcall;
 external netapi32lib name 'DsGetDcNameA';
 function NetQueryDisplayInformation(ServerName: PWideChar; Level: DWORD;
 Index: DWORD; EntriesRequested: DWORD; PreferredMaximumLength: DWORD;
 var ReturnedEntryCount: DWORD; SortedBuffer: Pointer): DWORD; stdcall;
 external netapi32lib;
 function NetGroupGetUsers(ServerName: PWideChar; GroupName: PWideChar; Level: DWORD;
 var Bufptr: Pointer; PrefMaxLen: DWORD; var EntriesRead: DWORD;
 var TotalEntries: DWORD; ResumeHandle: PDWORD): DWORD; stdcall;
 external netapi32lib;
 function NetUserGetGroups(ServerName: PWideChar; UserName: PWideChar; Level: DWORD;
 var Bufptr: Pointer; PrefMaxLen: DWORD; var EntriesRead: DWORD;
 var TotalEntries: DWORD): DWORD; stdcall; external netapi32lib;
 function NetEnumerateTrustedDomains(ServerName: PWideChar;
 DomainNames: PWideChar): DWORD; stdcall; external netapi32lib;
 procedure ConvertSidToStringSid(SID: PSID; var StringSid: LPSTR); stdcall;
 external advapi32 name 'ConvertSidToStringSidA';
var
 frmDomainInfo: TfrmDomainInfo;
implementation
{$R *.dfm}
// Данная функция получает информацию о всех группах присутствующих в домене
// =============================================================================
function TfrmDomainInfo.EnumAllGroups: Boolean;
var
 Tmp, Info: PNetDisplayGroup;
 I, CurrIndex, EntriesRequest,
 PreferredMaximumLength,
 ReturnedEntryCount: Cardinal;
 Error: DWORD;
begin
 CurrIndex := 0;
 repeat
 Info := nil;
 // NetQueryDisplayInformation возвращает информацию только о 100-а записях
 // для того чтобы получить всю информацию используется третий параметр,
 // передаваемый функции, который определяет с какой записи продолжать
 // вывод информации
 EntriesRequest := 100;
 PreferredMaximumLength := EntriesRequest * SizeOf(TNetDisplayGroup);
 ReturnedEntryCount := 0;
 // Для выполнения функции, в нее нужно передать DNS имя контролера домена
 // (или его IP адрес), с которого мы хотим получить информацию
 // Для получения информации о группах используется структура NetDisplayGroup
 // и ее идентификатор 3 (тройка) во втором параметре
 Error := NetQueryDisplayInformation(StringToOleStr(ledControllerName.Text), 3, CurrIndex,
 EntriesRequest, PreferredMaximumLength, ReturnedEntryCount, @Info);
 // При безошибочном выполнении функции будет результат либо
 // 1. NERR_Success - все записи возвращены
 // 2. ERROR_MORE_DATA - записи возвращены, но остались еще и нужно вызывать функцию повторно
 if Error in [NERR_Success, ERROR_MORE_DATA] then
 try
 Tmp := Info;
 // Выводим информацию которую вернула функция в структуру
 for I := 0 to ReturnedEntryCount - 1 do
 begin
 with lvGroups.Items.Add do
 begin
 Caption := Tmp^.grpi3_name; // Имя группы
 SubItems.Add(Tmp^.grpi3_comment); // Комментарий
 SubItems.Add(GetSID(Caption)); // SID группы
 // Запоминаем индекс с которым будем вызывать повторно функцию (если нужно)
 CurrIndex := Tmp^.grpi3_next_index;
 end;
 Inc(Tmp);
 end;
 finally
 // Чтобы не было утечки ресурсов, освобождаем память занятую функцией под структуру
 NetApiBufferFree(Info);
 end;
 // Если результат выполнения функции ERROR_MORE_DATA - вызываем функцию повторно
 until Error in [NERR_Success, ERROR_ACCESS_DENIED];
 // Ну и возвращаем результат всего что мы тут накодили
 Result := Error = NERR_Success;
end;
// Данная функция получает информацию о всех доверенных доменах
// =============================================================================
function TfrmDomainInfo.EnumAllTrustedDomains: Boolean;
var
 Tmp, DomainList: PWideChar;
begin
 // Используем недокументированную функцию NetEnumerateTrustedDomains
 // (только не пойму, с какого перепуга она не документирована?)
 // Тут все очень просто, на вход имя контролера домена, ны выход - список доверенных доменов
 Result := NetEnumerateTrustedDomains(StringToOleStr(ledControllerName.Text),
 @DomainList) = NERR_Success;
 // Если вызов функции успешен, то...
 if Result then
 try
 Tmp := DomainList;
 while Length(Tmp) > 0 do
 begin
 memTrustedDomains.Lines.Add(Tmp); // Банально выводим список на экран
 Tmp := Tmp + Length(Tmp) + 1;
 end;
 finally
 // Не забываем про память
 NetApiBufferFree(DomainList);
 end;
end;
// Данная функция получает информацию о всех пользователях присутствующих в домене
// =============================================================================
function TfrmDomainInfo.EnumAllUsers: Boolean;
var
 Tmp, Info: PNetDisplayUser;
 I, CurrIndex, EntriesRequest,
 PreferredMaximumLength,
 ReturnedEntryCount: Cardinal;
 Error: DWORD;
begin
 CurrIndex := 0;
 repeat
 Info := nil;
 // NetQueryDisplayInformation возвращает информацию только о 100-а записях
 // для того чтобы получить всю информацию используется третий параметр,
 // передаваемый функции, который определяет с какой записи продолжать
 // вывод информации
 EntriesRequest := 100;
 PreferredMaximumLength := EntriesRequest * SizeOf(TNetDisplayUser);
 ReturnedEntryCount := 0;
 // Для выполнения функции, в нее нужно передать DNS имя контролера домена
 // (или его IP адрес), с которого мы хотим получить информацию
 // Для получения информации о пользователях используется структура NetDisplayUser
 // и ее идентификатор 1 (единица) во втором параметре
 Error := NetQueryDisplayInformation(StringToOleStr(ledControllerName.Text), 1, CurrIndex,
 EntriesRequest, PreferredMaximumLength, ReturnedEntryCount, @Info);
 // При безошибочном выполнении функции будет результат либо
 // 1. NERR_Success - все записи возвращены
 // 2. ERROR_MORE_DATA - записи возвращены, но остались еще и нужно вызывать функцию повторно
 if Error in [NERR_Success, ERROR_MORE_DATA] then
 try
 Tmp := Info;
 // Выводим информацию которую вернула функция в структуру
 for I := 0 to ReturnedEntryCount - 1 do
 begin
 with lvUsers.Items.Add do
 begin
 Caption := Tmp^.usri1_name; // Имя пользователя
 SubItems.Add(Tmp^.usri1_comment); // Комментарий
 SubItems.Add(GetSID(Caption)); // Его SID
 // Запоминаем индекс с которым будем вызывать повторно функцию (если нужно)
 CurrIndex := Tmp^.usri1_next_index;
 end;
 Inc(Tmp);
 end;
 finally
 // Грохаем выделенную при вызове NetQueryDisplayInformation память
 NetApiBufferFree(Info);
 end;
 // Если результат выполнения функции ERROR_MORE_DATA
 // (т.е. есть еще данные) - вызываем функцию повторно
 until Error in [NERR_Success, ERROR_ACCESS_DENIED];
 // Ну и возвращаем результат всего что мы тут накодили
 Result := Error = NERR_Success;
end;
// Данная функция получает информацию о всех рабочих станциях присутствующих в домене
// Вообще-то так делать немного не верно, дело в том что рабочие станции могут
// присутствовать в списке не только те, которые завел сисадмин (но для демки сойдет и так)
// =============================================================================
function TfrmDomainInfo.EnumAllWorkStation: Boolean;
var
 Tmp, Info: PNetDisplayMachine;
 I, CurrIndex, EntriesRequest,
 PreferredMaximumLength,
 ReturnedEntryCount: Cardinal;
 Error: DWORD;
begin
 CurrIndex := 0;
 repeat
 Info := nil;
 // NetQueryDisplayInformation возвращает информацию только о 100-а записях
 // для того чтобы получить всю информацию используется третий параметр,
 // передаваемый функции, который определяет с какой записи продолжать
 // вывод информации
 EntriesRequest := 100;
 PreferredMaximumLength := EntriesRequest * SizeOf(TNetDisplayMachine);
 ReturnedEntryCount := 0;
 // Для выполнения функции, в нее нужно передать DNS имя контролера домена
 // (или его IP адрес), с которого мы хотим получить информацию
 // Для получения информации о рабочих станциях используется структура NetDisplayMachine
 // и ее идентификатор 2 (двойка) во втором параметре
 Error := NetQueryDisplayInformation(StringToOleStr(ledControllerName.Text), 2, CurrIndex,
 EntriesRequest, PreferredMaximumLength, ReturnedEntryCount, @Info);
 // При безошибочном выполнении функции будет результат либо
 // 1. NERR_Success - все записи возвращены
 // 2. ERROR_MORE_DATA - записи возвращены, но остались еще и нужно вызывать функцию повторно
 if Error in [NERR_Success, ERROR_MORE_DATA] then
 try
 Tmp := Info;
 // Выводим информацию которую вернула функция в структуру
 for I := 0 to ReturnedEntryCount - 1 do
 begin
 with lvWorkStation.Items.Add do
 begin
 Caption := Tmp^.usri2_name; // Имя рабочей станции
 SubItems.Add(Tmp^.usri2_comment); // Комментарий
 SubItems.Add(GetSID(Caption)); // Её SID
 // Запоминаем индекс с которым будем вызывать повторно функцию (если нужно)
 CurrIndex := Tmp^.usri2_next_index;
 end;
 Inc(Tmp);
 end;
 finally
 // Дабы не было утечек
 NetApiBufferFree(Info);
 end;
 // Если результат выполнения функции ERROR_MORE_DATA
 // (т.е. есть еще данные) - вызываем функцию повторно
 until Error in [NERR_Success, ERROR_ACCESS_DENIED];
 // Ну и возвращаем результат всего что мы тут накодили
 Result := Error = NERR_Success;
end;
procedure TfrmDomainInfo.FormCreate(Sender: TObject);
begin
 // Просто вызываем все функции подряд (не делал проверок на результат функций)
 ledUserName.Text := GetCurrentUserName;
 ledCompName.Text := GetCurrentComputerName;
 ledDomainName.Text := CurrentDomainName;
 ledControllerName.Text := GetDomainController(CurrentDomainName);
 // Единственно, если нет контролера домена, то дальше определять бесполезно
 if ledControllerName.Text = then Exit;
 ledDNSName.Text := GetDNSDomainName(CurrentDomainName);
 EnumAllTrustedDomains;
 EnumAllUsers;
 EnumAllWorkStation;
 EnumAllGroups;
end;
// Довольно простая функция, возвращает только имена пользователей принадлежащих группе
// =============================================================================
function TfrmDomainInfo.GetAllGroupUsers(const GroupName: String): Boolean;
var
 Tmp, Info: PGroupUsersInfo0;
 PrefMaxLen, EntriesRead,
 TotalEntries, ResumeHandle: DWORD;
 I: Integer;
begin
 // На вход подается список который мы будем заполнять
 lbInfo.Items.Clear;
 // Обязательная инициализация
 ResumeHandle := 0;
 PrefMaxLen := DWORD(-1);
 // Выполняем
 Result := NetGroupGetUsers(StringToOleStr(ledControllerName.Text),
 StringToOleStr(GroupName), 0, Pointer(Info), PrefMaxLen,
 EntriesRead, TotalEntries, @ResumeHandle) = NERR_Success;
 // Смотрим результат...
 if Result then
 try
 Tmp := Info;
 for I := 0 to EntriesRead - 1 do
 begin
 lbInfo.Items.Add(Tmp^.grui0_name); // Банально выводим результат из структуры
 Inc(Tmp);
 end;
 finally
 // Не забываем, ибо может быть склероз :)
 NetApiBufferFree(Info);
 end;
end;
// Аналогично предыдущей функции (заметьте - структура та же)
// =============================================================================
function TfrmDomainInfo.GetAllUserGroups(const UserName: String): Boolean;
var
 Tmp, Info: PGroupUsersInfo0;
 PrefMaxLen, EntriesRead,
 TotalEntries: DWORD;
 I: Integer;
begin
 lbInfo.Items.Clear;
 PrefMaxLen := DWORD(-1);
 Result := NetUserGetGroups(StringToOleStr(ledControllerName.Text),
 StringToOleStr(UserName), 0, Pointer(Info), PrefMaxLen,
 EntriesRead, TotalEntries) = NERR_Success;
 if Result then
 try
 Tmp := Info;
 for I := 0 to EntriesRead - 1 do
 begin
 lbInfo.Items.Add(Tmp^.grui0_name);
 Inc(Tmp);
 end;
 finally
 NetApiBufferFree(Info);
 end;
end;
// Получаем имя компьютера и имя домена
// =============================================================================
function TfrmDomainInfo.GetCurrentComputerName: String;
var
 Info: PWkstaInfo100;
 Error: DWORD;
begin
 // А для этого мы воспользуемся следующей функцией
 Error := NetWkstaGetInfo(nil, 100, @Info);
 if Error <> 0 then
 raise Exception.Create(SysErrorMessage(Error));
 // Как видно, вызов который возвращает обычную структуру, из которой и прочитаем, все что нужно :)
 // А именно имя компьютера в сети
 Result := Info^.wki100_computername;
 // И где он находиться
 CurrentDomainName := info^.wki100_langroup;
end;
// Без комментариев
// =============================================================================
function TfrmDomainInfo.GetCurrentUserName: String;
var
 Size: Cardinal;
begin
 Size := MAXCHAR;
 SetLength(Result, Size);
 GetUserName(PChar(Result), Size);
 SetLength(Result, Size);
end;
// Получаем DNS имя контроллера домена
// =============================================================================
function TfrmDomainInfo.GetDNSDomainName(const DomainName: String): String;
const
 DS_IS_FLAT_NAME = $00010000;
 DS_RETURN_DNS_NAME = $40000000;
var
 GUID: PGUID;
 DomainControllerInfo: PDomainControllerInfoA;
begin
 GUID := nil;
 // Для большинства операций нам потребуется IP адрес контроллера домена
 // или его DNS имя, которое мы получим вот так:
 if DsGetDcName(nil, PChar(CurrentDomainName), GUID, nil,
 DS_IS_FLAT_NAME or DS_RETURN_DNS_NAME, DomainControllerInfo) = NERR_Success then
 // Параметры которые мы передаем означают:
 // DS_IS_FLAT_NAME - передаем просто имя домена
 // DS_RETURN_DNS_NAME - ждем получения DNS имени
 try
 Result := DomainControllerInfo^.DomainControllerName; // Результат собсно тут...
 finally
 // Склероз это болезнь, ее нужно лечить...
 NetApiBufferFree(DomainControllerInfo);
 end;
end;
// Ну тут без комментариев - просто получаем имя контроллера домена
// =============================================================================
function TfrmDomainInfo.GetDomainController(const DomainName: String): String;
var
 Domain: WideString;
 Server: PWideChar;
begin
 Domain := StringToOleStr(DomainName);
 if NetGetDCName(nil, @Domain[1], Server) = NERR_Success then
 try
 Result := Server;
 finally
 NetApiBufferFree(Server);
 end;
end;
// Не знаю зачем добавил это, ну раз добавил - получение SID объекта
// Без комментариев...
// =============================================================================
function TfrmDomainInfo.GetSID(const SecureObject: String): String;
var
 SID: PSID;
 StringSid: PChar;
 ReferencedDomain: String;
 cbSid, cbReferencedDomain:DWORD;
 peUse: SID_NAME_USE;
begin
 cbSID := 128;
 cbReferencedDomain := 16;
 GetMem(SID, cbSid);
 try
 SetLength(ReferencedDomain, cbReferencedDomain);
 if LookupAccountName(PChar(ledDNSName.Text),
 PChar(SecureObject), SID, cbSid,
 @ReferencedDomain[1], cbReferencedDomain, peUse) then
 begin
 ConvertSidToStringSid(SID, StringSid);
 Result := StringSid;
 end;
 finally
 FreeMem(SID);
 end;
end;
procedure TfrmDomainInfo.lvGroupsClick(Sender: TObject);
var
 Value: String;
begin
 if (Sender as TListView).Selected = nil then Exit;
 Value := (Sender as TListView).Selected.Caption;
 case (Sender as TListView).Tag of
 0:
 begin
 gbInfo.Caption := Format('Группы в которые входит пользователь "%s"', [Value]);
 GetAllUserGroups(Value);
 end;
 1:
 begin
 gbInfo.Caption := Format('Группы в которые входит рабочая станция "%s"', [Value]);
 GetAllUserGroups(Value);
 end;
 2:
 begin
 gbInfo.Caption := Format('Объекты входящие в группу "%s"', [Value]);
 GetAllGroupUsers(Value);
 end;
 end;
end;
end.
Author: Rouse_
Source: http://forum.sources.ru
ID: 03322
Category:Связь