获取系统用户所对应的配置路径
在 Windows 系统中,如何通过一个用户的名称而得到其所对应的配置路径呢?具体来说就是得到 C:/Documents and Settings/<username> (假设为 Windows XP 系统,安装在 C 盘下)呢?
这里有一个简单的方法。既然已经知晓了系统的安装盘符,还知道了用户的名称,那么简单的使用字符串拼凑起来不就可以了么?
一般来说呢,这种方法得到的结果可以说正确性很高(没谁闲着没事做该目录的名称吧? :-) )。但是很遗憾,这种方法是错误的(废话,只要是程序员都知道不应该这么做)。那么到底该如何做呢?
Windows 提供给所有的程序员大量的系统 API 用以完成各种系统相关的强大的功能。SHGetSpecialFolderPath 就是一个能完成上述功能的 API。这个函数功能很简单,根据一个指定的 CSIDL 获取其对应的文件夹路径。CSIDL 中包括很多值,基本都和系统相关,具体可以查看 MSDN。根据 MSDN 的介绍,这个函数已经被 SHGetFolderPath 所替代。这个函数也挺简单,容易上手。但是,请注意: SHGetFolderPath 函数只能得到真实的目录路径,而对于虚拟目录的路径却无能为力。而 SHGetSpecialFolderPath 可以获取虚拟目录和真实目录的路径。例如桌面、我的电脑等。所以,结论就不用再说了吧?(*^__^*) 嘻嘻……
上面的话都是顺带说的。实际上这两天项目中遇到一个问题。这个问题是这样的(以 Windows XP 为例):假设系统存在一个用户 UserA 并已经登录系统。此时在 C:/Documents and Settings 目录下建立一个新的目录 UserB。然后在用户管理中新建一个用户,名称为 UserB。注销 UserA,使用 UserB 登录系统。这样的话,在 C:/Documents and Settings 的目录下,会出现一个新的目录—— UserB.<ComputerName> (或者是其它的名称)。这个新目录下将存储 UserB 相关的文件和文件夹,例如用户 UserB 的收藏夹、文档、本地设置等。
现在问题来了。如果使用 UserA 登录系统后,选择一个可执行程序,例如 pro.exe,使用 UserB 的身份运行它(右键点击可执行程序,在右键菜单中,选择“运行方式”,选择 UserB 身份运行这个程序)。那么如何在 pro.exe 这个程序中获得 UserA 的 Profile 信息?
实际上,这个问题可以简化为:如何根据用户名称,获得其对应的 Profile 信息,即获得正确的 C:/Documents and Settings/<username> 路径?
如果在程序中,简单的使用上面提到的方法是不行的。理由很简单,可执行程序 pro.exe 已经运行在 UserB 空间下了,其所有的环境都是和 UserB 相关的。例如在 .net Framework 中,Environment.UserName 得到的是 UserB,而不是 UserA。
曾经有高手介绍了一个 API —— NetUserGetInfo,Netapi32.dll 中的一个 API,可以得到一个指定服务器上的指定名字的用户的信息(The NetUserGetInfo function retrieves information about a particular user account on a server. —— MSDN)。其原型为:
NET_API_STATUS NetUserGetInfo(
LPCWSTR servername,
LPCWSTR username,
DWORD level,
LPBYTE* bufptr
);
函数运行成功,返回 NERR_Success,失败返回 ERROR_ACCESS_DENIED 或 NERR_InvalidComputer 或 NERR_UserNotFound。
参数 servername 为空时,表示本地的计算机。根据 level 的不同,bufptr 指向不同的结构。简单点,当 level = n 时,bufptr = &LPUSER_INFO_n (^_^,一般情况下如此)。例如使用 USER_INFO_1,即 bufptr = &LPUSER_INFO_1,则根据 MSDN,bufptr->usri1_home_dir 就是我们所要的结果。
可惜,很可惜!运行了一遍,结果竟然和我的想像相左。我无语了。在 CSDN 上问了一遍,无人回答(也不完全,有人回答,可惜是错误的)。只好上微软的新闻组上去问问,看看微软的工程师们是否有办法。
高人,果然永远都是存在的!微软的在线工程师首先提供了上面的方法(即使用 NetUserGetInfo 函数)。在知晓结果不正确之后,又提出了另一个方法:查找注册表。高!不是一般的高!难道这就是传说中的高手,高手,高高手!?
“我们可以通过读注册表的方法获得 home directory:所有用户的 home directory 都可以在 HKEY_LOCAL_MACHINE/SOFTWARE/Microsoft/Windows NT/CurrentVersion/ProfileList
下找到,可以通过 user account name (LookupAccountName )获得该用户的 SID,在通过 SID 获得 home directory。”——注:是该注册表项的一个子项“S-1-......”。该子项下,名为“ProfileImagePath”的二进制键所对应的值就是那个目录路径,“Sid”的多字符串值就是用户名对应的 SID 值。
这是那位高手的原话。郭轩——微软全球技术支持中心在线技术支持工程师。很奇怪,微软全球技术支持中心是不是只有这一位在线技术支持工程师?因为前些日子,一个同事也在那里提问题,也是他回答的。看过一些其它的帖子,好像也都是他给出答案。真奇怪。
不管怎样,这个问题算是解决了。获得指定用户名称所对应的目录方法为:
BYTE abSID[128];
PSID psid = (PSID)&abSID;
DWORD cbSid = 100;
TCHAR ReferencedDomainName[MAX_PATH];
DWORD cchReferencedDomainName = MAX_PATH;
SID_NAME_USE eUser;
if (::LookupAccountName(NULL, _T("用户名"), psid, &cbSid, ReferencedDomainName, &cchReferencedDomainName , &eUser))
{
TCHAR szBuffer[MAX_PATH]; // SID 字符串
GetSidString(psid, szBuffer);
}
// 使SID信息转换成字符串显示出来
void GetSidString(PSID pSid, TCHAR * szBuffer)
{
SID_IDENTIFIER_AUTHORITY *psia = GetSidIdentifierAuthority( pSid );
DWORD dwTopAuthority = psia->Value[5];
_stprintf(szBuffer, _T("S-1-%lu"), dwTopAuthority);
TCHAR szTemp[32];
int iSubAuthorityCount = *(GetSidSubAuthorityCount(pSid));
for (int i = 0; i < iSubAuthorityCount; i++)
{
DWORD dwSubAuthority = *(GetSidSubAuthority(pSid, i));
_stprintf(szTemp, _T("-%lu"), dwSubAuthority);
_tcscat(szBuffer, szTemp);
}
}
当然,还有其它方法得到某一个用户的 SID 信息。那就是大名鼎鼎的 WMI !在 Win32_UserAccount 中,就包含了 string 类型的 变量 SID(省却了自己拼字符串的过程,简单啊!)。不要怀疑,就是它了!而如果是使用 .net Framework,在C#下编程的话,可以使用 System.Management 命名空间下的类实现同样的功能,具体使用到的类有 ManagementClass、ManagementObject、PropertyData。代码如下:
string strUserName = "用户名";
string strSID = string.Empty;
ManagementClass mc = new ManagementClass("Win32_UserAccount");
foreach (ManagementObject mo in mc.GetInstances())
{
if (strUserName.CompareTo(mo.Properties["Name"].Value.ToString()) == 0)
{
strSID = mo.Properties["sid"].Value.ToString();
break;
}
}
Debug.WriteLine(strSID);
注意:上面这段代码可能在 Guest 权限用户下产生异常。具体使用时,请使用异常捕捉。