For general scripting/automating on Windows, I generally prefer PowerShell.  There are times, however, when “batch scripting” with CMD.EXE is needed.  If you’ve ever tried writing scripts of any complexity, you know that “batch scripts” can be a real pain to deal with.  In particular, expansion of variables happens by default when a command is read, which is not what many other scripting languages do.  Raymond Chen has a nice post describing this in further detail.

For this reason, it can be useful to turn on delayed expansion if you’re doing anything even remotely interesting.  One of the unexpected downsides — or bugs, in fact — of delayed variable expansion is that the ‘!’ character seems to eaten… so if you have a command you’re calling that uses a ‘!’ in its syntax, you’re plain out of luck.  The good news is that you can use the delayed-expansion behavior to detect and work around itself:

set X=foo
if "!X!"=="foo" (
  cmd.exe /v:off /c %0 $*
  goto :eof

If delayed expansion is disabled, “!X!” is just the string “!X!”.  If it’s enabled, the delayed expansion takes place and it is evaluated (as “foo”), resulting in the if-clause being run.  This in turn re-runs the current batch script with delayed expansion disabled (“/v:off”).  Putting this snippet at the top of your script will ensure that the remainder of the script is only run in a “no delayed-expansion” context.

But it turns out there’s an even easier way to fix this!  With command extensions enabled (which has been the default since CMD.EXE replaced COMMAND.COM in Windows 2000 and XP), you can just use:

setlocal disabledelayedexpansion

to turn off delayed-expansion until the matching endlocal (or the end of the script).

What I haven’t been able to solve is how to elegantly deal with both needing delayed-expansion behavior in part of the script, and also needing a ‘!’ elsewhere.  You have to be very careful using setlocal/endlocal blocks, because any environment values set inside a setlocal will be lost when the block ends.  If you’re running into a problem like this, it’s probably time to move up to a better scripting language.

Comments are closed