tags:

views:

7255

answers:

10

I have a C# application that scans a directory and gathers some information. I would like to display the account name for each file. I can do this on the local system by getting the SID for the FileInfo object, and then doing:

string GetNameFromSID( SecurityIdentifier sid )
{
    NTAccount ntAccount = (NTAccount)sid.Translate( typeof( NTAccount ) );
    return ntAccount.ToString();
}

However, this does not work for files on a network, presumably because the Translate() function only works with local user accounts. I thought maybe I could do an LDAP lookup on the SID, so I tried the following:

string GetNameFromSID( SecurityIdentifier sid )
{
    string str = "LDAP://<SID=" + sid.Value + ">";
    DirectoryEntry dirEntry = new DirectoryEntry( str );
    return dirEntry.Name;
}

This seems like it will work, in that the access to "dirEntry.Name" hangs for a few seconds, as if it is going off and querying the network, but then it throws a System.Runtime.InteropServices.COMException

Does anyone know how I can get the account name of an arbitrary file or SID? I don't know much about networking or LDAP or anything. There's a class called DirectorySearcher that maybe I'm supposed to use, but it wants a domain name, and I don't know how to get that either - all I have is the path to the directory I'm scanning.

Thanks in advance.

A: 

This one is a stumper. You are in an Active Directory environment right? Just checking:)

Anyhow, instead of binding with sid.Value,

string str = "LDAP://<SID=" + sid.Value + ">";

I would try converting the SID's byte array to an Octet String and bind with that instead.

There is a sweet example here on page 78. This will get you closer. To be honest, I've not tried binding with a SID before. But I've had success binding with a user's GUID though :)

Good luck and let me know how it goes.

barneytron
A: 

That's not it. I didn't try that before, because in the documentation for the function it said that the string version should work, but I've tried the hexadecimal version now and it didn't make any difference. Any other ideas?

To be honest I don't know if I'm in an Active Directory environment or not. But I tried it with "WinNT" instead of "LDAP" in the query, and that didn't make any difference either. Ideally I would be able to find out the account name in all types of network, but at the moment I don't know how to do it in any of them.

By the way, I'm using the SID because I thought that is what I was supposed to use. If I can do this with a GUID instead then that's fine by me, but I can't see how to get the owner's GUID from a file, or what I would then do with it. Can you give me any tips on that?

Thanks.

+1  A: 

Ooh, then it's possible that the LDAP call is not working because you might not be in an Active Directory environment. If this is the case, then each of your machines is responsible for its own identity store. And your first code sample is not working across the network because the machine on which you are executing your code does not know how to resolve the SID that only makes sense on the remote machine.

You really should check if your machines are a part of an Active Directory. You would know this during the logon process. Or you can check by right clicking on "My Computer", select "Properties", the "Computer Name" tab, then see if your computer is part of a domain.

barneytron
A: 

Okay, well I'm definitely not in an Active Directory environment then, because I don't have a domain set up here. I'll try sending my sample app to someone who is in a domain and see if it works for them. Thanks for your help.

However, my question then becomes - how can I do it when I'm not in that environment? If my code is running across a plain Microsoft Windows Network or SMB type of network, is there a nice easy way to discover the account name for an arbitrary file read from another system?

+1  A: 

I am quite sure you will be able to use the accepted answer from here: http://stackoverflow.com/questions/204942/determine-the-localsystem-account-name-using-c

Basically, you can translate an instance of the SecurityIdentifier class to type NTAccount, from which you can get the user name. In code:

using System.Security.Principal;

        SecurityIdentifier sid = new SecurityIdentifier("S-1-5-18");
        NTAccount acct = (NTAccount)sid.Translate(typeof(NTAccount));
        Console.WriteLine(acct.Value);
driis
While this is not incorrect, it just repeats the code that the poster has already pointed out does not work in his situation.
Stephen Martin
A: 

Thanks, but that code is basically the same as the first code snippet I originally posted, which only works on the local system, and throws an exception for non-local SIDs.

+1  A: 

The SecurityReference object's Translate method does work on non-local SIDs but only for domain accounts. For accounts local to another machine or in a non-domain setup you would need to PInvoke the function LookupAccountSid specifying the specific machine name on which the look up needs to be performed.

Stephen Martin
I've just started writing some code to do something similar and found this to be correct. P/Invoking LookupAccountSid is better than using SecurityIdentifier.Translate(). I'm working in a multi-forest/domain environment with 60k+ users.
BrianLy
+1  A: 

Great. I cribbed some LookupAccountSid() code from here:

http://www.pinvoke.net/default.aspx/advapi32.LookupAccountSid

And that worked, though I had to provide the host name myself. In the case of a UNC path I can just take the first component of it. When it's a mapped drive, I use this code to convert the path to a UNC one:

http://www.wiredprairie.us/blog/index.php/archives/22

It seems to work, so that's how I'll do it, unless someone comes up with a situation in which the first component of a UNC path isn't the host name...

Thank you all for your help.

A: 

See here for a good answer:

http://stackoverflow.com/questions/380031/the-best-way-to-resolve-display-username-by-sid

The gist of it is this bit:

string sid="S-1-5-21-789336058-507921405-854245398-9938";
string account = new System.Security.Principal.SecurityIdentifier(sid).Translate(typeof(System.Security.Principal.NTAccount)).ToString();

This approach works for me for non-local SID's over the active directory.

Chris
A: 

Get the current domain:

System.DirectoryServices.ActiveDirectory.Domain.GetCurrentDomain();

Get a directory entry from ldap and the domain name:

DirectoryEntry de = new DirectoryEntry(string.Format("LDAP://{0}", domain));

Get the sid from an ActiveDirectoryMembershipProvider ActiveDirectoryMembershipUser:

ActiveDirectoryMembershipUser user = (ActiveDirectoryMembershipUser)Membership.GetUser();
var sid = (SecurityIdentifier)user.ProviderUserKey;

Get the username from the SecurityIdentifier:

(NTAccount)sid.Translate(typeof(NTAccount));

Get directory search done on an activedirectory with the domain directory entry and username:

DirectorySearcher search = new DirectorySearcher(entry);
        search.Filter = string.Format("(SAMAccountName={0})", username);
        search.PropertiesToLoad.Add("Name");
        search.PropertiesToLoad.Add("displayName");
        search.PropertiesToLoad.Add("company");
        search.PropertiesToLoad.Add("homePhone");
        search.PropertiesToLoad.Add("mail");
        search.PropertiesToLoad.Add("givenName");
        search.PropertiesToLoad.Add("lastLogon");
        search.PropertiesToLoad.Add("userPrincipalName");
        search.PropertiesToLoad.Add("st");
        search.PropertiesToLoad.Add("sn");
        search.PropertiesToLoad.Add("telephoneNumber");
        search.PropertiesToLoad.Add("postalCode");
        SearchResult result = search.FindOne();
        if (result != null)
        {
            foreach (string key in result.Properties.PropertyNames)
            {
                // Each property contains a collection of its own
                // that may contain multiple values
                foreach (Object propValue in result.Properties[key])
                {
                    outputString += key + " = " + propValue + ".<br/>";
                }
            }
        }

Depending on the data in your active directory, you will get a varied response in the output.

Here is a site that has all the user properties I needed: