如何可靠地读取所有版本的 Windows 上用户的显示(名字和姓氏)名称?

发布于 2024-09-18 10:17:53 字数 616 浏览 9 评论 0原文

我发现在 Windows 7 64 位上,在具有域名的计算机上, GetUserNameEx( 3, .... ) 应该将扩展名称格式 DisplayName (==3) 获取到缓冲区中,工作正常。

但是,它不适用于 Windows 7 32 位、位于工作组而不是域上的虚拟机,它返回 ERROR_NONE_MAPPED。

例如,如何以适用于 Windows 的方式读取此人的友好名称“Fred Smith”? GetUserNameEx 显然已损坏。事实上,我被告知,这并没有被破坏,只是不适合不在域中的用户。我想知道,既然本地 SAM 信息存在,为什么不呢?而且似乎没有其他直接的 API 可以做到这一点。

如果 Windows 给你 ERROR_NONE_MAPPED,那么你就不走运了,而且可能不在域上。所以这并不是 API 的友好区域。

[看起来,当不在域上时,可以调用 NetUserGetInfo 来读取本地 SAM 信息,但您需要首先知道用户名和密码,然后它可能会查找友好名称。]

相关问题:此处未提及问题

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

她如夕阳 2024-09-25 10:17:53

这是 Warren 的解决方案移植到 C#。我添加了从域名检索域控制器 IP 的功能,因为至少在我的域上,仅使用 \\ 作为服务器名称是行不通的。

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;
}

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;
}
野鹿林 2024-09-25 10:17:53

我有一个似乎有效的解决方案,这通常意味着:

  1. 如果 secure32.dll 中的 GetUserNameEx(3,...) 函数有效,则使用该值。
  2. 回退到 GetUserNameEx(2,...) 调用和从 netapi32.dll 导入的 NetUserGetInfo 调用的组合
  3. 首先调用 NetUserGetInfo 的问题是它在域名上失败,或者至少,下面的 NetUserGetInfo 实现仅在以下情况下才有效从非域/非 ActiveDirectory 用户命名空间上的本地计算机名称读取 SAM 信息。

示例代码(Delphi 中),在 Matt 的回答中移植到 C#:

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;

编辑:2011 年 11 月;删除了死链接。

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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文