2010-09-10 11 views
3

J'ai trouvé que sur Windows 7 64 bits, sur une machine avec un nom de domaine, GetUserNameEx (3, ....) qui devrait obtenir le format de nom étendu DisplayName (== 3), dans un tampon, fonctionne très bien.Comment lisez-vous le nom de l'utilisateur (prénom et nom) sur toutes les versions de Windows de manière fiable?

Toutefois, il ne fonctionne pas sur Windows 7 32 bits, vm qui est sur un groupe de travail, plutôt que sur un domaine, il renvoie ERROR_NONE_MAPPED. Comment interprétez-vous le nom convivial de la personne "Fred Smith", par exemple, d'une manière qui fonctionne sur Windows? GetUserNameEx est manifestement cassé. On m'a dit que je n'avais pas l'intention de travailler pour des utilisateurs qui ne sont pas sur un domaine. Pourquoi pas, je me le demande, puisque les informations SAM locales existent? Et il semble qu'il n'y ait aucune autre API directe pour le faire.

Si Windows vous donne ERROR_NONE_MAPPED, vous n'avez pas de chance, et probablement pas sur un domaine. Donc, ce n'est pas exactement une zone conviviale de l'API. [Il est possible, il semble, d'appeler NetUserGetInfo, pour lire les infos SAM locales, quand ce n'est pas sur un domaine, mais vous devez d'abord connaître le nom d'utilisateur et le mot de passe, puis il recherchera peut-être le nom convivial]

RElated Question: does not mention the problem here

+0

Ma réponse fonctionne pour Delphi, la réponse de Matt fonctionne pour C#. Je retagerez ceci comme visuel C++ si quelqu'un transporte cet extrait de code au C++. –

Répondre

3

j'ai une solution qui semble fonctionner, ce qui signifie en général:.

  1. si la fonction GetUserNameEx (3, ...) des œuvres Secur32.dll, utilise cette valeur.
  2. revenir à une combinaison de GetUserNameEx (2, ...) les appels et les appels NetUserGetInfo importés de netapi32.dll
  3. Le problème avec l'invocation NetUserGetInfo premier, est qu'il échoue sur les noms de domaine, ou au moins, la mise en œuvre ci-dessous sur NetUserGetInfo ne fonctionne que lors de la lecture des informations SAM à partir d'un nom de machine local, sur un espace de noms d'utilisateur non-domaine/non-ActiveDirectory.

Exemple de code (en Delphi), porté à C# ci-dessous dans la réponse de Matt:

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; 

Modifier: Nov 2011; Lien mort supprimé.

+2

Combien de temps ce lien sera-t-il actif? Dans l'intérêt de la postérité, je pense qu'il serait utile d'inclure également un échantillon de code paraphrasé plutôt que de simples noms de fonctions. – mskfisher

+1

Pour le bénéfice des lecteurs SO, il suffit de coller le code dans votre réponse ici! –

+0

+1 aux deux ci-dessus. Les réponses doivent vraiment inclure la réponse complète: le Web dans son ensemble souffre de la pourriture des liens. Si vous postez un long code, SO le formate automatiquement avec des barres de défilement, il n'y a donc pas de "trop ​​long" pour autant que je sache. –

6

Voici la solution de Warren portée sur C#. J'ai ajouté la récupération de l'adresse IP d'un contrôleur de domaine à partir du nom de domaine car au moins sur mon domaine, en utilisant simplement \\<domain> car le nom du serveur ne fonctionnait pas.

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

Serait plus pertinent si c'était C++ (comme le OP marqué 'VisualC++' sur les étiquettes de question) .... –

+0

Exactement. C# n'est pas vraiment pertinent ici à cette question Delphi qui n'est pas étiquetée C#. –

+2

Puisque vous avez déjà répondu à votre question, j'ai posté une autre version de la langue (ce qui serait d'ailleurs plus facile à porter en C++) car je pensais que cela aiderait quelqu'un d'autre à trouver la même question raison que j'ai fait. –

Questions connexes