NetGroupGetUsers only ever returns NERR_GroupNotFound (2220)

462 Views Asked by At

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

0

There are 0 best solutions below