ulistpg.cpp
Upload User: xhy777
Upload Date: 2007-02-14
Package Size: 24088k
Code Size: 36k
Category:

Windows Kernel

Development Platform:

Visual C++

  1. /********************************************************
  2.  ulistpg.cpp
  3.   User Manager user list property page
  4.  History:
  5.   09/23/98: dsheldon created
  6. ********************************************************/
  7. #include "stdafx.h"
  8. #include "resource.h"
  9. #include "ulistpg.h"
  10. #include "data.h"
  11. #include "unpage.h"
  12. #include "pwpage.h"
  13. #include "grppage.h"
  14. #include "netpage.h"
  15. #include "autolog.h"
  16. #include "usercom.h"
  17. #include "misc.h"
  18. // Help ID array
  19. static const DWORD rgHelpIds[] =
  20. {
  21.     IDC_AUTOLOGON_CHECK,        IDH_AUTOLOGON_CHECK,
  22.     IDC_LISTTITLE_STATIC,       IDH_USER_LIST,
  23.     IDC_USER_LIST,              IDH_USER_LIST,
  24.     IDC_ADDUSER_BUTTON,         IDH_ADDUSER_BUTTON,
  25.     IDC_REMOVEUSER_BUTTON,      IDH_REMOVEUSER_BUTTON,
  26.     IDC_USERPROPERTIES_BUTTON,  IDH_USERPROPERTIES_BUTTON,
  27.     IDC_PASSWORD_STATIC,        IDH_PASSWORD_BUTTON,
  28.     IDC_CURRENTUSER_ICON,       IDH_PASSWORD_BUTTON,
  29.     IDC_PASSWORD_BUTTON,        IDH_PASSWORD_BUTTON,
  30.     IDC_PWGROUP_STATIC,         (DWORD) -1,
  31.     IDC_ULISTPG_TEXT,           (DWORD) -1,
  32.     IDC_USERLISTPAGE_ICON,      (DWORD) -1,
  33.     0, 0
  34. };
  35. // Control ID arrays for enabling/disabling/moving
  36. static const UINT rgidDisableOnAutologon[] =
  37. {
  38.     IDC_USER_LIST,
  39.     IDC_ADDUSER_BUTTON,
  40.     IDC_REMOVEUSER_BUTTON,
  41.     IDC_USERPROPERTIES_BUTTON,
  42.     IDC_PASSWORD_BUTTON
  43. };
  44. static const UINT rgidDisableOnNoSelection[] =
  45. {
  46.     IDC_REMOVEUSER_BUTTON,
  47.     IDC_USERPROPERTIES_BUTTON,
  48.     IDC_PASSWORD_BUTTON
  49. };
  50. static const UINT rgidMoveOnNoAutologonCheck[] =
  51. {
  52.     IDC_LISTTITLE_STATIC,
  53.     IDC_USER_LIST,
  54.     // IDC_ADDUSER_BUTTON,
  55.     // IDC_e_BUTTON,
  56.     // IDC_USERPROPERTIES_BUTTON,
  57.     // IDC_PWGROUP_STATIC,
  58.     // IDC_CURRENTUSER_ICON,
  59.     // IDC_PASSWORD_STATIC,
  60.     // IDC_PASSWORD_BUTTON
  61. };
  62. CUserlistPropertyPage::~CUserlistPropertyPage()
  63. {
  64.     if (m_himlLarge != NULL)
  65.         ImageList_Destroy(m_himlLarge);
  66. }
  67. INT_PTR CUserlistPropertyPage::DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
  68. {
  69.     switch (uMsg)
  70.     {
  71.         HANDLE_MSG(hwndDlg, WM_INITDIALOG, OnInitDialog);
  72.         HANDLE_MSG(hwndDlg, WM_NOTIFY, OnNotify);
  73.         HANDLE_MSG(hwndDlg, WM_COMMAND, OnCommand);
  74.         HANDLE_MSG(hwndDlg, WM_SETCURSOR, OnSetCursor);
  75.         case WM_HELP: return OnHelp(hwndDlg, (LPHELPINFO) lParam);
  76.         case WM_CONTEXTMENU: return OnContextMenu((HWND) wParam);
  77.         case WM_ADDUSERTOLIST: return SUCCEEDED(AddUserToListView(GetDlgItem(hwndDlg, IDC_USER_LIST),
  78.             (CUserInfo*) lParam, (BOOL) wParam));
  79.     }
  80.     
  81.     return FALSE;
  82. }
  83. BOOL CUserlistPropertyPage::OnHelp(HWND hwnd, LPHELPINFO pHelpInfo)
  84. {
  85.     TraceEnter(TRACE_USR_CORE, "CUserlistPropertyPage::OnHelp");
  86.     WinHelp((HWND) pHelpInfo->hItemHandle, m_pData->GetHelpfilePath(), 
  87.         HELP_WM_HELP, (ULONG_PTR) (LPTSTR) rgHelpIds);
  88.     TraceLeaveValue(TRUE);
  89. }
  90. BOOL CUserlistPropertyPage::OnContextMenu(HWND hwnd)
  91. {
  92.     TraceEnter(TRACE_USR_CORE, "CUserlistPropertyPage::OnContextMenu");
  93.     WinHelp(hwnd, m_pData->GetHelpfilePath(), 
  94.         HELP_CONTEXTMENU, (ULONG_PTR) (LPTSTR) rgHelpIds);
  95.     TraceLeaveValue(TRUE);
  96. }
  97. BOOL CUserlistPropertyPage::OnInitDialog(HWND hwnd, HWND hwndFocus, LPARAM lParam)
  98. {
  99.     TraceEnter(TRACE_USR_CORE, "CUserlistPropertyPage::OnInitDialog");
  100.     HWND hwndList = GetDlgItem(hwnd, IDC_USER_LIST);
  101.     InitializeListView(hwndList, m_pData->IsComputerInDomain());
  102.     m_pData->Initialize(hwnd);
  103.     SetupList(hwnd);
  104.     
  105.     m_fAutologonCheckChanged = FALSE;
  106.     TraceLeaveValue(TRUE);
  107. }
  108. BOOL CUserlistPropertyPage::OnListViewDeleteItem(HWND hwndList,
  109.                                                  int iItem)
  110. {
  111.     TraceEnter(TRACE_USR_CORE, "CUserlistPropertyPage::OnListViewDeleteItem");
  112.     LVITEM lvi = {0};
  113.     lvi.iItem = iItem;
  114.     lvi.mask = LVIF_PARAM;
  115.     ListView_GetItem(hwndList, &lvi);
  116.     CUserInfo* pUserInfo = (CUserInfo*) lvi.lParam;
  117.     if (NULL != pUserInfo)
  118.     {
  119.         delete pUserInfo;
  120.     }
  121.     TraceLeaveValue(TRUE);
  122. }
  123. BOOL CUserlistPropertyPage::OnNotify(HWND hwnd, int idCtrl, LPNMHDR pnmh)
  124. {
  125.     TraceEnter(TRACE_USR_CORE, "CUserlistPropertyPage::OnNotify");
  126.     BOOL fResult = TRUE;
  127.     switch (pnmh->code)
  128.     {
  129.     case PSN_APPLY:
  130.         {
  131.             long applyEffect = OnApply(hwnd);
  132.             SetWindowLongPtr(hwnd, DWLP_MSGRESULT, applyEffect);
  133.         }
  134.         break;
  135.     case LVN_GETINFOTIP:
  136.         fResult = OnGetInfoTip(pnmh->hwndFrom, (LPNMLVGETINFOTIP) pnmh);
  137.         break;
  138.     case LVN_ITEMCHANGED:
  139.         fResult = OnListViewItemChanged(hwnd);
  140.         break;
  141.     case LVN_DELETEITEM:
  142.         fResult = OnListViewDeleteItem(GetDlgItem(hwnd, IDC_USER_LIST), 
  143.             ((LPNMLISTVIEW) pnmh)->iItem);
  144.         break;
  145.     case NM_DBLCLK:
  146.         LaunchUserProperties(hwnd);
  147.         fResult = TRUE;
  148.         break;
  149.     case LVN_COLUMNCLICK:
  150.         {
  151.             int iColumn = ((LPNMLISTVIEW) pnmh)->iSubItem;
  152.             
  153.             // Want to work with 1-based columns so we can use zero as
  154.             // a special value
  155.             iColumn += 1;
  156.             // If we aren't showing the domain column because we're in
  157.             // non-domain mode, then map column 2 (group since we're not in
  158.             // domain mode to column 3 since the callback always expects 
  159.             // the columns to be, "username", "domain", "group".
  160.             if ((iColumn == 2) && (!m_pData->IsComputerInDomain()))
  161.             {
  162.                 iColumn = 3;
  163.             }
  164.             if (m_iReverseColumnIfSelected == iColumn)
  165.             {
  166.                 m_iReverseColumnIfSelected = 0;
  167.                 iColumn = -iColumn;
  168.             }
  169.             else
  170.             {
  171.                 m_iReverseColumnIfSelected = iColumn;
  172.             }
  173.             ListView_SortItems(pnmh->hwndFrom, ListCompare, (LPARAM) iColumn);
  174.             fResult = TRUE;
  175.         }
  176.         break;
  177.     default:
  178.         fResult = FALSE;
  179.         break;
  180.     }
  181.     TraceLeaveValue(fResult);
  182. }
  183. BOOL CUserlistPropertyPage::OnCommand(HWND hwnd, int id, HWND hwndCtl, UINT codeNotify)
  184. {
  185.     TraceEnter(TRACE_USR_CORE, "CUserlistPropertyPage::OnCommand");
  186.     
  187.     switch (id)
  188.     {
  189.     case IDC_ADDUSER_BUTTON:
  190.         if (m_pData->IsComputerInDomain())
  191.         {
  192.             // Launch the wizard to add a network user to a local group
  193.             LaunchAddNetUserWizard(hwnd);
  194.         }
  195.         else
  196.         {
  197.             // No domain; create a new local machine user
  198.             LaunchNewUserWizard(hwnd);
  199.         }
  200.         TraceLeaveValue(TRUE);
  201.     case IDC_REMOVEUSER_BUTTON:
  202.         OnRemove(hwnd);
  203.         TraceLeaveValue(TRUE);       
  204.         
  205.     case IDC_AUTOLOGON_CHECK:
  206.         m_fAutologonCheckChanged = TRUE;
  207.         // We may need some user input to change the autologon state
  208.         SetAutologonState(hwnd, BST_UNCHECKED == 
  209.             SendMessage(GetDlgItem(hwnd, IDC_AUTOLOGON_CHECK), BM_GETCHECK, 0, 0));
  210.         SetPageState(hwnd);
  211.         break;
  212.     case IDC_ADVANCED_BUTTON:
  213.         {
  214.             // Launch the MMC local user manager
  215.             STARTUPINFO startupinfo = {0};
  216.             startupinfo.cb = sizeof (startupinfo);
  217.             PROCESS_INFORMATION process_information;
  218.             // Consider using env. vars and ExpandEnvironmentString here
  219.             static const TCHAR szMMCCommandLineFormat[] = 
  220.                 TEXT("mmc.exe /computer=%s %%systemroot%%\system32\lusrmgr.msc");
  221.             
  222.             TCHAR szMMCCommandLine[MAX_PATH];
  223.             TCHAR szExpandedCommandLine[MAX_PATH];
  224.             wnsprintf(szMMCCommandLine, ARRAYSIZE(szMMCCommandLine), szMMCCommandLineFormat,
  225.             m_pData->GetComputerName());
  226.             if (ExpandEnvironmentStrings(szMMCCommandLine, szExpandedCommandLine, 
  227.                 ARRAYSIZE(szExpandedCommandLine)) > 0)
  228.             {
  229.                 if (CreateProcess(NULL, szExpandedCommandLine, NULL, NULL, FALSE, 0, NULL, NULL,
  230.                     &startupinfo, &process_information))
  231.                 {
  232.                     CloseHandle(process_information.hProcess);
  233.                     CloseHandle(process_information.hThread);
  234.                 }
  235.             }
  236.         }
  237.         break;
  238.     case IDC_PASSWORD_BUTTON:
  239.         LaunchSetPasswordDialog(hwnd);
  240.         break;
  241.     case IDC_USERPROPERTIES_BUTTON:
  242.         LaunchUserProperties(hwnd);
  243.         break;
  244.     }
  245.     TraceLeaveValue(FALSE);
  246. }
  247. BOOL CUserlistPropertyPage::OnSetCursor(HWND hwnd, HWND hwndCursor, UINT codeHitTest, UINT msg)
  248. {
  249.     TraceEnter(TRACE_USR_CORE, "CUserlistPropertyPage::OnSetCursor");
  250.     BOOL fHandled = FALSE;
  251.     // If the thread is filling, handle by setting the appstarting cursor
  252.     if (m_pData->GetUserListLoader()->InitInProgress())
  253.     {
  254.         fHandled = TRUE;
  255.         SetCursor(LoadCursor(NULL, IDC_APPSTARTING));
  256.     }
  257.     SetWindowLongPtr(hwnd, DWLP_MSGRESULT, fHandled);
  258.     TraceLeaveValue(TRUE);
  259. }
  260. BOOL CUserlistPropertyPage::OnGetInfoTip(HWND hwndList, LPNMLVGETINFOTIP pGetInfoTip)
  261. {
  262.     TraceEnter(TRACE_USR_CORE, "CUserlistPropertyPage::OnGetInfoTip");
  263.     // Get the UserInfo structure for the selected item
  264.     LVITEM lvi;
  265.     lvi.mask = LVIF_PARAM;
  266.     lvi.iItem = pGetInfoTip->iItem;
  267.     lvi.iSubItem = 0;
  268.     if ((lvi.iItem >= 0) && (ListView_GetItem(hwndList, &lvi)))
  269.     {
  270.         CUserInfo* pUserInfo = (CUserInfo*) lvi.lParam;
  271.         TraceAssert(pUserInfo != NULL);
  272.         // Ensure full name and comment are available
  273.         pUserInfo->GetExtraUserInfo();
  274.         // Make a string containing our "Full Name: %snComment: %s" message
  275.         if ((pUserInfo->m_szFullName[0] != TEXT('')) &&
  276.             (pUserInfo->m_szComment[0] != TEXT('')))
  277.         {
  278.             // We have a full name and comment
  279.             FormatMessageString(IDS_USR_TOOLTIPBOTH_FORMAT, pGetInfoTip->pszText, pGetInfoTip->cchTextMax, pUserInfo->m_szFullName, pUserInfo->m_szComment);
  280.         }
  281.         else if (pUserInfo->m_szFullName[0] != TEXT(''))
  282.         {
  283.             // We only have full name
  284.             FormatMessageString(IDS_USR_TOOLTIPFULLNAME_FORMAT, pGetInfoTip->pszText, pGetInfoTip->cchTextMax, pUserInfo->m_szFullName);
  285.         }
  286.         else if (pUserInfo->m_szComment[0] != TEXT(''))
  287.         {
  288.             // We only have comment
  289.             FormatMessageString(IDS_USR_TOOLTIPCOMMENT_FORMAT, pGetInfoTip->pszText, pGetInfoTip->cchTextMax, pUserInfo->m_szComment);
  290.         }
  291.         else
  292.         {
  293.             // We have no extra information - do nothing (show no tip)
  294.         }
  295.     }
  296.     TraceLeaveValue(TRUE);
  297. }
  298. struct MYCOLINFO
  299. {
  300.     int percentWidth;
  301.     UINT idString;
  302. };
  303. HRESULT CUserlistPropertyPage::InitializeListView(HWND hwndList, BOOL fShowDomain)
  304. {
  305.     // Array of icon ids icons 0, 1, and 2 respectively
  306.     static const UINT rgIcons[] = 
  307.     {
  308.         IDI_USR_LOCALUSER_ICON,
  309.         IDI_USR_DOMAINUSER_ICON,
  310.         IDI_USR_GROUP_ICON
  311.     };
  312.     // Array of relative column widths, for columns 0, 1, and 2 respectively
  313.     static const MYCOLINFO rgColWidthsWithDomain[] = 
  314.     {
  315.         {40, IDS_USR_NAME_COLUMN},
  316.         {30, IDS_USR_DOMAIN_COLUMN},
  317.         {30, IDS_USR_GROUP_COLUMN}
  318.     };
  319.     static const MYCOLINFO rgColWidthsNoDomain[] =
  320.     {
  321.         {50, IDS_USR_NAME_COLUMN},
  322.         {50, IDS_USR_GROUP_COLUMN}
  323.     };
  324.     TraceEnter(TRACE_USR_CORE, "CUserlistPropertyPage::InitializeListView");
  325.     HRESULT hr = S_OK;
  326.     // Create a listview with three columns
  327.     RECT rect;
  328.     GetClientRect(hwndList, &rect);
  329.     // Width of our window minus width of a verticle scroll bar minus one for the
  330.     // little bevel at the far right of the header.
  331.     int cxListView = (rect.right - rect.left) - GetSystemMetrics(SM_CXVSCROLL) - 1;
  332.     // Make our columns
  333.     int i;
  334.     int nColumns; 
  335.     const MYCOLINFO* pColInfo;
  336.     if (fShowDomain)
  337.     {
  338.         nColumns = ARRAYSIZE(rgColWidthsWithDomain);
  339.         pColInfo = rgColWidthsWithDomain;
  340.     }
  341.     else
  342.     {
  343.         nColumns = ARRAYSIZE(rgColWidthsNoDomain);
  344.         pColInfo = rgColWidthsNoDomain;
  345.     }      
  346.     LVCOLUMN lvc;
  347.     lvc.mask = LVCF_TEXT | LVCF_SUBITEM | LVCF_WIDTH;
  348.     for (i = 0; i < nColumns; i++)
  349.     {
  350.         TCHAR szText[MAX_PATH];
  351.         // Load this column's caption
  352.         LoadString(g_hInstance, pColInfo[i].idString, szText, ARRAYSIZE(szText));
  353.         lvc.iSubItem = i;
  354.         lvc.cx = (int) MulDiv(pColInfo[i].percentWidth, cxListView, 100);
  355.         lvc.pszText = szText;
  356.         ListView_InsertColumn(hwndList, i, &lvc);
  357.     }
  358.     UINT flags = ILC_MASK;
  359.     if(IS_WINDOW_RTL_MIRRORED(hwndList))
  360.     {
  361.         flags |= ILC_MIRROR;
  362.     }
  363.     // Create an image list for the listview
  364.     HIMAGELIST himlSmall = ImageList_Create(GetSystemMetrics(SM_CXSMICON), 
  365.         GetSystemMetrics(SM_CYSMICON), flags, 0, ARRAYSIZE(rgIcons));
  366.     // Large image lists for the "set password" group icon
  367.     m_himlLarge = ImageList_Create(32, 32, flags, 0, ARRAYSIZE(rgIcons));
  368.     if (himlSmall && m_himlLarge)
  369.     {
  370.         // Add our icons to the image list
  371.         for(i = 0; i < ARRAYSIZE(rgIcons); i ++)
  372.         {
  373.             HICON hIconSmall = (HICON) LoadImage(g_hInstance, MAKEINTRESOURCE(rgIcons[i]), IMAGE_ICON,
  374.                 16, 16, 0);
  375.             if (hIconSmall)
  376.             {
  377.                 ImageList_AddIcon(himlSmall, hIconSmall);
  378.                 DestroyIcon(hIconSmall);
  379.             }
  380.             HICON hIconLarge = (HICON) LoadImage(g_hInstance, MAKEINTRESOURCE(rgIcons[i]), IMAGE_ICON,
  381.                 32, 32, 0);
  382.             if (hIconLarge)
  383.             {
  384.                 ImageList_AddIcon(m_himlLarge, hIconLarge);
  385.                 DestroyIcon(hIconLarge);
  386.             }
  387.         }
  388.     }
  389.     ListView_SetImageList(hwndList, himlSmall, LVSIL_SMALL);
  390.     // Set extended styles for the listview
  391.     ListView_SetExtendedListViewStyleEx(hwndList, 
  392.         LVS_EX_LABELTIP | LVS_EX_FULLROWSELECT | LVS_EX_INFOTIP, 
  393.         LVS_EX_LABELTIP | LVS_EX_FULLROWSELECT | LVS_EX_INFOTIP);
  394.     // Set some settings for our tooltips - stolen from defview.cpp code
  395.     HWND hwndInfoTip = ListView_GetToolTips(hwndList);
  396.     if (hwndInfoTip != NULL)
  397.     {
  398.         //make the tooltip window  to be topmost window
  399.         SetWindowPos(hwndInfoTip, HWND_TOPMOST, 0,0,0,0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
  400.         // increase the ShowTime (the delay before we show the tooltip) to 2 times the default value
  401.         LRESULT uiShowTime = SendMessage(hwndInfoTip, TTM_GETDELAYTIME, TTDT_INITIAL, 0);
  402.         SendMessage(hwndInfoTip, TTM_SETDELAYTIME, TTDT_INITIAL, uiShowTime * 2);
  403.     }
  404.     TraceLeaveResult(hr);
  405. }
  406. HRESULT CUserlistPropertyPage::AddUserToListView(HWND hwndList, 
  407.                                                  CUserInfo* pUserInfo,
  408.                                                  BOOL fSelectUser /* = FALSE */)
  409. {
  410.     TraceEnter(TRACE_USR_CORE, "CUserlistPropertyPage::AddUserToListView");
  411.     HRESULT hr = S_OK;
  412.     // Do the add
  413.     LVITEM lvi;
  414.     int iItem;
  415.     lvi.mask = LVIF_TEXT | LVIF_IMAGE | LVIF_PARAM; 
  416.     // Always select the first loaded user
  417.     if (ListView_GetItemCount(hwndList) == 0)
  418.     {
  419.         fSelectUser = TRUE;
  420.     }
  421.     if (fSelectUser)
  422.     {
  423.         lvi.mask |= LVIF_STATE;
  424.         lvi.state = lvi.stateMask = LVIS_SELECTED | LVIS_FOCUSED;
  425.     }
  426.     lvi.iItem = 0;
  427.     lvi.iSubItem = 0;
  428.     lvi.pszText = pUserInfo->m_szUsername;
  429.     lvi.iImage = pUserInfo->m_userType;
  430.     lvi.lParam = (LPARAM) pUserInfo;
  431.     iItem = ListView_InsertItem(hwndList, &lvi);
  432.     if (iItem >= 0)
  433.     {
  434.         if (fSelectUser)
  435.         {
  436.             // Make the item visible
  437.             ListView_EnsureVisible(hwndList, iItem, FALSE);
  438.         }
  439.         // Success! Now add the subitems (domain, groups)
  440.         lvi.iItem = iItem;
  441.         lvi.mask = LVIF_TEXT;
  442.     
  443.         // Only add the domain field if the user is in a domain
  444.         if (::IsComputerInDomain())
  445.         {
  446.             lvi.iSubItem = 1;
  447.             lvi.pszText = pUserInfo->m_szDomain;
  448.             ListView_SetItem(hwndList, &lvi);
  449.             // User is in a domain; group should be third column
  450.             lvi.iSubItem = 2;
  451.         }
  452.         else
  453.         {
  454.             // User isn't in a domain, group should be second column
  455.             lvi.iSubItem = 1;
  456.         }
  457.         // Add group regardless of whether user is in a domain
  458.         lvi.pszText = pUserInfo->m_szGroups;
  459.         ListView_SetItem(hwndList, &lvi);
  460.     }
  461.     TraceLeaveResult(hr);
  462. }
  463. HRESULT CUserlistPropertyPage::LaunchNewUserWizard(HWND hwndParent)
  464. {
  465.     TraceEnter(TRACE_USR_CORE, "CUserlistPropertyPage::LaunchNewUserWizard");
  466.     
  467.     HRESULT hr = S_OK;
  468.     static const int nPages = 3;
  469.     int cPages = 0;
  470.     HPROPSHEETPAGE rghPages[nPages];
  471.     // Create a new user record
  472.     CUserInfo* pNewUser = new CUserInfo;
  473.     if (pNewUser != NULL)
  474.     {
  475.         pNewUser->InitializeForNewUser();
  476.         pNewUser->m_userType = CUserInfo::LOCALUSER;
  477.         PROPSHEETPAGE psp = {0};
  478.         // Common propsheetpage settings
  479.         psp.dwSize = sizeof (psp);
  480.         psp.hInstance = g_hInstance;
  481.         psp.dwFlags = PSP_DEFAULT | PSP_HIDEHEADER;
  482.         // Page 1: Username entry page
  483.         psp.pszTemplate = MAKEINTRESOURCE(IDD_USR_USERNAME_WIZARD_PAGE);
  484.         CUsernameWizardPage page1(pNewUser);
  485.         page1.SetPropSheetPageMembers(&psp);
  486.         rghPages[cPages++] = CreatePropertySheetPage(&psp);
  487.         // Page 2: Password page
  488.         psp.pszTemplate = MAKEINTRESOURCE(IDD_USR_PASSWORD_WIZARD_PAGE);
  489.         CPasswordWizardPage page2(pNewUser);
  490.         page2.SetPropSheetPageMembers(&psp);
  491.         rghPages[cPages++] = CreatePropertySheetPage(&psp);
  492.         // Page 3: Local group addition
  493.         psp.pszTemplate = MAKEINTRESOURCE(IDD_USR_CHOOSEGROUP_WIZARD_PAGE);
  494.         CGroupWizardPage page3(pNewUser, m_pData->GetGroupList());
  495.         page3.SetPropSheetPageMembers(&psp);
  496.         rghPages[cPages++] = CreatePropertySheetPage(&psp);
  497.         TraceAssert(cPages <= nPages);
  498.         PROPSHEETHEADER psh = {0};
  499.         psh.dwSize = sizeof (psh);
  500.         psh.dwFlags = PSH_NOCONTEXTHELP | PSH_WIZARD | PSH_WIZARD_LITE;
  501.         psh.hwndParent = hwndParent;
  502.         psh.hInstance = g_hInstance;
  503.         psh.nPages = nPages;
  504.         psh.phpage = rghPages;
  505.  
  506.         int iRetCode = (int)PropertySheet(&psh);
  507.     
  508.         if (iRetCode == IDOK)
  509.         {
  510.             AddUserToListView(GetDlgItem(hwndParent, IDC_USER_LIST), pNewUser, TRUE);
  511.         }
  512.         else
  513.         {
  514.             // User clicked cancel
  515.             delete pNewUser;
  516.             pNewUser = NULL;
  517.         }
  518.     }
  519.     else
  520.     {
  521.         hr = E_OUTOFMEMORY;
  522.         TraceMsg("CreateNewUserInfo() failed!");
  523.         DisplayFormatMessage(hwndParent, IDS_USR_NEWUSERWIZARD_CAPTION, 
  524.             IDS_USR_CREATE_MISC_ERROR, MB_OK | MB_ICONERROR);
  525.     }
  526.     if (FAILED(hr))
  527.     {
  528.         if (pNewUser)
  529.         {
  530.             delete pNewUser;
  531.         }
  532.     }
  533.     TraceLeaveResult(hr);
  534. }
  535. HRESULT CUserlistPropertyPage::LaunchAddNetUserWizard(HWND hwndParent)
  536. {
  537.     TraceEnter(TRACE_USR_CORE, "CUserlistPropertyPage::LaunchAddNetUserWizard");
  538.     HRESULT hr = S_OK;
  539.     static const int nPages = 2;
  540.     int cPages = 0;
  541.     HPROPSHEETPAGE rghPages[nPages];
  542.     // Create a new user record
  543.     CUserInfo* pNewUser = new CUserInfo;
  544.     if (pNewUser != NULL)
  545.     {
  546.         pNewUser->InitializeForNewUser();
  547.         pNewUser->m_userType = CUserInfo::DOMAINUSER;
  548.         PROPSHEETPAGE psp = {0};
  549.         // Common propsheetpage settings
  550.         psp.dwSize = sizeof (psp);
  551.         psp.hInstance = g_hInstance;
  552.         psp.dwFlags = PSP_DEFAULT | PSP_HIDEHEADER;
  553.         // Page 1: Find a network user page
  554.         psp.pszTemplate = MAKEINTRESOURCE(IDD_USR_FINDNETUSER_WIZARD_PAGE);
  555.         CNetworkUserWizardPage page1(pNewUser);
  556.         page1.SetPropSheetPageMembers(&psp);
  557.         rghPages[cPages++] = CreatePropertySheetPage(&psp);
  558.         // Page 2: Local group addition
  559.         psp.pszTemplate = MAKEINTRESOURCE(IDD_USR_CHOOSEGROUP_WIZARD_PAGE);
  560.         CGroupWizardPage page2(pNewUser, m_pData->GetGroupList());
  561.         page2.SetPropSheetPageMembers(&psp);
  562.         rghPages[cPages++] = CreatePropertySheetPage(&psp);
  563.         TraceAssert(cPages <= nPages);
  564.         PROPSHEETHEADER psh = {0};
  565.         psh.dwSize = sizeof (psh);
  566.         psh.dwFlags = PSH_NOCONTEXTHELP | PSH_WIZARD | PSH_WIZARD_LITE;
  567.         psh.hwndParent = hwndParent;
  568.         psh.hInstance = g_hInstance;
  569.         psh.nPages = nPages;
  570.         psh.phpage = rghPages;
  571.  
  572.         int iRetCode = (int)PropertySheet(&psh);
  573.     
  574.         if (iRetCode == IDOK)
  575.         {
  576.             AddUserToListView(GetDlgItem(hwndParent, IDC_USER_LIST), pNewUser, TRUE);
  577.             m_pData->UserInfoChanged(pNewUser->m_szUsername, pNewUser->m_szDomain);
  578.         }
  579.         else
  580.         {
  581.             // No errors, but the user clicked Cancel...
  582.             delete pNewUser;
  583.             pNewUser = NULL;
  584.         }
  585.     }
  586.     else
  587.     {
  588.         hr = E_OUTOFMEMORY;
  589.         TraceMsg("CreateNewUserInfo() failed!");
  590.         DisplayFormatMessage(hwndParent, IDS_USR_NEWUSERWIZARD_CAPTION,
  591.             IDS_USR_CREATE_MISC_ERROR, MB_OK | MB_ICONERROR);
  592.     }
  593.     if (FAILED(hr))
  594.     {
  595.         if (pNewUser)
  596.         {
  597.             delete pNewUser;
  598.         }
  599.     }
  600.     TraceLeaveResult(hr);
  601. }
  602. HRESULT CUserlistPropertyPage::LaunchUserProperties(HWND hwndParent)
  603. {
  604.     TraceEnter(TRACE_USR_CORE, "CUserlistPropertyPage::LaunchUserProperties");
  605.     
  606.     HRESULT hr = S_OK;
  607.     ADDPROPSHEETDATA apsd;
  608.     apsd.nPages = 0;
  609.     // Create a new user record
  610.     HWND hwndList = GetDlgItem(hwndParent, IDC_USER_LIST);
  611.     CUserInfo* pUserInfo = GetSelectedUserInfo(hwndList);
  612.     if (pUserInfo != NULL)
  613.     {
  614.         pUserInfo->GetExtraUserInfo();
  615.         PROPSHEETPAGE psp = {0};
  616.         // Common propsheetpage settings
  617.         psp.dwSize = sizeof (psp);
  618.         psp.hInstance = g_hInstance;
  619.         psp.dwFlags = PSP_DEFAULT;
  620.         // If we have a local user, show both the username and group page, ow
  621.         // just the group page
  622.         // Page 1: Username entry page
  623.         psp.pszTemplate = MAKEINTRESOURCE(IDD_USR_USERNAME_PROP_PAGE);
  624.         CUsernamePropertyPage page1(pUserInfo);
  625.         page1.SetPropSheetPageMembers(&psp);
  626.         // Only actually create the prop page if we have a local user
  627.         if (pUserInfo->m_userType == CUserInfo::LOCALUSER)
  628.         {
  629.             apsd.rgPages[apsd.nPages++] = CreatePropertySheetPage(&psp);
  630.         }
  631.         // Always add the second page
  632.         // Page 2: Local group addition
  633.         psp.pszTemplate = MAKEINTRESOURCE(IDD_USR_CHOOSEGROUP_PROP_PAGE);
  634.         CGroupPropertyPage page2(pUserInfo, m_pData->GetGroupList());
  635.         page2.SetPropSheetPageMembers(&psp);
  636.         apsd.rgPages[apsd.nPages++] = CreatePropertySheetPage(&psp);
  637.         HPSXA hpsxa = AddExtraUserPropPages(&apsd, pUserInfo->m_psid);
  638.         PROPSHEETHEADER psh = {0};
  639.         psh.dwSize = sizeof (psh);
  640.         psh.dwFlags = PSH_DEFAULT | PSH_PROPTITLE;
  641.         TCHAR szDomainUser[MAX_USER + MAX_DOMAIN + 2];
  642.         MakeDomainUserString(pUserInfo->m_szDomain, pUserInfo->m_szUsername, szDomainUser,
  643.             ARRAYSIZE(szDomainUser));
  644.         psh.pszCaption = szDomainUser;
  645.         psh.hwndParent = hwndParent;
  646.         psh.hInstance = g_hInstance;
  647.         psh.nPages = apsd.nPages;
  648.         psh.phpage = apsd.rgPages;
  649.  
  650.         int iRetCode = (int)PropertySheet(&psh);
  651.         if (hpsxa != NULL)
  652.             SHDestroyPropSheetExtArray(hpsxa);
  653.         if (iRetCode == IDOK)
  654.         {
  655.             // PropSheet_Changed(GetParent(hwndParent), hwndParent);
  656.             // So that we don't delete this pUserInfo when we remove
  657.             // this user from the list:
  658.             m_pData->UserInfoChanged(pUserInfo->m_szUsername, (pUserInfo->m_szDomain[0] == 0) ? NULL : pUserInfo->m_szDomain);
  659.             RemoveSelectedUserFromList(hwndList, FALSE);
  660.             AddUserToListView(hwndList, pUserInfo, TRUE);
  661.         }
  662.     }
  663.     else
  664.     {
  665.         TraceMsg("Couldn't Get selected CUserInfo");
  666.     }
  667.     TraceLeaveResult(hr);
  668. }
  669. CUserInfo* CUserlistPropertyPage::GetSelectedUserInfo(HWND hwndList)
  670. {
  671.     TraceEnter(TRACE_USR_CORE, "CUserlistPropertyPage::GetSelectedUserInfo");
  672.     CUserInfo* pUserInfo = NULL;
  673.         
  674.     int iItem = ListView_GetNextItem(hwndList, -1, LVNI_SELECTED);
  675.     if (iItem >= 0)
  676.     {
  677.         LVITEM lvi = {0};
  678.         lvi.mask = LVIF_PARAM;
  679.         lvi.iItem = iItem;
  680.         if (ListView_GetItem(hwndList, &lvi))
  681.         {
  682.             pUserInfo = (CUserInfo*) lvi.lParam;
  683.         }
  684.     }
  685.     TraceLeaveValue(pUserInfo);
  686. }
  687. void CUserlistPropertyPage::RemoveSelectedUserFromList(HWND hwndList, 
  688.                                                        BOOL fFreeUserInfo)
  689. {
  690.     TraceEnter(TRACE_USR_CORE, "CUserlistPropertyPage::RemoveSelectedUserFromList");
  691.     int iItem = ListView_GetNextItem(hwndList, -1, LVNI_SELECTED);
  692.     // If we don't want to delete this user info, better set it to NULL
  693.     if (!fFreeUserInfo)
  694.     {
  695.         LVITEM lvi = {0};
  696.         lvi.iItem = iItem;
  697.         lvi.mask = LVIF_PARAM;
  698.         lvi.lParam = (LPARAM) (CUserInfo*) NULL;
  699.         ListView_SetItem(hwndList, &lvi);
  700.     }
  701.     ListView_DeleteItem(hwndList, iItem);
  702.     int iSelect = iItem > 0 ? iItem - 1 : 0;
  703.     ListView_SetItemState(hwndList, iSelect, LVIS_SELECTED | LVIS_FOCUSED, LVIS_SELECTED | LVIS_FOCUSED);
  704.     
  705.     SetFocus(hwndList);
  706.     TraceLeaveVoid();
  707. }
  708. void CUserlistPropertyPage::OnRemove(HWND hwnd)
  709. {
  710.     TraceEnter(TRACE_USR_CORE, "CUserlistPropertyPage::OnRemove");
  711.     HWND hwndList = GetDlgItem(hwnd, IDC_USER_LIST);
  712.     CUserInfo* pUserInfo = GetSelectedUserInfo(hwndList);
  713.     if (pUserInfo != NULL)
  714.     {
  715.         if (ConfirmRemove(hwnd, pUserInfo) == IDYES)
  716.         {
  717.             if (SUCCEEDED(pUserInfo->Remove()))
  718.             {
  719.                 RemoveSelectedUserFromList(hwndList, TRUE);
  720.             }
  721.             else
  722.             {
  723.                 // Error removing user
  724.                TCHAR szDisplayName[MAX_USER + MAX_DOMAIN + 2];
  725.         
  726.                 ::MakeDomainUserString(pUserInfo->m_szDomain, pUserInfo->m_szUsername, szDisplayName,
  727.                     ARRAYSIZE(szDisplayName));
  728.                 DisplayFormatMessage(hwnd, IDS_USR_APPLET_CAPTION,
  729.                     IDS_USR_REMOVE_MISC_ERROR, MB_ICONERROR | MB_OK, szDisplayName);
  730.             }
  731.         }
  732.     }
  733.     else
  734.     {
  735.         // Unexpected! There should always be a selection
  736.         TraceMsg("GetSelectedUserInfo failed");
  737.     }
  738.     TraceLeaveVoid();
  739. }
  740. int CUserlistPropertyPage::ConfirmRemove(HWND hwnd, CUserInfo* pUserInfo)
  741. {
  742.     TraceEnter(TRACE_USR_CORE, "CUserlistPropertyPage::ConfirmRemove");
  743.     
  744.     TCHAR szDomainUser[MAX_USER + MAX_DOMAIN + 2];
  745.     MakeDomainUserString(pUserInfo->m_szDomain, pUserInfo->m_szUsername, szDomainUser,
  746.         ARRAYSIZE(szDomainUser));
  747.     int iReturn = DisplayFormatMessage(hwnd, IDS_USR_APPLET_CAPTION, IDS_USR_REMOVEUSER_WARNING,
  748.         MB_ICONEXCLAMATION | MB_YESNO, szDomainUser);
  749.     TraceLeaveValue(iReturn);
  750. }
  751. void CUserlistPropertyPage::SetPageState(HWND hwnd)
  752. {
  753.     TraceEnter(TRACE_USR_CORE, "CUserlistPropertyPage::SetPageState");
  754.     BOOL fAutologon = (BST_UNCHECKED == 
  755.         SendMessage(GetDlgItem(hwnd, IDC_AUTOLOGON_CHECK), BM_GETCHECK, 0, 0));
  756.     EnableControls(hwnd, rgidDisableOnAutologon, ARRAYSIZE(rgidDisableOnAutologon),
  757.         !fAutologon);
  758.     HWND hwndList = GetDlgItem(hwnd, IDC_USER_LIST);
  759.     CUserInfo* pUserInfo = GetSelectedUserInfo(hwndList);
  760.     if (pUserInfo != NULL)
  761.     {
  762.         // EnableControls(hwnd, rgidDisableOnNoSelection, ARRAYSIZE(rgidDisableOnNoSelection),
  763.         //     TRUE);
  764.         
  765.         TCHAR szPWGroup[128];
  766.         FormatMessageString(IDS_USR_PWGROUP_FORMAT, szPWGroup, ARRAYSIZE(szPWGroup), pUserInfo->m_szUsername);
  767.         SetWindowText(GetDlgItem(hwnd, IDC_PWGROUP_STATIC), szPWGroup);
  768.         TCHAR szPWMessage[128];
  769.         // If the logged on user is the selected user
  770.         CUserInfo* pLoggedOnUser = m_pData->GetLoggedOnUserInfo();
  771.         if ((StrCmpI(pUserInfo->m_szUsername, pLoggedOnUser->m_szUsername) == 0) &&
  772.             (StrCmpI(pUserInfo->m_szDomain, pLoggedOnUser->m_szDomain) == 0))
  773.         {
  774.             LoadString(g_hInstance, IDS_USR_YOURPWMESSAGE_FORMAT, szPWMessage,
  775.                 ARRAYSIZE(szPWMessage));
  776.             EnableWindow(GetDlgItem(hwnd, IDC_PASSWORD_BUTTON), FALSE);
  777.         }
  778.         // If the user is a local user
  779.         else if (pUserInfo->m_userType == CUserInfo::LOCALUSER)
  780.         {
  781.             // We can set this user's password
  782.             FormatMessageString(IDS_USR_PWMESSAGE_FORMAT, szPWMessage, ARRAYSIZE(szPWMessage), pUserInfo->m_szUsername);
  783.         }
  784.         else
  785.         {
  786.             // Nothing can be done with this user's password
  787.             // the selected user may be a domain user or a group or something
  788.             // We can set this user's password
  789.             FormatMessageString(IDS_USR_CANTCHANGEPW_FORMAT, szPWMessage, ARRAYSIZE(szPWMessage), pUserInfo->m_szUsername);
  790.             EnableWindow(GetDlgItem(hwnd, IDC_PASSWORD_BUTTON), FALSE);
  791.         }
  792.         SetWindowText(GetDlgItem(hwnd, IDC_PASSWORD_STATIC), szPWMessage);
  793.         // Set the icon for the user
  794.         HICON hIcon = ImageList_GetIcon(m_himlLarge, pUserInfo->m_userType, ILD_NORMAL);
  795.         Static_SetIcon(GetDlgItem(hwnd, IDC_CURRENTUSER_ICON), hIcon);
  796.     }
  797.     else
  798.     {
  799.         EnableControls(hwnd, rgidDisableOnNoSelection, ARRAYSIZE(rgidDisableOnNoSelection),
  800.             FALSE);
  801.     }
  802.     // Ensure the password button wasn't enabled in ANY CASE when autologon is
  803.     // enabled
  804.     /*if (fAutologon)
  805.     {
  806.         EnableWindow(GetDlgItem(hwnd, IDC_PASSWORD_BUTTON), FALSE);
  807.     }
  808. */
  809.     TraceLeaveVoid();
  810. }
  811. HRESULT CUserlistPropertyPage::SetAutologonState(HWND hwnd, BOOL fAutologon)
  812. {
  813.     TraceEnter(TRACE_USR_CORE, "CUserlistPropertyPage::SetAutologonState");
  814.     HRESULT hr = S_OK;
  815.     PropSheet_Changed(GetParent(hwnd), hwnd);
  816.     TraceLeaveResult(hr);
  817. }
  818. BOOL CUserlistPropertyPage::OnListViewItemChanged(HWND hwnd)
  819. {
  820.     TraceEnter(TRACE_USR_CORE, "CUserlistPropertyPage::OnListViewItemChanged");
  821.     SetPageState(hwnd);
  822.     TraceLeaveValue(TRUE);
  823. }
  824. long CUserlistPropertyPage::OnApply(HWND hwnd)
  825. {
  826.     TraceEnter(TRACE_USR_CORE, "CUserlistPropertyPage::OnApply");
  827.     long applyEffect = PSNRET_NOERROR;
  828.     // Use a big buffer to catch any error that might occur so we can report them
  829.     // to the user
  830.     static TCHAR szErrors[2048];
  831.     szErrors[0] = TEXT('');
  832.     BOOL fAutologonSet = (BST_UNCHECKED == SendMessage(GetDlgItem(hwnd, IDC_AUTOLOGON_CHECK), BM_GETCHECK, 0, 0));
  833.     if (!fAutologonSet)
  834.     {
  835.         // Ensure autologon is cleared
  836.         ClearAutoLogon();
  837.     }
  838.     else
  839.     {
  840.         // Autologon should be set - ask for credentials if this is a change...
  841.         if (m_fAutologonCheckChanged)
  842.         {
  843.             CUserInfo* pSelectedUser = GetSelectedUserInfo(GetDlgItem(hwnd, IDC_USER_LIST));
  844.             TCHAR szNullName[] = TEXT("");
  845.             CAutologonUserDlg dlg((pSelectedUser != NULL) ? 
  846.                 pSelectedUser->m_szUsername : szNullName);
  847.             if (dlg.DoModal(g_hInstance, MAKEINTRESOURCE(IDD_USR_AUTOLOGON_DLG), hwnd) == IDCANCEL)
  848.             {
  849.                 applyEffect = PSNRET_INVALID_NOCHANGEPAGE;
  850.             }
  851.         }
  852.     }
  853.     m_fAutologonCheckChanged = FALSE;
  854.     if (applyEffect == PSNRET_INVALID_NOCHANGEPAGE)
  855.     {
  856.         // Reload the data and list
  857.         m_pData->Initialize(hwnd);
  858.         SetupList(hwnd);
  859.     }
  860.     TraceLeaveValue(applyEffect);
  861. }
  862. void CUserlistPropertyPage::SetupList(HWND hwnd)
  863. {
  864.     TraceEnter(TRACE_USR_CORE, "CUserlistPropertyPage::SetupList");
  865.     HWND hwndList = GetDlgItem(hwnd, IDC_USER_LIST);
  866.     
  867.     // Disable autologon check box in the domain case where autologon isn't
  868.     // enabled
  869.     HWND hwndCheck = GetDlgItem(hwnd, IDC_AUTOLOGON_CHECK);
  870.     if (m_pData->IsComputerInDomain() && !m_pData->IsAutologonEnabled())
  871.     {
  872.         ShowWindow(hwndCheck, SW_HIDE);
  873.         EnableWindow(hwndCheck, FALSE);
  874.         // Move most controls up a bit if the autologon is not visible
  875.         RECT rcBottom;
  876.         GetWindowRect(GetDlgItem(hwnd, IDC_LISTTITLE_STATIC), &rcBottom);
  877.         RECT rcTop;
  878.         GetWindowRect(hwndCheck, &rcTop);
  879.         int dy = rcTop.top - rcBottom.top;
  880.         OffsetControls(hwnd, rgidMoveOnNoAutologonCheck, 
  881.             ARRAYSIZE(rgidMoveOnNoAutologonCheck), 0, dy);
  882.         // Grow the list by this amount also
  883.         RECT rcList;
  884.         GetWindowRect(hwndList, &rcList);
  885.         SetWindowPos(hwndList, NULL, 0, 0, rcList.right - rcList.left, 
  886.             rcList.bottom - rcList.top - dy, SWP_NOZORDER|SWP_NOMOVE);
  887.     }
  888.     SendMessage(hwndCheck, BM_SETCHECK, 
  889.         m_pData->IsAutologonEnabled() ? BST_UNCHECKED : BST_CHECKED, 0);
  890.     // Set the text in the set password group.
  891.     SetPageState(hwnd);
  892.     TraceLeaveVoid();
  893. }
  894. HRESULT CUserlistPropertyPage::LaunchSetPasswordDialog(HWND hwndParent)
  895. {
  896.     TraceEnter(TRACE_USR_CORE, "CUserlistPropertyPage::LaunchSetPasswordDialog");
  897.     HRESULT hr = S_OK;
  898.     CUserInfo* pUserInfo = GetSelectedUserInfo(GetDlgItem(hwndParent, IDC_USER_LIST));
  899.     if ((pUserInfo != NULL) && (pUserInfo->m_userType == CUserInfo::LOCALUSER))
  900.     {
  901.         CChangePasswordDlg dlg(pUserInfo);
  902.         dlg.DoModal(g_hInstance, MAKEINTRESOURCE(IDD_USR_SETPASSWORD_DLG), hwndParent);
  903.     }
  904.     else
  905.     {
  906.         // Unexpected: The Set Password button should be disabled if we don't have 
  907.         // a valid selected user
  908.         TraceMsg("LaunchSetPasswordDialog called with no user or wrong user type selected!");
  909.         hr = E_FAIL;
  910.     }
  911.    
  912.     TraceLeaveResult(hr);
  913. }
  914. #define MAX_EXTRA_USERPROP_PAGES    10
  915. HPSXA CUserlistPropertyPage::AddExtraUserPropPages(ADDPROPSHEETDATA* ppsd, PSID psid)
  916. {
  917.     TraceEnter(TRACE_USR_CORE, "CUserlistPropertyPage::AddExtraUserPropPages");
  918.     HPSXA hpsxa = NULL;
  919.     CUserSidDataObject* pDataObj = new CUserSidDataObject();
  920.     if (pDataObj != NULL)
  921.     {
  922.         HRESULT hr = pDataObj->SetSid(psid);
  923.         if (SUCCEEDED(hr))
  924.         {
  925.             hpsxa = SHCreatePropSheetExtArrayEx(HKEY_LOCAL_MACHINE, REGSTR_USERPROPERTIES_SHEET, 
  926.                 MAX_EXTRA_USERPROP_PAGES, pDataObj);
  927.             if (hpsxa != NULL)
  928.             {
  929.                 UINT nPagesAdded = SHAddFromPropSheetExtArray(hpsxa, AddPropSheetPageCallback, (LPARAM) ppsd);
  930.             }
  931.         }
  932.         pDataObj->Release();
  933.     }
  934.     TraceLeaveValue(hpsxa);
  935. }
  936. // ListCompare
  937. //  Compares list items in for sorting the listview by column
  938. //  lParamSort gets the 1-based column index. If lParamSort is negative
  939. //  it indicates that the given column should be sorted in reverse.
  940. int CUserlistPropertyPage::ListCompare(LPARAM lParam1, LPARAM lParam2, 
  941. LPARAM lParamSort)
  942. {
  943.     TraceEnter(TRACE_USR_CORE, "CUserlistPropertyPage::ListCompare");
  944.     
  945.     CUserInfo* pUserInfo1 = 
  946.         (CUserInfo*) lParam1;
  947.     CUserInfo* pUserInfo2 = 
  948.         (CUserInfo*) lParam2;
  949.     LPTSTR psz1;
  950.     LPTSTR psz2;
  951.     int iColumn = (int) lParamSort;
  952.     BOOL fReverse;
  953.     if (iColumn < 0)
  954.     {
  955.         fReverse = TRUE;
  956.         iColumn = -iColumn;
  957.     }
  958.     else
  959.     {
  960.         fReverse = FALSE;
  961.     }
  962.     
  963.     switch (iColumn)
  964.     {
  965.     case 1:
  966.         // user name column
  967.         psz1 = pUserInfo1->m_szUsername;
  968.         psz2 = pUserInfo2->m_szUsername;
  969.         break;
  970.     case 2:
  971.         // domain column
  972.         psz1 = pUserInfo1->m_szDomain;
  973.         psz2 = pUserInfo2->m_szDomain;
  974.         break;
  975.     case 3:
  976.         psz1 = pUserInfo1->m_szGroups;
  977.         psz2 = pUserInfo2->m_szGroups;
  978.         break;
  979.     }
  980.     
  981.     
  982.     int iResult = lstrcmp(psz1, psz2);
  983.     if (fReverse)
  984.         iResult = -iResult;
  985.     
  986.     TraceLeaveValue(iResult);
  987. }