Friday, January 23, 2009

Numbered Output Redirection

Today my boss pointed out something to me which is kind of shocking even though you already know how stupid the command processor is.  Now we all know 2> is for redirecting stderr.  Simply > or 1> is stdout.  For stdin you use < or (less widely known) 0<.

But what if you do 3> or 4> or 5<?  Surely it does not interpret that as some special file handle?  Well, if you thought that, you’d be wrong.  Just like I was.  Try this:

echo Hi 3> out.txt

You’d expect to see Hi 3, wouldn’t you?  But nooooo, you’ll see Hi instead.

Where does the 3 go?  Well, the good interpreter thinks it’s a special file handle.  Crazy isn’t it?  But what if you really want to print Hi 3?  What do you do?  Thankfully the solution is easy and pretty obvious.

echo Hi 3 > out.txt

Yup, just put a space between 3 and >, and you’re good to go.

Sunday, November 16, 2008

Quotes In For Loop Set

One thing that just bit me the other day is how for /F loop treats a set enclosed in quotes as a literal string rather than a file name.  This is actually mentioned in the help for page, but still, this is something I did not anticipate.

Consider this example:

set FILEPATH=”c:\some dir with space\somefile.txt”

Now I wanted to go through the lines in that file, so naturally I tried to do this:

for /F %i in (%FILEPATH%) do (echo %i)

What do I get?  I get:

c:\some

Why?  Well, it turns out for /F treats anything in the set with quotes as a string instead of a filename.  OK, so let me try without the quotes then.

set FILEPATH=c:\some dir with space\somefile.txt

for /F %i in (%FILEPATH%) do (echo %i)

But now I get this error:

The system cannot find the file c:\some.

So it refuses to recognize the filename with spaces if I remove the quotes.  So what to do then?  The ugly way, unfortunately.

set FILEPATH=”c:\some dir with space\somefile.txt”

for /F “usebackq” %i in (`type %FILEPATH%`) do (echo %i)

Sigh…

Thursday, March 6, 2008

Setx Escape Character

I recently found out that there is something rather strange with how setx interprets character you pass to it when setting a variable.  It appears that setx has singled out \" as an escaped character.  So if you want to set variable MYVAR to have the value of My name is "Batcheero", here's how you do it:

setx MYVAR "My name is \"Batcheero\""

This is weird because I don't know of any other Windows commands that does this without specifically saying that it is taking regular expressions (like findstr /R).  And the other weird thing is, \" seems to be the only pair that it escapes.  If you try \' or \n or \\, it treats those verbatim.

The bad thing about this singled out treatment is, you have to be careful not to pass a value that ends with a backslash to setx.  For example, if you have a path of a directory that ends with a backslash, which is quite common, then you are in trouble.

set MYPATH=c:\program files\my app\

setx /M MYPATH "%MYPATH%"

This is going to be fine in the current context, but what gets set in global machine environment is c:\program files\my app" (notice the double quote in the end instead of backslash), which is definitely not what you want.

Just one more thing to worry about in Batch world.  Let me know if you are aware of other commands that has this kind of behavior.

Thursday, February 14, 2008

Set And Setx

Everybody knows to use set to set environment variable values.  And I guess everybody should know setx.  The crucial differences between set and setx are:

  • set takes effect in local cmd context.  Meaning once you exit or close the cmd window, you lose the environment variable.
  • setx takes effect in future cmd context.  So you won't see the environment variable and its value in the current cmd.  You need to open a new cmd window to see it.

This really leads to one obvious conclusion: always use set and setx side by side if you want to set something globally but you want to see it in effect immediately.

A good example is if you want to set an environment variable containing your current IP address.  You would do something like this:

ipconfig | findstr IPv4 > %TEMP%\ipaddress.out

REM This will give you something like this

REM IPv4 Address. . . . . . . . . . . : 192.168.1.3

REM You then parse the output file, taking the second token after splitting at the ":"

for /F "delims=: tokens=2" %i in (%TEMP%\ipaddress.out) do (

    set IPADDR=%i

)

REM Then remove the spaces

set IPADDR=%IPADDR: =%

REM Then to persist this in the system environment, you call setx.

setx /M IPADDR %IPADDR%

Now of course setx is much more powerful than simply calling it the way I did in the above example.  Take a look at setx /? to see the complete options.  For example the above usage can also be done using setx alone.

ipconfig | findstr IPv4 > %TEMP%\ipaddress.out

REM Then you use setx to read its value from a file, keying in on a string,

REM and get the value relative to that string. In this case we key in on the ":"

REM and get the token after that.  And setx coordinates start at 0, not 1.

setx /M IPADDR /F %TEMP%\ipaddress.out /R 0,1 ":"

The reason I hardly ever use this is because most of the time I need the environment variable defined in the current context first, and then also persisting it for future cmd contexts.   The second example above only does it for future context.

Monday, January 21, 2008

Creating Compressed Directory

Often times I find myself in need of creating a directory with compressed attribute turned on, so I can put some text log files in with less space consumption.  While md or mkdir doesn't support doing this directly, you can create the directory and later apply the compression attribute, so that any subsequent files copied to that directory will be compressed.

md SomeDir

compact /C SomeDir

copy SomeText.log SomeDir

That does it.  Not the most useful thing in the world.  But hey, you never know when you will need it.

Friday, January 18, 2008

Out Parameter In Function Call

Let's say you write a function or procedure or sub program or whatever you want to call it in batch.  How do you pass back a value to the caller?  You can of course set an environment variable, but that's like setting a global variable.  And that's not very nice if you need to call the function multiple times and only use the value after all function calls are made.

Consider this example:

@echo off

call :GETVALUE

set INPUT1=%USERINPUT%

call :GETVALUE

set INPUT2=%USERINPUT%

echo %INPUT1% %INPUT2%

goto :EOF

 

:GETVALUE

set /P USERINPUT=Input?

goto :EOF

Like I said, it's doable, but not very nice.  I find it more soothing to the eyes if I can pass the variable name as an argument to the function.  And here's how you do it:

@echo off

call :GETVALUE INPUT1

call :GETVALUE INPUT2

echo %INPUT1% %INPUT2%

goto :EOF

 

:GETVALUE

set /P %1=Input?

goto :EOF

Nice and simple.  All you need to do is remember that you can use %1 and other %<number> on the left hand side of an assignment.  And it makes you think for a second that you are programming a real scripting language.

Thursday, January 17, 2008

Escape Characters

I know this is going to be a confusing year, because my first post of this year is on a topic I don't really understand.

For some time now I have been confused what the correct method to escape certain characters from being interpreted when I try to print them out.  Let's say I want to print this line:

< & >

I can't just say

echo < & >

Because I'll get an error that way (try it yourself if you don't believe me).  I have to escape those special characters.  To do that I can use the caret (^).

echo ^< ^& ^>

Nice and easy.  But then if I want to print this line:

%SYSTEMDRIVE%

The batch interpreter does something really baffling.  If I'm doing it from command line, I can escape it with a caret like other special characters.

echo ^%SYSTEMDRIVE^%

But it I do it from inside a batch file, that no longer works.  I have to escape the % not with a caret, but with another %.

echo %%SYSTEMDRIVE%

Confuse the hell out of me, I tell you.  So now I live by these rules:

  1. Always escape special characters using a caret in front of the special character.
  2. Except when you are in a batch file and need to escape a %, then use a %%.

I hope that helps some of you.  Oh wait, nobody reads this blog but me.  Oh, well.

Friday, October 19, 2007

Label at the End of Block

One thing I hate about batch file is, there is no real language definition you can refer to (or is there?).  So a lot of things I have to find out by myself the hard way.

Here's an example of this:

@echo off

for /L %%i in (1,1,10) do (

  if %%i GEQ 5 goto :NOPRINT

  echo %%i

  :NOPRINT

)

This gives an error saying that ") was unexpected at this time".  It is obvious what this means, and it is obvious how to fix it.  Just put a comment between the label and the closing bracket.

@echo off

for /L %%i in (1,1,10) do (

  if %%i GEQ 5 goto :NOPRINT

  echo %%i

  :NOPRINT

  REM can't have label immediately preceding closing bracket
)

I know this is not really anything useful, just a rant, that's all.

Evil Delayed Expansion

Wait, what's this?  How come something so useful as delayed expansion (see previous posts here and here to see how useful it is) be evil?  Well, it's not the feature itself that's evil, but rather the way you can end up spending a long time trying to figure out what's wrong if you make a simple, stupid mistake.

What mistake am I talking about?  Consider this:

@echo off

setlocal enabledelayedexpansion

set COUNT=0

for /L %%i in (1,1,10) do (

  set /A COUNT=%COUNT% + 1

  echo !COUNT!
)

Simple enough, you said.  And you must have spotted the bug (if you have not, try to run it and see what happens).  Now, of course if the loop body is much more complicated it would not be as easy.  You see, the problem is that what you are doing is completely legal.  Having %COUNT% in the loop body is fine as long as you don't expect it to be delay-expanded.  It's a feature, but when you make that silly mistake (or more likely, someone else in your team), then it's not going to be pleasant to hunt the problem down.

Wednesday, September 26, 2007

Using WaitFor

In Vista, there is a command called waitfor, which as the name suggests, waits for a signal.  It is also the command to send the signal to awaiting waitfor instances.  Since waitfor comes with a timeout option, it's a good substitute for sleep (which, until I found out about waitfor, really was the number one thing I could not understand why it's missing from standard Windows command).

waitfor /T 10 SomeSignal

The above command will simply times out after 10 seconds.

OK, I lied.  There is actually a real substitute for sleep in Windows.  It's the timeout command.

timeout /T 10

That command does the exact same thing as the waitfor example, but you can cut short the sleep for waitfor with some signal, whereas for timeout you cut it short by pressing a key.

But obviously the purpose of the waitfor command is to allow you to start some long running command, do some other shorter things, and  wait for the long running command to finish before continuing.

REM Let's pretend we need to setup something

start cmd /c "DoSetup.exe & waitfor /S %COMPUTERNAME% /SI ThisSignal"

DoSomethingElse.exe

waitfor /T 60 ThisSignal

The first waitfor sends the signal, the second waits for the signal.  Of course you want to make sure DoSomethingElse.exe actually finishes before DoSetup.exe for this to work.  Otherwise the second waitfor will just timeout.  And I'd advise against waiting forever in the second waitfor, since if your DoSetup.exe fails or crashes and exits early, you'll most likely end up waiting too late for a signal that's already sent.

Saturday, August 4, 2007

Adding Accounts To Local Admin Group

Well, OK, it's not only for adding accounts to local admin group.  You can use the commands I'm about to show here to add accounts to any local group.  But I use it most often for making some account a local admin on a machine.

It's a pretty well known command actually.  Say I want to add my domain account as local admin on a machine.

net localgroup Administrators MYDOMAIN\batcheero /ADD

Or you can do the same thing to add another group to local admin.

net localgroup Administrators SomeOtherGroup /ADD

So yeah, you already know that, of course.  But what if you have a service running on some remote machine under that machine's local system account, and you want that service to have admin access to your local machine?  Well, for that, you need to add the machine account of the remote machine to your local machine.  How do you do that?  Well to do that, you use the command ever so slightly differently.

net localgroup Administrators MYDOMAIN\remotemachine$ /ADD

Notice the $ sign at the end of MYDOMAIN\remotemachine$.  This tells the command it's a machine account, and not to be confused with a regular user account, where there is no $ sign at the end.

I know what you're asking.  Why oh why, dont' they just make another switch to say that it's a machine account?  Well, like always, that would be too easy now wouldn't it?

Wednesday, July 25, 2007

Regex Using Findstr

OK, so I'm reeeeally busy these days, and I only have time to post a lame-ass topic like this one.  I mean, who wouldn't know about using findstr to search for regular expressions in a file, right?

Anyway, to do that you all you have to do is pass the /R option, and put the regular expression as /C option argument.  Say you want to search for every lines in a file that begins with a Foo and ends with a Bar, with any string in between.

findstr /R /C:"^Foo.*Bar$" some.txt

To make it search recursively in a directory, use the /S option.  If you want to ignore case, use the /I option.

OK, gotta go.

Wednesday, July 18, 2007

Turning Off Screen Saver from Registry

I have found myself in a situation where I would like to have my screen saver deactivated.  I know, you can do it from the display property just fine, but sometimes I want to be able to do that from the command line so I can make that part of a setup script that I run for all my machines.

There are a couple of registry keys you need to tweak to turn it off.

HKCU\Control Panel\Desktop\ScreenSaveActive  :  "0"

HKCU\Control Panel\Desktop\SCRNSAVE.EXE      :  ""

The first one is the one that really matters.  You are basically turning it off just by setting that to 0.  But this will leave whatever default screensaver in the second registry, and that has the rather undesirable effect of showing that as the active screensaver if you open your display property.

Setting the second registry to an empty string basically just sets the active screensaver to none, which is consistent with how you would do it from the display property.

To actually set those values up in the command line, you can put the registry keys and values in a .reg file the way I did it in the Delayed Expansion article, or you can do it using the reg utility.

reg add "HKCU\Control Panel\Desktop" /v "ScreenSaveActive" /t REG_SZ /d "0" /f

reg add "HKCU\Control Panel\Desktop" /v "SCRNSAVE.EXE" /t REG_SZ /d "" /f

The /v switch specified the value (not to be confused with data), /t specifies the type of that value, and /d specifies the data you want to set the value to.  We also want to do /f so it does not prompt us when it sees that the values already exist.  Do a reg /? to find out more about the reg command.  And to check if they're indeed modified, you can use the reg query option.

reg query "HKCU\Control Panel\Desktop" /v "ScreenSaveActive" /t REG_SZ

reg query "HKCU\Control Panel\Desktop" /v "SCRNSAVE.EXE" /t REG_SZ

The meaning of the switches are as before, except this time we don't use the /d and /f option.  If you need to check if a particular value is set or not, you can simply pipe it to a findstr and see if it finds the value you are looking for.  For easiest result, you might want to use the findstr regular expression option.

reg query "HKCU\Control Panel\Desktop" /v "ScreenSaveActive" /t REG_SZ | findstr /R /C:"ScreenSaveActive.*0"

if "%errorlevel%" EQU "0" (

  echo Screensaver is off

) else (

  echo Screensaver is on

)

As you can see, reg is a pretty useful command to get and set registry keys and as long as you know what to change, it will get the job done for you.

Friday, July 13, 2007

Dynamic Variable Name

Sometimes I find myself missing the ability to use arrays in batch scripts. OK, a lot of times. So to get around that, I often have to resort to using variable names that start with the same string and end with numbers. Like VAR1, VAR2, etc.

Now I know that is lame but it gets the job done. But what if I want to create these variables dynamically? I mean, if I want to read a file, and assign each line in the file to a separate variable, I need to know how many lines there are in the file, otherwise I'm screwed. Right? Well, not quite.

What I find is, some people quite often missed the fact that you can use variable expansion as a name of a new variable in batch. I guess that's because it's rather counter intuitive from other programming languages.

Anyway, the following example reads a file, assign each line to a new variable name and prints the file back, line by line, in reverse order.

@echo off

setlocal enabledelayedexpansion

set COUNT=0

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

  set /A COUNT=!COUNT! + 1

  echo %%n

  set LINE!COUNT!=%%n

)

for /L %%c in (!COUNT!,-1,1) do (

  echo !LINE%%c!

)

First of all, you need to enable delayed expansion for this to work (see my earlier post about that). Then you want to treat everything on one line as one value, so you use the "tokens=*" option for the firs for loop.

In that first loop you maintain a count, and create a new variable named LINE!COUNT! in each iteration, and assign the whole line to that new variable. See how simple that was?

The second loop just iterates through the count backward using the for /L option, and print the variables in that reverse order. Now take a look at how I'm printing the line. I use !LINE%%c! which uses both the ! and the % to allow the proper expansion of the variable name.

You can also replace the second loop with some other thing like string replacement.

echo. & echo Replace all 'h' with 'y' & echo.

for /L %%c in (1,1,!COUNT!) do (

  set CURRLINE=!LINE%%c!

  set CURRLINE=!CURRLINE:h=y!

  echo !CURRLINE!

)

Voila! Quick and dirty poorman's implementation of UNIX tr command. Of course you don't need to even save the lines to their own separate variables to do that. You can just do the replacement as you read the lines, and print the line out all in the same loop iteration. But hey, that's why they're called examples.

Wednesday, July 11, 2007

Never Edit A Running Batch Script

If you have a long running batch script, you should be careful to never edit and then save the newly edited file while the script is running.

Consider the following simple example.

@echo off

:START

echo hello

pause

goto START

echo oops

goto :EOF

Now run it, and while it's waiting for your input to continue, open the file again, and remove the first line, @echo off and save the file.  Now once you give your input to let it continue, instead of executing the next line after pause, which is goto START, it executes the next line after that, so you will see this output.

hello

Press any key to continue . . .

oops

The reason it skips a line is because you just deleted a line.  The batch processor executes your script line by line, and it does not load your script in memory when it starts.  Instead, it remembers which line it needs to fetch next, and when it finishes executing the current line, it reads the file again and gets that next line.  Now obviously changing the content of the script file while you are executing it will give you undesirable execution order of your script.

Sunday, July 8, 2007

Environment Variable Editing

Say you have and environment variable, and you want to tweak the value, change it a little bit, get the first few letters, or whatever.  You can do this using command processor's built-in variable substring and string substitution feature.

This is really useful feature that I like to use all the time.  Here's an example of how I can compose my own date string in the YYYY.MM.DD format out of the regular %DATE% environment variable that I covered here.

set MY_YYYY=%DATE:~-4%

set MY_MM=%DATE:~4,2%

set MY_DD=%DATE:~7,2%

set MY_DATE=%MY_YYYY%.%MY_MM%.%MY_DD%

The first line takes the substring of %DATE% starting from the 4th character from the end, which gives me the year.  The second line takes the two characters starting from position 4.  Similarly on the third line, I got the two characters starting from position 7.  The last line simply concatenates them together into the format I wanted.

I can use substring to determine if today is Sunday for example.

if /I "%DATE:~0,3%" EQU "Sun" (

    echo Today is Sunday

)

So that shows you how to do substring.  Another cool usage of this is in doing string substitution.  Here's an example where I am replacing the year with my own string.

set MY_DATE=%DATE:2007=9999%

This will replace the year 2007 with 9999.  You can also simply leave the substitution string empty to remove a string from the variable value.

set MY_DATE=%DATE:2007=%

That will remove all occurrences of 2007 from the value.  I usually find this really handy to remove certain directories from my PATH.

You can also use this for testing if a certain string exists in a variable.  Take a look at the following example where I want to see if today is Sunday like the earlier example, but using substring.

if "%DATE:Sun=%" EQU "%DATE%" (

    echo No, today is not Sunday

) else (

    echo Today is Sunday

)

As you can see, what I did there is remove the string Sun from the date, and if it is successfully removed, it means today is Sunday, and the condition will become false.  If it does not find the string Sun, the condition will be true and we know today is not Sunday.

Friday, July 6, 2007

Never Set Errorlevel

What I mean is, don't ever have an environment variable named errorlevel. That's just a bad idea. Windows command processor will take your definition every time. This means if you set your errorlevel, subsequent check for any command's errorlevel will not work.

set errorlevel=0

call some_command_that_returns_non_zero.cmd

if "%errorlevel%" EQU "0" (

echo SUCCESS

) else (

echo FAIL

)

You will always get errorlevel set to 0 or whatever value you happen to have it set to. So the check in the above snippet will always return success.

Well, OK, I admit. When I said above that your subsequent errorlevel check will not work, I lied. The other way of checking errorlevel will still work.

set errorlevel=0

call some_command_that_returns_non_zero.cmd

if errorlevel 1 (

echo FAIL

) else (

echo SUCCESS

)

That would still work. But just save yourself some trouble and stay away from it.

Perl In Batch Clothing

As someone who writes a lot of scripts, Perl is my language of choice. But it does not mean I can live without Windows batch. Sometimes though, it is necessary to combine the two by disguising a Perl script as a batch file.

When might this come in handy? Well, to be honest I can't really think of any right now, but this is certainly cool. This trick is not a batch file trick per se. It's really a Perl trick, and you'll see soon enough why that is. But I'd like to put it here just the same because hey, it's got some batch scripting in it.

To write your Perl script as a batch file, all you have to do is add a few lines at the top of your Perl script, rename the script extension to .bat or .cmd, and you're done.

@echo off

perl -x %~dpf0 %*

exit /b %errorlevel%

#!perl

use strict;

print "Hey $ARGV[0] I'm in Perl!\n";

exit 0;

This file, being a batch file, is executed by the command processor as any batch file. It reads each line and executes that line.

The first line is straightforward. The second line is really the trick. Doing perl -x tells Perl to read the file you pass as argument, %~dpf0, which is the complete path of the batch script, and ignore anything in the file until it finds the #!perl line. Then it treats it like any other Perl script.

The last argument on that line is simply passing all the command line arguments you pass to your batch script to the Perl as well.

So try and invoke the script. Let's say you named the script foo.bat.

foo.bat Batcheero

You'll see the following printed output.

Hey Batcheero I'm in Perl!

Like I said, not much real use I can think of, but it's nifty all the same.

Monday, July 2, 2007

Function Call In Batch

In any programming language, you can write functions or procedures to factor out a piece of code you use often.  In batch you can do the same.  Two ways you can do it.  You can create a separate batch file, and call it from inside the batch file you're scripting, or you can make it as an in-file function.

To call a separate batch file, it's really simple.  All you have to do is precede the batch name with the call operative, and you're good to go.  And example I like to use is to print a log entry both on console and into an output file.

REM PrLog.bat

REM  This batch file prints the message to the console and to an output file specified in the environment variable OUTP

echo %*

echo %* >> %OUTP%

REM End of file

Now every time we want to use this in a separate batch file all we have to do is call it after making sure you initialize the output file.

REM Initialize OUTP just once

set OUTP=%TEMP%\output.txt

del /y %OUTP%

REM Subsequently you just call the PrLog.bat with the message as arguments

call PrLog.bat Hello world 

Now this is nice and dandy, but you now have two files to watch for.  And if you need to copy your script, you need to make sure you don't forget to copy PrLog.bat too.

The more integrated way of doing this is to simply put the PrLog function in file.  To do this we need a label preceding the function, and end it with a goto :EOF or an exit /b.

REM Initialize OUTP just once

set OUTP=%TEMP%\output.txt

del /y %OUTP%

REM Subsequently you just call the :PRLOG function label with the message as arguments

call :PRLOG Hello world 

goto :EOF

REM End of main.

 

REM Start of function PrLog

:PRLOG

echo %*

echo %* >> %OUTP%

goto :EOF

REM End of function PrLog

Note that the batch processor treats the :PRLOG as its own batch context, meaning when you goto :EOF at the end of that function, you don't go to the end of the current batch file, but you simple exit the function's batch context, which essentially means a function return.  You can also do an exit /b from the function if you need to return a meaningful return value to the caller signifying success or fail.

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.