Leaked source code of windows server 2003
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

735 lines
20 KiB

  1. /*++
  2. Copyright (c) 1999 Microsoft Corporation
  3. Module Name:
  4. asr_dlg.cpp
  5. Abstract:
  6. Top level code to backup and restore information about the volumes on
  7. a system. This module handles the main application dialogue, including
  8. parsing the command-line, and updating the progress bar and UI status
  9. text.
  10. Authors:
  11. Steve DeVos (Veritas) (v-stevde) 15-May-1998
  12. Guhan Suriyanarayanan (guhans) 21-Aug-1999
  13. Environment:
  14. User-mode only.
  15. Revision History:
  16. 15-May-1998 v-stevde Initial creation
  17. 21-Aug-1999 guhans Cleaned up and re-wrote this module.
  18. --*/
  19. #include "stdafx.h"
  20. #include "asr_fmt.h"
  21. #include "asr_dlg.h"
  22. #ifdef _DEBUG
  23. #define new DEBUG_NEW
  24. #undef THIS_FILE
  25. static char THIS_FILE[] = __FILE__;
  26. #endif
  27. BOOLEAN g_bQuickFormat = FALSE;
  28. /////////////////////////////////////////////////////////////////////////////
  29. // CAsr_fmtDlg dialog
  30. CAsr_fmtDlg::CAsr_fmtDlg(CWnd* pParent /*=NULL*/)
  31. : CDialog(CAsr_fmtDlg::IDD, pParent)
  32. {
  33. //{{AFX_DATA_INIT(CAsr_fmtDlg)
  34. //}}AFX_DATA_INIT
  35. }
  36. void CAsr_fmtDlg::DoDataExchange(CDataExchange* pDX)
  37. {
  38. CDialog::DoDataExchange(pDX);
  39. //{{AFX_DATA_MAP(CAsr_fmtDlg)
  40. DDX_Control(pDX, IDC_PROGRESS, m_Progress);
  41. //}}AFX_DATA_MAP
  42. }
  43. BEGIN_MESSAGE_MAP(CAsr_fmtDlg, CDialog)
  44. //{{AFX_MSG_MAP(CAsr_fmtDlg)
  45. //}}AFX_MSG_MAP
  46. // manually added message handlers (for user-defined messages) should be added OUTSIDE
  47. // the AFX_MSG_MAP part above
  48. ON_MESSAGE(WM_WORKER_THREAD_DONE, OnWorkerThreadDone)
  49. ON_MESSAGE(WM_UPDATE_STATUS_TEXT, OnUpdateStatusText)
  50. END_MESSAGE_MAP()
  51. /////////////////////////////////////////////////////////////////////////////
  52. // CAsr_fmtDlg message handlers
  53. // ON_BN_CLICKED(IDOK, OnWorkerThreadDone)
  54. BOOL CAsr_fmtDlg::OnInitDialog()
  55. {
  56. CDialog::OnInitDialog();
  57. // initialize the progress range and start posiiton
  58. m_Progress.SetRange(0, 100);
  59. m_Progress.SetPos(0);
  60. m_ProgressPosition = 0;
  61. // Launch the worker thread.
  62. CreateThread(NULL, // no sid
  63. 0, // no initial stack size
  64. (LPTHREAD_START_ROUTINE) CAsr_fmtDlg::DoWork,
  65. this, // parameter
  66. 0, // no flags
  67. NULL // no thread ID
  68. );
  69. return TRUE; // return TRUE unless you set the focus to a control
  70. }
  71. LRESULT
  72. CAsr_fmtDlg::OnWorkerThreadDone(
  73. WPARAM wparam,
  74. LPARAM lparam
  75. )
  76. {
  77. EndDialog(m_dwEndStatus);
  78. return 0;
  79. }
  80. LRESULT
  81. CAsr_fmtDlg::OnUpdateStatusText(
  82. WPARAM wparam,
  83. LPARAM lparam
  84. )
  85. {
  86. SetDlgItemText(IDC_STATUS_TEXT, m_strStatusText);
  87. m_Progress.SetPos(m_ProgressPosition);
  88. return 0;
  89. }
  90. /**
  91. Name: DoWork()
  92. Description: This function runs as a thread launched form the Init
  93. of the dialog. This function determines what needs to be done, then
  94. calls the appropraite function to do the work.
  95. Modified: 8/31/1998
  96. Returns: TRUE
  97. Notes: If an error occurs EndDialog will be called with the
  98. exit status.
  99. Declaration:
  100. **/
  101. long
  102. CAsr_fmtDlg::DoWork(
  103. CAsr_fmtDlg *_this
  104. )
  105. {
  106. ASRFMT_CMD_OPTION cmdOption = cmdUndefined;
  107. cmdOption = _this->ParseCommandLine();
  108. ((CAsr_fmtDlg *)_this)->m_AsrState = NULL;
  109. _this->m_dwEndStatus = ERROR_SUCCESS;
  110. switch (cmdOption) {
  111. case cmdBackup: {
  112. if (!(_this->BackupState())) {
  113. _this->m_dwEndStatus = GetLastError();
  114. }
  115. break;
  116. }
  117. case cmdRestore: {
  118. if (!(_this->RestoreState())) {
  119. _this->m_dwEndStatus = GetLastError();
  120. }
  121. break;
  122. }
  123. case cmdDisplayHelp: {
  124. _this->m_dwEndStatus = ERROR_INVALID_FUNCTION; // display help ...
  125. break;
  126. }
  127. }
  128. if (ERROR_INVALID_FUNCTION != _this->m_dwEndStatus) { // display help ...
  129. _this->PostMessage(WM_WORKER_THREAD_DONE, 0, 0);
  130. }
  131. return 0;
  132. }
  133. /**
  134. Name: BackupState()
  135. Description: This function reads the current state file to get
  136. the current data for the sections to modify. It then updates
  137. [VOLUMES], [REMOVABLEMEDIA] and [COMMANDS] sections.
  138. Notes: If an error occurs an Error message popup is provided to
  139. the user.
  140. Declaration:
  141. **/
  142. BOOL
  143. CAsr_fmtDlg::BackupState()
  144. {
  145. BOOL result = FALSE;
  146. HANDLE hHeap = GetProcessHeap();
  147. int i = 0;
  148. CString strPleaseWait;
  149. strPleaseWait.LoadString(IDS_PLEASE_WAIT_BACKUP);
  150. SetDlgItemText(IDC_PROGRESS_TEXT, strPleaseWait);
  151. m_AsrState = (PASRFMT_STATE_INFO) HeapAlloc(
  152. hHeap,
  153. HEAP_ZERO_MEMORY,
  154. sizeof (ASRFMT_STATE_INFO)
  155. );
  156. //
  157. // The only purpose of the for loops below are to slow down the
  158. // UI and make the progress bar proceed smoothly, so that the user
  159. // can read the dialogue box on screen.
  160. //
  161. m_strStatusText.LoadString(IDS_QUERY_SYS_FOR_INFO);
  162. for (i = 3; i < 15; i++) {
  163. m_ProgressPosition = i;
  164. PostMessage(WM_UPDATE_STATUS_TEXT);
  165. Sleep(50);
  166. }
  167. if (m_AsrState) {
  168. result = BuildStateInfo(m_AsrState);
  169. }
  170. if (!result) {
  171. goto EXIT;
  172. }
  173. //
  174. // The only purpose of the for loops below are to slow down the
  175. // UI and make the progress bar proceed smoothly, so that the user
  176. // can read the dialogue box on screen.
  177. //
  178. m_strStatusText.LoadString(IDS_QUERY_SYS_FOR_INFO);
  179. for (i = 15; i < 45; i++) {
  180. m_ProgressPosition = i;
  181. PostMessage(WM_UPDATE_STATUS_TEXT);
  182. Sleep(50);
  183. }
  184. //
  185. // Pretend we're doing something else (change UI text)
  186. //
  187. m_strStatusText.LoadString(IDS_BUILDING_VOL_LIST);
  188. for (i = 45; i < 80; i++) {
  189. m_ProgressPosition = i;
  190. PostMessage(WM_UPDATE_STATUS_TEXT);
  191. Sleep(50);
  192. }
  193. m_strStatusText.LoadString(IDS_WRITING_TO_SIF);
  194. for (i = 80; i < 90; i++) {
  195. m_ProgressPosition = i;
  196. PostMessage(WM_UPDATE_STATUS_TEXT);
  197. Sleep(50);
  198. }
  199. result = WriteStateInfo(m_dwpAsrContext, m_AsrState);
  200. if (!result) {
  201. goto EXIT;
  202. }
  203. m_strStatusText.LoadString(IDS_WRITING_TO_SIF);
  204. for (i = 90; i < 101; i++) {
  205. m_ProgressPosition = i;
  206. PostMessage(WM_UPDATE_STATUS_TEXT);
  207. Sleep(50);
  208. }
  209. EXIT:
  210. FreeStateInfo(&m_AsrState);
  211. return result;
  212. }
  213. /**
  214. Name: RestoreState()
  215. Description: This function restores the state found in the [VOLUMES]
  216. section of the asr.sif file. The state restored includs:
  217. the drive letter.
  218. If drive is inaccessible it is formated.
  219. the volume label.
  220. Notes: If an error occurs an Error message popup is provided to
  221. the user.
  222. **/
  223. BOOL
  224. CAsr_fmtDlg::RestoreState()
  225. {
  226. BOOL bErrorsEncountered = FALSE,
  227. result = TRUE;
  228. CString strPleaseWait;
  229. strPleaseWait.LoadString(IDS_PLEASE_WAIT_RESTORE);
  230. SetDlgItemText(IDC_PROGRESS_TEXT, strPleaseWait);
  231. m_ProgressPosition = 0;
  232. m_strStatusText.LoadString(IDS_READING_SIF);
  233. PostMessage(WM_UPDATE_STATUS_TEXT);
  234. AsrfmtpInitialiseErrorFile(); // in case we need to log error messages
  235. //
  236. // 1. Read the state file
  237. //
  238. result = ReadStateInfo( (LPCTSTR)m_strSifPath, &m_AsrState);
  239. if (!result || !m_AsrState) {
  240. DWORD status = GetLastError();
  241. CString strErrorTitle;
  242. CString strErrorMessage;
  243. CString strErrorFormat;
  244. strErrorTitle.LoadString(IDS_ERROR_TITLE);
  245. strErrorFormat.LoadString(IDS_ERROR_NO_DRSTATE);
  246. strErrorMessage.Format(strErrorFormat, (LPCTSTR)m_strSifPath, status);
  247. AsrfmtpLogErrorMessage(_SeverityWarning, (LPCTSTR)strErrorMessage);
  248. AsrfmtpCloseErrorFile();
  249. return FALSE;
  250. }
  251. //
  252. // 2. Loop through all the volumes listed in the state file.
  253. //
  254. PASRFMT_VOLUME_INFO pVolume = m_AsrState->pVolume;
  255. PASRFMT_REMOVABLE_MEDIA_INFO pMedia = m_AsrState->pRemovableMedia;
  256. UINT driveType = DRIVE_UNKNOWN;
  257. PWSTR lpDrive = NULL; // Display string, of the form \DosDevices\C: or \??\Volume{Guid}
  258. DWORD cchVolumeGuid = 0;
  259. WCHAR szVolumeGuid[MAX_PATH + 1];
  260. int sizeIncrement = 0, i = 0;
  261. BOOL first = TRUE, isIntact = FALSE, isLabelIntact = TRUE;
  262. //
  263. // We need to first set all the guids a volume has, then try setting the drive
  264. // letters. Also, we need to go through the guid loop twice, to handle this case:
  265. //
  266. // Consider a sif where we had the entries
  267. // ... \vol1, \vol2
  268. // ... \vol1, \x:
  269. // ... \vol1, \vol3
  270. //
  271. // where vol1, vol2 and vol3 are volume guids (\??\Volume{Guid}),
  272. // and x: is the drive letter (\DosDevices\X:)
  273. //
  274. // Now, our list will contain three nodes:
  275. // -->(vol1, vol3)-->(vol1, x:)-->(vol1, vol2)-->/
  276. //
  277. // The problem is, the partition could currently have the guid vol2. (since
  278. // it is free to have any one of the three guids).
  279. //
  280. // If we only went through the loop once, we'll try to map vol1 to vol3, or
  281. // vice-versa if vol1 doesn't exist. Since neither exist yet, we would've
  282. // complained to the user.
  283. //
  284. // The advantage to doing it twice is this: the first time, since neither vol1
  285. // nor vol3 exist yet, we'll skip that node. Then we'll skip the drive letter for
  286. // now (since we're only doing guids), and eventually we'll map vol2 to vol1.
  287. //
  288. // The second time, we'll find vol1 exists (mapped to vol2), and we'll be
  289. // able to map vol1 to vol3. In a later loop, vol1 will also get the drive
  290. // letter X, and all's well.
  291. //
  292. // By the way, we could optimise this by creating a better data structure,
  293. // but it's too late in the game now for that. If the performance is
  294. // really bad, we could come back to this.
  295. //
  296. if (m_AsrState->countVolume > 1) {
  297. sizeIncrement = 50 / (m_AsrState->countVolume - 1);
  298. }
  299. else {
  300. sizeIncrement = 100;
  301. }
  302. m_ProgressPosition = 0;
  303. for (i = 0; i < 2; i++) {
  304. pVolume = m_AsrState->pVolume;
  305. while (pVolume) {
  306. m_ProgressPosition += sizeIncrement;
  307. m_strStatusText.Format(IDS_REST_SYM_LINKS, pVolume->szGuid);
  308. PostMessage(WM_UPDATE_STATUS_TEXT);
  309. if ((!pVolume->IsDosPathAssigned) &&
  310. ASRFMT_LOOKS_LIKE_VOLUME_GUID(pVolume->szDosPath, (wcslen(pVolume->szDosPath) * sizeof(WCHAR)))
  311. ) {
  312. pVolume->IsDosPathAssigned = SetDosName(pVolume->szGuid, pVolume->szDosPath);
  313. if (!pVolume->IsDosPathAssigned) {
  314. pVolume->IsDosPathAssigned = SetDosName(pVolume->szDosPath, pVolume->szGuid);
  315. }
  316. }
  317. pVolume = pVolume->pNext;
  318. }
  319. }
  320. pVolume = m_AsrState->pVolume;
  321. FormatInitialise();
  322. while (pVolume) {
  323. lpDrive = ((wcslen(pVolume->szDosPath) > 0) ? pVolume->szDosPath : pVolume->szGuid);
  324. //
  325. // Check if the drive is accessible. If not, we log an error message
  326. // and continue. GetDriveType requires the name in the DOS name space, so we
  327. // convert our GUID (NT name space) to the required format first
  328. //
  329. cchVolumeGuid = wcslen(pVolume->szGuid);
  330. if (cchVolumeGuid >= MAX_PATH) {
  331. cchVolumeGuid = MAX_PATH - 1;
  332. }
  333. wcsncpy(szVolumeGuid, pVolume->szGuid, cchVolumeGuid);
  334. szVolumeGuid[1] = L'\\';
  335. szVolumeGuid[cchVolumeGuid] = L'\\'; // Trailing back-slash
  336. szVolumeGuid[cchVolumeGuid+1] = L'\0';
  337. driveType = GetDriveType(szVolumeGuid);
  338. if (DRIVE_NO_ROOT_DIR == driveType) {
  339. CString strErrorTitle;
  340. CString strErrorMessage;
  341. CString strErrorFormat;
  342. strErrorTitle.LoadString(IDS_ERROR_TITLE);
  343. strErrorFormat.LoadString(IDS_ERROR_MISSING_VOL);
  344. strErrorMessage.Format(strErrorFormat, (PWSTR) pVolume->szGuid);
  345. AsrfmtpLogErrorMessage(_SeverityWarning, (LPCTSTR)strErrorMessage);
  346. bErrorsEncountered = TRUE;
  347. }
  348. if (DRIVE_FIXED != driveType) {
  349. //
  350. // Strange. The volumes section should have only listed the drives that
  351. // were fixed (At backup time). This could probably mean that a volume that
  352. // used to be on a fixed drive is now on a removable drive?!
  353. //
  354. pVolume = pVolume->pNext;
  355. continue;
  356. }
  357. //
  358. // Set the drive letter of the volume
  359. //
  360. result = TRUE;
  361. if (!pVolume->IsDosPathAssigned){
  362. m_ProgressPosition = 0;
  363. m_strStatusText.Format(IDS_REST_DRIVE_LETTER, lpDrive);
  364. PostMessage(WM_UPDATE_STATUS_TEXT);
  365. result = SetDosName(pVolume->szGuid, pVolume->szDosPath);
  366. }
  367. if (!result) {
  368. CString strErrorTitle;
  369. CString strErrorMessage;
  370. CString strErrorFormat;
  371. strErrorTitle.LoadString(IDS_ERROR_TITLE);
  372. strErrorFormat.LoadString(IDS_ERROR_MOUNTING);
  373. strErrorMessage.Format(strErrorFormat, (PWSTR) pVolume->szDosPath, (PWSTR) pVolume->szGuid);
  374. AsrfmtpLogErrorMessage(_SeverityWarning, (LPCTSTR)strErrorMessage);
  375. bErrorsEncountered = TRUE;
  376. pVolume = pVolume->pNext;
  377. continue;
  378. }
  379. //
  380. // Check if the volume needs to be formatted
  381. //
  382. m_ProgressPosition = 0;
  383. m_strStatusText.Format(IDS_CHECKING_VOLUME, lpDrive);
  384. PostMessage(WM_UPDATE_STATUS_TEXT);
  385. isIntact = IsFsTypeOkay(pVolume, &isLabelIntact);
  386. if (isIntact) {
  387. isIntact = IsVolumeIntact(pVolume);
  388. }
  389. //
  390. // Format the volume if needed
  391. //
  392. if (!isIntact && FormatVolume(pVolume)) {
  393. isLabelIntact = FALSE;
  394. m_ProgressPosition = 0;
  395. m_strStatusText.Format(IDS_FORMATTING_VOLUME, lpDrive);
  396. PostMessage(WM_UPDATE_STATUS_TEXT);
  397. //
  398. // FormatVolume is asynchronous.
  399. //
  400. first = TRUE;
  401. while (g_bFormatInProgress) {
  402. if (g_iFormatPercentComplete >= 100) {
  403. if (first) {
  404. m_ProgressPosition = 100;
  405. m_strStatusText.Format(IDS_CREATING_FS_STRUCT, lpDrive);
  406. PostMessage(WM_UPDATE_STATUS_TEXT);
  407. first = FALSE;
  408. }
  409. }
  410. else {
  411. m_Progress.SetPos(g_iFormatPercentComplete);
  412. }
  413. Sleep(100);
  414. }
  415. if (!g_bFormatSuccessful) {
  416. CString strErrorTitle;
  417. CString strErrorMessage;
  418. CString strErrorFormat;
  419. strErrorTitle.LoadString(IDS_ERROR_TITLE);
  420. strErrorFormat.LoadString(IDS_ERROR_FORMATTING);
  421. strErrorMessage.Format(strErrorFormat, (PWSTR) lpDrive);
  422. AsrfmtpLogErrorMessage(_SeverityWarning, (LPCTSTR)strErrorMessage);
  423. bErrorsEncountered = TRUE;
  424. pVolume = pVolume->pNext;
  425. continue;
  426. }
  427. //
  428. // Force the file-system to be mounted
  429. //
  430. MountFileSystem(pVolume);
  431. }
  432. //
  433. // Set the volume label if it isn't intact
  434. //
  435. if (!isLabelIntact) {
  436. m_ProgressPosition = 0;
  437. m_strStatusText.Format(IDS_REST_VOL_LABEL, lpDrive);
  438. PostMessage(WM_UPDATE_STATUS_TEXT);
  439. if ((wcslen(pVolume->szFsName) > 0) &&
  440. !SetVolumeLabel(szVolumeGuid, pVolume->szLabel)) {
  441. bErrorsEncountered = TRUE;
  442. pVolume = pVolume->pNext;
  443. continue;
  444. }
  445. }
  446. pVolume = pVolume->pNext;
  447. }
  448. FormatCleanup();
  449. while (pMedia) {
  450. lpDrive = ((wcslen(pMedia->szDosPath) > 0) ? pMedia->szDosPath : pMedia->szVolumeGuid);
  451. //
  452. // set the drive letter
  453. //
  454. m_ProgressPosition = 0;
  455. m_strStatusText.Format(IDS_REST_DRIVE_LETTER, lpDrive);
  456. PostMessage(WM_UPDATE_STATUS_TEXT);
  457. result = SetRemovableMediaGuid(pMedia->szDevicePath, pMedia->szVolumeGuid);
  458. if (result) {
  459. result = SetDosName(pMedia->szVolumeGuid, pMedia->szDosPath);
  460. }
  461. if (!result) {
  462. CString strErrorTitle;
  463. CString strErrorMessage;
  464. CString strErrorFormat;
  465. strErrorTitle.LoadString(IDS_ERROR_TITLE);
  466. strErrorFormat.LoadString(IDS_ERROR_MOUNTING);
  467. strErrorMessage.Format(IDS_ERROR_MOUNTING, (PWSTR) pMedia->szDosPath, (PWSTR) pMedia->szVolumeGuid);
  468. AsrfmtpLogErrorMessage(_SeverityWarning, (LPCTSTR)strErrorMessage);
  469. // ignore failures setting drive letter on CD.
  470. // bErrorsEncountered = TRUE;
  471. }
  472. pMedia = pMedia->pNext;
  473. }
  474. AsrfmtpCloseErrorFile();
  475. return (bErrorsEncountered ? FALSE : TRUE);
  476. }
  477. inline
  478. BOOL
  479. IsGuiModeAsr()
  480. {
  481. WCHAR szAsrFlag[20];
  482. //
  483. // If (and only if) this is GUI-mode ASR, the ASR_C_CONTEXT
  484. // environment variable is set to "AsrInProgress"
  485. //
  486. return (GetEnvironmentVariable(L"ASR_C_CONTEXT", szAsrFlag, 20) &&
  487. !wcscmp(szAsrFlag, L"AsrInProgress"));
  488. }
  489. /**
  490. Name: ParseCommandLine()
  491. Description: This function reads the comand line and looks for the
  492. "backup" or "restore" options.
  493. Modified: 8/31/1998
  494. Returns: cmdBackup, cmdRestore, or cmdDisplayHelp.
  495. Notes: If neither backup or restore are found, then the usage
  496. is displayed in the error box.
  497. Declaration:
  498. **/
  499. ASRFMT_CMD_OPTION
  500. CAsr_fmtDlg::ParseCommandLine()
  501. {
  502. CString cmd;
  503. m_dwpAsrContext = 0;
  504. cmd = GetCommandLine();
  505. cmd.MakeLower();
  506. if (cmd.Find(TEXT("/backup")) != -1) {
  507. int pos = cmd.Find(TEXT("/context="));
  508. if (pos > -1) {
  509. #ifdef _IA64_
  510. _stscanf(cmd.Mid(pos, cmd.GetLength() - pos + 1), TEXT("/context=%I64u"), &m_dwpAsrContext);
  511. #else
  512. _stscanf(cmd.Mid(pos, cmd.GetLength() - pos + 1), TEXT("/context=%lu"), &m_dwpAsrContext);
  513. #endif
  514. return cmdBackup;
  515. }
  516. } else if (cmd.Find(TEXT("/restore")) != -1) {
  517. if (cmd.Find(TEXT("/full")) != -1) {
  518. g_bQuickFormat = FALSE;
  519. }
  520. else if (cmd.Find(TEXT("/quick")) != -1) {
  521. g_bQuickFormat = TRUE;
  522. }
  523. else if (IsGuiModeAsr()) {
  524. g_bQuickFormat = FALSE;
  525. }
  526. else {
  527. g_bQuickFormat = TRUE;
  528. }
  529. int pos = cmd.Find(TEXT("/sifpath="));
  530. if (pos > -1) {
  531. _stscanf(cmd.Mid(pos, cmd.GetLength() - pos + 1), TEXT("/sifpath=%ws"), m_strSifPath.GetBuffer(1024));
  532. m_strSifPath.ReleaseBuffer();
  533. return cmdRestore;
  534. }
  535. }
  536. CString strErrorTitle;
  537. CString strErrorMessage;
  538. INT space_offset;
  539. strErrorTitle.LoadString(IDS_ERROR_TITLE);
  540. strErrorMessage.LoadString(IDS_ERROR_USAGE);
  541. space_offset = cmd.Find(' ');
  542. if (space_offset >0) {
  543. cmd = cmd.Left(cmd.Find(' '));
  544. }
  545. SetDlgItemText(IDC_PROGRESS_TEXT, strErrorMessage);
  546. CWnd *pText = GetDlgItem(IDC_STATUS_TEXT);
  547. pText->ShowWindow(SW_HIDE);
  548. CButton *pButton = (CButton*)GetDlgItem(IDOK);
  549. pButton->ShowWindow(SW_SHOW);
  550. pButton->SetState(TRUE);
  551. pButton->SetCheck(TRUE);
  552. CWnd *pBar = GetDlgItem(IDC_PROGRESS);
  553. pBar->ShowWindow(SW_HIDE);
  554. return cmdDisplayHelp;
  555. }