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 examp开发者_Go百科le, 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
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;
}
I have a solution that appears to work, which in general means:
- if the GetUserNameEx(3,...) function from secur32.dll works, use that value.
- fall back to a combination of GetUserNameEx(2,...) calls and NetUserGetInfo calls imported from netapi32.dll
- 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.
精彩评论