Windows Shell Scripting

Introduction

This article is translated to Serbo-Croatian by WHGeeks. Thanks!

The term "shell script" comes from UNIX, the DOS term is "batch files." UNIX shell scripts are very powerful and flexible, they are essentially programming languages unto themselves. Windows or more rightfully DOS batch files are a pale imitation. However, sometimes you need to write something that will just work on any plain old out-of-the-box Windows install someone has--without adding all kinds of other tools.

Before getting too deep into this topic, consider if there is another tool you might use. Here is a list of tools, all of which are far more powerful, flexible and are probably easier to use than batch files:

Powershell

If you follow Windows at all you will be aware that Powershell is Microsoft's new command line tool, and that you will be required to use it more and more with newer Windows versions. That is a Good Thing, in my opinion, and it only took them about 20 years to realize, but that is not covered here! I don't really do Windows anymore and I have not bothered to learn Powershell, which reminds me unpleasantly of Java's verbose ugliness. So this page is somewhat historical, though most everything should work to at least Win7.

If you are interested in current Windows command line scripting and Powershell (and if you like Windows you should be), there are any number of other resources and books that will help. These are probably good but I haven't read them:


A Tweak

Did you know that Window's cmd.exe has file and directory name completion, like UNIX shells? It does, and that can be amazingly useful. But in most versions of Windows it's not turned on by default. (I believe it may be on in Windows 2003, but can't swear to it.)

To enable file and directory name completion under Windows, download this registry file and remove the .txt, then double-click on it and answer yes to the question about importing into the Registry. If you can't download for some reason you can copy the text below into a new file and import it, or just open regedit, navigate to the key, and change the values for CompletionChar and PathCompletionChar to 9. One you've done that, open a new command prompt and type dir c:\win then hit the TAB key and watch what happens. Of course the up arrow and other command line editing functions will still work as always.

REGEDIT4

; NT-TAB.reg -- Sets the NT Command Completion Character to TAB
; Use "RegEdit /s NT-TAB.reg" for silent installations
; v1.0 1998-10-22 JP Vossen  http://www.jpsdomain.org/
; v1.1 2001-09-06 JPV Added PathCompletionChar
; v1.2 2003-03-30 JPV Added .DEFAULT and SOFTWARE sections

[HKEY_CURRENT_USER\Software\Microsoft\Command Processor]
"CompletionChar"=dword:00000009
"PathCompletionChar"=dword:00000009


; Can also do this if you have the permissions
;[HKEY_USERS\.DEFAULT\Software\Microsoft\Command Processor]
;"CompletionChar"=dword:00000009
;"PathCompletionChar"=dword:00000009

The Dirt

OK, if you are still going to go through with this, the first thing you need is Tim Hill's Windows NT Shell Scripting, otherwise you don't have a chance. For Windows 9x/ME, you are still toast, but for NT/2000 this book is really great. It's the only way you can navigate the bazaar, inconsistent, contradictory and often asinine "scripting" language built into cmd.exe.

Using material from that book, plus my own almost 20 years experience with DOS batch files, I still had a hell of a time writing the following script. All it does is give you some basic file information (similar to UNIX stat) and tell you if a file will fit on a floppy disk.


The Scripts

Clicking on the name of a script will open that script in a new window.

Stat v1.0 2000-12-03
NT Batch file to provide similar info to the UNIX (file) stat command.
nt-cmd.cmd v1.2 2001-08-29
Sample/demo code I wrote after reading Windows NT Shell Scripting.
drives v1.1 2000-01-11
A tiny script to display active drives.

Simple Sleep

Another of the many lacking tools is a simple "sleep" command, but you can easily fake that using the "ping" command of all things. The following will "sleep" for about 5 seconds, give or take:

C:\> ping -n 5 localhost > NUL

Obviously you adjust the 5 as needed for the number of seconds. You can even write a trivial "sleep" function in your scripts:

@echo off
REM sleep_demo.cmd--Simple "sleep" command demo
REM 2012-07-26

echo Before sleep
call :sleep 7
echo After sleep

REM End of Main program
REM ###################################################################
goto :EOF

REM +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
REM sleep for a specified number of seconds, more-or-less...
REM Called like:  call :sleep 7
:sleep
    set sleep_secs=%1
    ping -n %sleep_secs% localhost > NUL
goto EoF

The Date

How to get and use dates and time in Windows scripts.

This is trivially easy in UNIX. You want to copy a log file to a dated name? "cp mylog `date '+%Y-%m-%d'`-mylog" will copy mylog to 2002-11-27 (as of this writing). What could be easier? But in Windows, it sucks.

There are two basic ways to approach this, both with advantages and disadvantages. The native way is the "for" and "date /t" commands under NT/2000/XP. These do NOT work under Windows 9x and they do not consistently use 2 digit time fields, which totally screws you up if you need the time. The second way is to use the UNIX date command, then do whatever you please. This is very flexible, but requires you to download and have the executable (date) handy. You will also want to rename it (I use udate.exe) so you don't conflict with the built-in date command.

UPDATE (2012-07-26): All versions of Unix "date" commands that I have tested under both WinXP and Win7 have a bug that causes them to skip skip Mar-11 and/or Apr-04! That's pretty annoying but has never been fixed as far as I know. Since it affects both tools I've tested (UnxUtils and GNU Win32), I suspect the Windows strftime lib is the problem. But I can't prove it. And someone else replied to the bug he could not reproduce the problem. So I'd say it's something I'm doing, but I find it off that both the WinXP I've been using forever and a much newer Win7 do the same thing.

UPDATE (2003-06-07): Here is a third way that's trivial! It seems there are built-in but undocumented environment variables %time% and %date% in Windows 2000. I have not tested other platforms (let me know if you do). Due to the format, you can't easily use the date in file copy operations (for example), but the time should be OK. And it's by far the easiest option if you are just going to display (writing to a log file or something).

C:\> echo %date% %time%
Sat 06/07/2003 18:32:30.52

Windows Trivial

@echo off
REM Play with W2K date/time env. vars.

echo The date: %date%
echo The time: %time%

UPDATE (2006-05-11): Here is a another trivial way! Thanks to Richard Blake (RBlake {at} nea {DOT} org) for this great hack. In addition to the above %time% and %date% variables, there is a %VAR:offset,len% construct documented for the SET command, which works elsewhere. As above, the use of a two digit time code can mess you up, but for just the date it will work very well. Code to deal with non zero padded hours is left as an exercise for the reader.

C:\tmp> set MyNewFileName=%DATE:~10,4%%DATE:~4,2%%DATE:~7,2%%TIME:~0,2%%TIME:~3,2%%TIME:~6,2%

C:\tmp> echo %MyNewFileName%
20060511 21921

This RedmondMag.com Backup Basics in Windows Server 2008 R2 article expands on the same method, but they are not portable because they depend on how your system time is displayed, and that will vary from machine to machine based on locale and user preference. For example, I loath any date/time format except for ISO8601 so I have my Windows formats set as close to that as possible, which then breaks the assumptions in the first block:

@echo off
REM date_demo.cmd--Simple date parsing demo
REM 2012-03-10

echo Current dates (Windows default date format for US)
set year=%date:~10,4%
set month=%date:~4,2%
set day=%date:~7,2%
set hour=%time:~0,2%
set min=%time:~3,2%
set sec=%time:~6,2%
echo Date: %date% Time: %time%
echo ISO-8601: %year%-%month%-%day%_%hour%:%min%:%sec%

echo.
echo Current dates (ISO-8601 date format)
REM 2012-03-10
set year=%date:~0,4%
set month=%date:~5,2%
set day=%date:~8,2%
set hour=%time:~0,2%
set min=%time:~3,2%
set sec=%time:~6,2%
echo Date: %date% Time: %time%
echo ISO-8601: %year%-%month%-%day%_%hour%:%min%:%sec%

Other Ways

windate v1.0 sometime in 2001 or 2002
Native Windows Date commands
unixdate v1.0 sometime in 2001 or 2002
Using a UNIX date command in Windows

Getting Input

There are various tools like ask.exe and choice.exe that allow you to get input. There there's an even easier, although undocumented, way: set /P. As in:

set /P MyAnswer=Your Prompt Here!

That prompts the user with "Your Prompt Here!" and puts whatever they type into %MyAnswer%. Very cool.


Simple Utilities

Except for FindZero.bat, all of these batch files will work under DOS, or any Windows.

  • DOS Commands are not case sensitive, unlike UNIX commands.
  • An "@" as the first character of a line prevents the command from echoing whether echo is on or off.
  • echo. will echo a blank line (CRLF).
  • Command line parameters are specified with %1, %2, etc. not $1, $2 as in UNIX.
  • %0 is the name of the program, as invoked. In other words, if you type "mybatch" %0 will be "mybatch". If you type "c:\utils\mybatch.bat" %0 will be "c:\utils\mybatch.bat".
  • ^G is a 'control G' which makes the console beep. This tiny batch file has a ^G in in, which you can cut & paste into scripts. There are lots of other ways to get control characters into files, but they depend on your OS and text editor. In most DOS windows, holding down the <ALT> key while typing the ASCII code on the numeric keypad will produce that character. ^G is 007, you you hold down <ALT>, type 007 on the numeric keypad, then release <ALT> to get a beep.
  • Tim Hill's Windows NT Shell Scripting for much more information and detail. Much of the book applies to DOS and Windows (other than NT) as well.

AddPath.bat

@path=%1;%path%

MCD.bat

@md %1
@cd %1

auto-ftp v1.2 1999-09-16

Automatically Download a file using FTP (not secure!).

CLR.bat

@echo off
cd c:\
cd d:\
c:
cls
ver

aformat v2.4 2002-11-09

Format Floppy with no user prompts.

SPrompt.bat

Requires ANSI.sys, included with DOS & Windows, or PC Magazine's free AnsiCom.

@echo off
REM SPrompt.bat -- Dynamically Set Prompt
REM Created sometime in 1992
REM 03-Mar-1998 JPV
REM 19-Feb-1999 JPV Added "neat" prompt from JPS mail list

REM Neat PROMPT `$+[%user@$P]%@EXECSTR[if %@LEN[%_CWD] GT 20 ECHOS $_:$s]`

prompt $e[0;33;1;44m$P$e[36;44m$G $e[0;37;44m
if not "%1" == "" prompt $e[32;1m%1 %2 %3 %4 %5 %6 %7 %8 %9$_$e[33m%prompt%
rem prompt $e[s$e[1;7f$e[0;45;37;1m$e[K($z)   $d   $t$e[u$e[1m$P$e[0m$G $e[0m
rem set WINPMT=$e[0;33;1;44mEXIT to Windows$_$P$e[36;44m$G $e[0;37;44m
if {%OS%}=={Windows_NT} prompt $P$G

WhoAmI.bat

Requires Microsoft Networking to be installed and active, and the DOS find command. If you have a UNIX find command in the path, you'll probably get a "No such file or directory" error.

@echo off
echo.
net config /yes | find "name"
echo.
pause

Sending e-mail

Something else that is taken for granted on UNIX is the ability to send e-mail from the command line or a script. As usual, windows makes this a challenge. There are a few free and commercial solutions for this, including but not limited to the following list (I've only ever used Blat):

  1. Blat "is a Win32 command line utility that sends eMail using the SMTP or NNTP protocols."
  2. NTsendmail "is Highly Acclaimed UNIX Sendmail replacement for NT. NTsendmail is realeased under the GNU Public License. NTsendmail was designed to enable script writers to use their UNIX CGIs on Windows 95/98/NT/2000."
  3. And I'm sure there's at least one or two Perl modules that can do this.

Upload from Frontpage to your ISP

I used to use MS FrontPage to maintain this site (don't ask me why). My old IS, did not support FrontPage or its extensions, for excellent security reasons. Using FrontPage to create pages, then uploading them to a hosted site is a gigantic pain in the ass because of the way FrontPage keeps all of its proprietary information in various "_VTI_CNF" and other subdirectories. So simply zipping up the directories and dumping them onto a host is not ideal. So I came up with the following solution.

The old code I posted to a Netaxs news group was WRONG in places! THIS stuff works.

I don't use this any more, I use GenSite.pl and rsync.

Open issues are:

  1. If you do a ZIP, and upload it, but do not delete it from the PC, the next run will append, not overwrite. Your "diff" zip will just keep growing. You need to manually delete the zip file after each run is successful.
  2. Since find is working off of the modification time, if you move a file into the structure that has not been modified (i.e. a tool or utility program) it will not be picked up by a diff. You have to add that to the zip manually.
upload.cmd v1.0 2000-12-14
Upload.bat runs on my Windows workstation, and creates a ZIP file I can upload, but it does not grab the stupid "_VTI_CNF" directories!
upload.sh v1.1 2001-01-24
Unzip upload.zip and set permissions on www directory.

Windows Scripting Resources


I will add more to this page as time allows. If you have a specific question, let me know, I might be able to help.

This article is translated to Serbo-Croatian by WHGeeks. Thanks!