How can an application delete itselfQ:We're very close to shipping our product. In order for us to get the "Designed for Windows® 95" logo, we must supply an application that uninstalls our software from the user's machine. We have developed an application called UNSETUP.EXE. But how can we make UNSETUP.EXE delete itself? Right now, we can remove all of the application's files and subdirectories but we cannot delete the program that deletes these files and the subdirectory containing it. How can we write a program that can delete itself? Anthony Spica
TCHAR szEXEPathname[_MAX_PATH]; GetModuleFileName(NULL, szEXEPathname, _MAX_PATH); DeleteFile(szEXEPathname); This code gets the full pathname of the currently running executable and then attempts to delete it by calling DeleteFile. Because Windows 95 and Windows NTª load EXE files into memory as memory-mapped files, the system opens the EXE file when the process is started and automatically closes the file when the process terminates. When DeleteFile is called, the system sees that the EXE file is still opened, DeleteFile returns FALSE, and a subsequent call to GetLastError returns 5, which identifies an access violation error (ERROR_ACCESS_DENIED). That straightforward technique won't work. The first thing that popped into my mind to solve this problem was the little-known MoveFileEx function: BOOL MoveFileEx(LPCTSTR lpExistingFileName, LPCTSTR lpNewFileName, DWORD dwFlags); Unfortunately, there are three problems with the MoveFileEx technique. It doesn't remove the subdirectory that contains the file. Also, the file is not deleted immediately. I know several people who've had Windows NT running continuously over a full year without ever rebooting! If these people install and uninstall several applications, their hard drives would soon start to be populated with a whole bunch of UNSETUP.EXE files. The third and final problem with MoveFileEx is the biggest: it's not implemented on Windows 95. Windows 95 does offer a technique that lets you move/delete a file when the user reboots. When Windows 95 boots, it runs an application called WININIT.EXE. This application looks for a file called WININIT.INI. If this file exists, WININIT.EXE then looks for a section within the file labeled Rename. [Rename] NUL=C:\Program Files\Win95ADG\UNSETUP.EXE NUL=C:\Program Files\SomeApp\UNSETUP.EXE C:\Calc.exe=C:\Windows\Calc.exe
The ReplaceFileOnReboot function shown in Figure 1 hides from you the implementation differences of moving/deleting a file on reboot between Windows 95 and Windows NT. The function first tries to call MoveFileEx. If this function fails, it then opens (or creates) a WININIT.INI file and properly adds entries to the Rename section. Of course, in a future version of Windows 95, Microsoft will fully support the MoveFileEx function. I have written the ReplaceFileOnReboot function so that it always calls MoveFileEx and will continue to work correctly on future versions of Windows 95.
int WINAPI WinMain (HINSTANCE hinstExe, HINSTANCE hinstExePrev, LPSTR lpszCmdLine, int nCmdShow) { TCHAR szEXEPathname[_MAX_PATH]; HANDLE hfile; GetModuleFileName(NULL, szEXEPathname, _MAX_PATH); hfile = CreateFile(szEXEPathname, GENERIC_READ, FILE_SHARE_READ, NULL,OPEN_EXISTING, FILE_FLAG_DELETE_ON_CLOSE, NULL); CloseHandle(hfile); return(0); }
After writing the small test application and experimenting with it, I soon discovered that this technique doesn't work at all. When I debugged the program under Windows 95, I saw that the call to CreateFile was succeeding but the system was just ignoring my request to open the file with delete-on-close access. When I closed the file and terminated the process, the executable file remained on my hard disk. I then went and tested my application under Windows NT. On Windows NT, the call to CreateFile fails returning INVALID_HANDLE_VALUE and GetLastError returns ERROR_ACCESS_DENIED. This is because Windows NT supports the ability to share a file with delete access. When I attempt to open a file specifying the FILE_FLAG_DELETE_ON_CLOSE flag, the system checks to see if the file has already been opened with the FILE_SHARE_DELETE flag. FILE_SHARE_DELETE is not documented in the Win32 SDK documentation, but it can be found in the Windows NT DDK's NTDDK.H file: #define FILE_SHARE_DELETE 0x00000004
BOOL FreeLibrary(HINSTANCE hinstDll);
My code to test this theory looks like this: int WINAPI WinMain (HINSTANCE hinstExe, HINSTANCE hinstExePrev, LPSTR lpszCmdLine, int nCmdShow) { TCHAR szEXEPathname[_MAX_PATH]; GetModuleFileName(NULL, szEXEPathname, _MAX_PATH); FreeLibrary(hinstExe); DeleteFile(szEXEPathname); return(0); }
This is a relatively easy problem to solve, especially if you remember my article "Load Your 32-bit DLL into Another Process's Address Space Using INJLIB," MSJ May 1994. (My Advanced Windows book, Microsoft Press, 1995 has the latest version of this code and some bug fixes I've made since the article appeared.) That article described a technique that allows you to dynamically inject code into another process's address space and execute this injected code. This injected code in the remote process does not come from a DLL or an EXE but instead comes from copying bytes from the local process's address space into the remote process's address space. Your problem requires a stripped-down version of the inject library code (see Figure 2).
After the data structure is initialized, the code in the memory block is called and is passed the address of the DELEXEINFO structure (which is on the thread's stack). The code in the memory block uses the information in the DELEXEINFO structure to call FreeLibrary to unload the EXE file, then DeleteFile to delete the EXE file (which will succeed now), and then call ExitProcess so that the process terminates. It would be a bad mistake to allow the code in the memory block to return because the calling code no longer exists after FreeLibrary unloads the EXE file. To use my function, all you have to do is include the DELEXE.H header file and add the DELEXE.C source file to your project. Then in your code, just place a call to my DeleteExecutable function: void WINAPI DeleteExecutable(DWORD dwExitCode, BOOL fRemoveDir);
This is my favorite solution because it deletes the EXE file immediately. But now I have some bad news: this method doesn't work on Windows NT. The reason is that Windows NT doesn't allow FreeLibrary to unload the process's EXE file. At first, I thought that Windows NT just sets a really big usage count on the EXE file module. I figured I could call FreeLibrary passing the hinstExe value continuously in a loop. Each call to FreeLibrary would decrement the EXE file's usage count until the EXE file is unloaded. I thought I could detect this because FreeLibrary will return FALSE if I pass an address that does not represent a module. The pseudocode would be something like this: while (FreeLibrary(hinstExe)) ; DeleteFile(szEXEPathname);
After all of this, I decided on a brute-force method that consistently works on both Windows 95 and Windows NT: batch files. Batch files have the unique ability to delete themselves. If you create a batch file containing this single command: del %0.bat
The batch file cannot be found. :Repeat del "C:\Win95ADG\UNSETUP.EXE" if exist "UNSETUP.EXE" goto Repeat rmdir "C:\Win95ADG" del "\DelUS.bat"
You'll notice that the DeleteExecutableBF function does a lot of work to run the batch file. The system always executes batch files inside a console window. There is no need for the user to know that you are running a batch file, so I set the STARTUPINFO's wShowWindow member to SW_HIDE. Another thing to be aware of is that the batch file polls for the existence of the executable file. In a preemptive, multithreaded environment, polling is an awful thing to do because you are causing the system to waste precious CPU cycles. But in batch files, there is no way to call WaitForSingleObject or WaitForMultipleObjects, so you must use a polling technique. However, you can adjust the priority class and relative thread priorities of the processes and threads involved with my solution. When the DeleteExecutableBF function calls CreateProcess to spawn the batch file, I specify both the CREATE_SUSPENDED and IDLE_PRIORITY_CLASS flags. The CREATE_SUSPENDED flag tells the system to create the console window (which will be invisible) to run the batch file, but the system is not allowed to schedule it any CPU time yet. The IDLE_PRIORITY_CLASS flag tells the system that this process should not be scheduled CPU time frequently. This way fewer CPU cycles will be wasted by the batch file while it polls for the executable file to terminate. After CreateProcess returns, I explicitly set its primary thread's relative thread priority to THREAD_PRIORITY_IDLE. This further reduces the amount of CPU time that will be wasted by the batch file. Then I set the executable thread's relative priority to time critical and the executable's priority class to high. This causes the system to schedule CPU time to the executable's thread very frequently so that it can terminate as soon as possible. I do not set the priority class to real time because this would interfere with threads that are responsible for processing various hardware events. After all of the thread priorities have been adjusted, I close the handle to the batch file's process and then resume the batch file's thread. This allows the system to schedule CPU time to execute the batch file, but not a lot of time to the batch file because its priority is so low. Finally, I close the handle to the batch file's thread and return from the DeleteExecutableBF function. At this point, you want the executable process to terminate as soon as possible so that the batch file will terminate, stop polling, and stop wasting CPU cycles.
|
||