When I wrote my Windows program What To Do, I knew I was going write my own install and uninstall code. I wanted to have full control over what users saw from the moment they began their install to the last thing they saw during a possible uninstall. This article covers some of the techniques I found most useful.
Auto Uninstall
The most challenging part of writing the uninstaller was figuring out how to make it delete itself after it was done removing program files and directories. I wanted it to work on everything from Windows 95 to Windows XP, without making the user download any additional components. I searched on the Web and found several references to self-deleting executables, but all the proposed solutions had problems. Most only worked with certain versions of Windows. Others modified thread priorities in ways that could cause timing problems. Some caused extra windows or error messages to appear. I figured there must be a better solution. I discovered that you can use a self-deleting DLL to create a self-deleting executable with none of the limitations of previous solutions.
The Windows rundll32.exe Utility
How can we execute code in a DLL without creating an EXE to load and call it? Every Windows version beginning with Windows 95 has shipped with a system utility called "rundll32.exe." It allows you to execute any function exported from a DLL. You use it like this:
rundll32.exe DllName,ExportedName args
ExportedName is the exported name of the function in the DLL. When writing a DLL to use with rundll32, I declare the function to be exported like this:
extern "C" __declspec(dllexport)
void CALLBACK FunctionName (
HWND hwnd,
HINSTANCE hInstance,
LPTSTR lpCmdLine,
int nCmdShow
)
{ ... }
The rundll32.exe documentation lists the function arguments, but empirically, I've found that the only value you can rely on is lpCmdLine, which receives the value of args passed when you run rundll32.exe. Using __declspec(dllexport) causes the function to be exported, and using extern"C" makes the exported name _FunctionName@16 (the function name gets mangled to include the size of the function arguments).
rundll32.exe loads the specified DLL and then calls the exported function passing as args the value of lpCmdLine. The official documentation for rundll32.exe can be found at http://msdn.microsoft.com/library/default.asp?url=/library/en-us/tools/tools/rundll32.asp.
A Self-Deleting DLL
Listing 1 shows the source code for a DLL that will delete a file and then delete itself. DllMain will be called when the DLL is first loaded, and there it records the module handle, which will be used later to get the DLL's filename.
In the function MagicDel, lpCmdLine will be the name of the executable file that the DLL should delete (for example, the uninstaller's filename). Deleting it is easy sleep for a while to allow the executable's process time to exit and then call DeleteFile. To get fancier, pass the handle of the executable's process to MagicDel and wait on it before calling DeleteFile.
Making the DLL delete itself is a little trickier. rundll32 calls LoadModule to load the DLL into its address space. If the DLL function was allowed to return, rundll32 would just exit, causing the DLL to be released (but not deleted). Instead of letting that happen, we want to execute the following code:
FreeLibrary(DLL module handle);
DeleteFile(DLL filename);
ExitProcess(0);
The function can't just make this sequence of calls directly because FreeLibary would make the code page invalid. To work around this, the function pushes an equivalent set of assembler instructions onto the stack and then begins executing them with a ret instruction. The final call to ExitProcess prevents the process from trying to run any more of the code. I created the assembly code block using the same technique that Gary Nebbit presented in a former Windows Developer Network "Tech Tip" (see http://www.windevnet.com/documents/s=7257/wdj0109g/).
If you build the DLL using Visual Studio with default options, the resulting binary is about 40K. By removing the unused C runtime code, you can shrink it to 2.5K (see the sidebar "Shrinking the DLL").
A Self-Deleting Executable
An executable can delete itself by storing a copy of the self-deleting DLL as a resource, then recreating it and launching a process running rundll32.exe to do the deletion.
Listing 2 shows the header and resource files used to store the DLL as a resource. Resource type values 256 and above are available for user-defined types. Alternately, the DLL binary could be stored directly in source as an array of bytes.
Listing 3 shows the rest of the code for the executable. WriteResourceToFile accesses the binary resource so that it can recreate the DLL on disk. The Windows Resource APIs provide a pointer to the raw data.
SelfDelete recreates the DLL and then builds the command line to launch rundll32.exe. The command line will look like this:
path\rundll32.exe magicdel.dll,_MagicDel@16 path\executableName
rundll32.exe resides in either the Windows or System directory, so SelfDelete tests to find the right location. When CreateProcess is called to execute the command line, it sets the STARTF_FORCEOFFFEEDBACK flag to prevent Windows from displaying the busy cursor while rundll32.exe runs. That way, there will be no indication to the user that a new process is running. After this new process exits, both the DLL and the original executable are gone.
To make a self-deleting executable that does not depend on the C runtime library DLL, the executable must statically link in the runtime library code. Change the project options under C/C++/Code Generation to set "Runtime Library" to either "Single Threaded" or "Multi Threaded" (or any value that does not include the DLL).
This self-deleting technique works reliably with all Windows versions. The actual uninstaller for my Windows program first copies itself to the Windows temp directory so that it can remove all program files and directories. Finally, it uses the self-deleting DLL to delete itself.
Install
I knew how I wanted my installation program to work. The user would just download my executable over the Internet and run it. I wanted the file to be compressed for faster downloading, and I wanted the first thing the user saw to be my program instead of some other company's installer. Fortunately, Windows provided just the support I needed.
First, I created an interactive setup program that displays a license agreement, prompts the user for install choices, copies files, and does the rest of the setup work. Then I stored a compressed version of that setup program as a binary resource inside the installer. All the installer has to do is write the binary resource to disk, decompress it, and launch it as a new process. Storing and writing the binary resource was easy I used the aforementioned binary resource handling code.
Every Windows platform since Windows 95 has had an API for decompressing files LZCopy. Listing 4 shows the source for the installer that makes use of it. The compressed setup program is stored as a binary resource, just as before. DecompressFile shows how to use the LZCopy API. The installer recreates the AppSetup.exe, and then runs it. To build successfully, the installer executable needs to add lz32.lib to the project options under Linker/Input/Additional Dependencies. And as before, statically link in the runtime library code by changing the project options under C/C++/Code Generation to set "Runtime Library" to either "Single Threaded" or "Multi Threaded." Note that the installer doesn't have to wait for the setup program to complete because AppSetup.exe can use the self-deleting DLL to delete itself when it's done.
The trickiest part of using LZCopy is that it seems to only decompress files that have been compressed with the Microsoft compression utility compress.exe, a command-line utility that used to ship in some Microsoft SDKs. The only official source for it now appears to be as part of a self-extracting archive available at ftp://ftp.microsoft.com/ softlib/mslfiles/CP0982.EXE. If you download and run that, it will unpack compress.exe (as well as several other files that you can ignore and delete). You use it like this:
compress SourceName DestinationName
Using the decompression support built into all versions of Windows made it easy to write the installer. Note that all Windows versions include the utility expand.exe, which allows you to decompress files from the command line.
Complete Control
Using a self-deleting DLL, binary resources, and the decompression support
built into Windows can help you create your own installer and uninstaller. And
that allows you to control every facet of the experience from the moment users
begin installing your software.
w::d
Alex Tilles is the creator of What To Do, a to-do list manager that adapts to the way you organize. He was also a development lead at Microsoft for several years in the Systems and Internal Tools groups. He can be reached at [email protected].