To Coinstallers Hell and Back Again
Coinstallers (in Microsoft jargon) are executable units optionally included in a driver distribution package. A coinstaller allows the driver provider to interact with the 'black box' driver installation services that are integral part of Windows (Windows Device Management).
If you are a driver developer who is tackling the issue of creating a driver distribution package, you have come to the right place. If you are not... well, I recommend you to engage in more relaxing activities like saving whales or running a mid-size state.
In this article, I will elaborate on various issues that I had to resolve during the development of a coinstaller. Be advised, this not a guide to coinstallers development - this subject is thoroughly covered in the DDK. This article only complements the Microsoft documentation by highlighting some important issues and possible pitfalls. I recommend you to read the sections dealing with coinstaller development in the DDK before reading on.
My mission was to create a coinstaller to perform some Registry cleanup operation when a driver is uninstalled. Since I am new to this subject, I decided to start with a simple prototype device-coinstaller which logs all calls to it in a textual log file.
The basic architecture of a coinstaller is clean and simple: a user mode DLL with a a single entry point. The entry point is a single exported function with a well specified interface:
DWORD APIENTRY CoInstallerEntry (
IN DI_FUNCTION InstallFunction,
IN HDEVINFO DeviceInfoSet,
IN PSP_DEVINFO_DATA DeviceInfoData OPTIONAL,
IN OUT PCOINSTALLER_CONTEXT_DATA Context);
InstallFunction: a device installation function (DIF) which is the action being performed over the device node ('devnode').
DeviceInfoData: the devnode being handled.
Context: a context structure with some extra information and general purpose storage area.
Once the coinstaller is up and running, Windows will call it for most DIFs it handles for the devnode.
To deploy the compiled coinstaller, you need to include it in the driver installation package, together with the .sys, .inf, .cat and all the other files your package might contain. To make the coinstaller existence known to Windows, some extra entries are required in the .inf file.
Once all this technical stuff is behind you, you are home free. After the .inf file has been selected for installation, Windows will issue calls to your coinstaller, allowing you more control over the installation / uninstallation processes of your device. Simple enough. Or is it...
The DDK claims coinstallers are standard user mode DLLs. Well, I have Visual Studio 2005 that can produce such DLLs, so why not use it?
I have created a new C++ project without support for MFC, CLR, and all other three letter acronym Microsoft compilers piled up during the years. Just a good old Win32 DLL. I've added the required entry point, compiled, linked, and added it to my package. The device went in, the device went out, and no log file was to be seen anywhere.
Coinstaller rule number 1
Make sure you are exporting the entry point function name undecorated.
OK, my bad. I've fixed the code so the compiler will not decorate my function name (prefixing the entry point with the
APIENTRY macro did the trick), and this time verified the exported function name using a DLL inspection tool. I've also rechecked that the entry point registration in the .inf file matches the one the tool is showing me. A perfect match now.
The device went in again, and this time Windows did acknowledge my coinstaller, with a severe allergic reaction.
It appears that Windows has yet another security mechanism called DEP (Data Execution Prevention). Although I'm quite sure my coinstaller was innocent, DEP decided that it is a threat to world safety, engaged in illegal activity, and should be shot on the spot. I don't want to go into all the gory details, but DEP decided the Windows executable running my DLL is executing code from a compiled data section rather than a code section (this is common to 'stack overflow' attacks). This suspicious behavior was punished by a message box and immediate crash of the installation process. The only problem is that all my coinstaller ever tried to do is to write a few lines into a text file located in a directory shared between all users (no security issues here).
Coinstaller rule number 2
Use the DDK Nmake compiler to build you coinstaller DLL.
It appears that somehow the DLLs Visual Studio 2005 produces are not binary compatible with Windows Device Management. Oh well, we have Nmake. If any of you savvy readers can suggest a way to configure the Visual Studio 2005 IDE to produce a compatible DLL, this would be helpful.
Fun with Nmake
I have always loved makefile's. I'm so bored at my work and have so much free time I don't care spending half a day composing a makefile for a single function DLL. Manually configuring everything is fun and constructive.
Coinstaller rule number 3
Include the following in your 'sources' file (remove the lines starting with //):
C_DEFINES= $(C_DEFINES) -DUNICODE
Before we go on, take notice of...
Coinstaller rule number 4
Make sure there are no spaces in the path where your sources are.
Nmake will not compile anything if you do have spaces (but of course, it will not complain anything is wrong). Other DOS legacy issues will be presented shortly.
So by now, you have built a DEP friendly coinstaller. Say you partly ignored rule number 3 and decided to name your DLL MyDeviceXpCoinst.dll. This sounds reasonable. The problem is this is not. We have reached...
Coinstaller rule number 5
The name of the coinstaller's DLL must be of 8 or less characters.
Puzzled? Did you think that NTFS based Windows XP with SP2 can handle the unthinkable 16 characters name you have selected for your coinstaller DLL? Well, it can't. Of course, all will work fine during the installation of the device. The coinstaller is called and the log is created. The problem manifests itself when you uninstall the driver - Windows suddenly gives the coinstaller a cold shoulder, and refuses to call it with DIF_REMOVE. Renaming your DLL to something more readable like MyDvXpCI.dll will solve that. The only clue for this solution is the DDK's Toaster coinstaller example. A comfy 8 characters name is used for the DLL...
Coinstaller rule number 5 - remark 1
Don't forget that inside every Windows core there is a little piece of enthusiastic, 100 years old DOS code just waiting for its 15 milliseconds of CPU time.
By now, you should have a coinstaller that actually works, but there are few more issues you should consider.
Tips and Points to Consider
- You can inspect and control the trace of Windows Device Manager. Read the following article by Microsoft: "Troubleshooting Device Installation with the SetupAPI Log File". This will greatly improve your ability to debug your coinstaller.
DIF_REMOVE post-processing, most of the driver Registry entries have had already been deleted. Cache these values during pre-processing if they are required during post-processing.
- You should define the "Unicode" flag in the sources file for a Unicode build. To signal the compiler to treat a constant string as a Unicode string, use the
TEXT("abc") macro. Do not use any other alternative macros (
_T("abc") and so on) - they don't seem to work well.
- The co-installer does not have full permissions during uninstallation. For example, it cannot access files located in system directories. If you have a log, place it in some public access directory such as %ALLUSERSPROFILE%\Application Data.
- Notice that files copied from the distribution media to the system directory (among them, the coinstaller) inherit the file permissions of the system directory. This may become an issue if a non-privileged user should ever access these files. You can override this using security descriptors in the .inf
CopyFiles section (see DDK).
I've composed this article to assist any programmer out there that develops a coinstaller. I've tried to keep the article concise and to the point as much as possible. Unfortunately, I'm unable to provide any working code sample. The main reason for that is I cannot publish any code I've composed for my employer. Another reason is that I believe this is unnecessary due to the documentation provided in the DDK. If you encounter any of the problems I had, I hope you will benefit from my solutions. If you stumble upon other problems in this subject, feel free to complement my article.