Sources
Delphi Russian Knowledge Base
DRKB - это самая большая и удобная в использовании база знаний по Дельфи в рунете, составленная Виталием Невзоровым

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

01.01.2007
////////////////////////////////////////////////////////////////////////////////
//
//  ****************************************************************************
//  * 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.

Проект также доступен по адресу: http://rouse.front.ru/domaininfo.zip

Взято из https://forum.sources.ru

Автор: Rouse_