Source code of Windows XP (NT5)
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.

760 lines
21 KiB

  1. /*
  2. **------------------------------------------------------------------------------
  3. ** Module: Disk Cleanup Applet
  4. ** File: cleanmgr.cpp
  5. **
  6. ** Purpose: WinMain for the Disk Cleanup applet.
  7. ** Notes:
  8. ** Mod Log: Created by Jason Cobb (2/97)
  9. **
  10. ** Copyright (c)1997 Microsoft Corporation, All Rights Reserved
  11. **------------------------------------------------------------------------------
  12. */
  13. /*
  14. **------------------------------------------------------------------------------
  15. ** Project include files
  16. **------------------------------------------------------------------------------
  17. */
  18. #include "common.h"
  19. #define CPP_FUNCTIONS
  20. #include "crtfree.h"
  21. #include "dmgrinfo.h"
  22. #include "diskguid.h"
  23. #include "resource.h"
  24. #include "textout.h"
  25. #include "dmgrdlg.h"
  26. #include "msprintf.h"
  27. #include "diskutil.h"
  28. #include "seldrive.h"
  29. #include "drivlist.h"
  30. /*
  31. **------------------------------------------------------------------------------
  32. ** Global Defines
  33. **------------------------------------------------------------------------------
  34. */
  35. #define SWITCH_HIDEUI 'N'
  36. #define SWITCH_HIDEMOREOPTIONS 'M'
  37. #define SWITCH_DRIVE 'D'
  38. #define SZ_SAGESET TEXT("/SAGESET")
  39. #define SZ_SAGERUN TEXT("/SAGERUN")
  40. #define SZ_TUNEUP TEXT("/TUNEUP")
  41. #define SZ_SETUP TEXT("/SETUP")
  42. #define SZ_LOWDISK TEXT("/LOWDISK")
  43. #define SZ_VERYLOWDISK TEXT("/VERYLOWDISK")
  44. /*
  45. **------------------------------------------------------------------------------
  46. ** Global variables
  47. **------------------------------------------------------------------------------
  48. */
  49. HINSTANCE g_hInstance = NULL;
  50. HWND g_hDlg = NULL;
  51. BOOL g_bAlreadyRunning = FALSE;
  52. /*
  53. **------------------------------------------------------------------------------
  54. ** ParseCommandLine
  55. **
  56. ** Purpose: Parses command line for switches
  57. ** Parameters:
  58. ** lpCmdLine command line string
  59. ** pdwFlags pointer to flags DWORD
  60. ** pDrive pointer to a character that the drive letter
  61. ** is returned in
  62. ** Return: TRUE if command line contains /SAGESET or
  63. ** /SAGERUN
  64. ** FALSE on failure
  65. ** Notes;
  66. ** Mod Log: Created by Jason Cobb (7/97)
  67. **------------------------------------------------------------------------------
  68. */
  69. BOOL
  70. ParseCommandLine(
  71. LPTSTR lpCmdLine,
  72. PDWORD pdwFlags,
  73. PULONG pulProfile
  74. )
  75. {
  76. LPTSTR lpStr = lpCmdLine;
  77. BOOL bRet = FALSE;
  78. int i;
  79. TCHAR szProfile[4];
  80. *pulProfile = 0;
  81. //
  82. //Look for /SAGESET:n on the command line
  83. //
  84. if ((lpStr = StrStrI(lpCmdLine, SZ_SAGESET)) != NULL)
  85. {
  86. lpStr += lstrlen(SZ_SAGESET);
  87. if (*lpStr && *lpStr == ':')
  88. {
  89. lpStr++;
  90. i = 0;
  91. while (*lpStr && *lpStr != ' ' && i < 4)
  92. {
  93. szProfile[i] = *lpStr;
  94. lpStr++;
  95. i++;
  96. }
  97. *pulProfile = StrToInt(szProfile);
  98. }
  99. *pdwFlags = FLAG_SAGESET;
  100. bRet = TRUE;
  101. }
  102. //
  103. //Look for /SAGERUN:n on the command line
  104. //
  105. else if ((lpStr = StrStrI(lpCmdLine, SZ_SAGERUN)) != NULL)
  106. {
  107. lpStr += lstrlen(SZ_SAGERUN);
  108. if (*lpStr && *lpStr == ':')
  109. {
  110. lpStr++;
  111. i = 0;
  112. while (*lpStr && *lpStr != ' ' && i < 4)
  113. {
  114. szProfile[i] = *lpStr;
  115. lpStr++;
  116. i++;
  117. }
  118. *pulProfile = StrToInt(szProfile);
  119. }
  120. *pdwFlags = FLAG_SAGERUN;
  121. bRet = TRUE;
  122. }
  123. //
  124. //Look for /TUNEUP:n
  125. //
  126. else if ((lpStr = StrStrI(lpCmdLine, SZ_TUNEUP)) != NULL)
  127. {
  128. lpStr += lstrlen(SZ_TUNEUP);
  129. if (*lpStr && *lpStr == ':')
  130. {
  131. lpStr++;
  132. i = 0;
  133. while (*lpStr && *lpStr != ' ' && i < 4)
  134. {
  135. szProfile[i] = *lpStr;
  136. lpStr++;
  137. i++;
  138. }
  139. *pulProfile = StrToInt(szProfile);
  140. }
  141. *pdwFlags = FLAG_TUNEUP | FLAG_SAGESET;
  142. bRet = TRUE;
  143. }
  144. //
  145. //Look for /LOWDISK
  146. //
  147. else if ((lpStr = StrStrI(lpCmdLine, SZ_LOWDISK)) != NULL)
  148. {
  149. lpStr += lstrlen(SZ_LOWDISK);
  150. *pdwFlags = FLAG_LOWDISK;
  151. bRet = TRUE;
  152. }
  153. //
  154. //Look for /VERYLOWDISK
  155. //
  156. else if ((lpStr = StrStrI(lpCmdLine, SZ_VERYLOWDISK)) != NULL)
  157. {
  158. lpStr += lstrlen(SZ_VERYLOWDISK);
  159. *pdwFlags = FLAG_VERYLOWDISK | FLAG_SAGERUN;
  160. bRet = TRUE;
  161. }
  162. //
  163. //Look for /SETUP
  164. //
  165. else if ((lpStr = StrStrI(lpCmdLine, SZ_SETUP)) != NULL)
  166. {
  167. lpStr += lstrlen(SZ_SETUP);
  168. *pdwFlags = FLAG_SETUP | FLAG_SAGERUN;
  169. bRet = TRUE;
  170. }
  171. return bRet;
  172. }
  173. /*
  174. **------------------------------------------------------------------------------
  175. ** ParseForDrive
  176. **
  177. ** Purpose: Parses command line for switches
  178. ** Parameters:
  179. ** lpCmdLine command line string
  180. ** pDrive Buffer that the drive string will be returned
  181. ** in, the format will be x:\
  182. ** Return: TRUE on sucess
  183. ** FALSE on failure
  184. ** Notes;
  185. ** Mod Log: Created by Jason Cobb (7/97)
  186. **------------------------------------------------------------------------------
  187. */
  188. BOOL
  189. ParseForDrive(
  190. LPTSTR lpCmdLine,
  191. PTCHAR pDrive
  192. )
  193. {
  194. LPTSTR lpStr = lpCmdLine;
  195. GetBootDrive(pDrive, 4);
  196. while (*lpStr)
  197. {
  198. //
  199. //Did we find a '-' or a '/'?
  200. //
  201. if ((*lpStr == '-') || (*lpStr == '/'))
  202. {
  203. lpStr++;
  204. //
  205. //Is this the Drive switch?
  206. //
  207. if (*lpStr && (toupper(*lpStr) == SWITCH_DRIVE))
  208. {
  209. //
  210. //Skip any white space
  211. //
  212. lpStr++;
  213. while (*lpStr && *lpStr == ' ')
  214. lpStr++;
  215. //
  216. //The next character is the driver letter
  217. //
  218. if (*lpStr)
  219. {
  220. pDrive[0] = (TCHAR)toupper(*lpStr);
  221. pDrive[1] = ':';
  222. pDrive[2] = '\\';
  223. pDrive[3] = '\0';
  224. return TRUE;
  225. }
  226. }
  227. }
  228. lpStr++;
  229. }
  230. return FALSE;
  231. }
  232. BOOL CALLBACK EnumWindowsProc(
  233. HWND hWnd,
  234. LPARAM lParam
  235. )
  236. {
  237. TCHAR szWindowTitle[260];
  238. GetWindowText(hWnd, szWindowTitle, ARRAYSIZE(szWindowTitle));
  239. if (StrCmp(szWindowTitle, (LPTSTR)lParam) == 0)
  240. {
  241. MiDebugMsg((0, "There is already an instance of cleanmgr.exe running on this drive!"));
  242. SetForegroundWindow(hWnd);
  243. g_bAlreadyRunning = TRUE;
  244. return FALSE;
  245. }
  246. return TRUE;
  247. }
  248. /*
  249. **------------------------------------------------------------------------------
  250. **
  251. ** ProcessMessagesUntilEvent() - This does a message loop until an event or a
  252. ** timeout occurs.
  253. **
  254. **------------------------------------------------------------------------------
  255. */
  256. DWORD ProcessMessagesUntilEvent(HWND hwnd, HANDLE hEvent, DWORD dwTimeout)
  257. {
  258. MSG msg;
  259. DWORD dwEndTime = GetTickCount() + dwTimeout;
  260. LONG lWait = (LONG)dwTimeout;
  261. DWORD dwReturn;
  262. for (;;)
  263. {
  264. dwReturn = MsgWaitForMultipleObjects(1, &hEvent,
  265. FALSE, lWait, QS_ALLINPUT);
  266. // were we signalled or did we time out?
  267. if (dwReturn != (WAIT_OBJECT_0 + 1))
  268. {
  269. break;
  270. }
  271. // we woke up because of messages.
  272. while (PeekMessage(&msg, hwnd, 0, 0, PM_REMOVE))
  273. {
  274. TranslateMessage(&msg);
  275. if (msg.message == WM_SETCURSOR)
  276. {
  277. SetCursor(LoadCursor(NULL, IDC_WAIT));
  278. }
  279. else
  280. {
  281. DispatchMessage(&msg);
  282. }
  283. }
  284. // calculate new timeout value
  285. if (dwTimeout != INFINITE)
  286. {
  287. lWait = (LONG)dwEndTime - GetTickCount();
  288. }
  289. }
  290. return dwReturn;
  291. }
  292. /*
  293. **------------------------------------------------------------------------------
  294. **
  295. ** WaitForARP() - Waits for the "Add/Remove Programs" Control Panel applet to
  296. ** be closed by the user.
  297. **
  298. **------------------------------------------------------------------------------
  299. */
  300. void WaitForARP()
  301. {
  302. HWND hwndARP = NULL;
  303. HANDLE hProcessARP = NULL;
  304. DWORD dwProcId = 0;
  305. TCHAR szARPTitle[128];
  306. // We want to wait until the user closes "Add/Remove Programs" to continue.
  307. // To do this, we must first get an HWND to the dialog window. This is
  308. // accomplished by trying to find the window by its title for no more than
  309. // about 5 seconds (looping 10 times with a 0.5 second delay between attempts).
  310. LoadString(g_hInstance, IDS_ADDREMOVE_TITLE, szARPTitle, ARRAYSIZE(szARPTitle));
  311. for (int i = 0; (i < 10) && (!hwndARP); i++)
  312. {
  313. hwndARP = FindWindow(NULL, szARPTitle);
  314. Sleep(500);
  315. }
  316. // If we got the HWND, then we can get the process handle, and wait
  317. // until the Add/Remove process goes away to continue.
  318. if (hwndARP)
  319. {
  320. GetWindowThreadProcessId(hwndARP, &dwProcId);
  321. hProcessARP = OpenProcess(SYNCHRONIZE, FALSE, dwProcId);
  322. if (hProcessARP)
  323. {
  324. ProcessMessagesUntilEvent(hwndARP, hProcessARP, INFINITE);
  325. CloseHandle(hProcessARP);
  326. }
  327. }
  328. }
  329. int APIENTRY WinMainT(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nCmdShow)
  330. {
  331. DWORD dwFlags = 0;
  332. CleanupMgrInfo *pcmi = NULL;
  333. TCHAR szDrive[4];
  334. TCHAR szSageDrive[4];
  335. TCHAR szCaption[64];
  336. TCHAR szInitialMessage[812];
  337. TCHAR szFinalMessage[830];
  338. ULONG ulProfile = 0;
  339. WNDCLASS cls = {0};
  340. TCHAR *psz;
  341. TCHAR szVolumeName[MAX_PATH];
  342. int RetCode = RETURN_SUCCESS;
  343. int nDoAgain = IDYES;
  344. ULARGE_INTEGER ulFreeBytesAvailable,
  345. ulTotalNumberOfBytes,
  346. ulTotalNumberOfFreeBytes;
  347. UINT uiTotalFreeMB;
  348. STARTUPINFO si;
  349. PROCESS_INFORMATION pi;
  350. BOOL fFirstInstance = TRUE;
  351. HWND hwnd = NULL;
  352. HANDLE hEvent = NULL;
  353. //
  354. // Decide if this is the first instance
  355. //
  356. hEvent = CreateEvent (NULL, FALSE, FALSE, TEXT("Cleanmgr: Instance event"));
  357. if (hEvent)
  358. {
  359. if (GetLastError() == ERROR_ALREADY_EXISTS)
  360. {
  361. fFirstInstance = FALSE;
  362. }
  363. }
  364. g_hInstance = hInstance;
  365. InitCommonControls();
  366. //
  367. //Initialize support classes
  368. //
  369. CleanupMgrInfo::Register(hInstance);
  370. cls.lpszClassName = SZ_CLASSNAME;
  371. cls.hCursor = LoadCursor(NULL, IDC_ARROW);
  372. cls.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(ICON_CLEANMGR));
  373. cls.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
  374. cls.hInstance = hInstance;
  375. cls.style = CS_HREDRAW | CS_VREDRAW;
  376. cls.lpfnWndProc = DefDlgProc;
  377. cls.cbWndExtra = DLGWINDOWEXTRA;
  378. RegisterClass(&cls);
  379. //
  380. //Parse the command line
  381. //
  382. ParseCommandLine(lpCmdLine, &dwFlags, &ulProfile);
  383. if (!ParseForDrive(lpCmdLine, szDrive) &&
  384. !(dwFlags & FLAG_SAGESET) &&
  385. !(dwFlags & FLAG_SAGERUN))
  386. {
  387. PromptForDisk:
  388. if (!SelectDrive(szDrive))
  389. goto Cleanup_Exit;
  390. }
  391. //
  392. //Create window title for comparison
  393. //
  394. if (dwFlags & FLAG_SAGESET)
  395. {
  396. psz = SHFormatMessage( MSG_APP_SETTINGS_TITLE );
  397. }
  398. else
  399. {
  400. GetVolumeInformation(szDrive, szVolumeName, ARRAYSIZE(szVolumeName), NULL, NULL, NULL, NULL, 0);
  401. psz = SHFormatMessage( MSG_APP_TITLE, szVolumeName, szDrive[0] );
  402. }
  403. if (psz)
  404. {
  405. EnumWindows(EnumWindowsProc, (LPARAM)psz);
  406. LocalFree(psz);
  407. }
  408. // Also check for any of the final series of dialogs which may display after the main UI has gone away
  409. if (!g_bAlreadyRunning)
  410. {
  411. LoadString(g_hInstance, IDS_LOWDISK_CAPTION, szCaption, ARRAYSIZE(szCaption));
  412. EnumWindows(EnumWindowsProc, (LPARAM)szCaption);
  413. LoadString(g_hInstance, IDS_LOWDISK_SUCCESS_CAPTION, szCaption, ARRAYSIZE(szCaption));
  414. EnumWindows(EnumWindowsProc, (LPARAM)szCaption);
  415. }
  416. // If we didn't catch another instance of cleanmgr via EnumWindows(), we catch it with a
  417. // named event. We wait until now to do it so EnumWindows() can bring the other instance's
  418. // window to the foreground if it is up.
  419. if (!fFirstInstance)
  420. {
  421. g_bAlreadyRunning = TRUE;
  422. }
  423. if (g_bAlreadyRunning)
  424. {
  425. RetCode = FALSE;
  426. goto Cleanup_Exit;
  427. }
  428. if (dwFlags & FLAG_SAGERUN)
  429. {
  430. szSageDrive[1] = TCHAR(':');
  431. szSageDrive[2] = TCHAR('\\');
  432. szSageDrive[3] = TCHAR('\0');
  433. for (TCHAR c = 'A'; c <= 'Z'; c++)
  434. {
  435. szSageDrive[0] = c;
  436. //
  437. //Create CleanupMgrInfo object for this drive
  438. //
  439. pcmi = new CleanupMgrInfo(szSageDrive, dwFlags, ulProfile);
  440. if (pcmi != NULL && pcmi->isAbortScan() == FALSE && pcmi->isValid())
  441. {
  442. pcmi->purgeClients();
  443. }
  444. // Keep the latest scan window handle (but hide the window)
  445. if (pcmi && pcmi->hAbortScanWnd)
  446. {
  447. hwnd = pcmi->hAbortScanWnd;
  448. ShowWindow(hwnd, SW_HIDE);
  449. }
  450. //
  451. //Destroy the CleanupMgrInfo object for this drive
  452. //
  453. if (pcmi)
  454. {
  455. RetCode = pcmi->dwReturnCode;
  456. delete pcmi;
  457. pcmi = NULL;
  458. }
  459. }
  460. }
  461. else
  462. {
  463. //
  464. //Create CleanupMgrInfo object
  465. //
  466. pcmi = new CleanupMgrInfo(szDrive, dwFlags, ulProfile);
  467. if (pcmi != NULL && pcmi->isAbortScan() == FALSE)
  468. {
  469. //
  470. //User specified an invalid drive letter
  471. //
  472. if (!(pcmi->isValid()))
  473. {
  474. // dismiss the dialog first
  475. if ( pcmi->hAbortScanWnd )
  476. {
  477. pcmi->bAbortScan = TRUE;
  478. //
  479. //Wait for scan thread to finish
  480. //
  481. WaitForSingleObject(pcmi->hAbortScanThread, INFINITE);
  482. pcmi->bAbortScan = FALSE;
  483. }
  484. TCHAR szWarningTitle[256];
  485. TCHAR *pszWarning;
  486. pszWarning = SHFormatMessage( MSG_BAD_DRIVE_LETTER, szDrive );
  487. LoadString(g_hInstance, IDS_TITLE, szWarningTitle, ARRAYSIZE(szWarningTitle));
  488. MessageBox(NULL, pszWarning, szWarningTitle, MB_OK | MB_SETFOREGROUND);
  489. LocalFree(pszWarning);
  490. if (pcmi)
  491. {
  492. delete pcmi;
  493. pcmi = NULL;
  494. goto PromptForDisk;
  495. }
  496. }
  497. else
  498. {
  499. //Bring up the main dialog
  500. int nResult = DisplayCleanMgrProperties(NULL, (LPARAM)pcmi);
  501. if (nResult)
  502. {
  503. pcmi->dwUIFlags |= FLAG_SAVE_STATE;
  504. //
  505. //Need to purge the clients if we are NOT
  506. //in the SAGE settings mode.
  507. //
  508. if (!(dwFlags & FLAG_SAGESET) && !(dwFlags & FLAG_TUNEUP) && pcmi->bPurgeFiles)
  509. pcmi->purgeClients();
  510. }
  511. }
  512. }
  513. //
  514. //Destroy the CleanupMgrInfo object
  515. //
  516. if (pcmi)
  517. {
  518. RetCode = pcmi->dwReturnCode;
  519. delete pcmi;
  520. pcmi = NULL;
  521. }
  522. }
  523. GetStartupInfo(&si);
  524. // If we were called on a low free disk space case, we want to inform the user of how much space remains,
  525. // and encourage them to free up space via Add/Remove programs until they reach 200MB free in the /LOWDISK
  526. // case, or 50MB free in the /VERYLOWDISK case.
  527. while (nDoAgain == IDYES)
  528. {
  529. BOOL bFinalTime = FALSE;
  530. nDoAgain = IDNO;
  531. // Bring up the Low Disk message box
  532. if (dwFlags & FLAG_LOWDISK)
  533. {
  534. GetDiskFreeSpaceEx(szDrive, &ulFreeBytesAvailable, &ulTotalNumberOfBytes, &ulTotalNumberOfFreeBytes);
  535. uiTotalFreeMB = (UINT) (ulTotalNumberOfFreeBytes.QuadPart / (NUM_BYTES_IN_MB));
  536. if (uiTotalFreeMB < 200)
  537. {
  538. if (uiTotalFreeMB < 80)
  539. {
  540. LoadString(g_hInstance, IDS_LOWDISK_MESSAGE2, szInitialMessage, ARRAYSIZE(szInitialMessage));
  541. }
  542. else
  543. {
  544. LoadString(g_hInstance, IDS_LOWDISK_MESSAGE, szInitialMessage, ARRAYSIZE(szInitialMessage));
  545. }
  546. LoadString(g_hInstance, IDS_LOWDISK_CAPTION, szCaption, ARRAYSIZE(szCaption));
  547. wsprintf(szFinalMessage, szInitialMessage, uiTotalFreeMB);
  548. nDoAgain = MessageBox(hwnd, szFinalMessage, szCaption, MB_YESNO | MB_ICONWARNING | MB_TOPMOST);
  549. }
  550. else
  551. {
  552. LoadString(g_hInstance, IDS_LOWDISK_SUCCESS_CAPTION, szCaption, ARRAYSIZE(szCaption));
  553. LoadString(g_hInstance, IDS_LOWDISK_SUCCESS_MESSAGE, szInitialMessage, ARRAYSIZE(szInitialMessage));
  554. wsprintf(szFinalMessage, szInitialMessage, uiTotalFreeMB);
  555. nDoAgain = MessageBox(hwnd, szFinalMessage, szCaption, MB_YESNO | MB_ICONQUESTION | MB_DEFBUTTON2 | MB_TOPMOST);
  556. bFinalTime = TRUE;
  557. }
  558. }
  559. else if (dwFlags & FLAG_VERYLOWDISK)
  560. {
  561. // Bring up the Very Low Disk message box
  562. GetDiskFreeSpaceEx(szDrive, &ulFreeBytesAvailable, &ulTotalNumberOfBytes, &ulTotalNumberOfFreeBytes);
  563. uiTotalFreeMB = (UINT) (ulTotalNumberOfFreeBytes.QuadPart / (NUM_BYTES_IN_MB));
  564. if (uiTotalFreeMB < 50)
  565. {
  566. LoadString(g_hInstance, IDS_LOWDISK_CAPTION, szCaption, ARRAYSIZE(szCaption));
  567. LoadString(g_hInstance, IDS_VERYLOWDISK_MESSAGE, szInitialMessage, ARRAYSIZE(szInitialMessage));
  568. wsprintf(szFinalMessage, szInitialMessage, uiTotalFreeMB);
  569. nDoAgain = MessageBox(hwnd, szFinalMessage, szCaption, MB_YESNO | MB_ICONSTOP | MB_TOPMOST);
  570. }
  571. else
  572. {
  573. LoadString(g_hInstance, IDS_LOWDISK_SUCCESS_CAPTION, szCaption, ARRAYSIZE(szCaption));
  574. LoadString(g_hInstance, IDS_LOWDISK_SUCCESS_MESSAGE, szInitialMessage, ARRAYSIZE(szInitialMessage));
  575. wsprintf(szFinalMessage, szInitialMessage, uiTotalFreeMB);
  576. nDoAgain = MessageBox(hwnd, szFinalMessage, szCaption, MB_YESNO | MB_ICONQUESTION | MB_DEFBUTTON2 | MB_TOPMOST);
  577. bFinalTime = TRUE;
  578. }
  579. }
  580. if (nDoAgain == IDYES)
  581. {
  582. // Launch the Add/Remove Programs dialog
  583. lstrcpy(szInitialMessage, SZ_RUN_INSTALLED_PROGRAMS);
  584. if (CreateProcess(NULL, szInitialMessage, NULL, NULL, FALSE, NORMAL_PRIORITY_CLASS, NULL, NULL, &si, &pi))
  585. {
  586. CloseHandle(pi.hProcess);
  587. CloseHandle(pi.hThread);
  588. // Only bother to wait around if it is not our final time through
  589. if (! bFinalTime)
  590. {
  591. WaitForARP();
  592. }
  593. else
  594. {
  595. // If this was our final time through, then set the flag
  596. // to break out of the loop
  597. nDoAgain = IDNO;
  598. }
  599. }
  600. else
  601. {
  602. // If we cannot launch Add/Remove programs for some reason, we break
  603. // out of the loop
  604. nDoAgain = IDNO;
  605. }
  606. }
  607. }
  608. Cleanup_Exit:
  609. if (hEvent)
  610. {
  611. CloseHandle (hEvent);
  612. }
  613. CleanupMgrInfo::Unregister();
  614. return RetCode;
  615. }
  616. STDAPI_(int) ModuleEntry(void)
  617. {
  618. int i;
  619. STARTUPINFOA si;
  620. LPTSTR pszCmdLine = GetCommandLine();
  621. //
  622. // We don't want the "No disk in drive X:" requesters, so we set
  623. // the critical error mask such that calls will just silently fail
  624. //
  625. SetErrorMode(SEM_FAILCRITICALERRORS);
  626. if ( *pszCmdLine == TEXT('\"') ) {
  627. /*
  628. * Scan, and skip over, subsequent characters until
  629. * another double-quote or a null is encountered.
  630. */
  631. while ( *++pszCmdLine && (*pszCmdLine
  632. != TEXT('\"')) );
  633. /*
  634. * If we stopped on a double-quote (usual case), skip
  635. * over it.
  636. */
  637. if ( *pszCmdLine == TEXT('\"') )
  638. pszCmdLine++;
  639. }
  640. else {
  641. while (*pszCmdLine > TEXT(' '))
  642. pszCmdLine++;
  643. }
  644. /*
  645. * Skip past any white space preceeding the second token.
  646. */
  647. while (*pszCmdLine && (*pszCmdLine <= TEXT(' '))) {
  648. pszCmdLine++;
  649. }
  650. si.dwFlags = 0;
  651. GetStartupInfoA(&si);
  652. i = WinMainT(GetModuleHandle(NULL), NULL, pszCmdLine,
  653. si.dwFlags & STARTF_USESHOWWINDOW ? si.wShowWindow : SW_SHOWDEFAULT);
  654. // Since we now have a way for an extension to tell us when it is finished,
  655. // we will terminate all processes when the main thread goes away.
  656. return i;
  657. }
  658. void _cdecl main()
  659. {
  660. ModuleEntry();
  661. }