0

I am trying to call the Winapi function NetGroupGetUsers:

The NetGroupGetUsers function retrieves a list of the members in a particular global group in the security database, which is the security accounts manager (SAM) database or, in the case of domain controllers, the Active Directory.

I've tried calling the function with every variation of serverName and groupName i can think of:

ServerName GroupName Error code Description
null docker-users 2220 The group name could not be found
null OBSIDIAN\docker-users 2220 The group name could not be found
null .\docker-users 2220 The group name could not be found
OBSIDIAN docker-users 2220 The group name could not be found
OBSIDIAN OBSIDIAN\docker-users 2220 The group name could not be found
OBSIDIAN .\docker-users 2220 The group name could not be found
. docker-users 2220 The group name could not be found
. OBSIDIAN\docker-users 2220 The group name could not be found
. .\docker-users 2220 The group name could not be found

Where the error code 2220 corresponds to the constant:

  • NERR_GroupNotFound: The global group name in the structure pointed to by bufptr parameter could not be found.

Long Version

There is a group on my local workstation called docker-users:

>whoami /groups

GROUP INFORMATION
-----------------

Group Name            Type  SID                                           Attributes
===================== ===== ============================================= ==================================================
OBSIDIAN\docker-users Alias S-1-5-21-502352433-3072756349-3142140079-1006 Mandatory group, Enabled by default, Enabled group

And you can see the group members in netplwiz:

enter image description here

You can also see the group members in the Local Users and Groups MMC snap-in.

  1. Starting with the SID (e.g. S-1-5-21-502352433-3072756349-3142140079-1006)
  2. Then call LookupAccountSID to have it return:
    • DomainName
    • AccountName
    • SID type
  3. If the SidType represents a group (i.e. SidTypeAlias, SidTypeWellKnownGroup, or SidTypeGroup - which all mean "group")

We want the group members. So we call NetGroupGetUsers:

NetGroupGetUsers(null, "DomainName\AccountName", 1, out buffer, MAX_PREFERRED_LENGTH, out entriesRead, out totalEntries, null);

Except it fails. Every time. No matter what.

The question

Why does the call fail?

What is the correct way to specify:

  • Server name
  • group name

Of course, for all i know it could be an ABI alignment issue. The only way to find out is if someone else tries calling the function on their local (domain joined or non-domain joined PC - doesn't matter).

CRME for the lazy

program GetGroupUsersDemo;

{$APPTYPE CONSOLE}

{$R *.res}

uses
  System.SysUtils,
  Windows;

function NetGroupGetUsers(servername: LPCWSTR; groupname: LPCWSTR; level: DWORD;
        out bufptr: Pointer; prefmaxlen: DWORD; out entriesread: DWORD;
        out totalentries: DWORD; ResumeHandle: PDWORD): DWORD; stdcall; external 'netapi32.dll';

function GetGroupMembers(ServerName, GroupName: UnicodeString): HRESULT;
var
    res: DWORD; {NET_API_STATUS}
    buf: Pointer;
    entriesRead: DWORD;
    totalEntries: DWORD;
    i: Integer;
    server: PWideChar;
const
    MAX_PREFERRED_LENGTH = Cardinal(-1);
begin
    server := PWideChar(ServerName);
    if server = '' then
        server := nil;

    res := NetGroupGetUsers(server, PWideChar(GroupName), 1,
            {var}buf,
            MAX_PREFERRED_LENGTH, //Let the function allocate everything for us
            {var}entriesRead,
            {var}totalEntries,
            nil);
    Result := HResultFromWin32(res);
end;

procedure Test(ServerName, GroupName: UnicodeString);
var
    hr: HRESULT;
    s: string;
begin
    hr := GetGroupMembers(ServerName, GroupName);

    s := ServerName;
    if s = '' then
        s := 'null'; //can't have people not reading the question

    Writeln('| '+s+' | '+GroupName+' | '+IntToStr(hr and $0000FFFF)+' | '+SysErrorMessage(hr)+' |');
end;

procedure Main;
begin
    Writeln('| ServerName | GroupName | Error code | Description |');
    Writeln('|------------|-----------|------------|-------------|');

    Test('', 'OBSIDIAN\docker-users');
    Test('', '.\docker-users');

    Test('OBSIDIAN', 'docker-users');
    Test('OBSIDIAN', 'OBSIDIAN\docker-users');
    Test('OBSIDIAN', '.\docker-users');

    Test('.', 'docker-users');
    Test('.', 'OBSIDIAN\docker-users');
    Test('.', '.\docker-users');
end;

begin
  try
     Main;
        Readln;
  except
    on E: Exception do
      Writeln(E.ClassName, ': ', E.Message);
  end;
end.

Bonus Reading

Ian Boyd
  • 246,734
  • 253
  • 869
  • 1,219
  • Have you debugged netplwiz to see if it actually uses that function? – Anders May 10 '22 at 17:46
  • I have not debugged netplwiz. But i have spent the last few hours scouring the Windows source code for clues on what the parameters to **NetGroupGetUsers** are. – Ian Boyd May 10 '22 at 17:52
  • Your screenshot is from a mmc snap-in and not netplwiz? – Anders May 10 '22 at 19:11
  • Netplwiz. What do you get when you call the API? – Ian Boyd May 11 '22 at 04:26
  • I did not call anything, after wasting time looking at Netplwiz only to discover that the dialog from your screenshot lives somewhere else I had already wasted enough time and moved on. lusers.msc or something along those lines and then you can debug mmc.exe – Anders May 11 '22 at 07:20
  • Unfortunately I don't have easy access to a domain-joined machine at the moment, but you can enumerate groups with NetGroupEnum() to see what format NetGroupGetUsers() would expect. As someone in the other thread suggested, there is also NetLocalGroupEnum(). It's been a long time since I've used these APIs and I don't recall what the distinction is between a 'group' and a 'local group', so perhaps that's part of the issue as well. – Luke May 11 '22 at 11:49
  • @Luke My first attempt is to call `NetGroupGetUsers` with a `local` group - no domain joined machine needed. If i can ever get that to work, i'll move on to trying to call `NetGroupGetUsers` with the name of a domain group. – Ian Boyd May 11 '22 at 12:23
  • because *docker-users* not a group. this is alias. so you and must got error `NERR_GroupNotFound` . you must call `NetLocalGroupGetMembers` on alias. – RbMm May 11 '22 at 20:48
  • also you need use *'docker-users'* but not *'OBSIDIAN\docker-users'* – RbMm May 11 '22 at 21:00
  • @RbMm Without specifying the *"Domain"*, how does the function know if it is a *"local"* group or a *"domain"* group? Also: i did try just `docker-users`. (see above) – Ian Boyd May 11 '22 at 23:24
  • function dont know are this group or alias. `NetGroupGetUsers` work only with groups. `NetLocalGroupGetMembers` work only with aliases. need pass only group/alias name without domain name. this api enumerate domains in SAM db by self and search in every domain – RbMm May 11 '22 at 23:30
  • if you want use Net api - https://pastebin.com/nKykti9i . if want max power and perfomance - direct use Sam api (internally Net api of course call this ) https://pastebin.com/peWkZi5i – RbMm May 12 '22 at 00:26
  • With [this code](https://pastebin.com/b4q03JmB) on my non-domain machine I get [this output](https://pastebin.com/w1NUe5mg). – Luke May 12 '22 at 11:56

0 Answers0