1 /* 2 * COPYRIGHT: See COPYING in the top level directory 3 * PROJECT: ReactOS User Manager Control Panel 4 * FILE: dll/cpl/usrmgr/groupprops.c 5 * PURPOSE: Group property sheet 6 * 7 * PROGRAMMERS: Eric Kohl 8 */ 9 10 #include "usrmgr.h" 11 12 typedef struct _GENERAL_GROUP_DATA 13 { 14 TCHAR szGroupName[1]; 15 } GENERAL_GROUP_DATA, *PGENERAL_GROUP_DATA; 16 17 18 static VOID 19 GetTextSid(PSID pSid, 20 LPTSTR pTextSid) 21 { 22 PSID_IDENTIFIER_AUTHORITY psia; 23 DWORD dwSubAuthorities; 24 DWORD dwSidRev = SID_REVISION; 25 DWORD dwCounter; 26 DWORD dwSidSize; 27 28 psia = GetSidIdentifierAuthority(pSid); 29 30 dwSubAuthorities = *GetSidSubAuthorityCount(pSid); 31 32 dwSidSize = wsprintf(pTextSid, TEXT("S-%lu-"), dwSidRev); 33 34 if ((psia->Value[0] != 0) || (psia->Value[1] != 0)) 35 { 36 dwSidSize += wsprintf(pTextSid + lstrlen(pTextSid), 37 TEXT("0x%02hx%02hx%02hx%02hx%02hx%02hx"), 38 (USHORT)psia->Value[0], 39 (USHORT)psia->Value[1], 40 (USHORT)psia->Value[2], 41 (USHORT)psia->Value[3], 42 (USHORT)psia->Value[4], 43 (USHORT)psia->Value[5]); 44 } 45 else 46 { 47 dwSidSize += wsprintf(pTextSid + lstrlen(pTextSid), 48 TEXT("%lu"), 49 (ULONG)(psia->Value[5]) + 50 (ULONG)(psia->Value[4] << 8) + 51 (ULONG)(psia->Value[3] << 16) + 52 (ULONG)(psia->Value[2] << 24)); 53 } 54 55 for (dwCounter = 0 ; dwCounter < dwSubAuthorities ; dwCounter++) 56 { 57 dwSidSize += wsprintf(pTextSid + dwSidSize, TEXT("-%lu"), 58 *GetSidSubAuthority(pSid, dwCounter)); 59 } 60 } 61 62 63 static VOID 64 InitGroupMembersList(HWND hwndDlg, 65 PGENERAL_GROUP_DATA pGroupData) 66 { 67 HWND hwndLV; 68 LV_COLUMN column; 69 RECT rect; 70 TCHAR szStr[32]; 71 HIMAGELIST hImgList; 72 HICON hIcon; 73 74 NET_API_STATUS netStatus; 75 PUSER_INFO_20 pUserBuffer; 76 DWORD entriesread; 77 DWORD totalentries; 78 DWORD resume_handle = 0; 79 DWORD i; 80 LV_ITEM lvi; 81 INT iItem; 82 83 hwndLV = GetDlgItem(hwndDlg, IDC_USER_ADD_MEMBERSHIP_LIST); 84 GetClientRect(hwndLV, &rect); 85 86 hImgList = ImageList_Create(16,16,ILC_COLOR32 | ILC_MASK,5,5); 87 hIcon = LoadImage(hApplet,MAKEINTRESOURCE(IDI_GROUP),IMAGE_ICON,16,16,LR_DEFAULTCOLOR); 88 ImageList_AddIcon(hImgList,hIcon); 89 DestroyIcon(hIcon); 90 hIcon = LoadImage(hApplet, MAKEINTRESOURCE(IDI_USER), IMAGE_ICON, 16, 16, LR_DEFAULTCOLOR); 91 ImageList_AddIcon(hImgList, hIcon); 92 DestroyIcon(hIcon); 93 hIcon = LoadImage(hApplet, MAKEINTRESOURCE(IDI_LOCKED_USER), IMAGE_ICON, 16, 16, LR_DEFAULTCOLOR); 94 ImageList_AddIcon(hImgList, hIcon); 95 DestroyIcon(hIcon); 96 97 (void)ListView_SetImageList(hwndLV, hImgList, LVSIL_SMALL); 98 (void)ListView_SetExtendedListViewStyle(hwndLV, LVS_EX_FULLROWSELECT); 99 100 memset(&column, 0x00, sizeof(column)); 101 column.mask = LVCF_FMT | LVCF_WIDTH | LVCF_SUBITEM | LVCF_TEXT; 102 column.fmt = LVCFMT_LEFT; 103 column.cx = (INT)((rect.right - rect.left) * 0.40); 104 column.iSubItem = 0; 105 LoadString(hApplet, IDS_NAME, szStr, sizeof(szStr) / sizeof(szStr[0])); 106 column.pszText = szStr; 107 (void)ListView_InsertColumn(hwndLV, 0, &column); 108 109 column.cx = (INT)((rect.right - rect.left) * 0.60); 110 column.iSubItem = 1; 111 LoadString(hApplet, IDS_DESCRIPTION, szStr, sizeof(szStr) / sizeof(szStr[0])); 112 column.pszText = szStr; 113 (void)ListView_InsertColumn(hwndLV, 1, &column); 114 115 /* TODO: Enumerate global groups and add them to the list! */ 116 117 for (;;) 118 { 119 netStatus = NetUserEnum(NULL, 20, FILTER_NORMAL_ACCOUNT, 120 (LPBYTE*)&pUserBuffer, 121 1024, &entriesread, 122 &totalentries, &resume_handle); 123 if (netStatus != NERR_Success && netStatus != ERROR_MORE_DATA) 124 break; 125 126 for (i = 0; i < entriesread; i++) 127 { 128 memset(&lvi, 0x00, sizeof(lvi)); 129 lvi.mask = LVIF_TEXT | LVIF_STATE | LVIF_IMAGE; 130 lvi.pszText = pUserBuffer[i].usri20_name; 131 lvi.state = 0; 132 lvi.iImage = (pUserBuffer[i].usri20_flags & UF_ACCOUNTDISABLE) ? 2 : 1; 133 iItem = ListView_InsertItem(hwndLV, &lvi); 134 135 ListView_SetItemText(hwndLV, iItem, 1, 136 pUserBuffer[i].usri20_full_name); 137 138 ListView_SetItemText(hwndLV, iItem, 2, 139 pUserBuffer[i].usri20_comment); 140 } 141 142 NetApiBufferFree(pUserBuffer); 143 144 /* No more data left */ 145 if (netStatus != ERROR_MORE_DATA) 146 break; 147 } 148 } 149 150 151 static BOOL 152 AddSelectedUsersToGroup(HWND hwndDlg, 153 PGENERAL_GROUP_DATA pGroupData) 154 { 155 HWND hwndLV; 156 INT nSelectedItems; 157 INT nItem; 158 TCHAR szUserName[UNLEN]; 159 BOOL bResult = FALSE; 160 LOCALGROUP_MEMBERS_INFO_3 memberInfo; 161 NET_API_STATUS status; 162 163 hwndLV = GetDlgItem(hwndDlg, IDC_USER_ADD_MEMBERSHIP_LIST); 164 165 nSelectedItems = ListView_GetSelectedCount(hwndLV); 166 if (nSelectedItems > 0) 167 { 168 nItem = ListView_GetNextItem(hwndLV, -1, LVNI_SELECTED); 169 while (nItem != -1) 170 { 171 /* Get the new user name */ 172 ListView_GetItemText(hwndLV, 173 nItem, 0, 174 szUserName, 175 UNLEN); 176 177 DebugPrintf(_TEXT("Selected user: %s"), szUserName); 178 179 memberInfo.lgrmi3_domainandname = szUserName; 180 181 status = NetLocalGroupAddMembers(NULL, pGroupData->szGroupName, 3, 182 (LPBYTE)&memberInfo, 1); 183 if (status != NERR_Success && status != ERROR_MEMBER_IN_ALIAS) 184 { 185 TCHAR szText[256]; 186 wsprintf(szText, TEXT("Error: %u"), status); 187 MessageBox(NULL, szText, TEXT("NetLocalGroupAddMembers"), MB_ICONERROR | MB_OK); 188 } 189 else 190 { 191 bResult = TRUE; 192 } 193 194 nItem = ListView_GetNextItem(hwndLV, nItem, LVNI_SELECTED); 195 } 196 } 197 198 return bResult; 199 } 200 201 202 INT_PTR CALLBACK 203 AddUsersToGroupDlgProc(HWND hwndDlg, 204 UINT uMsg, 205 WPARAM wParam, 206 LPARAM lParam) 207 { 208 PGENERAL_GROUP_DATA pGroupData; 209 210 UNREFERENCED_PARAMETER(wParam); 211 212 pGroupData = (PGENERAL_GROUP_DATA)GetWindowLongPtr(hwndDlg, DWLP_USER); 213 214 switch (uMsg) 215 { 216 case WM_INITDIALOG: 217 pGroupData = (PGENERAL_GROUP_DATA)lParam; 218 SetWindowLongPtr(hwndDlg, DWLP_USER, (INT_PTR)pGroupData); 219 InitGroupMembersList(hwndDlg, pGroupData); 220 break; 221 222 case WM_COMMAND: 223 switch (LOWORD(wParam)) 224 { 225 case IDOK: 226 if (AddSelectedUsersToGroup(hwndDlg, pGroupData)) 227 EndDialog(hwndDlg, IDOK); 228 else 229 EndDialog(hwndDlg, IDCANCEL); 230 break; 231 232 case IDCANCEL: 233 EndDialog(hwndDlg, IDCANCEL); 234 break; 235 } 236 break; 237 238 default: 239 return FALSE; 240 } 241 242 return TRUE; 243 } 244 245 246 static VOID 247 AddUsersToGroup(HWND hwndDlg, 248 PGENERAL_GROUP_DATA pGroupData) 249 { 250 HWND hwndLV; 251 // NET_API_STATUS status; 252 PLOCALGROUP_MEMBERS_INFO_1 membersInfo = NULL; 253 DWORD dwRead; 254 DWORD dwTotal; 255 DWORD_PTR resumeHandle = 0; 256 DWORD i; 257 LV_ITEM lvi; 258 TCHAR szGroupName[256]; 259 260 if (DialogBoxParam(hApplet, 261 MAKEINTRESOURCE(IDD_USER_ADD_MEMBERSHIP), 262 hwndDlg, 263 AddUsersToGroupDlgProc, 264 (LPARAM)pGroupData) == IDOK) 265 { 266 hwndLV = GetDlgItem(hwndDlg, IDC_GROUP_GENERAL_MEMBERS); 267 268 (void)ListView_DeleteAllItems(hwndLV); 269 270 // DebugPrintf(_T("Removed all users from the list!")); 271 272 /* Set group members */ 273 NetLocalGroupGetMembers(NULL, pGroupData->szGroupName, 1, (LPBYTE*)&membersInfo, 274 MAX_PREFERRED_LENGTH, &dwRead, &dwTotal, 275 &resumeHandle); 276 277 for (i = 0; i < dwRead; i++) 278 { 279 ZeroMemory(&lvi, sizeof(lvi)); 280 lvi.mask = LVIF_TEXT | LVIF_STATE | LVIF_IMAGE; 281 lvi.pszText = membersInfo[i].lgrmi1_name; 282 lvi.state = 0; 283 lvi.iImage = (membersInfo[i].lgrmi1_sidusage == SidTypeGroup || 284 membersInfo[i].lgrmi1_sidusage == SidTypeWellKnownGroup) ? 1 : 0; 285 286 if (membersInfo[i].lgrmi1_sidusage == SidTypeWellKnownGroup) 287 { 288 TCHAR szSid[256]; 289 290 GetTextSid(membersInfo[i].lgrmi1_sid, szSid); 291 292 wsprintf(szGroupName, 293 TEXT("%s (%s)"), 294 membersInfo[i].lgrmi1_name, 295 szSid); 296 297 lvi.pszText = szGroupName; 298 } 299 300 301 (void)ListView_InsertItem(hwndLV, &lvi); 302 } 303 304 NetApiBufferFree(membersInfo); 305 } 306 } 307 308 309 static VOID 310 RemoveUserFromGroup(HWND hwndDlg, 311 PGENERAL_GROUP_DATA pGroupData) 312 { 313 TCHAR szUserName[UNLEN]; 314 TCHAR szText[256]; 315 LOCALGROUP_MEMBERS_INFO_3 memberInfo; 316 HWND hwndLV; 317 INT nItem; 318 NET_API_STATUS status; 319 320 hwndLV = GetDlgItem(hwndDlg, IDC_GROUP_GENERAL_MEMBERS); 321 nItem = ListView_GetNextItem(hwndLV, -1, LVNI_SELECTED); 322 if (nItem == -1) 323 return; 324 325 /* Get the new user name */ 326 ListView_GetItemText(hwndLV, 327 nItem, 0, 328 szUserName, 329 UNLEN); 330 331 /* Display a warning message because the remove operation cannot be reverted */ 332 wsprintf(szText, TEXT("Do you really want to remove the user \"%s\" from the group \"%s\"?"), 333 szUserName, pGroupData->szGroupName); 334 if (MessageBox(NULL, szText, TEXT("User Accounts"), MB_ICONWARNING | MB_YESNO) == IDNO) 335 return; 336 337 memberInfo.lgrmi3_domainandname = szUserName; 338 339 status = NetLocalGroupDelMembers(NULL, pGroupData->szGroupName, 340 3, (LPBYTE)&memberInfo, 1); 341 if (status != NERR_Success) 342 { 343 TCHAR szText[256]; 344 wsprintf(szText, TEXT("Error: %u"), status); 345 MessageBox(NULL, szText, TEXT("NetLocalGroupDelMembers"), MB_ICONERROR | MB_OK); 346 return; 347 } 348 349 (void)ListView_DeleteItem(hwndLV, nItem); 350 351 if (ListView_GetItemCount(hwndLV) == 0) 352 EnableWindow(GetDlgItem(hwndDlg, IDC_GROUP_GENERAL_REMOVE), FALSE); 353 } 354 355 356 static BOOL 357 OnGroupPropSheetNotify(HWND hwndDlg, 358 PGENERAL_GROUP_DATA pGroupData, 359 LPARAM lParam) 360 { 361 LPNMLISTVIEW lpnmlv = (LPNMLISTVIEW)lParam; 362 363 switch (((LPNMHDR)lParam)->idFrom) 364 { 365 case IDC_GROUP_GENERAL_MEMBERS: 366 switch (((LPNMHDR)lParam)->code) 367 { 368 case NM_CLICK: 369 EnableWindow(GetDlgItem(hwndDlg, IDC_GROUP_GENERAL_REMOVE), (lpnmlv->iItem != -1)); 370 break; 371 372 case LVN_KEYDOWN: 373 if (((LPNMLVKEYDOWN)lParam)->wVKey == VK_DELETE) 374 { 375 RemoveUserFromGroup(hwndDlg, pGroupData); 376 } 377 break; 378 379 } 380 break; 381 } 382 383 return FALSE; 384 } 385 386 387 static VOID 388 GetGeneralGroupData(HWND hwndDlg, 389 PGENERAL_GROUP_DATA pGroupData) 390 { 391 PLOCALGROUP_INFO_1 groupInfo = NULL; 392 PLOCALGROUP_MEMBERS_INFO_2 membersInfo = NULL; 393 DWORD dwRead; 394 DWORD dwTotal; 395 DWORD_PTR resumeHandle = 0; 396 DWORD i; 397 LV_ITEM lvi; 398 HWND hwndLV; 399 LV_COLUMN column; 400 RECT rect; 401 HIMAGELIST hImgList; 402 HICON hIcon; 403 TCHAR szGroupName[256]; 404 405 406 hwndLV = GetDlgItem(hwndDlg, IDC_GROUP_GENERAL_MEMBERS); 407 408 /* Create the image list */ 409 hImgList = ImageList_Create(16, 16, ILC_COLOR32 | ILC_MASK, 5, 5); 410 hIcon = LoadImage(hApplet, MAKEINTRESOURCE(IDI_GROUP), IMAGE_ICON, 16, 16, LR_DEFAULTCOLOR); 411 ImageList_AddIcon(hImgList, hIcon); 412 DestroyIcon(hIcon); 413 hIcon = LoadImage(hApplet, MAKEINTRESOURCE(IDI_USER), IMAGE_ICON, 16, 16, LR_DEFAULTCOLOR); 414 ImageList_AddIcon(hImgList, hIcon); 415 DestroyIcon(hIcon); 416 417 (void)ListView_SetImageList(hwndLV, hImgList, LVSIL_SMALL); 418 419 /* Set the list column */ 420 GetClientRect(hwndLV, &rect); 421 422 memset(&column, 0x00, sizeof(column)); 423 column.mask = LVCF_FMT | LVCF_WIDTH | LVCF_SUBITEM; 424 column.fmt = LVCFMT_LEFT; 425 column.cx = (INT)(rect.right - rect.left); 426 column.iSubItem = 0; 427 (void)ListView_InsertColumn(hwndLV, 0, &column); 428 429 /* Set group name */ 430 SetDlgItemText(hwndDlg, IDC_GROUP_GENERAL_NAME, pGroupData->szGroupName); 431 432 /* Set group description */ 433 NetLocalGroupGetInfo(NULL, pGroupData->szGroupName, 1, (LPBYTE*)&groupInfo); 434 SetDlgItemText(hwndDlg, IDC_GROUP_GENERAL_DESCRIPTION, groupInfo->lgrpi1_comment); 435 NetApiBufferFree(groupInfo); 436 437 /* Set group members */ 438 NetLocalGroupGetMembers(NULL, pGroupData->szGroupName, 2, (LPBYTE*)&membersInfo, 439 MAX_PREFERRED_LENGTH, &dwRead, &dwTotal, 440 &resumeHandle); 441 442 for (i = 0; i < dwRead; i++) 443 { 444 ZeroMemory(&lvi, sizeof(lvi)); 445 lvi.mask = LVIF_TEXT | LVIF_STATE | LVIF_IMAGE; 446 lvi.state = 0; 447 if (membersInfo[i].lgrmi2_sidusage == SidTypeGroup || 448 membersInfo[i].lgrmi2_sidusage == SidTypeWellKnownGroup) 449 { 450 lvi.iImage = 0; 451 } 452 else if (membersInfo[i].lgrmi2_sidusage == SidTypeUser) 453 { 454 /* FIXME: handle locked user properly! */ 455 lvi.iImage = 1; 456 } 457 458 if (membersInfo[i].lgrmi2_sidusage == SidTypeWellKnownGroup) 459 { 460 TCHAR szSid[256]; 461 462 GetTextSid(membersInfo[i].lgrmi2_sid, szSid); 463 464 wsprintf(szGroupName, 465 TEXT("%s (%s)"), 466 membersInfo[i].lgrmi2_domainandname, 467 szSid); 468 469 lvi.pszText = szGroupName; 470 } 471 else 472 { 473 LPWSTR ptr; 474 475 ptr = wcschr(membersInfo[i].lgrmi2_domainandname, L'\\'); 476 if (ptr != NULL) 477 { 478 lvi.pszText = ++ptr; 479 } 480 else 481 { 482 lvi.pszText = membersInfo[i].lgrmi2_domainandname; 483 } 484 } 485 486 (void)ListView_InsertItem(hwndLV, &lvi); 487 } 488 489 NetApiBufferFree(membersInfo); 490 } 491 492 493 static BOOL 494 SetGeneralGroupData(HWND hwndDlg, 495 PGENERAL_GROUP_DATA pGroupData) 496 { 497 LOCALGROUP_INFO_1 groupInfo; 498 LPTSTR pszComment = NULL; 499 INT nLength; 500 NET_API_STATUS status; 501 DWORD dwIndex; 502 503 /* Get the group description */ 504 nLength = GetWindowTextLength(GetDlgItem(hwndDlg, IDC_GROUP_GENERAL_DESCRIPTION)); 505 if (nLength == 0) 506 { 507 groupInfo.lgrpi1_comment = NULL; 508 } 509 else 510 { 511 pszComment = HeapAlloc(GetProcessHeap(), 0, (nLength + 1) * sizeof(TCHAR)); 512 GetDlgItemText(hwndDlg, IDC_GROUP_GENERAL_DESCRIPTION, pszComment, nLength + 1); 513 groupInfo.lgrpi1_comment = pszComment; 514 } 515 516 status = NetLocalGroupSetInfo(NULL, pGroupData->szGroupName, 1, (LPBYTE)&groupInfo, &dwIndex); 517 if (status != NERR_Success) 518 { 519 DebugPrintf(_T("Status: %lu Index: %lu"), status, dwIndex); 520 } 521 522 if (pszComment) 523 HeapFree(GetProcessHeap(), 0, pszComment); 524 525 return TRUE; 526 } 527 528 529 INT_PTR CALLBACK 530 GroupGeneralPageProc(HWND hwndDlg, 531 UINT uMsg, 532 WPARAM wParam, 533 LPARAM lParam) 534 { 535 PGENERAL_GROUP_DATA pGroupData; 536 537 UNREFERENCED_PARAMETER(lParam); 538 UNREFERENCED_PARAMETER(wParam); 539 UNREFERENCED_PARAMETER(hwndDlg); 540 541 pGroupData= (PGENERAL_GROUP_DATA)GetWindowLongPtr(hwndDlg, DWLP_USER); 542 543 switch (uMsg) 544 { 545 case WM_INITDIALOG: 546 pGroupData = (PGENERAL_GROUP_DATA)HeapAlloc(GetProcessHeap(), 547 HEAP_ZERO_MEMORY, 548 sizeof(GENERAL_GROUP_DATA) + 549 lstrlen((LPTSTR)((PROPSHEETPAGE *)lParam)->lParam) * sizeof(TCHAR)); 550 lstrcpy(pGroupData->szGroupName, (LPTSTR)((PROPSHEETPAGE *)lParam)->lParam); 551 552 SetWindowLongPtr(hwndDlg, DWLP_USER, (INT_PTR)pGroupData); 553 554 GetGeneralGroupData(hwndDlg, 555 pGroupData); 556 break; 557 558 case WM_COMMAND: 559 switch (LOWORD(wParam)) 560 { 561 case IDC_GROUP_GENERAL_DESCRIPTION: 562 if (HIWORD(wParam) == EN_CHANGE) 563 PropSheet_Changed(GetParent(hwndDlg), hwndDlg); 564 break; 565 566 case IDC_GROUP_GENERAL_ADD: 567 AddUsersToGroup(hwndDlg, pGroupData); 568 break; 569 570 case IDC_GROUP_GENERAL_REMOVE: 571 RemoveUserFromGroup(hwndDlg, pGroupData); 572 break; 573 } 574 break; 575 576 case WM_NOTIFY: 577 if (((LPPSHNOTIFY)lParam)->hdr.code == PSN_APPLY) 578 { 579 SetGeneralGroupData(hwndDlg, pGroupData); 580 return TRUE; 581 } 582 else 583 { 584 return OnGroupPropSheetNotify(hwndDlg, pGroupData, lParam); 585 } 586 break; 587 588 case WM_DESTROY: 589 HeapFree(GetProcessHeap(), 0, pGroupData); 590 break; 591 } 592 593 return FALSE; 594 } 595 596 597 static VOID 598 InitGroupPropSheetPage(PROPSHEETPAGE *psp, WORD idDlg, DLGPROC DlgProc, LPTSTR pszGroup) 599 { 600 ZeroMemory(psp, sizeof(PROPSHEETPAGE)); 601 psp->dwSize = sizeof(PROPSHEETPAGE); 602 psp->dwFlags = PSP_DEFAULT; 603 psp->hInstance = hApplet; 604 psp->pszTemplate = MAKEINTRESOURCE(idDlg); 605 psp->pfnDlgProc = DlgProc; 606 psp->lParam = (LPARAM)pszGroup; 607 } 608 609 610 BOOL 611 GroupProperties(HWND hwndDlg) 612 { 613 PROPSHEETPAGE psp[1]; 614 PROPSHEETHEADER psh; 615 TCHAR szGroupName[UNLEN]; 616 INT nItem; 617 HWND hwndLV; 618 619 hwndLV = GetDlgItem(hwndDlg, IDC_GROUPS_LIST); 620 nItem = ListView_GetNextItem(hwndLV, -1, LVNI_SELECTED); 621 if (nItem == -1) 622 return FALSE; 623 624 /* Get the new user name */ 625 ListView_GetItemText(hwndLV, 626 nItem, 0, 627 szGroupName, 628 UNLEN); 629 630 ZeroMemory(&psh, sizeof(PROPSHEETHEADER)); 631 psh.dwSize = sizeof(PROPSHEETHEADER); 632 psh.dwFlags = PSH_PROPSHEETPAGE | PSH_PROPTITLE; 633 psh.hwndParent = hwndDlg; 634 psh.hInstance = hApplet; 635 psh.hIcon = NULL; 636 psh.pszCaption = szGroupName; 637 psh.nPages = sizeof(psp) / sizeof(PROPSHEETPAGE); 638 psh.nStartPage = 0; 639 psh.ppsp = psp; 640 641 InitGroupPropSheetPage(&psp[0], IDD_GROUP_GENERAL, GroupGeneralPageProc, szGroupName); 642 643 return (PropertySheet(&psh) == IDOK); 644 } 645