windows-nt/Source/XPSP1/NT/shell/applets/cleanup/cleanmgr/cleanmgr.cpp
2020-09-26 16:20:57 +08:00

761 lines
21 KiB
C++

/*
**------------------------------------------------------------------------------
** 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();
}