By Tim Roberts, community contributor
Installers. The mere word can strike an icy cold stab of fear into the hearts of driver developers. It’s not clear why that should be the case; after all, installers run in the relatively safe comfort of user mode, where the APIs are well developed, there are few sharp edges, and the penalty for mistakes is smaller. However, I understand the feeling. I avoided installers as if they were infectious, until I finally decided to dig in and see what the fuss was about.
In this article, I’m going to try to alleviate any fears you may have about driver installers, and describe for you a relatively simple but complete mechanism for installing drivers. One of the problems with installers is that there are a lot of options. I’m not claiming my method is the best one, but it is one that has worked for me for many years.
Which Driver Type?
There are a number of different types of drivers, and those driver types require different types of installers. A partial list might include:
- PnP drivers
- Device filter drivers
- Class filter drivers
- Legacy drivers
PnP drivers require an INF file. That’s the most common type of driver (I assert without evidence), so that’s the type I’m going to focus on in this article. I will make some comments about class filter drivers and legacy drivers towards the end.
There are several distinct steps required in order to get a driver package installed and operational. In this article, I’m going to divide the process into three steps. To make things more interesting, I will discuss those steps in reverse order.
Installation
The final part of the driver installation process is that part we usually think of as “installation”. It is the process of copying files into their operational locations (\Windows\Inf, \Windows\System32, and \Windows\System32\Drivers), creating services, making registry entries, loading the driver, and calling the driver’s initial entry points.
For PnP drivers, this part of the installation process is handled entirely by Device Manager, as directed by the INF file. When some bus driver reports the creation of a PDO to the PnP Manager, and that PDO has a device type that is not already known to the system, Device Manager asks for its hardware ID, and goes on a treasure hunt to find a driver to handle it. It first looks through all of the INF files that have previously been installed in the \Windows\Inf directory. If there is no match there, it searches through the driver store, which contains the pre-installed driver packages.
When Device Manager finds a matching driver package, it “executes” the INF file, copying files into the System32 directory, creating services, registering DLLs, making registry entries, running coinstallers, then loading the driver into memory and executing it.
In order for Device Manager to do its job, the driver package has to be pre-installed into the driver store for the PnP Manager to find. That is the next stop in our backwards tour of the installation process.
Pre-Installation
Pre-installation is the process of copying a driver package to the “driver store”. On Windows XP, the driver store is in \Windows\System32\Drvstore. On Vista and later systems, the driver store is in \Windows\System32\DriverStore.
The key API in this process is SetupCopyOEMInf. You can call SetupCopyOEMInf from your own application, or you can use “devcon dp_add”. However, given that you can’t ship the “devcon” utility with your product, the most convenient option is to use the DPInst tool that is included in the WDK in redist\difx\DPInst. You are allowed to redistribute the DPInst executables in your own installer packages.
DPInst is a narrowly focused application. In its simplest use case, it displays a welcome dialog, then calls SetupCopyOEMInf on every INF file it finds in the same directory as the executable. It then calls UpdateDriverForPlugAndPlayDevices, which forces Device Manager to take another look around for devices that need drivers. In some cases, this is exactly what you need. If you need to do something a bit different, DPInst’s activities can be customized through the use of an XML file. For example, you can have DPInst display an end-user license agreement, you can customize the welcome and finish messages, you can customize the icon, and you can add a bitmap to the wizard dialog it displays. DPInst even registers an uninstaller for you, listed in Control Panel as “Windows Driver Package” with your hardware’s description. It’s important to note that you must use the 64-bit DPInst on a 64-bit system and a 32-bit DPInst on a 32-bit system.
The DPInst.xml file needs to be in the same directory as the DPInst executable. See the example in Figure 1.
<?xml version="1.0" ?> <dpinst> <!-- English --> <language code="0x0409"> <dpinstTitle>XYZ Sonic Screwdriver Driver Installation</dpinstTitle> <welcomeTitle>Welcome</welcomeTitle> <welcomeIntro>Welcome to the XYZ Sonic Screwdriver Driver Setup program. This program will install the XYZ Sonic Screwdriver Drivers on your computer. </welcomeIntro> <installHeaderTitle>Please wait while Setup finishes installing the driver.</installHeaderTitle> <finishTitle>Setup complete</finishTitle> <finishText>Setup has finished installing the Sonic Screwdriver Drivers.</finishText> </language> <legacyMode/> <forceIfDriverIsNotBetter/> <deleteBinaries/> <suppressEulaPage/> <enableNotListedLanguages/> <suppressAddRemovePrograms/> </dpinst>
Figure 1
At the bottom, after the <language> block, you’ll see a list of standalone tags selecting various DPInst options. All of these options have equivalent command-line switches, if you prefer to go that route. The <legacyMode/> switch is important; without it, DPInst will not handle non-WHQL-signed packages. The <deleteBinaries/> options tells the DPInst uninstaller to remove any binaries it created when it installed. The <suppressAddRemovePrograms/> switch tells DPInst not to create an uninstaller at all. I do that only because I have my own installer handle that step (which you will see shortly), and it confused users to have two entries.
For DPInst to do its job, it needs to be in the same directory as the root of your driver package (along with DPInst.xml). For testing, it’s easy enough to copy your driver package onto a test system and run DPinst by hand. You might even be able to gather everything up into a zip file and distribute that to members of your own team. However, it should be clear that what has been described here does not provide the installation experience that most end users want. Users expect to have a single executable that does the whole job. For that, we move back in time one more step.
Pre-Pre-Installation
Pre-pre-installation is what I call the step of getting your driver package (along with DPInst) loaded on to a client’s computer, and then running DPInst to do a pre-installation. In the spirit of full disclosure, you need to know that the term “pre-pre-installation” is one that I made up for this article. Don’t go searching for it.
The pre-pre-installation process is just a normal application-style installation, and can be done using traditional installer tools. Many people use WiX, which is an XML-based utility that builds Microsoft Installer (MSI) files. Some people use InstallShield. I happen to use NSIS, the Nullsoft Scriptable Installer System, at http://nsis.sourceforge.net. It is an open source command-line tool that compiles a script into an executable.
Whichever tool you use, your pre-pre-installer has two basic jobs: copy the driver package onto disk, and run DPInst. In this section, I will show you a simple NSIS script to preload and pre-install a driver package.
One of the things that makes the pre-pre-install script tricky is the 32-bit/64-bit problem. You need to decide whether you want two separate installers (one for each bittedness), or one installer with both drivers. For mostly historical reasons, I tend to build my driver packages with one INF and two subdirectories. However, the samples in the WDK have all gone the other direction, where you get one driver package, complete with INF, per architecture. The script I’m about to demonstrate assumes this layout.
The NSIS script language is an odd beast, somewhere between batch files and the Basic language. It has some rather primitive constructs that derive from being a single-pass compilation process. I’ll point those out when they come up.
See Figure 2 for a complete NSIS script to create a moderately-featured installer for fictional “Sonic Screwdriver” from the XYZ Company.
Name "XYZ Sonic Screwdriver Driver" Caption "XYZ Sonic Screwdriver Driver" InstallDir "$PROGRAMFILES\XYZ\SonicScrewdriver" DirText "This will install the XYZ Sonic Screwdriver drivers on your computer. You may choose a directory to hold the driver files:" OutFile "setup_ssd.exe" ; The stuff to install Section "Driver" ; If the uninstall key exists, run DPInst to uninstall. ReadRegStr $0 HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\xyzsonic" \ "UninstallString" StrCmp $0 "" after ; We always put double-quotes in the path, so $0 starts with one. StrCpy $1 $0 loop: IntOp $1 $1 - 1 StrCpy $2 $0 1 $1 StrCmp $2 "\" 0 loop StrCpy $0 $0 $1 StrCpy $0 '$0\DPInst.exe" /q /u $0\xyzsonic.inf"' DetailPrint "Uninstalling old driver" DetailPrint $0 ExecWait $0 after: ; Copy the files. SetShellVarContext all SetOutPath $INSTDIR IfFileExists $WINDIR\SysWOW64\*.* 0 else1 File "\ddk\7600\redist\DIFx\DPInst\EngMui\amd64\DPInst.exe" File "..\Lag\amd64\xyzsonic.sys" File "..\Lag\amd64\xyzsonic.inf" File "..\Lag\amd64\xyzsonic.cat" File "..\Lag\amd64\WdfCoInstaller01009.dll" Goto endif1 else1: File "\ddk\7600\redist\DIFx\DPInst\EngMui\x86\DPInst.exe" File "..\Lag\x86\xyzsonic.sys" File "..\Lag\x86\xyzsonic.inf" File "..\Lag\x86\xyzsonic.cat" File "..\Lag\x86\WdfCoInstaller01009.dll" endif1: File "DPInst.xml" ; Install the driver. ExecWait '"$INSTDIR\DPInst.exe" /lm' ; Create the uninstaller. WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\xyzsonic" \ "DisplayName" "XYZ Sonic Screwdriver" WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\xyzsonic" \ "UninstallString" '"$INSTDIR\Uninstall.exe"' WriteUninstaller "Uninstall.exe" SectionEnd ; end the section Section "Uninstall" SetShellVarContext all DeleteRegKey HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\xyzsonic" ExecWait '"$INSTDIR\DPInst.exe" /u xyzsonic.inf' Delete $INSTDIR\DPInst.exe Delete $INSTDIR\DPInst.xml Delete $INSTDIR\xyzsonic.sys Delete $INSTDIR\xyzsonic.inf Delete $INSTDIR\xyzsonic.cat Delete $INSTDIR\WdfCoInstaller01009.dll Delete $INSTDIR\Uninstall.exe RMDir $INSTDIR SectionEnd
Figure 2
The script begins with a set of global declarations, followed by a set of sections, each of which has instructions for a particular part of the installer. It is possible to create a script with multiple selections that presents a menu to the user, allowing them to choose which subcomponents to include. I have not done that here.
The initial section declares the installer name, the dialog caption, and the default install directory name (in this case, \Program Files\XYZ\SonicScrewdriver). $PROGRAMFILES is an NSIS variable that expands to the proper Program Files directory. (Because NSIS creates a 32-bit executable, this actually expands to “Program Files (x86)” on a 64-bit system.) The OutFile statement then defines the name of the executable that NSIS will create.
After that, we start the main installation section. The first thing I do here is check the registry to see if a previous run of this installer registered an uninstaller. If it did, I run the uninstaller to clear out any previous instances of the driver package. End-users expect an installer to be able to upgrade itself in place; I discovered that repeated instructions to manually run the uninstaller first were futile.
After that, we begin the process of copying files. The SetOutPath statement tells the installer where the files should go. We could have additional SetOutPath statements to create a directory tree. For example, when I have a driver package that includes both 32-bit and 64-bit drivers in a single package, I use statements similar to the following to place the files accordingly:
SetOutPath $INSTDIR\32 File “drv\32\driver.sys” File “drv\32\helper.dll” … other 32-bit files … SetOutPath $INSTDIR\64 File “drv\64\driver.sys” File “drv\64\helper.dll” … other 64-bit files …
One of the tricky things to keep track of is whether a particular directive applies to the build machine or the eventual client machine. For this particular example, I’m assuming we have separate driver packages for 32-bit and 64-bit. In that case, I only need to copy one package onto any given client. The IfFileExists statement checks to see whether a SysWOW64 directory exists; if it does, then this is a 64-bit machine, and so I load the 64-bit driver package. The conditional directives in NSIS are not modern structured programming constructs. The way ”if” works in NSIS is if the condition is true (in this case, if $WINDOWS\SysWOW64\* exists), control jumps to the statement given by the 3rd parameter. If the condition is false, control jumps to the statement given by the 4th parameter. The statement can be specified as a signed integer (+3 meaning “skip to the 3rd line following this one), or with a named label. In this case, the 0 means to fall through to the next line if t he directory exists, otherwise jump to the label else1.
Each File directive identifies a file to be copied into the current SetOutPath directory. The directive specifies the file’s location on the disk where the compilation is being done. The file will keep the same name, although you can specify a new name if you wish.
After copying the files for the driver package and the appropriate WDF co-installer, I add the DPInst.xml file to the package. That completes the file list.
The next step is to run DPInst itself (on the client system). This is accomplished by the ExecWait statement. As the name implies, the script will not continue until the DPInst command completes.
After that, all that’s left is to create and register an uninstaller. We specify the uninstaller’s name with the WriteUninstaller directive, and we specify the actions it has to take in the separate “Uninstall” section. All we have to do is run DPInst with the “/u” parameter, to have it undo all the magic it did, and then delete any files and directories we created, and any registry keys we created.
This script can be extended in an infinite number of ways. The NSIS package provides a large number of samples and plugins to perform additional functions, most of which don’t really apply in the driver world. It is possible to pass parameters to the “makensis” command line, similar to pre-processor variables on the C compiler command line. For example, I have one script that can build either a “checked” or a “free” installer, based on the contents of a command-line parameter.
Other Driver Types
As I noted at the beginning, the above description applies to PnP drivers. Other types of drivers have different requirements. Legacy drivers and class filter drivers, for example, can both be installed using an INF with a [DefaultInstall] section. In that case, you can use a very similar NSIS script to copy a driver package without DPInst, and then execute RunDll32 to run that section:
rundll32 setupapi.dll,InstallHinf DefaultInstall 132 c:\install\mydrv.inf
However, this mechanism has the huge disadvantage that it does not run the WDF co-installer. This is a non-PnP installation, and co-installers only run for a PnP installation. This problem was described in The NT Insider back in March of 2008 (see the article http://www.osronline.com/article.cfm?article=446). If your driver package needs to run on a system that might have an older version of KMDF than the one you need, you will have to supply an application. Device filter drivers also generally require a custom application, because it is necessary to identify the exact hardware IDs to be filtered. Even in these cases, however, one could have an NSIS script copy the driver file plus the installer application, and then run the installer.
Conclusion
I hope this article has alleviated some of the horror you might have felt towards driver installations. Like most computer tasks, installation becomes much more manageable when you chop it up into smaller pieces and tackle those pieces.
Tim Roberts has been wrangling computers from mainframes to micros for 40 years, and has been part of the Windows driver world since Windows 3.0. An 18-year MVP, Tim is a principal in P&B, a consulting company providing custom hardware and software solutions to difficult computing situations. Tim can be reached at timr@probo.com.