My project at work has historically been a small component in a much larger pile of APIs and applications all tied to the same build system, a Windows scheduled task and a pile of incoherent batch scripts. Since then, we have grown up into our own small pile of APIs and connected applications and the time to divorce the parent project has finally come.

In order to make the transition as clean as possible, I've been removing dependencies, reorganising the project structure and cleaning up our build scripts. Our Linux builds have always been isolated from the rest of the system so getting that to work in isolation was easy. The Windows builds on the other hand, took a lot more work.

I needed to write a new script to build the Visual Studio solutions on a new continuous integration server. This is largely because the server can't really handle Visual Studio 2003 without a little help. The script started out simple enough, using the command line arguments to set a few variables, select a version of Visual Studio and a build configuration, then eventually calling devenv with the required parameters. The batch language is primitive and buggy so it took a few goes to get this right. Eventually, I got it working on my machine, so I commited the script and watched it die on the CI server.

The Visual Studio installation wasn't found in Program Files. Well, this is a 64 bit server, so I queried %PROCESSOR_ARCHITECTURE% and had an alternate path on the 64 bit machine. Searching around the topic, you will sometimes see people checking specifically for %ProgramFiles(x86)% or even if the raw path C:\Program Files (x86) exists. I did something like this:

rem select program files directory
if /I "%PROCESSOR_ARCHITECTURE%" EQU "AMD64" (
  echo building on 64-bit Windows
  set _VS7DIR_="C:\Program Files (x86)\Microsoft Visual Studio .NET 7.1\Common7\IDE"
  set _VS8DIR_="C:\Program Files (x86)\Microsoft Visual Studio 8\Common7\IDE"
) else (
  echo building on 32-bit Windows
  set _VS7DIR_="C:\Program Files\Microsoft Visual Studio .NET 7.1\Common7\IDE"
  set _VS8DIR_="C:\Program Files\Microsoft Visual Studio 8\Common7\IDE"
)

This threw up errors in the script parser, saying things like '\Microsoft' not expected at this time. After a bit of searching around, I found that the parser was interpreting the closing parenthesis in Program Files (x86) as the end of the IF statement. This behaviour is obviously crazy, but nobody is ever going to update such an ancient and terrible scripting language, so we just have to accept that nesting parentheses are never going to work properly in a batch file and work around it.

In the end, the mighty goto solved the problem:

rem select program files directory
if /I "%PROCESSOR_ARCHITECTURE%" EQU "AMD64" goto progfiles_win64
:progfiles_win32
echo building on 32-bit Windows
set _VS7DIR_="C:\Program Files\Microsoft Visual Studio .NET 7.1\Common7\IDE"
set _VS8DIR_="C:\Program Files\Microsoft Visual Studio 8\Common7\IDE"
goto progfiles_done

:progfiles_win64
echo building on 64-bit Windows
set _VS7DIR_="C:\Program Files (x86)\Microsoft Visual Studio .NET 7.1\Common7\IDE"
set _VS8DIR_="C:\Program Files (x86)\Microsoft Visual Studio 8\Common7\IDE"
goto progfiles_done

:progfiles_done

Parsing command line options to correctly handle VC7 and VC8 was much the same, but more complicated:

rem establish paths and defaults
set _VS7DIR_=%_VS7DIR_:"="""%
set _VS8DIR_=%_VS8DIR_:"="""%
set _VSDIR_=%_VS7DIR_%
set _VC7SLN_=MyProject_71.sln
set _VC8SLN_=MyProject_80.sln
set _SLN_=%_VC7SLN_%
set _OPTS_=/Build
set _MODE_=Debug

rem command line option parsing
:begin_opts
if /I "%1" EQU "" goto done_opts
if /I "%1" EQU "clean" (set _OPTS_=/Clean) && goto next_opts
if /I "%1" EQU "build" (set _OPTS_=/Build) && goto next_opts
if /I "%1" EQU "rebuild" (set _OPTS_=/Rebuild) && goto next_opts
if /I "%1" EQU "release" (set _MODE_=Release) && goto next_opts
if /I "%1" EQU "debug" (set _MODE_=Debug) && goto next_opts

rem find the visual studio installation
rem forgive the spaghetti, Windows cannot parse parentheses in IF, FOR, etc.
rem when we might have (x86) in the path
if /I "%1" EQU "VC7" goto vc7_opts
if /I "%1" EQU "VC8" goto vc8_opts

rem catch-all for unknown options
echo unknown option '%1' - clean, build, release or debug expected >&2
goto :failed_opts

:next_opts
shift
goto begin_opts

:vc7_opts
echo VC7 selected
set _SLN_=%_VC7SLN_%
if exist %_VS7DIR_% goto vc7_exist_opts
goto vc7_failed_opts

:vc7_exist_opts
echo using Visual Studio from %_VS7DIR_:"""='%
set _VSDIR_=%_VS7DIR_%
goto next_opts

:vc8_opts
echo VC8 selected
set _SLN_=%_VC8SLN_%
if exist %_VS8DIR_% goto vc8_exist_opts
goto vc8_failed_opts

:vc8_exist_opts
echo using Visual Studio from %_VS8DIR_:"""='%
set _VSDIR_=%_VS8DIR_%
goto next_opts

:done_opts

:vc7_failed_opts
echo Visual Studio 7 not found >&2
goto failed_opts

:vc8_failed_opts
echo Visual Studio 8 not found >&2
goto failed_opts

:failed_opts
echo option parsing failed >&2
goto 2>nul:

I hope this is useful to anyone suffering under the tyranny of the Windows batch scripting language. Where possible it is probably better to use Powershell or a third party scripting language like Python, Ruby, Perl, etc. In my situation I had to hit the lowest common denominator across various editions of Windows.