This is the best method I have come up with recently:
In the build options, select create batch file.
When you initiate a build from the IDE, a batch file along with several text files are created based on the options set in the IDE. You need to track these IDE generated files in source control:
Then foo.bat can be launched from a build script.
While this does create extra files that need to tracked in source control if you want to build reliably from the generated batch file, it does remove the need to rely on the Keil project file (foo.uvproj) and the IDE. I find it easier to compare differences, and thus track changes, to the generated text files (*.__i) that contain compiler flags than to the .uvproj file. Additionally, the batch file calls the various tools, armasm, armcc, armlink, directly. This gives you the direct output of each of those steps as well as a seemingly better potential for migrating a project to a different tool chain in the future if necessary.
I realize this answer sounds a lot like my original question, but I genuinely don't know of a better way of running a scripted build with Keil's tools. I asked to see what might come up from others. I don't completely disagree with the answer from @digikata, but I prefer to have compiler flags and the memory map in an easier format for tracking and to use more unix-style tools for compilation rather than launching an all-in-one compilation with the IDE. I think the all-in-one compilation from the IDE works well at my workstation, but not for the build server.
EDIT: The build server runs on Windows Server 2003. I must confess that I have relented to using the IDE command line interface rather than a batch file. This just became too difficult to manage.