/* **------------------------------------------------------------------------------ ** Module: Disk Cleanup Applet ** File: cleanmgr.cpp ** ** Purpose: WinMain for the Disk Cleanup applet. ** Notes: ** Mod Log: Created by Jason Cobb (2/97) ** ** Copyright (c)1997 Microsoft Corporation, All Rights Reserved **------------------------------------------------------------------------------ */ /* **------------------------------------------------------------------------------ ** Project include files **------------------------------------------------------------------------------ */ #include "common.h" #define CPP_FUNCTIONS #include "crtfree.h" #include "dmgrinfo.h" #include "diskguid.h" #include "resource.h" #include "textout.h" #include "dmgrdlg.h" #include "msprintf.h" #include "diskutil.h" #include "seldrive.h" #include "drivlist.h" /* **------------------------------------------------------------------------------ ** Global Defines **------------------------------------------------------------------------------ */ #define SWITCH_HIDEUI 'N' #define SWITCH_HIDEMOREOPTIONS 'M' #define SWITCH_DRIVE 'D' #define SZ_SAGESET TEXT("/SAGESET") #define SZ_SAGERUN TEXT("/SAGERUN") #define SZ_TUNEUP TEXT("/TUNEUP") #define SZ_SETUP TEXT("/SETUP") #define SZ_LOWDISK TEXT("/LOWDISK") #define SZ_VERYLOWDISK TEXT("/VERYLOWDISK") /* **------------------------------------------------------------------------------ ** Global variables **------------------------------------------------------------------------------ */ HINSTANCE g_hInstance = NULL; HWND g_hDlg = NULL; BOOL g_bAlreadyRunning = FALSE; /* **------------------------------------------------------------------------------ ** ParseCommandLine ** ** Purpose: Parses command line for switches ** Parameters: ** lpCmdLine command line string ** pdwFlags pointer to flags DWORD ** pDrive pointer to a character that the drive letter ** is returned in ** Return: TRUE if command line contains /SAGESET or ** /SAGERUN ** FALSE on failure ** Notes; ** Mod Log: Created by Jason Cobb (7/97) **------------------------------------------------------------------------------ */ BOOL ParseCommandLine( LPTSTR lpCmdLine, PDWORD pdwFlags, PULONG pulProfile ) { LPTSTR lpStr = lpCmdLine; BOOL bRet = FALSE; int i; TCHAR szProfile[4]; *pulProfile = 0; // //Look for /SAGESET:n on the command line // if ((lpStr = StrStrI(lpCmdLine, SZ_SAGESET)) != NULL) { lpStr += lstrlen(SZ_SAGESET); if (*lpStr && *lpStr == ':') { lpStr++; i = 0; while (*lpStr && *lpStr != ' ' && i < 4) { szProfile[i] = *lpStr; lpStr++; i++; } *pulProfile = StrToInt(szProfile); } *pdwFlags = FLAG_SAGESET; bRet = TRUE; } // //Look for /SAGERUN:n on the command line // else if ((lpStr = StrStrI(lpCmdLine, SZ_SAGERUN)) != NULL) { lpStr += lstrlen(SZ_SAGERUN); if (*lpStr && *lpStr == ':') { lpStr++; i = 0; while (*lpStr && *lpStr != ' ' && i < 4) { szProfile[i] = *lpStr; lpStr++; i++; } *pulProfile = StrToInt(szProfile); } *pdwFlags = FLAG_SAGERUN; bRet = TRUE; } // //Look for /TUNEUP:n // else if ((lpStr = StrStrI(lpCmdLine, SZ_TUNEUP)) != NULL) { lpStr += lstrlen(SZ_TUNEUP); if (*lpStr && *lpStr == ':') { lpStr++; i = 0; while (*lpStr && *lpStr != ' ' && i < 4) { szProfile[i] = *lpStr; lpStr++; i++; } *pulProfile = StrToInt(szProfile); } *pdwFlags = FLAG_TUNEUP | FLAG_SAGESET; bRet = TRUE; } // //Look for /LOWDISK // else if ((lpStr = StrStrI(lpCmdLine, SZ_LOWDISK)) != NULL) { lpStr += lstrlen(SZ_LOWDISK); *pdwFlags = FLAG_LOWDISK; bRet = TRUE; } // //Look for /VERYLOWDISK // else if ((lpStr = StrStrI(lpCmdLine, SZ_VERYLOWDISK)) != NULL) { lpStr += lstrlen(SZ_VERYLOWDISK); *pdwFlags = FLAG_VERYLOWDISK | FLAG_SAGERUN; bRet = TRUE; } // //Look for /SETUP // else if ((lpStr = StrStrI(lpCmdLine, SZ_SETUP)) != NULL) { lpStr += lstrlen(SZ_SETUP); *pdwFlags = FLAG_SETUP | FLAG_SAGERUN; bRet = TRUE; } return bRet; } /* **------------------------------------------------------------------------------ ** ParseForDrive ** ** Purpose: Parses command line for switches ** Parameters: ** lpCmdLine command line string ** pDrive Buffer that the drive string will be returned ** in, the format will be x:\ ** Return: TRUE on sucess ** FALSE on failure ** Notes; ** Mod Log: Created by Jason Cobb (7/97) **------------------------------------------------------------------------------ */ BOOL ParseForDrive( LPTSTR lpCmdLine, PTCHAR pDrive ) { LPTSTR lpStr = lpCmdLine; GetBootDrive(pDrive, 4); while (*lpStr) { // //Did we find a '-' or a '/'? // if ((*lpStr == '-') || (*lpStr == '/')) { lpStr++; // //Is this the Drive switch? // if (*lpStr && (toupper(*lpStr) == SWITCH_DRIVE)) { // //Skip any white space // lpStr++; while (*lpStr && *lpStr == ' ') lpStr++; // //The next character is the driver letter // if (*lpStr) { pDrive[0] = (TCHAR)toupper(*lpStr); pDrive[1] = ':'; pDrive[2] = '\\'; pDrive[3] = '\0'; return TRUE; } } } lpStr++; } return FALSE; } BOOL CALLBACK EnumWindowsProc( HWND hWnd, LPARAM lParam ) { TCHAR szWindowTitle[260]; GetWindowText(hWnd, szWindowTitle, ARRAYSIZE(szWindowTitle)); if (StrCmp(szWindowTitle, (LPTSTR)lParam) == 0) { MiDebugMsg((0, "There is already an instance of cleanmgr.exe running on this drive!")); SetForegroundWindow(hWnd); g_bAlreadyRunning = TRUE; return FALSE; } return TRUE; } /* **------------------------------------------------------------------------------ ** ** ProcessMessagesUntilEvent() - This does a message loop until an event or a ** timeout occurs. ** **------------------------------------------------------------------------------ */ DWORD ProcessMessagesUntilEvent(HWND hwnd, HANDLE hEvent, DWORD dwTimeout) { MSG msg; DWORD dwEndTime = GetTickCount() + dwTimeout; LONG lWait = (LONG)dwTimeout; DWORD dwReturn; for (;;) { dwReturn = MsgWaitForMultipleObjects(1, &hEvent, FALSE, lWait, QS_ALLINPUT); // were we signalled or did we time out? if (dwReturn != (WAIT_OBJECT_0 + 1)) { break; } // we woke up because of messages. while (PeekMessage(&msg, hwnd, 0, 0, PM_REMOVE)) { TranslateMessage(&msg); if (msg.message == WM_SETCURSOR) { SetCursor(LoadCursor(NULL, IDC_WAIT)); } else { DispatchMessage(&msg); } } // calculate new timeout value if (dwTimeout != INFINITE) { lWait = (LONG)dwEndTime - GetTickCount(); } } return dwReturn; } /* **------------------------------------------------------------------------------ ** ** WaitForARP() - Waits for the "Add/Remove Programs" Control Panel applet to ** be closed by the user. ** **------------------------------------------------------------------------------ */ void WaitForARP() { HWND hwndARP = NULL; HANDLE hProcessARP = NULL; DWORD dwProcId = 0; TCHAR szARPTitle[128]; // We want to wait until the user closes "Add/Remove Programs" to continue. // To do this, we must first get an HWND to the dialog window. This is // accomplished by trying to find the window by its title for no more than // about 5 seconds (looping 10 times with a 0.5 second delay between attempts). LoadString(g_hInstance, IDS_ADDREMOVE_TITLE, szARPTitle, ARRAYSIZE(szARPTitle)); for (int i = 0; (i < 10) && (!hwndARP); i++) { hwndARP = FindWindow(NULL, szARPTitle); Sleep(500); } // If we got the HWND, then we can get the process handle, and wait // until the Add/Remove process goes away to continue. if (hwndARP) { GetWindowThreadProcessId(hwndARP, &dwProcId); hProcessARP = OpenProcess(SYNCHRONIZE, FALSE, dwProcId); if (hProcessARP) { ProcessMessagesUntilEvent(hwndARP, hProcessARP, INFINITE); CloseHandle(hProcessARP); } } } int APIENTRY WinMainT(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nCmdShow) { DWORD dwFlags = 0; CleanupMgrInfo *pcmi = NULL; TCHAR szDrive[4]; TCHAR szSageDrive[4]; TCHAR szCaption[64]; TCHAR szInitialMessage[812]; TCHAR szFinalMessage[830]; ULONG ulProfile = 0; WNDCLASS cls = {0}; TCHAR *psz; TCHAR szVolumeName[MAX_PATH]; int RetCode = RETURN_SUCCESS; int nDoAgain = IDYES; ULARGE_INTEGER ulFreeBytesAvailable, ulTotalNumberOfBytes, ulTotalNumberOfFreeBytes; UINT uiTotalFreeMB; STARTUPINFO si; PROCESS_INFORMATION pi; BOOL fFirstInstance = TRUE; HWND hwnd = NULL; HANDLE hEvent = NULL; // // Decide if this is the first instance // hEvent = CreateEvent (NULL, FALSE, FALSE, TEXT("Cleanmgr: Instance event")); if (hEvent) { if (GetLastError() == ERROR_ALREADY_EXISTS) { fFirstInstance = FALSE; } } g_hInstance = hInstance; InitCommonControls(); // //Initialize support classes // CleanupMgrInfo::Register(hInstance); cls.lpszClassName = SZ_CLASSNAME; cls.hCursor = LoadCursor(NULL, IDC_ARROW); cls.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(ICON_CLEANMGR)); cls.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1); cls.hInstance = hInstance; cls.style = CS_HREDRAW | CS_VREDRAW; cls.lpfnWndProc = DefDlgProc; cls.cbWndExtra = DLGWINDOWEXTRA; RegisterClass(&cls); // //Parse the command line // ParseCommandLine(lpCmdLine, &dwFlags, &ulProfile); if (!ParseForDrive(lpCmdLine, szDrive) && !(dwFlags & FLAG_SAGESET) && !(dwFlags & FLAG_SAGERUN)) { PromptForDisk: if (!SelectDrive(szDrive)) goto Cleanup_Exit; } // //Create window title for comparison // if (dwFlags & FLAG_SAGESET) { psz = SHFormatMessage( MSG_APP_SETTINGS_TITLE ); } else { GetVolumeInformation(szDrive, szVolumeName, ARRAYSIZE(szVolumeName), NULL, NULL, NULL, NULL, 0); psz = SHFormatMessage( MSG_APP_TITLE, szVolumeName, szDrive[0] ); } if (psz) { EnumWindows(EnumWindowsProc, (LPARAM)psz); LocalFree(psz); } // Also check for any of the final series of dialogs which may display after the main UI has gone away if (!g_bAlreadyRunning) { LoadString(g_hInstance, IDS_LOWDISK_CAPTION, szCaption, ARRAYSIZE(szCaption)); EnumWindows(EnumWindowsProc, (LPARAM)szCaption); LoadString(g_hInstance, IDS_LOWDISK_SUCCESS_CAPTION, szCaption, ARRAYSIZE(szCaption)); EnumWindows(EnumWindowsProc, (LPARAM)szCaption); } // If we didn't catch another instance of cleanmgr via EnumWindows(), we catch it with a // named event. We wait until now to do it so EnumWindows() can bring the other instance's // window to the foreground if it is up. if (!fFirstInstance) { g_bAlreadyRunning = TRUE; } if (g_bAlreadyRunning) { RetCode = FALSE; goto Cleanup_Exit; } if (dwFlags & FLAG_SAGERUN) { szSageDrive[1] = TCHAR(':'); szSageDrive[2] = TCHAR('\\'); szSageDrive[3] = TCHAR('\0'); for (TCHAR c = 'A'; c <= 'Z'; c++) { szSageDrive[0] = c; // //Create CleanupMgrInfo object for this drive // pcmi = new CleanupMgrInfo(szSageDrive, dwFlags, ulProfile); if (pcmi != NULL && pcmi->isAbortScan() == FALSE && pcmi->isValid()) { pcmi->purgeClients(); } // Keep the latest scan window handle (but hide the window) if (pcmi && pcmi->hAbortScanWnd) { hwnd = pcmi->hAbortScanWnd; ShowWindow(hwnd, SW_HIDE); } // //Destroy the CleanupMgrInfo object for this drive // if (pcmi) { RetCode = pcmi->dwReturnCode; delete pcmi; pcmi = NULL; } } } else { // //Create CleanupMgrInfo object // pcmi = new CleanupMgrInfo(szDrive, dwFlags, ulProfile); if (pcmi != NULL && pcmi->isAbortScan() == FALSE) { // //User specified an invalid drive letter // if (!(pcmi->isValid())) { // dismiss the dialog first if ( pcmi->hAbortScanWnd ) { pcmi->bAbortScan = TRUE; // //Wait for scan thread to finish // WaitForSingleObject(pcmi->hAbortScanThread, INFINITE); pcmi->bAbortScan = FALSE; } TCHAR szWarningTitle[256]; TCHAR *pszWarning; pszWarning = SHFormatMessage( MSG_BAD_DRIVE_LETTER, szDrive ); LoadString(g_hInstance, IDS_TITLE, szWarningTitle, ARRAYSIZE(szWarningTitle)); MessageBox(NULL, pszWarning, szWarningTitle, MB_OK | MB_SETFOREGROUND); LocalFree(pszWarning); if (pcmi) { delete pcmi; pcmi = NULL; goto PromptForDisk; } } else { //Bring up the main dialog int nResult = DisplayCleanMgrProperties(NULL, (LPARAM)pcmi); if (nResult) { pcmi->dwUIFlags |= FLAG_SAVE_STATE; // //Need to purge the clients if we are NOT //in the SAGE settings mode. // if (!(dwFlags & FLAG_SAGESET) && !(dwFlags & FLAG_TUNEUP) && pcmi->bPurgeFiles) pcmi->purgeClients(); } } } // //Destroy the CleanupMgrInfo object // if (pcmi) { RetCode = pcmi->dwReturnCode; delete pcmi; pcmi = NULL; } } GetStartupInfo(&si); // If we were called on a low free disk space case, we want to inform the user of how much space remains, // and encourage them to free up space via Add/Remove programs until they reach 200MB free in the /LOWDISK // case, or 50MB free in the /VERYLOWDISK case. while (nDoAgain == IDYES) { BOOL bFinalTime = FALSE; nDoAgain = IDNO; // Bring up the Low Disk message box if (dwFlags & FLAG_LOWDISK) { GetDiskFreeSpaceEx(szDrive, &ulFreeBytesAvailable, &ulTotalNumberOfBytes, &ulTotalNumberOfFreeBytes); uiTotalFreeMB = (UINT) (ulTotalNumberOfFreeBytes.QuadPart / (NUM_BYTES_IN_MB)); if (uiTotalFreeMB < 200) { if (uiTotalFreeMB < 80) { LoadString(g_hInstance, IDS_LOWDISK_MESSAGE2, szInitialMessage, ARRAYSIZE(szInitialMessage)); } else { LoadString(g_hInstance, IDS_LOWDISK_MESSAGE, szInitialMessage, ARRAYSIZE(szInitialMessage)); } LoadString(g_hInstance, IDS_LOWDISK_CAPTION, szCaption, ARRAYSIZE(szCaption)); wsprintf(szFinalMessage, szInitialMessage, uiTotalFreeMB); nDoAgain = MessageBox(hwnd, szFinalMessage, szCaption, MB_YESNO | MB_ICONWARNING | MB_TOPMOST); } else { LoadString(g_hInstance, IDS_LOWDISK_SUCCESS_CAPTION, szCaption, ARRAYSIZE(szCaption)); LoadString(g_hInstance, IDS_LOWDISK_SUCCESS_MESSAGE, szInitialMessage, ARRAYSIZE(szInitialMessage)); wsprintf(szFinalMessage, szInitialMessage, uiTotalFreeMB); nDoAgain = MessageBox(hwnd, szFinalMessage, szCaption, MB_YESNO | MB_ICONQUESTION | MB_DEFBUTTON2 | MB_TOPMOST); bFinalTime = TRUE; } } else if (dwFlags & FLAG_VERYLOWDISK) { // Bring up the Very Low Disk message box GetDiskFreeSpaceEx(szDrive, &ulFreeBytesAvailable, &ulTotalNumberOfBytes, &ulTotalNumberOfFreeBytes); uiTotalFreeMB = (UINT) (ulTotalNumberOfFreeBytes.QuadPart / (NUM_BYTES_IN_MB)); if (uiTotalFreeMB < 50) { LoadString(g_hInstance, IDS_LOWDISK_CAPTION, szCaption, ARRAYSIZE(szCaption)); LoadString(g_hInstance, IDS_VERYLOWDISK_MESSAGE, szInitialMessage, ARRAYSIZE(szInitialMessage)); wsprintf(szFinalMessage, szInitialMessage, uiTotalFreeMB); nDoAgain = MessageBox(hwnd, szFinalMessage, szCaption, MB_YESNO | MB_ICONSTOP | MB_TOPMOST); } else { LoadString(g_hInstance, IDS_LOWDISK_SUCCESS_CAPTION, szCaption, ARRAYSIZE(szCaption)); LoadString(g_hInstance, IDS_LOWDISK_SUCCESS_MESSAGE, szInitialMessage, ARRAYSIZE(szInitialMessage)); wsprintf(szFinalMessage, szInitialMessage, uiTotalFreeMB); nDoAgain = MessageBox(hwnd, szFinalMessage, szCaption, MB_YESNO | MB_ICONQUESTION | MB_DEFBUTTON2 | MB_TOPMOST); bFinalTime = TRUE; } } if (nDoAgain == IDYES) { // Launch the Add/Remove Programs dialog lstrcpy(szInitialMessage, SZ_RUN_INSTALLED_PROGRAMS); if (CreateProcess(NULL, szInitialMessage, NULL, NULL, FALSE, NORMAL_PRIORITY_CLASS, NULL, NULL, &si, &pi)) { CloseHandle(pi.hProcess); CloseHandle(pi.hThread); // Only bother to wait around if it is not our final time through if (! bFinalTime) { WaitForARP(); } else { // If this was our final time through, then set the flag // to break out of the loop nDoAgain = IDNO; } } else { // If we cannot launch Add/Remove programs for some reason, we break // out of the loop nDoAgain = IDNO; } } } Cleanup_Exit: if (hEvent) { CloseHandle (hEvent); } CleanupMgrInfo::Unregister(); return RetCode; } STDAPI_(int) ModuleEntry(void) { int i; STARTUPINFOA si; LPTSTR pszCmdLine = GetCommandLine(); // // We don't want the "No disk in drive X:" requesters, so we set // the critical error mask such that calls will just silently fail // SetErrorMode(SEM_FAILCRITICALERRORS); if ( *pszCmdLine == TEXT('\"') ) { /* * Scan, and skip over, subsequent characters until * another double-quote or a null is encountered. */ while ( *++pszCmdLine && (*pszCmdLine != TEXT('\"')) ); /* * If we stopped on a double-quote (usual case), skip * over it. */ if ( *pszCmdLine == TEXT('\"') ) pszCmdLine++; } else { while (*pszCmdLine > TEXT(' ')) pszCmdLine++; } /* * Skip past any white space preceeding the second token. */ while (*pszCmdLine && (*pszCmdLine <= TEXT(' '))) { pszCmdLine++; } si.dwFlags = 0; GetStartupInfoA(&si); i = WinMainT(GetModuleHandle(NULL), NULL, pszCmdLine, si.dwFlags & STARTF_USESHOWWINDOW ? si.wShowWindow : SW_SHOWDEFAULT); // Since we now have a way for an extension to tell us when it is finished, // we will terminate all processes when the main thread goes away. return i; } void _cdecl main() { ModuleEntry(); }