Tuesday, May 06, 2008

Configuration Files - a pattern for windows batch scripts

It's a very common practice in perl and linux/unix shell scripting to use an external configuration file (.conf or .properties), keeping all the script's necessary settings organised tidily in one place. This makes it easier to configure in the first place (because you don't have to hunt through the whole script to find what needs tweaking, or which environment variables need to be set), and also makes maintenance of the script so much easier (because you don't need to reconfigure after installing an updated script).

For some reason though, this practice never seemed to get widely adopted in the Windows batch file world. Either Windows script-kiddies just never learned to think like a sysadmin, or we never expected the DOS shell to be capable enough! Or maybe we never bothered, because surely windows batch files should be extinct by now?

I guess the dominant approaches to configuring batch files on Windows are:

  • Command line arguments [painful if you have too many, annoying if they are invariant for your setup, and may entice you to write a wrapper script just to save all the typing]

  • Environment variables [can lead to pollution of your environment, but also not usually backed up along with your scripts]

  • Good ol' "just edit the batch file and fill in the settings" approach


Well, it is possible to take advantage of the elegance of external configuration files. For some time I've been using a technique that I adapted from what I'd normally do with perl and bash scripts. I'm sure this was invented decades ago, but it surprises me that I haven't yet found a good example or tutorial on the net.

So I thought maybe time to polish up the "pattern" and share it here.

The Pattern

It's pretty simple! The flowchart on the right provides an overview.

To make this work with simple windows batch files, we use a configuration file that it itself a batch file (.bat or .cmd) and it is called from the main script. The configuration file would usually set a range of environment variables

A nice usability touch is that when the script is first executed (i.e. no configuration file present), it will generate a default configuration file, give the user some helpful instructions on how to edit the configuration file, and exit.

This makes your script "safe" for users who are liable to run it just to find out what it does(!).

But it also means that you can maintain the default configuration file structure - with as much built-in documentation as you like - inside the script itself (therefore only one file to maintain).

The Script Template

Here's a script template that includes the configuration file handling (also available for download here). As you can see, not rocket science!

If you have multiple scripts that all need to share a common configuration, you can extract the config file handling as a separate script and include (call) that from all the others. See the scriptTemplate.cmd and config.cmd in this kit of examples.
@echo off
REM $Id$

setlocal

echo Welcome to %0

REM Initialise the config flag [CONFIGSET] and config file [LOCALCONF]
REM You must name the config file .bat or .cmd to ensure Windows can execute it cleanly
set CONFIGSET=NO
set LOCALCONF=%~dp0conf\%~n0-%COMPUTERNAME%.cmd

REM ===============================================================
REM Configuration section
REM ---------------------------------------------------------------
if "%LOCALCONF%"=="" goto config_help
goto config_do


:config_help
echo This is a configuration help script
echo Call from another script with first parameter being the config file name
echo This script will set the variable CONFIGSET
echo CONFIGSET=NO in the case of error or undefined configuration
echo CONFIGSET=YES in the case where configuration has been successfully read
goto config_exit


:config_do
REM handle configuration file
IF EXIST %LOCALCONF% goto config_cont

REM generate default setting file
REM adapt this to you needs. Here are some samples
echo REM configuration file> %LOCALCONF%
echo set JAVA_HOME=C:\bin\jdk1.6.0_03>> %LOCALCONF%
echo set TMPFILE=c:\temp\mytemp.txt>> %LOCALCONF%
echo set SUBJECT=A subject line>> %LOCALCONF%
echo set DBUID=dbusername>> %LOCALCONF%
echo set DBPWD=dbpassword>> %LOCALCONF%

echo #
echo # Local configuration not yet set.
echo # A default configuration file (%LOCALCONF%) has been created.
echo # Review and edit this file, then run this process again.
echo #
goto config_exit


:config_cont
call %LOCALCONF%
set CONFIGSET=YES


:config_exit
if "%CONFIGSET%"=="YES" goto config_ok
echo Configuration is not set
goto exit
:config_ok
REM ---------------------------------------------------------------
REM Configuration section ends
REM ===============================================================


echo The main script starts from here.

echo The following configuration is set:
echo JAVA_HOME=%JAVA_HOME%
echo TMPFILE=%TMPFILE%
echo SUBJECT=%SUBJECT%
echo DBUID=%DBUID%
echo DBPWD=%DBPWD%


:exit
endlocal


Sample Output

Here's an example of running the script above. The first time through, no configuration file is detected, so it creates one and exits.

At that point, the user could edit the configuration file if necessary.

Subsequent runs pick up the settings in the configuration file.

D:\MyDocs>scriptWithConfigTemplate.cmd
#
# Local configuration not yet set.
# A default configuration file (scriptWithConfigTemplate-conf.bat) has been created.
# Review and edit this file, then run this process again.
#
Configuration is not set

D:\MyDocs>scriptWithConfigTemplate.cmd
The main script starts from here.
The following configuration is set:
JAVA_HOME=C:\bin\jdk1.6.0_03
TMPFILE=c:\temp\mytemp.txt
SUBJECT=A subject line
DBUID=dbusername
DBPWD=dbpassword

D:\MyDocs>

No comments: