How do you read the user's display (first and last) name on all versions of Windows reliably?

前端 未结 2 758
抹茶落季
抹茶落季 2020-12-19 17:13

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,

相关标签:
2条回答
  • 2020-12-19 18:04

    I have a solution that appears to work, which in general means:

    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.

    Sample code (in Delphi), ported to C# below in Matt's answer:

    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;
    

    Edit: Nov 2011; Removed dead link.

    0 讨论(0)
  • 2020-12-19 18:15

    Here is Warren's solution ported to C#. I added retrieval of a domain controller's IP from the domain name because at least on my domain, just using \\<domain> as the server name didn't work.

    using System;
    using System.Text;
    using System.Net;
    using System.Runtime.InteropServices;
    using System.DirectoryServices.ActiveDirectory;
    
    [DllImport("secur32.dll", CharSet = CharSet.Auto)]
    private static extern int GetUserNameEx (int nameFormat, StringBuilder userName, ref uint userNameSize);
    
    [DllImport("netapi32.dll", CharSet = CharSet.Unicode, ExactSpelling = true)]
    private static extern int NetUserGetInfo ([MarshalAs(UnmanagedType.LPWStr)] string serverName,
                                              [MarshalAs(UnmanagedType.LPWStr)] string userName,
                                              int level, out IntPtr bufPtr);
    
    [DllImport("netapi32.dll", CharSet = CharSet.Unicode, ExactSpelling = true)]
    private static extern long NetApiBufferFree (out IntPtr bufPtr);
    
    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
    public struct USER_INFO_10
    {
        [MarshalAs(UnmanagedType.LPWStr)] public string usri10_name;
        [MarshalAs(UnmanagedType.LPWStr)] public string usri10_comment;
        [MarshalAs(UnmanagedType.LPWStr)] public string usri10_usr_comment;
        [MarshalAs(UnmanagedType.LPWStr)] public string usri10_full_name;
    }
    
    private string getUserDisplayName ()
    {
        var username = new StringBuilder(1024);
        uint userNameSize = (uint) username.Capacity;
    
        // try to get display name and convert from "Last, First" to "First Last" if necessary
        if (0 != GetUserNameEx(3, username, ref userNameSize))
            return Regex.Replace(username.ToString(), @"(\S+), (\S+)", "$2 $1");
    
        // get SAM compatible name <server/machine>\\<username>
        if (0 != GetUserNameEx(2, username, ref userNameSize))
        {
            IntPtr bufPtr;
            try
            {
                string domain = Regex.Replace(username.ToString(), @"(.+)\\.+", @"$1");
                DirectoryContext context = new DirectoryContext(DirectoryContextType.Domain, domain);
                DomainController dc = DomainController.FindOne(context);
    
                if (0 == NetUserGetInfo(dc.IPAddress,
                                        Regex.Replace(username.ToString(), @".+\\(.+)", "$1"),
                                        10, out bufPtr))
                {
                    var userInfo = (USER_INFO_10) Marshal.PtrToStructure(bufPtr, typeof (USER_INFO_10));
                    return Regex.Replace(userInfo.usri10_full_name, @"(\S+), (\S+)", "$2 $1");
                }
            }
            finally
            {
                NetApiBufferFree(out bufPtr);
            }
        }
    
        return String.Empty;
    }
    
    0 讨论(0)
提交回复
热议问题