Building better scripts Part III

I’ve gotten some great feedback from the community on the last few posts. At some point, I’m considering integration of a comment engine like Disqus. While this content is static HTML, I could still embed such a solution in the site to facilitate comments. I’m trying to avoid the whole peanut gallery effect, which I don’t think adds anything, but I believe the benefit of allowing comments that add to to the conversation or allowing readers to easily ask questions would outweigh the possibility of misuse. This is something I will consider carefully over the next few weeks.

In Part III, we’re going to going to look more deeply at .bat files with examples of mediocre ways of doing things and some suggestions for cleaning up a poorly written script and implementing it in a better way. We’ve already seen some of these techniques, but we’re going to start putting them all together in a more meaningful way. A lot of the little details you need to know to build solid .bat files will provide context for more advanced scripts we’ll look at later. Also, despite their age, sometimes a quick and dirty .bat file is just needed to get something done. There is no shame in this and being able to quickly whip up a good solution using one is a sign of a good Windows sysadmin, in my opinion.

Please examine our starting point, which is the .bat file below:

cd c:\blarg\
blarg.exe
exit

Well, a couple of comments here— first, why are we using cd to change directories? One common misconception with .bat files is that we need to know the exact path of any executable we want to run. This is totally not true, as you can use %~dp0 as a batch parameter to refer to the directory containing the .bat file that is executing. While cd can be used to change working directly, it usually isn’t necessary since you can use a full or relative file path. Finally, putting an exit at the end of the .bat file is a bad practice since it can prematurely exit the shell that called the .bat file, which may cause unintended consequences such as killing another .bat file that called this one. Another example of where exit maybe be used is when you want to return a numeric result to indicate the success or failure of the .bat file as we did in Part I (exit %result% for example). Otherwise, “ending” a .bat file in this manner is unnecessary. A cleaner implementation might be as follows:

%~dp0blarg.exe

Keep in mind, our directory structure should be as follows:

C:.
└───blarg
        blarg.bat
        blarg.exe

Now, there’s a few more problems that can crop up. If we were to, for example, move the blarg folder into c:\program files\ we’d still have a problem:

C:\Program Files\blarg>blarg.bat

C:\Program Files\blarg>C:\Program Files\blarg\blarg.exe
'C:\Program' is not recognized as an internal or external command,
operable program or batch file.

C:\Program Files\blarg>

As you may have guessed, the space in c:\program files\blarg is causing us some heartache. In order to account for this, we need to update blarg.bat as follows:

"%~dp0blarg.exe"

Next, you are probably wondering what happens if blarg.exe fails for some reason. Does it generate some output that is important that is sent to STDOUT or STDERR? The next obvious thing to do is, as we mentioned in Part I of this series, might be to redirect all of this output to a log file:

"%~dp0blarg.exe" >> "%~dp0log.txt" 2>&1 

Now log.txt should begin capturing output on the first and subsequent runs in the blarg folder. Additionally, future runs will always append to the existing log file, which may or may not be desirable. One can always use overwrite (>) instead of append (>>), if needed.

At this point, we’re really starting to get somewhere. But maybe we’re interested doing something based on what the return code is from blarg.exe as we did in Part I. Perhaps the scenario is that we need to conditionally run blarg2.exe if blarg.exe fails:

"%~dp0blarg.exe" >> "%~dp0log.txt" 2>&1
if not "%exitresult%"=="0" goto fail
goto :eof

:fail
"%~dp0blarg2.exe" >> "%~dp0log.txt" 2>&1

One thing to note here is goto is not like calling a function. We simply skip to the specified label (:fail) and continue execution. Or in the case of :eof, this label is a special built-in label that ends the .bat file.

Finally, we might have a need to run blarg2.exe multiple times. Perhaps we need to run it over and over with a different set of parameters each time. What we could do is store some values for one of those parameters in a text file. Using the for command, we can do just that:

set %hostsFile%="%~dp0hosts.txt"
set %secondParameter="blargityBlarg"

"%~dp0blarg.exe" >> "%~dp0log.txt" 2>&1 
if not "%exitresult%"=="0" goto fail
goto :eof

:fail
for /f %%a in (%hostsFile%) do "%~dp0blarg2.exe" --firstParameter=%%a --secondParameter=%secondParameter% >> "%~dp0log.txt" 2>&1 

Our hosts.txt, by the way, should look like this:

someHost
someOtherHost
thisIsAnotherHost
yayOneMoreHost

With these simple .bat concepts and the understanding that most of these concepts transfer to other scripting languages (even on other platforms), I hope I have provided something that may be of use to you in the future. While .bat files have served for decades in Windows and MS-DOS as quick and dirty solutions for getting things done, they aren’t always the most elegant or maintainable solution to every problem. This statement is a prelude to Part IV, where we tackle much more complicated scripts and automation.

Comments !