Convert a username to a SID string in C#/.NET
Asked Answered
B

4

46

There's a question about converting from a SID to an account name; there isn't one for the other way around.

How do you convert a username to a SID string, for example, to find out which HKEY_USERS subkey relates to a user of a given name?

Balneal answered 24/6, 2009 at 19:58 Comment(0)
B
94

The podcast tells me I should ask, and answer, questions when they're not answered on SO already. Here goes.

The easy way, with .NET 2.0 and up, is this:

NTAccount f = new NTAccount("username");
SecurityIdentifier s = (SecurityIdentifier) f.Translate(typeof(SecurityIdentifier));
String sidString = s.ToString();

The hard way, which works when that won't, and works on .NET 1.1 also:

[DllImport("advapi32.dll", CharSet=CharSet.Auto, SetLastError=true)]
public static extern bool LookupAccountName([In,MarshalAs(UnmanagedType.LPTStr)] string systemName, [In,MarshalAs(UnmanagedType.LPTStr)] string accountName, IntPtr sid, ref int cbSid, StringBuilder referencedDomainName, ref int cbReferencedDomainName, out int use);

[DllImport("advapi32.dll", CharSet=CharSet.Auto, SetLastError=true)]
internal static extern bool ConvertSidToStringSid(IntPtr sid, [In,Out,MarshalAs(UnmanagedType.LPTStr)] ref string pStringSid);


/// <summary>The method converts object name (user, group) into SID string.</summary>
/// <param name="name">Object name in form domain\object_name.</param>
/// <returns>SID string.</returns>
public static string GetSid(string name) {
    IntPtr _sid = IntPtr.Zero; //pointer to binary form of SID string.
    int _sidLength = 0;   //size of SID buffer.
    int _domainLength = 0;  //size of domain name buffer.
    int _use;     //type of object.
    StringBuilder _domain = new StringBuilder(); //stringBuilder for domain name.
    int _error = 0;
    string _sidString = "";

    //first call of the function only returns the sizes of buffers (SDI, domain name)
    LookupAccountName(null, name, _sid, ref _sidLength, _domain, ref _domainLength, out _use);
    _error = Marshal.GetLastWin32Error();

    if (_error != 122) //error 122 (The data area passed to a system call is too small) - normal behaviour.
    {
        throw (new Exception(new Win32Exception(_error).Message));
    } else {
        _domain = new StringBuilder(_domainLength); //allocates memory for domain name
        _sid = Marshal.AllocHGlobal(_sidLength); //allocates memory for SID
        bool _rc = LookupAccountName(null, name, _sid, ref _sidLength, _domain, ref _domainLength, out _use);

        if (_rc == false) {
            _error = Marshal.GetLastWin32Error();
            Marshal.FreeHGlobal(_sid);
            throw (new Exception(new Win32Exception(_error).Message));
        } else {
            // converts binary SID into string
            _rc = ConvertSidToStringSid(_sid, ref _sidString);

            if (_rc == false) {
                _error = Marshal.GetLastWin32Error();
                Marshal.FreeHGlobal(_sid);
                throw (new Exception(new Win32Exception(_error).Message));
            } else {
                Marshal.FreeHGlobal(_sid);
                return _sidString;
            }
        }
    }
}
Balneal answered 24/6, 2009 at 19:59 Comment(5)
I am intrigued as to why I chose 'f' as the variable for the NTAccount!Balneal
"which works when that won't" ... any pointers to when the easy approach won't work, assuming I have .NET 2.0?Philipps
Not that I remember sorry. I probably meant pre-2.0 only; I expect it boils down to the same Win32 API calls.Balneal
One of my client report an exception: "Some or all identity references could not be translated" when I use the first method, I haven't tried the second method but I just leave my comment here for anyone to know.Contributory
I got System.Security.Principal.IdentityNotMappedExceptionTargum
M
1

The LookupAccountName() native method has the advantage of being able to be executed on a remote machine whereas the .NET methods can't be executed remotely.

Though the example doesn't show it LookupAccountName(null) <- this is the remote system to execute on.

Martin answered 7/3, 2014 at 19:28 Comment(0)
T
1
using System.Security.Principal;

var curUser = WindowsIdentity.GetCurrent().User.Value;
var otherUser = new WindowsIdentity("[email protected]").User.Value;
Tuberculous answered 20/4, 2021 at 6:22 Comment(3)
While your code can be self-explanatory it still is a bad idea to only provide code dumps. Without any explanation and code only answers are marked as low-quality and are subject for deletion.Burned
@NawedNabiZada This is a programmer's question to programmers. We're not writers 😁. I was looking for an answer to this question myself and found it and decided to share it. In my example, variable names and function names tell everything about themselves!Tuberculous
Believe it or not, I only commented to make you aware of the rules of SO.Burned
K
0
using System;
using System.Management;
using System.Windows.Forms;

namespace WMISample
{
public class MyWMIQuery
{
    public static void Main()
    {
        try
        {
            ManagementObjectSearcher searcher = 
                new ManagementObjectSearcher("root\\CIMV2", 
                "SELECT * FROM Win32_UserAccount where name='Galia'"); 

            foreach (ManagementObject queryObj in searcher.Get())
            {
                Console.WriteLine("-----------------------------------");
                Console.WriteLine("Win32_UserAccount instance");
                Console.WriteLine("-----------------------------------");
                Console.WriteLine("Name: {0}", queryObj["Name"]);
                Console.WriteLine("SID: {0}", queryObj["SID"]);
            }
        }
        catch (ManagementException e)
        {
            MessageBox.Show("An error occurred while querying for WMI 
            data: " + e.Message);
        }
      }
    }
}

/////////Remote:

            ConnectionOptions connection = new ConnectionOptions();
            connection.Username = userNameBox.Text;
            connection.Password = passwordBox.Text;
            connection.Authority = "ntlmdomain:WORKGROUP";

            ManagementScope scope = new ManagementScope(
                "\\\\ASUS\\root\\CIMV2", connection);
            scope.Connect();

            ObjectQuery query= new ObjectQuery(
                "SELECT * FROM Win32_UserAccount"); 

            ManagementObjectSearcher searcher = 
                new ManagementObjectSearcher(scope, query);

            foreach (ManagementObject queryObj in searcher.Get())
            {
                Console.WriteLine("-----------------------------------");
                Console.WriteLine("Win32_UserAccount instance");
                Console.WriteLine("-----------------------------------");
            }
Katheleenkatherin answered 13/2, 2018 at 13:30 Comment(3)
Presumably this only works for local user accounts?Ferdinana
for remote computer it's almost the sameKatheleenkatherin
Huh, I had no idea WMI could work for domain stuff, thanks!Ferdinana

© 2022 - 2024 — McMap. All rights reserved.