Enumerate Windows Usernames - Delphi

DarkCoderSc personal avatar
DarkCoderSc

Jean-Pierre LESUEUR

uses
  System.SysUtils, Winapi.Windows, Generics.Collections;

// ...

const MAX_PREFERRED_LENGTH  = $FFFFFFFF;
      NERR_Success          = 0;
      FILTER_NORMAL_ACCOUNT = 2;
      USER_INFO_2_LVL       = 2;

// ...

type
  NetApiStatus = DWORD;

  USER_INFO_2 = record
    usri2_name           : LPWSTR;
    usri2_password       : LPWSTR;
    usri2_password_age   : DWORD;
    usri2_priv           : DWORD;
    usri2_home_dir       : LPWSTR;
    usri2_comment        : LPWSTR;
    usri2_flags          : DWORD;
    usri2_script_path    : LPWSTR;
    usri2_auth_flags     : DWORD;
    usri2_full_name      : LPWSTR;
    usri2_usr_comment    : LPWSTR;
    usri2_parms          : LPWSTR;
    usri2_workstations   : LPWSTR;
    usri2_last_logon     : DWORD;
    usri2_last_logoff    : DWORD;
    usri2_acct_expires   : DWORD;
    usri2_max_storage    : DWORD;
    usri2_units_per_week : DWORD;
    usri2_logon_hours    : Pointer;
    usri2_bad_pw_count   : DWORD;
    usri2_num_logons     : DWORD;
    usri2_logon_server   : LPWSTR;
    usri2_country_code   : DWORD;
    usri2_code_page      : DWORD;
  end;
  TUserInfo2 = USER_INFO_2;
  PUserInfo2 = ^TUserInfo2;

// ...

// https://learn.microsoft.com/fr-fr/windows/win32/api/lmaccess/nf-lmaccess-netuserenum?WT_mc_id=SEC-MVP-5005282
function NetUserEnum(
  servername       : LPWSTR;
  level            : DWORD;
  filter           : DWORD;
  var bufptr       : Pointer;
  prefmaxlen       : DWORD;
  var entriesread  : DWORD;
  var totalentries : DWORD;
  resume_handle    : Pointer
) : NetApiStatus; stdcall; external 'Netapi32.dll';

// https://learn.microsoft.com/fr-fr/windows/win32/api/lmaccess/nf-lmaccess-netusergetinfo?WT_mc_id=SEC-MVP-5005282
function NtUserGetInfo(
  servername : LPWSTR;
  username   : LPWSTR;
  level      : DWORD;
  var bufptr : Pointer
) : NetApiStatus; stdcall; external 'Netapi32.dll';

// https://learn.microsoft.com/en-us/windows/win32/api/lmapibuf/nf-lmapibuf-netapibufferfree?WT_mc_id=SEC-MVP-5005282
function NetApiBufferFree(
  Buffer : Pointer
) : NetApiStatus; stdcall; external 'Netapi32.dll';

// ...

function EnumerateWindowsUsers(var AUsernames : TList<String>) : Cardinal;
begin
  if not Assigned(AUsernames) then
    AUsernames := TList<String>.Create()
  else
    AUsernames.Clear();
  ///

  var AStatus : NetApiStatus;
  repeat
    var AResume : DWORD := 0;
    var pBuffer : Pointer;
    var AEntriesRead : DWORD;
    var ATotalEntries : DWORD;

    AStatus := NetUserEnum(
          nil,
          USER_INFO_2_LVL,
          FILTER_NORMAL_ACCOUNT,
          pBuffer,
          MAX_PREFERRED_LENGTH,
          AEntriesRead,
          ATotalEntries,
          @AResume
    );

    case AStatus of
      NERR_SUCCESS, ERROR_MORE_DATA : begin
        var pUserInfo : PUserInfo2 := pBuffer;

        for var I := 0 to AEntriesRead -1 do begin
          AUsernames.Add(WideCharToString(pUserInfo^.usri2_name));

          ///
          Inc(pUserInfo);
        end;

        ///
        NetApiBufferFree(pBuffer);
      end;

      // See Microsoft Documentation to Handle Possible Errors:
      // https://learn.microsoft.com/fr-fr/windows/win32/api/lmaccess/nf-lmaccess-netuserenum#return-value
      // (ERROR_ACCESS_DENIED, ERROR_INVALID_LEVEL, NERR_BufTooSmall, NERR_InvalidComputer)
    end;
  until AStatus <> ERROR_MORE_DATA;

  ///
  result := AUsernames.Count;
end;

// ...

var AUsernames := TList<String>.Create();
try
  EnumerateWindowsUsers(AUsernames);

  for var AUsername in AUsernames do
    WriteLn(AUsername);

  // ...
finally
  FreeAndNil(AUsernames);
end;

Creating and researching code snippets takes time and effort. You’re welcome to share them through your own platforms, but please don’t forget to credit the original author, here: Jean-Pierre LESUEUR.


Created

April 23, 2025

Last Revised

April 23, 2025