none
请教,使用 NetUserEnum 获取账户名称以及 SID 时出现的问题 RRS feed

  • 问题

  • 由于项目需要枚举账户名称以及其对应的 SID,所以我找到了 Platform SDK 里面的这个方法。根据 MSDN 的说明:

    ms-help://MS.VSCC.v80/MS.MSDN.v80/MS.WIN32COM.v10.en/netmgmt/netmgmt/netuserenum.htm
    (我的环境是 Windows XP, Visual studio 2005)

    MSDN 上的例子使用 Level 0 的,里面没有 SID,所以我就改用 Level 23,代码如下:(绝大部分抄袭 MSDN,仅修改两处,使用 “//已修改” 注释做抬头)

    int _tmain(int argc, _TCHAR* argv[])
    {
    	//已修改, 适应 Level 23
    	LPUSER_INFO_23 pBuf = NULL;
    	LPUSER_INFO_23 pTmpBuf;
    	DWORD dwLevel = 23;
    
    	DWORD dwPrefMaxLen = MAX_PREFERRED_LENGTH;
    	DWORD dwEntriesRead = 0;
    	DWORD dwTotalEntries = 0;
    	DWORD dwResumeHandle = 0;
    	DWORD i;
    	DWORD dwTotalCount = 0;
    	NET_API_STATUS nStatus;
    	LPTSTR pszServerName = NULL;
    
    	if (argc > 2)
    	{
    	  fwprintf(stderr, L"Usage: %s [\\\\ServerName]\n", argv[0]);
    	  exit(1);
    	}
    	// The server is not the default local computer.
    	//
    	if (argc == 2)
    	  pszServerName = argv[1];
    	wprintf(L"\nUser account on %s: \n", pszServerName);
    	//
    	// Call the NetUserEnum function, specifying level 0; 
    	//   enumerate global user account types only.
    	//
    	do // begin do
    	{
    	  nStatus = NetUserEnum(pszServerName,
    							dwLevel,
    							FILTER_NORMAL_ACCOUNT, // global users
    							(LPBYTE*)&pBuf,
    							dwPrefMaxLen,
    							&dwEntriesRead,
    							&dwTotalEntries,
    							&dwResumeHandle);
    	  //
    	  // If the call succeeds,
    	  //
    	  if ((nStatus == NERR_Success) || (nStatus == ERROR_MORE_DATA))
    	  {
    		 if ((pTmpBuf = pBuf) != NULL)
    		 {
    			//
    			// Loop through the entries.
    			//
    			for (i = 0; (i < dwEntriesRead); i++)
    			{
    			   // assert(pTmpBuf != NULL);
    
    			   if (pTmpBuf == NULL)
    			   {
    				  fprintf(stderr, "An access violation has occurred\n");
    				  break;
    			   }
    			   //
    			   //  Print the name of the user account.
    			   //
    
    			   //已修改,变量名不同
    			   wprintf(L"\t-- %s\n", pTmpBuf->usri23_name);
    
    			   pTmpBuf++;
    			   dwTotalCount++;
    			}
    		 }
    	  }
    	  //
    	  // Otherwise, print the system error.
    	  //
    	  else
    		 fprintf(stderr, "A system error has occurred: %d\n", nStatus);
    	  //
    	  // Free the allocated buffer.
    	  //
    	  if (pBuf != NULL)
    	  {
    		 NetApiBufferFree(pBuf);
    		 pBuf = NULL;
    	  }
    	}
    	// Continue to call NetUserEnum while 
    	//  there are more entries. 
    	// 
    	while (nStatus == ERROR_MORE_DATA); // end do
    	//
    	// Check again for allocated memory.
    	//
    	if (pBuf != NULL)
    	  NetApiBufferFree(pBuf);
    	//
    	// Print the final count of users enumerated.
    	//
    	fprintf(stderr, "\nTotal of %d entries enumerated\n", dwTotalCount);
    
    	getchar();
    
    	return 0;
    }

    结果一开始 NetUserEnum 就没有预期结果。此函数的返回值 NET_API_STATUS nStatus 是一个对不上号的数值 “124”

    继而我又企图使用 Level 3,因为我看到 USER_INFO_3 里面的说明:

    It is recommended that you use the USER_INFO_4 structure instead

    所以就是用 Level 3 与 USER_INFO_4 来作为 NetUserEnum 的参数。

    结果是可以运行,但是当我企图用 ConvertSidToStringSid 来把 USER_INFO_4.usri4_user_sid 转换为 LPCTSTR  格式的时候, ConvertSidToStringSid 的返回结果是 FALSE, GetLastError 的结果是 ERROR_INVALID_SID。

    我在是无计可施了,恳请哪位有在这方面有经验的高人,指点一下我是用错了哪些东西。或者提供其他的能够枚举计算机上账户以及其对应 SID 的方法。谢谢

    2009年8月12日 2:45

答案

  • Specifies a DWORD value that contains the relative ID (RID) of the user 这个是关于useri3_user_sid的解释,其实用level3,和level20获得的都是这个,所以把它当成SID的话都会报ERROR_INVALID_SID的错误。
    可以用LookupAccountName 来用usriN_full_name获得SID
    • 已标记为答案 IGabriel 2009年8月13日 2:04
    2009年8月12日 10:06
  • Specifies a DWORD value that contains the relative ID (RID) of the user 这个是关于useri3_user_sid的解释,其实用level3,和level20获得的都是这个,所以把它当成SID的话都会报ERROR_INVALID_SID的错误。
    可以用LookupAccountName 来用usriN_full_name获得SID

    谢谢你的提示。

    LookupAccountName 的 lpAccountName  要求 domain_name\user_name 格式的名称,我个人感觉不好办。

    所以后来我采用了以下的解决方法

    1. NetUserEnum 采用 Level 0 以及 USER_INFO_0 做参数
    2. NetUserGetInfo 采用 Level 4 以及 USER_INFO_4 做参数, 此时的 USER_INFO_4.usri4_user_sid  才是真正的 PSID
    3. ConvertSidToStringSid,搞掂。

    感觉有一种“曲线救国”的味道...不知道有没有更好的办法
    • 已编辑 IGabriel 2009年8月13日 2:05 错别字
    • 已标记为答案 IGabriel 2009年8月13日 2:06
    2009年8月13日 2:04

全部回复

  • 我记得xp用的是NT内核。
    在MSDN中 level 23有这样一句:

    Windows 2000/NT:  This level is not supported.
    改用level 20就可以,还是用这个结构 USER_INFO_23 就可以枚举出用户,我在本机上测试用level23也是返回错误代码124 (xp,vs2005)
    2009年8月12日 7:50
  • Specifies a DWORD value that contains the relative ID (RID) of the user 这个是关于useri3_user_sid的解释,其实用level3,和level20获得的都是这个,所以把它当成SID的话都会报ERROR_INVALID_SID的错误。
    可以用LookupAccountName 来用usriN_full_name获得SID
    • 已标记为答案 IGabriel 2009年8月13日 2:04
    2009年8月12日 10:06
  • Specifies a DWORD value that contains the relative ID (RID) of the user 这个是关于useri3_user_sid的解释,其实用level3,和level20获得的都是这个,所以把它当成SID的话都会报ERROR_INVALID_SID的错误。
    可以用LookupAccountName 来用usriN_full_name获得SID

    谢谢你的提示。

    LookupAccountName 的 lpAccountName  要求 domain_name\user_name 格式的名称,我个人感觉不好办。

    所以后来我采用了以下的解决方法

    1. NetUserEnum 采用 Level 0 以及 USER_INFO_0 做参数
    2. NetUserGetInfo 采用 Level 4 以及 USER_INFO_4 做参数, 此时的 USER_INFO_4.usri4_user_sid  才是真正的 PSID
    3. ConvertSidToStringSid,搞掂。

    感觉有一种“曲线救国”的味道...不知道有没有更好的办法
    • 已编辑 IGabriel 2009年8月13日 2:05 错别字
    • 已标记为答案 IGabriel 2009年8月13日 2:06
    2009年8月13日 2:04