Friday, June 29, 2007

Robocopy

One other utility that Vista comes with which is way better than previous Windows is the robocopy utility.  It's got a lot of options and features, but one that I find very useful is the mirroring option.

With the mirroring option you can create exact copy of the source, meaning that if there are files in the source that are different from the destination, they will be copied, if there are extra files already in the destination that don't exist in the source, they will be deleted.

robocopy /MIR source destination

Try it out.  Play with it.  You'll like it.

One thing you want to watch out about robocopy however, is the error code it returns when it exits.  It's not the straightforward zero == success and non-zero == fail.  Oh, no.  That would be too easy.  So here is a list of the various error codes that robocopy gives out.

Error code Meaning
0 No change
1 Files were copies successfully
2 Extra files deleted from destination
4 Some mismatched files detected
8 Some files could not be copied
16 Fatal error

 

The error codes form a bit field, though.  So 3 really means 2+1, meaning there are extra files but copy was performed successfully.  That means actual failure is anything greater than or equal to error code 8.

robocopy /MIR source destination

if %errorlevel% geq 8 echo FAIL!

Once you know that, it's a sweet utility to have around.  If you're not on Vista, it is included in the Win2k3 Resource Kit Tools which you can download.

Symbolic Links

Symbolic links have existed for ages in UNIX, but Windows have not had any built in support for symbolic links until Vista.  Vista has a utility called mklink, which allows you to create symbolic links for files and directories.  Just be mindful that it requires admin privileges to run.

Symbolic links is useful for creating aliases for files or directories.  Say you do a daily build of something.  And you want to create an alias directory called current to point to the latest build you did.  For simplicity, let's assume your build version is stored in some file called buildver.txt.  You increment that every time you do a build.  You create a directory named after the build version, and you create a current directory pointing to that.

if not exist buildver.txt echo 0 > buildver.txt

set /P CURRBUILD=<buildver.txt

set /A CURRBUILD=%CURRBUILD% + 1

md %CURRBUILD%

pushd %CURRBUILD%

REM the following assumes you have build.bat in your path that will create your directory content

call build.bat

popd

mklink /D current %CURRBUILD%

This way a script that wants to access the current build can just always hit current without trying to figure out what the latest build version is.

If you want to delete it, you can safely use the usual rd command to remove directory.  This will only delete the link, not the actual directory it points to.  The same goes for file symbolic links.  To remove it you use the del command and it will delete the link, not the file.

You can find out if a file is a link or not when you do a dir in a directory by the <SYMLINK> or <SYMLINKD> type it shows you.

For XP and Win2003, you can use a Sysinternals utility called Junction to create NTFS reparse point that will do similar job as a symbolic link.  However unlike symbolic links which will work on files too, junctions only work for directories.

Another tool that can create junctions is the linkd utility from Win2k3 Resource Kit Tool which you can download.

Thursday, June 28, 2007

Setting Variable Value From File

Let's say you have file containing some string, and you want to get that string and assign it to an environment variable.

echo Hello World > foo.txt

There are two ways you can assign the content of that file to an environment variable.  One is to use redirection.  To do this, you need to make the set command interactive by using the /P switch.

set /P BAR=<foo.txt

echo %BAR%

If you have more than one lines in the file, this will set the variable value to the first line in the file.

The other method is using the for loop, telling it to read from the file.

for /F "tokens=*" %x in (foo.txt) do (

  set BAR=%x

)

The "tokens=*" option tells it to consume the whole line, so that if you have any white spaces in the line, it will not pick up the first word only.  If you use this approach and there are more than one lines in your file, your variable will end up with the last line as its value.

Wednesday, June 27, 2007

Printing Date and Time

What's this, you say?  We're getting down to such dumb topics after only a few posts?  Well, it might be dumb, but it's kind of useful.  Say you want to print the date and the time.  You would use the date and time command, obviously.  And they have the /t switch to allow no interaction which is nice for batch files.

date /t

time /t

Excellent.  Marvelous.  Jolly good.  But what if you want to put both of them on the same line?  Like if you want to create a log trace where you put date and time at the beginning of each line?  Well, the date and time commands won't really work well because they will put the date and time on different lines.  Luckily there is an alternative.

echo %DATE% %TIME% - Log message goes here.

This will print out the date, time and log message on one line.  Ideal for logging traces.

Random Number Generation

If you ever need to get a random number while writing a Windows batch script, you can use Windows command's handy little %RANDOM% environment variable.

echo %RANDOM%

And if you want to generate a random number within a range of values, all you have to do is perform some simple command line math.  Say you want to generate a random number between 50 and 150.

set MIN=50

set RANGE=100

set /A RN=%RANDOM% % %RANGE% + %MIN%

That should do it.

Tuesday, June 26, 2007

How To ENABLEDELAYEDEXPANSION

ENABLEDELAYEDEXPANSION is a useful property that allow you to do what you think should happen when you write a for loop or an if block. Consider this example.

set COUNT=0

for %%var in (1 2 3 4) do (

  set /A COUNT=%COUNT% + 1

  echo %COUNT%

)

Now in any other scripting or programming language, this would be just fine. Not so in windows batch. Since batch processor treats the whole for loop as one command, it expands the variables once and only once, before it executes the loop. So you end up with %COUNT% being expanded to its value, which is 0, before you start the loop, and you end up printing 0 four times in a row.

That's where delayed expansion comes in. As the name suggests, delayed expansion makes batch processor delay expanding the variable to its value until it actually loops through it.

But to make that happen, you need to do two things.

1. Enable delayed expansion. You can do this by doing setlocal ENABLEDELAYEDEXPANSION at the beginning of your script.

2. Use ! instead of % to expand environment variable value.

Now here's that example again using delayed expansion:

setlocal ENABLEDELAYEDEXPANSION

set COUNT=0

for %%var in (1 2 3 4) do (

  set /A COUNT=!COUNT! + 1

  echo !COUNT!

)

This time you will see 1 2 3 4 being printed, as you expect.

Another way of turning on delayed expansion is through the registry. You can go and add it manually through regedt32.exe, or you can load a registry file like the one shown here:

Windows Registry Editor Version 5.00[HKEY_CURRENT_USER\Software\Microsoft\Command Processor]

"DelayedExpansion"=dword:00000001

Just save it to a file called enabledelayedexpansion.reg or something, and just invoke that file from the command line.

enabledelayedexpansion.reg

This will enable delayed expansion for current user. You can do the same for HKEY_LOCAL_MACHINE.
You need to start a new console window to see its effect. To check, simply do

echo !COMPUTERNAME!

in the new command console. If it prints your computername, then it recognizes the ! variable expansion, which means delayed expansion is enabled.
This way you don't have to do setlocal ENABLEDELAYEDEXPANSION. But watch out, you need to make sure this thing is enabled if you are planning on skipping setlocal ENABLEDELAYEDEXPANSION in your script.

I'll get into how to check for this from your script some other time.

The Little Pipe That Could

So pipes can be bad at times (see my last post), but pipes can be really good for a lot of things too. I won't go and say a lot about the goodness of pipes.
Here I just want to give an example of how pipe can be used to get you through some of those old fashioned commands that just refuse to be invoked without user interaction.

In windows, comp.exe is one of those things. It's similar to diff, which is not a standard Windows utility. You invoke comp.exe by giving it two file names to compare. But after it compares the files, it will ask you if you want to compare any more files. It ALWAYS asks you that. There is no way to turn it off by passing some command line switch.

So what do you do? Well, one thing you can do feed it a file containing the letter "N" through it's input redirection.

echo N > answer.txt

comp file1.txt file2.txt

This works well.  But what if you don't want to create a temporary file just for answering that? You can use pipe of course. And since you need comp.exe to consume the answer, you just need it at the end of the pipe.

echo N ¦ comp file1.txt file2.txt

Nice and simple. And like I mentioned in previous post, this will get you the exit code of comp.exe itself if you decide to check for errorlevel afterwards.

Monday, June 25, 2007

Hidden Danger of Pipes

OK, this is my first post, so I figure I need to explain a bit what I intend this blog is about. Batch. That's what I intend to write. Specifically, tips, tricks and pitfalls of windows batch file.

Now that we have that out of the way, let's take a look at the first of such batch quirk.

When you are using pipes to do some chained processing, just remember that the exit code of the last command in the pipe is the one that prevails.
I've seen this bitten a lot of people:

test.bat ¦ tee.exe outputfile.log

exit /b %errorlevel%

The intention here is to exit with whatever the exit code of test.bat is, when in fact you're capturing exit code of tee.exe, which will most likely be zero.
You'll then end up masking the test.bat exit code. If this is in some pre-checkin test script, that usually ends up with bad codes sneaking in.
So save yourself some time, and output to a file instead, and post process the output.

test.bat > outputfile.log

set EXITCODE =%errorlevel%

type outputfile.log

This captures your intent just the same (well, almost the same).