Recently I found myself stuck with the unpleasant task of making a custom USB device work under Windows. The USB device in question would be used in a factory environment and it had the ability to have its firmware updated automatically. During the update process, the device would be enumerated with a different vendor and product ID, and required a different device driver. The biggest problem was that Windows has the nasty habit of bothering the user more than it should. I didn't want the operators to be stuck with a driver installation wizard during production, so I needed a way for a technician to pre-install the drivers, both those used for the application and those needed during the firmware update process.
Anyone who knows me knows that Windows is far from my favorite environment to work in. The Windows command line and batch file environment must be one of the most rudimentary and opaque scripting environments in existence. So to be clear: I don't claim to be an expert on any of this stuff, far from it. I am just documenting some of the things I learned during this painful process, in case it might be of help to some other poor individual stuck with a similar task. :)
I first decided that I should just find a way to pre-install the drivers into Windows, and things would work automatically and without user intervention after that. I had a set of .inf, .sys and .dll files, and I could point the driver wizard to the correct place to find them when I plugged in the device or switched it to firmware update mode. But I did not want the technician to have to do this. I wanted him to be able to install the drivers by running a simple wizard, and then they would be automatically loaded when needed. After some searching, I found out about Microsoft's DPInst program, which seemed to do exactly what I wanted. With this piece of software, you write an XML file describing where your driver files are and it will generate the wizard screens to install the drivers. Cool.
Only, it turned out it didn't do what I wanted after all. After the drivers were installed, Windows still didn't just use them when the device was plugged in, but required a Next/Next/Next sequence the first time. Only after that was done, would it behave the way I wanted it to the following times. (If anyone knows how to make Windows just use the driver immediately, without requiring user interaction the first time, I would greatly appreciate the info!)
Still, DPInst solved part of the problem. The technician would not have to go hunt for the driver files, he could just have Windows find them automatically. I decided that in addition to this, I had to make a program that would switch the device into its different modes after the drivers were installed, so the technician could go through the initial Next/Next/Next sequences as part of the setup procedure. After this was done, the device would then transparently switch and the factory operators would be left alone. I decided to do this job using a batch file, some small executables for managing and reprogramming the device, and the Wizard's Apprentice software to put up some friendly screens.
The Wizard's Apprentice has an interesting way of returning user input to the calling batch file. It will generate a temporary file with content like this:
set waoutput= ...some value... set waoutnum= ...some other value...
This file can then be called from your own batch file, which will set the waoutput and waoutnum variables to the ones defined in the batch file.
I decided I could use a similar system to get data I needed from the tools I was using to manage and program the USB device. The problem was that these tools, unlike the Wizard's Apprentice tool, weren't intended to return values to a batch file, so they didn't output nice set variable=value statements. So I figured I could do this instead:
echo set variable=> tempfile mytool parameters >> tempfile call tempfile
Only, instead of:
this produced a tempfile that contained:
set variable= value
This isn't valid code to be called from your batch file. I found out that Windows' brain-dead implementation of echo doesn't provide any way to suppress a linefeed. You can download replacement echo.exe files in various places that support this basic option, but I didn't really want to go there. I found another solution: the caret ^. In many programming languages, one can use the backslash \ at the end of a line to indicate the statement continues on the next line. Since Windows uses the backslash \ as path delimiter (one of Microsoft's many dumb ideas), they use the caret ^ as an escape character instead. So in a batch file you use the caret ^ for the same purpose.
So I changed my code to:
echo set variable=^^> tempfile mytool parameters >> tempfile call tempfile
Note the double caret ^^. We want to print a single caret ^ to the file, but since the caret ^ is the escape character, in the batch file we need to escape it with another caret ^ to indicate we want to output the actual character. This code produces a tempfile that contains:
set variable=^ value
On calling this file, the batch command interpreter sees the caret at the end of the line and it continues the statement on the next line, producing a valid set command.
This is not the cleanest solution for retrieving data from other programs though. I only mention it because having a batch statement generated from the output of external programs and split over multiple lines might be useful in some particular circumstances. A better way to assign the output of a program to a variable is this:
mytool parameters > tempfile set /p variable= < tempfile
After the project was already implemented I found yet another way. This method avoids using a temporary file, which might be very useful if there is no good place where a file can be written:
for /f %%a in ('mytool parameters') do set variable=%%a
A little explanation is in order here. The for command is kind of unusual in that it seems to be playing in another league than most other batch commands. Most batch commands are very rudimentary, missing even the most basic options (like echo missing the option to suppress newlines). The for command on the other hand is quite powerful. The details are beyond the scope of this article, a great resource for finding out more about it (plus other batch commands and even other command line languages) can be found at ss64.com. What I'll mention here is that this statement executes the command between single quotes and then parses its output and saves parsed out data to variables. The case shown in my example is pretty much the simplest parser possible (just assigns all the output to a variable), the for command is capable of parsing much more complicated output to extract information from it.
I hope some of this post may be helpful to someone not familiar with the details of Windows drivers and batch programming, like I was. As for myself, I will resume trying to stay away from it as much as possible. :)