如何在C#中将SID转换为帐户名

61

我有一个C#应用程序,可以扫描一个目录并收集一些信息。我想要显示每个文件的帐户名称。我可以通过获取FileInfo对象的SID,然后执行以下操作,在本地系统上完成此操作:

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

然而,这对于网络上的文件不起作用,可能是因为Translate()函数仅适用于本地用户帐户。我认为也许可以通过SID进行LDAP查找,因此我尝试了以下操作:

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

看起来这似乎是可行的,因为对“dirEntry.Name”的访问会停顿几秒钟,就像它正在离线查询网络一样,但然后它会抛出一个System.Runtime.InteropServices.COMException。

有人知道如何获取任意文件或SID的帐户名称吗?我不太了解网络或LDAP等内容。有一个名为DirectorySearcher的类也许我应该使用它,但它需要一个域名,而我不知道如何获取它 - 我只有正在扫描的目录的路径。


COMException会显示特定的错误信息吗? - David Brown
可能是组策略允许您在本地遍历目录服务,这就是为什么会出现COM异常的原因。错误消息显示了什么?尝试在访问它的网络机器上运行filemon并查看结果。 - Saif Khan
我是指“组策略不允许……” - Saif Khan
11个回答

55

这里有一个很好的答案:

The best way to resolve display username by SID?

大意是这段代码:

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();

对于活动目录上的非本地SID,这种方法对我有效。


21

SecurityReference对象的Translate方法可以处理非本地SID,但仅适用于域帐户。对于其他计算机上本地帐户或非域设置中的帐户,您需要使用PInvoke函数LookupAccountSid并指定要执行查找的特定计算机名称。


2
我刚开始编写一些类似的代码,并发现这是正确的。使用P/Invoke LookupAccountSid比使用SecurityIdentifier.Translate()更好。我正在一个拥有60k+用户的多森林/域环境中工作。 - Brian Lyttle
Translate方法返回组的Windows 2000之前的名称。除了PInvoke,您还可以在System.DirectoryServices.AccountManagement.GroupPrincipal上使用FindByIdentity方法。 - Daniel Fisher lennybacon

7

System.DirectoryServices.AccountManagement.UserPrincipal类(msdn链接)拥有一个静态函数FindByIdentity,可以将一个SID转换为用户对象。它可以对本地计算机或LDAP/Active Directory服务器进行操作。我仅在Active Directory中使用过它。

下面是我在IIS中使用的一个示例:

// Set the search context to a specific domain in active directory
var searchContext = new PrincipalContext(ContextType.Domain, "YOURDOMAIN", "OU=SomeOU,DC=YourCompany,DC=com");
// get the currently logged in user from IIS
MembershipUser aspUser = Membership.GetUser();
// get the SID of the user (stored in the SecurityIdentifier class)
var sid = aspUser.ProviderUserKey as System.Security.Principal.SecurityIdentifier;
// get the ActiveDirectory user object using the SID (sid.Value returns the SID in string form)
var adUser = UserPrincipal.FindByIdentity(searchContext, IdentityType.Sid, sid.Value);
// do stuff to user, look up group membership, etc.

2
这是查询外国域名的正确方式。不需要使用P/Invoke。 - Tamir Daniely

3

在 C# 中,通过以下方式获取用户 SID 并将其分配给字符串变量:

string strUser = System.Security.Principal.WindowsIdentity.GetCurrent().User.ToString();

你需要使用string,因为解析到UserName时只支持字符串。换句话说,使用var varUser会导致命名空间错误。

string strUserName = new System.Security.Principal.SecurityIdentifier(strUser).Translate(typeof(System.Security.Principal.NTAccount)).ToString();

这导致了问题 = “此工作站与主域之间的信任关系失败” - user1034912
@user1034912 你遵循了哪些故障排除步骤?你是否以管理员模式运行了VS?你是否检查了MSDN关于此错误的信息:https://support.microsoft.com/en-us/help/2771040/the-trust-relationship-between-this-workstation-and-the-primary-domain? - J Weezy
1
忘了它吧,我通过重新加入域来解决了这个问题...虽然很痛苦...但它起作用了! - user1034912

2
您可以使用以下代码获取特殊账户(如“Everyone”)的帐户名称,该代码将在不考虑用户语言设置的情况下工作:
   SecurityIdentifier everyoneSid = new SecurityIdentifier(WellKnownSidType.WorldSid, null);
   string everyone = everyoneSid.Translate(typeof(System.Security.Principal.NTAccount)).ToString();

谢谢。对我来说这真的救了我的命 :) - Regis St-Gelais

1

哦,那么可能是因为您不在Active Directory环境中,LDAP调用无法工作。如果是这种情况,那么每台机器都负责自己的身份存储。您的第一个代码示例无法跨网络工作,因为执行代码的计算机不知道如何解析仅在远程计算机上有意义的SID。

您真的应该检查一下您的计算机是否属于Active Directory。您可以在登录过程中了解到这一点。或者您可以右键单击“我的电脑”,选择“属性”,然后选择“计算机名”选项卡,看看您的计算机是否属于域。


1

太好了。我从这里抄了一些LookupAccountSid()代码:

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

这样做行得通,但我必须自己提供主机名。对于UNC路径,我只需取其第一个组件即可。当它是映射的驱动器时,我使用此代码将路径转换为UNC:

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

看起来它能工作,所以我会这样做,除非有人提出了第一个组件不是主机名的UNC路径的情况...

感谢大家的帮助。


0

对于所有的Windows开发者来说,答案就是LookupAccountSid

LookupAccountSid(null, Sid, username, userSize, domainName, domainSize, sidType);

0

获取当前域名:

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

从LDAP获取目录条目和域名:

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

从 ActiveDirectoryMembershipProvider ActiveDirectoryMembershipUser 中获取 sid:

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

从SecurityIdentifier获取用户名:

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

使用域目录条目和用户名在活动目录上完成目录搜索:

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

根据您的活动目录中的数据,输出结果会有所不同。

这里有一个网站,拥有我所需的所有用户属性:


0
这是一个难题。你在Active Directory环境中,对吧?只是确认一下 :)
无论如何,与其使用sid.Value进行绑定,
string str = "LDAP://<SID=" + sid.Value + ">";

我建议将SID的字节数组转换为八位字符串,然后再进行绑定。

第78页有一个很好的例子在这里。这会让你更接近成功。说实话,我以前没有尝试过使用SID进行绑定,但是我曾经成功地使用用户的GUID进行绑定 :)

祝你好运,让我知道进展如何。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接