Dec 152017
 
Great Batch File Enhancer. Many options.
File ERRLEVEL.ZIP from The Programmer’s Corner in
Category Batch Files
Great Batch File Enhancer. Many options.
File Name File Size Zip Size Zip Type
ERRLEVEL.DOC 15812 5106 deflated
ERRLEVEL.EXE 2048 1173 deflated

Download File ERRLEVEL.ZIP Here

Contents of the ERRLEVEL.DOC file


/* ---------------------- ERRLEVEL.C ------------------------------ */

I run several unattended systems that specialize in telecom-
munications. These systems run 24 hours per day, seven days a week,
and they all have some scheduling associated with them. Certain things
need to be done daily at certain times, while others must be done on a
particular day of the week. Still others are scheduled on a particular
day of each month. Some other items need to occur on only certain
Julian dates. I use batch files to get these things done, and it was a
bit of a hassle.

I ran across a program somewhere called GETTIME that basically
asked DOS what time it was and returned the hour as a DOS errorlevel
which was then accessible to a DOS BATch file. It was really helpfull.
However, I wanted to do some scheduling based on other parameters
besides the hour.

I looked into the magical DOS references, and discovered that
there was a GET-TIME function (2CH) that returned the time that DOS
thought it was, and a GETDATE (2AH) function that returned the date in
several bizarre and wonderful fashions. GETTIME returns the hour
(0-23) in the CH register, the current minute of the hour (0-59) in the
CL register, the current second (0-59) in the DH register, and the
current hundredths of a second (0-99) in the DL register. Meanwhile,
GETDATE returns the year as a 16 bit value (1980-2099) in register CX,
the current month as a number between one (January) and twelve
(December) in register DH, and the current day of the month as a number
between one and 31 in register DL. To round things out, although not
documented in most manuals, it also returns the day of the week as a
number between 0 (Sunday) and seven (Saturday) in the AL register.
What a delightfully useful provision!!

There was still the problem of communicating these things to
another program in a way that could be used in a BATch file. I knew
that many programs exited with an error level of some sort, so I began

looking for the way to do that as well. The answer seemed to be in the
DOS service 4CH, whose job was to terminate a process and return an
error level (whatever was currently in the AL register).

Suddenly, it all seemed too easy! It seemed that all that was
necessary was to use the DOS service for either time or date, put the
appropriate value into the AL register, and use DOS service 4CH to
commmunicate the value back as an error level. In fact, it seemed like
it was just necessary to set the AH register to the number of the DOS
service (2AH or 2CH), call interrupt 21H, move the value from whatever
register it was in to the AL register, set the AH register to the
number of the DOS service (4CH), and issue interrupt 21 again. Looked
like about 10 bytes of necessary code! How very useful, too!

I loaded DEBUG, asked for the assembler option, and looked at my
handy dandy cheat sheet, assembled it, and saved it. It was a mere 10
bytes each. In fact, for one of the functions, it was only eight bytes
long as the required values were already placed in the AL register by
the DOS function.

GETTIME.COM
-----------
MOV AH, 2C ; Place 2CH into the AH register. This sets up for
; the DOS GETTIME function request.
INT 21 ; Do an Interrupt 21 requesting DOS function in AH
MOV AL, CH ; Take the hour value (0-23) now stored in the CH
; register and move it to the AL register. This
; is where the error level is taken from for DOS
; Function 4CH, Terminate a Process with return
; code.
MOV AH, 4C ; Place 4CH into the AH register. This sets up for
; the DOS Terminate a Process Function.
INT 21 ; Do an Interrupt 21 requesting DOS function in AH

In similar fashion, I constructed GETDAY, GETDATE and GETMONTH
programs. They are listed here without the copious comments, as they
are all very similar.

GETMONTH.COM GETDATE.COM GETDAYWK.COM
----------- ----------- -----------
MOV AH, 2A MOV AH, 2A MOV AH, 2A
INT 21 INT 21 INT 21
MOV AL, DH MOV AL, DL INC AL ; Make day 1-7
MOV AH, 4C MOV AH, 4C MOV AH, 4C
INT 21 INT 21 INT 21

I now had GETTIME, GETDAY, GETDATE, and GETMONTH programs
functioning nicely. I could simply call one of these four small
programs and it would return the errorlevel that corresponded to the
appropriate thing I had asked for. I rewrote my batch files and was
happy as could be. I had the control I needed over batch file
operation. What more could a person want?

I had experienced a recurring problem with disk space, however.
Twenty megabytes had become cramped, so I added a second hard disk
drive to have forty megabytes. An RLL-type controller (and the luck of
the draw on my earlier drive purchases) took me to sixty megabytes on
line. This too became cramped. Each little eight or ten-byte program
allocated a 2 megabyte area of disk space. I decided that what I
really needed was a single program, smaller than 2048 bytes, that would
replace all five of my previous efforts. This mythical program would
be command-line driven, and would thus save about 6K of disk space
overall. It would also be nice if it had some sort of clever usage
routine to explain its operation if it were run without a proper
command line. Another nice touch would be the capability for the user
to have it return a command line supplied errorlevel to facilitate
batch file testing. It would also be nice to have the year returned as
an error level, and one of my friends suggested that it would really be
nice to return the Julian date as an error level. Still another
requested that the year be returned as an error level. The results are
ERRLEVEL.C.

Please note that the errorlevel returned by DOS is a single 8-bit
value (0-255) returned in the AL register. For working with the Julian
date or the year, DOS treats this as the remainder of that value
divided by 256 (modulus 256 division). Thus, Julian date 257 will
appear to be 2, and so on. If you schedule in these fashions, please
be aware of these ramifications.

/* ---------------------- ERRLEVEL.C ------------------------------ */
/*
* ERRLEVEL is a simple c program that takes command line arguments
* and returns an MSDOS/PCDOS errorlevel depending on the command line
* arguments.
*
* ERRLEVEL Copyright 1987 by Paul M. Sittler
*
* Returns DOS error level requested by single command line parameter
* Displays this screen and returns errorlevel 0 on error
*
* usage: errlevel [d|h|j|m|w|y|#]
*
* d = date [1-31] h = hour [0-23]
* j = Julian date [1-366] m = month [1-12]
* w = week day [1-7] y = year [1987]
*
* # = number (for testing BATch files)
*
* ERRLEVEL uses the DOS getdate and gettime functions and will return
* an errorlevel that can be used by other programs or in a batch file.
*
* The number function is included to facilitate testing of batch files
* for specific purposes. Some code may be compiler specific, such as
* the _doint() function and the treatment of registers (_rax, _rbx, etc).
* The program was written for the DeSmet C compiler, and using that
* compiler weighs in at 2048 bytes compiled.
*/

#include /* Preprocessor grabs standard header file */

/* If the comments are removed from around the next statement, the
* pre-processor will add the error-checking code for testing the program.
* If the #define line is left out, the debugging code is not compiled,
* and the program will be shorter.
*/
/**
#define DEBUG TRUE
**/

int _rax, _rcx, _rdx; /* variables to hold register values */
/* corresponding to what they look like */
/* DeSmet-C specific, other compilers may */
/* implement differently */

main(argc, argv)
int argc; /* Command line arguments count */
char *argv[]; /* Command line argument vector */
{
/* Declare local variables */
char week_day, /* these can be character or signed 8-bit values */
day_of_month, /* day of month variable, could be 1-31 */
not_leap, /* flage for a year that is not a leap year */
month, /* month variable, could be 1 -12 */
hour, /* hour might be a 0-23 value */
ch; /* all-purpose character variable */

int julian_date, /* These must be 16 bit values (> 128 ) */
year; /* thus mus tbe of type integer */

/* Now we will set up a table that tells how many days have occurred in
* a year up to a given month. The first element is for January, and we
* would add the day in January that it is to the offset in days from the
* beginning of the year until the first of that month. This is used to
* figure out the Julian Date without CPU intensive calculations.
**/
static int month_length[12] =
{
0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334
};

_rax = 0x2A00; /* Set up for DOS get-date function (2AH) */
/* by placing function number in the AH register. */
_doint(0x21); /* Do a DOS kitchen-sink interrupt (21H). */

week_day =((_rax & 0x00FF) + 1);/* AL register (0-6) is day of week. */
/* By ANDing the 16-bit register */
/* with a bit mask, we mask out */
/* the upper 8 bits (AH register) */
/* without costly division routines. */
/* Add one to the 0-6 value returned, */
/* so that we have an error level of */
/* one for Sunday rather than the */
/* zero that DOS returns. */

day_of_month = (_rdx & 0x00FF); /* DL register (1-31) is day of month.*/
/* By ANDing the 16-bit register */
/* with a bit mask, we mask out */
/* the upper 8 bits (AH register) */
/* without costly division routines. */

month = (_rdx >> 8); /* DH register (1-12) is month. */
/* By shifting 16-bit register */
/* right 8-bits, we look only at */
/* DH register without costly */
/* division routines. */

year = _rcx; /* Year is in CX register as 16-bit */
/* value (won't fit in 8 bits). */

_rax = 0x2C00; /* set up for DOS get-time function (2CH) */
/* by placing function number in the AH register. */

_doint(0x21); /* DOS kitchen-sink interrupt 21H */

hour = (_rcx >> 8); /* CH register (0-23) is hour */
/* By shifting 16-bit register */
/* right 8-bits, we look only at */
/* DH register without costly */
/* division routines. */

not_leap = (year & 0x0003); /* mask year to see if not leap year */
/* If we look at the last two bits, and */
/* one of them is true, then the year can't */
/* be divisible by four. Thus it is not */
/* a leap year, and not_leap is set to be */
/* TRUE. This will not work to rule out */
/* the leap century (2000 is next one). */
/* I did not add the necessary code for */
/* the leap century thing. */

julian_date = (month_length[month -1] /* 1) lookup days till now. */
+ day_of_month /* 2) add day of month. */
+ ( (month > 2) && /* 3) IF March or later AND */
(!not_leap) ) ); /* leap year THEN add 1. */
/* Logical 1 is truth */
/* 0 is not true */

#ifdef DEBUG /* Preprocessor directive */
/* Compile this code only if DEBUG is defined */
printf("\n\n\t%d-%d-%d, %d Julian and the %d hour of the %d day of week\n",
month, day_of_month, year, julian_date, hour, week_day);
#endif

/* The following statements take the first character */
/* of the first command line argument, changes it to upper case, */
/* and then acts on it. If it does not match the expected */
/* arguments, it checks to see if the first character is a digit.*/
/* If it is a digit, it changes that ascii argument to an */
/* integer, and outputs that value as the error level. This can */
/* be useful for testing a batch file to see if it works properly. */
/* If the first letter of the argument is neither an expected */
/* character nor a digit, it calls the usage routine and exits */
/* with an error level of zero. */
/* I had originally coded this as a relly neat switch statement, */
/* but when time came to make the program fit in 2K bytes, I recoded */
/* it into a series of if() statements to save space. */

ch = (toupper(argv[1][0])); /* get first char, capitalize it */

if(ch == 'D') /* Asking for Day of month? */
exit(day_of_month);
if(ch == 'H') /* Asking for Hour of day? */
exit(hour);
if(ch == 'J') /* Asking for Julian date? */
exit(julian_date);
if(ch == 'M') /* Asking for month? */
exit(month);
if(ch == 'W') /* Asking for Day of week? */
exit(week_day);
if(ch == 'Y') /* Asking for Year? */
exit(year);
if(isdigit(ch)) /* Is first char a digit? */
exit(atoi(argv[1])); /* Change arg to int and exit */
usage(); /* Otherwise, terse usage routine */
}


void usage() /* Terse usage routine to explain command line */
{ /* and exits to DOS with an errorlevel of zero. */
puts("\tERRLEVEL Copyright 1987 by Paul M. Sittler\n\n");
puts("\tReturns DOS error level requested by single command line parameter\n");
puts("\tDisplays this screen and returns errorlevel 0 on error\n\n");
puts("\tusage: errlevel [d|h|j|m|w|y|#]\n\n");
puts("\td = date\t[1-31]\t\th = hour\t[0-23]\n");
puts("\tj = Julian date\t[1-366]\t\tm = month\t[1-12]\n");
puts("\tw = week day\t[1-7]\t\ty = year\t[1987]\n\n");
puts("\t# = number (for testing BATch files)\n");
exit(0); /* Exit to DOS with an errorlevel of zero. */
}


 December 15, 2017  Add comments

Leave a Reply