views:

124

answers:

1

I have found that on Windows 7 64 bit, on a machine with a domain name, GetUserNameEx( 3, .... ) which should get the extended name format DisplayName (==3), into a buffer, works fine.

However, it does not work on Windows 7 32 bit, vm that is on a workgroup, rather than on a domain, it returns ERROR_NONE_MAPPED.

How do you read the person's friendly name "Fred Smith", for example, in a way that works on Windows? GetUserNameEx is manifestly broken. Actually, not broken, I'm told, just not intended to work for users who are not on a domain. Why not, I wonder, since the local SAM information exists? And there appears to be no other direct API to do this.

If Windows gives you ERROR_NONE_MAPPED, you are out of luck, and probably not on a domain. So this is not exactly a friendly area of the API.

[It is possible, it looks like, to call NetUserGetInfo, to read the local SAM info, when not on a domain, but you need to know the user name and password first, and then it will maybe look up the friendly name.]

RElated Question: does not mention the problem here

+1  A: 

I have a solution that appears to work:

  1. if the GetUserNameEx(3,...) function from secur32.dll works, use that value.
  2. fall back to a combination of GetUserNameEx(2,...) calls and NetUserGetInfo calls imported from netapi32.dll
  3. The problem with invoking NetUserGetInfo first, is that it fails on domain names, or at least, the implementation below on NetUserGetInfo works only when reading SAM information from a local machine name, on a non-domain/non-ActiveDirectory user namespace.

A download of a demo is here until it goes away: http://rapidshare.com/files/418296807/sources_NetUserGetInfoSample.zip

type
EProcError = class( Exception );
TGetUserNameExWProc = function( FormatType : Integer; Buffer : PWideChar; var BufSize : Integer ) : DWORD;      stdcall;
var
  _GetUserNameExW : TGetUserNameExWProc;
procedure GetProcedureAddress( var P : Pointer; const ModuleName, ProcName : string );
var
  ModuleHandle : HMODULE;
begin
  if not Assigned( P ) then
  begin
    ModuleHandle := GetModuleHandle( pChar( ModuleName ) );
    if ModuleHandle = 0 then
    begin
      ModuleHandle := SafeLoadLibrary( pChar( ModuleName ) );
      if ModuleHandle = 0 then
        raise EProcError.Create( 'Unable to load module' );
    end;
    P := GetProcAddress( ModuleHandle, pChar( ProcName ) );
    if not Assigned( P ) then
      raise EProcError.Create( 'Unable to get proc address' );
  end;
end;
function MyGetUserNameEx( aFormat : Integer ) : string;
var
  sz : Integer;
  sz2 : Integer;
  ret : Integer;
begin
  if not Assigned( _GetUserNameExW ) then
    GetProcedureAddress( Pointer( @_GetUserNameExW ), 'secur32.dll', 'GetUserNameExW' );
  if Assigned( _GetUserNameExW ) then
  begin
    sz := 2000;
    SetLength( Result, sz );
    Result[ 1 ] := Chr( 0 );
    ret := _GetUserNameExW( { 3=NameDisplay } aFormat, PWideChar( Result ), sz );
    if ret <> 0 then
    begin
      sz2 := StrLen( PWideChar( Result ) ); // workaround WinXP API bug
      if sz2 < sz then // WinXP bug.
        sz := sz2;
      SetLength( Result, sz )
    end
    else
    begin
      ret := GetLastError;
      if ret = ERROR_NONE_MAPPED then
        Result := ''
      else
        Result := 'E' + IntToStr( ret );
    end;
  end;
end;
function MyNetUserGetInfo : string;
const
  netapi32 = 'netapi32.dll';
type
  TNetUserGetInfo = function( servername, username : LPCWSTR; level : DWORD; var bufptr : PByte ) : DWORD; stdcall;
  TNetApiBufferFree = function( Buffer : PByte ) : DWORD; stdcall;
  USER_INFO_10 = record
    usri10_name : PWideChar;
    usri10_comment : PWideChar;
    usri10_usr_comment : PWideChar;
    usri10_full_name : PWideChar;
  end;
  P_USER_INFO_10 = ^USER_INFO_10;
var
  _NetUserGetInfo : TNetUserGetInfo;
  _NetApiBufferFree : TNetApiBufferFree;
  ret : DWORD;
  servername : string;
  username : string;
  level : Cardinal;
  info : P_USER_INFO_10;
  pbuf : PByte;
  pwuser : PWideChar;
  n : Integer;
begin
  ret := 0;
  _NetUserGetInfo := nil;
  GetProcedureAddress( Pointer( @_NetUserGetInfo ), netapi32, 'NetUserGetInfo' ); // raises EProcError
  if not Assigned( _NetUserGetInfo ) then
    Result := 'FunctionNotFound'
  else
  begin
    // usernamesize := 200;
    username := MyGetUserNameEx( 2 );
    if username = '' then
    begin
      Result := 'CanNotGetUserName';
      Exit;
    end;
    n := Pos( '\', username );      //' recover SO code formatting
    if n > 0 then
    begin
      servername := '\\' + Copy( username, 1, n - 1 );
      username := Copy( username, n + 1, Length( username ) );
    end;
    level := 10;
    pbuf := nil;
    pwuser := PWideChar( username );
    info := nil;
    if servername = '' then
      ret := _NetUserGetInfo( { servername } nil, pwuser, level, pbuf )
    else
      ret := _NetUserGetInfo( PWideChar( servername ), pwuser, level, pbuf );
    if ret = 0 then
    begin
      info := P_USER_INFO_10( pbuf );
      if Assigned( info ) then
        Result := info.usri10_full_name;
      GetProcedureAddress( Pointer( @_NetApiBufferFree ), netapi32, 'NetApiBufferFree' );
      if Assigned( info ) and Assigned( _NetApiBufferFree ) then
        _NetApiBufferFree( pbuf );
    end
    else
    begin
      if ret = 2221 then
        Result := 'Error_USER ' + username
      else if ret = 1722 then
        Result := 'Error_RPC ' + servername
      else
        Result := 'E' + IntToStr( ret );
    end;
  end;
end;
Warren P
How long will that link be active? In the interest of posterity, I think it would be useful to also include a paraphrased code sample rather than just function names.
mskfisher
For the benefit of SO readers, just paste the code in your answer right here!
Andreas Rejbrand
+1 to both the above. Answers really need to include the full answer: the web as a whole suffers from link-rot. If you post a long code sample, SO automatically formats it with scrollbars, so there isn't really any such thing as 'too long' as far as I'm aware.
David M
Oh yes there is. Whitespace really breaks SO's code feature. I had to go through the hundreds of lines and carefully reformat it to work with SO. This took me a long time. I wish that when you clicked "code sample" around a block of code, it didn't break up into different sections when it feels I've got a big whitespace break.
Warren P
Okay, there is a full code sample now.
Warren P