Batch file tricks ============================================ Subroutine Setup When you set up a new subroutine for calling, it is a good idea to make sure that there is only one entry point and one exit point. It is also a good practice to create a condition where the routine can return a variable. :SUBROUTINE !bat_echolog! call :SUBROUTINE [%*] set bat_retval= blah for /f blah do ( - You can't GOTO out of this loop - You can't !bat_return! out of this loop - So, do it like this: set bat_retval=failed ) if /i "!bat_retval!" equ "failed" (goto :SUBROUTINE_END blah ... :SUBROUTINE_END !bat_echolog! return-:SUBROUTINE [bat_retval=!bat_retval!] !bat_return! ============================================ Strip leading zeros set bat_source=010 set /a "bat_dest=1000!bat_source! % 1000" echo !bat_dest! ===> yields 10. For different number of digits, use 10^digits in place of 1000 :STRIPZEROS %in_val1% out_var1 -- converts in_val1 and sets out_var1 to the new value rem -- in_val1 [in] - zero leading number rem -- out_var1 [out] - variable name to set the output value to setlocal ENABLEDELAYEDEXPANSION enableextensions set bat_inval1=%~1 for /f "tokens=* delims=0" %%m in ("!bat_inval1!") do (set bat_nz=%%m) if not defined bat_nz ( set bat_outvar1=0 ) else ( set bat_outvar1=!bat_nz! ) ( ENDLOCAL & REM RETURN VALUES if "%~2" neq "" (set %~2=%bat_outvar1%) else echo.%bat_outvar1% ) EXIT /b ============================================ Batch file arithmetic To do any of these operators, use quotes around the expression. Note: the comma (,) can be used as a multi-expression separator as follows: set a/ "yyyy=100002011 % 10000, mm=10009 % 100, dd=10001 % 100" yields: yyyy=2011 mm=9 dd=1 () - grouping ! ~ - - unary operators set /a "!1" ===> 0 set /a "!2" ===> -3 set /a "-yyyy" ===> -2011 * / % - arithmetic operators (% is modulus[remainder]) + - - arithmetic operators << >> - logical shift set /a "1<<3" ===> 8 (same as 0b0000001 ===> 0b0001000) set /a "0x40>>2" ===>16 (same as 0b1000011 ===> 0b0010000) & - bitwise and set /a "0x43 & 0x40" ===> 64 (same as 0b00100011 & 0b00100000) ^ - bitwise exclusive or set /a "0x43 ^ 0x40" ===> 64 (same as 0b00100011 & 0b00100000 ===> 0b00000011) set /a "0x43 ^ 0x3" ===> 64 (same as 0b00100011 & 0b00000011 ===> 0b00100000) | - bitwise or set /a "0x43 ^ 0x8" ===> 64 (same as 0b00100011 & 0b00001000 ===> 0b00101011) = *= /= %= += -= - assignment &= ^= |= <<= >>= ============================================ As of Windows 2000, if Command Extensions are enabled, then there are several dynamic environment variables that can be expanded but which don't show up in the list of variables displayed by SET. These variable values are computed dynamically each time the value of the variable is expanded. If the user explicitly defines a variable with one of these names, then that definition will override the dynamic one described below: %=C:% expands to the current directory string on the C: drive %=D:% expands to the current directory string on the D: drive if drive D: has been accessed in the current CMD session %=ExitCode% expands to the hexadecimal value of the last return code set by EXIT /B %=ExitCodeAscii% expands to the ASCII value of the last return code set by EXIT /B if greater than 32 (decimal) %CD% expands to the current directory string (no trailing backslash, unless the current directory is a root directory) %__CD__% expands to the current directory string, always terminated with a trailing backslash Excellent resource for lots of batch files: http://www.robvanderwoude.com/batexamples.php @ECHO OFF SETLOCAL ENABLEDELAYEDEXPANSION FOR %%A IN (83 109 97 114 116 10 71 101 110 105 117 115) DO ( CMD /C EXIT /B %%A SET /P "Var=!=ExitCodeAscii!" < NUL ) ECHO. ENDLOCAL Produces: SmartGenious ============================================ redirecting stderr example: dir asdfasdfasd 1>nul 2>nul The trick is the "space 2>nul". This is what redirects stderr. Example, if you want it to not show the error, you could just: dir "asdasdfasdf" 2>nul Volume in drive C is 640gb Volume Serial Number is 6054-DE9A Directory of C:\Util dir "asdfasdf" 1>nul or dir "asdfasdf" >nul File Not Found Here's another example. In this case, you're doing a dir command and piping its output to the FIND command. However, if the directory doesn't exist, the error message is not displayed. for /f "usebackq tokens=*" %%a in (`dir "!bat_sourcedir!" 2^>nul^|find /i /c "1,228,820"`) do ( echo %%a ) or try this... for /f "usebackq tokens=*" %%a in (`dir "%bat_startlocation%\ConfirmedFiles" /s/b /ad /on 2^>nul`) do ( echo %%a ) ============================================ COMMENTS Use the colon "::" as a comment - but not inside code blocks with parenthesis. Can't use this in a "for" loop. Use REM instead. the "::" technique is not a supported comment. MULTI-LINE COMMENTS goto :ENDCOMMENT1 if exist "%file%" ( copy "%file%" "%dest%" ) else ( *** %file% does not exist! ) :ENDCOMMENT1 END OF LINE COMMENT use the &rem comment syntax at the end of a line. REM WITH REDIRECTION Problem using REM to comment out a line that uses redirection: REM dir > logfile.log ============================================ RETURNING VALUES FROM BATCH set RetVal= set RetVal1= set RetVal2= SETLOCAL ENABLEEXTENSIONS ENABLEDELAYEDEXPANSION rem... Then do the work of the batch file... setting RetVal stuff rem along the way. Then, when you're ready to end the script rem and return values, to the calling program. The calling program rem uses %%Errorlevel%% to determine which success (%%errorlevel%%=0) rem or the reason for the failure in the exit code. rem rem There are issues of defining the retval variables before rem the SETLOCAL call. I don't know all the particulars. echo Note: Return values are in RetVal environment variables RetVal, RetVal1 rem Same as: rem ENDLOCAL & SET RETVAL1=%INIVALUE% & SET RETVAL=%KEY%=INIVALUE% ( ENDLOCAL set RetVal1=%INIValue% set RetVal=%key%=%inivalue% ) exit /B 0 echo Note: Return values are in RetVal environment variables RetVal, RetVal1 ( ENDLOCAL set RetVal1=%INIValue% set RetVal=%key%=%inivalue% ) EXIT /B 3 Because the shell evaluates environments variables in a command before executing it, you could return var1 and var2 by changing the endlocal (above) to: endlocal & set /a var1=%var1% & set /a var2=%var2% The shell interprets this before execution as: endlocal & set /a var1=4 & set /a var2=8 see (http://technet.microsoft.com/en-us/library/bb490898.aspx) ============================================ BLOCK STANDARD INPUT SUPPRESS OUTPUT Use REM combined with piping to block standard input (http://www.robvanderwoude.com/comments.php) REM | CHOICE /C:AB /T:A,5 > NUL SUPPRESS OUTPUT redirect to the nul: device. However, some programs output to the standard error device. To suppress that, use: ...>nul: 2>&1 Example: pslist>nul: 2>&1 This works because STDOUT is file descriptor 1, and STDERR is file descriptor 2 by convention. (0 is STDIN). The "2>&1" copies output file descriptor 2 from the new value of 1, which was redirected to the nul device. ============================================ CHANGE DIRECTORY (PUSHD, POPD, NETCMD) PUSHD Change directory and remembe where it changed from. Also, works across network locations. c:\>PUSHD "c:\mydocuments\my pictures" c:\MyDocuments\My Pictures>cd 2011 c:\MyDocuments\My Pictures\2011> POPD Then, use POPD to return to the previous location, like this: c:\MyDocuments\My Pictures\2011>POPD c:\> PUSHD with UNC It works on network drives, kind of like NETCMD.EXE does. by mapping to an availabe drive letter. However, it isn't smart enough to not make another connection to the same share. Good news is that POPD removes the connection from the NET USE list. Look at these examples: c:\>PUSHD "\\server\c$\mydocuments\my pictures" Z:\MyDocuments\My Pictures>PUSHD "\\server\c$\mydocuments\temp" Y:\MyDocuments\Temp>NET USE Status Local Remote Network ------------------------------------------------------------------------------- OK Y: \\dsamuelstablet\c$ Microsoft Windows Network OK Z: \\dsamuelstablet\c$ Microsoft Windows Network The command completed successfully. POPD NET USE Status Local Remote Network ------------------------------------------------------------------------------- OK Z: \\dsamuelstablet\c$ Microsoft Windows Network The command completed successfully. POPD NET USE There are no entries in the list. NETCMD NETCMD was written in C with the Win32API a long time ago. It was designed to augment how the PowerTools CmdShell tool worked. CmdShell didn't work on network locations. NETCMD works on local and network paths. Just right-click and select NETCMD and a new command shell will be created with the directory starting at the directory that you right-clicked on. NETCMD scans the NET USE list and if the share already exists in the list, it does not create a new connection. If it doesn't exist, it creates a new connection to the share and then navigates to the directory. A shortcut to NETCMD.exe could be placed in "Send To" directory. To make it available in the right-click menus. NETCMD can be used this way in any File Open/Save dialog. NETCMD also sets the title bar to the directory. ============================================ To use a literal (like a quote) use the ^ escape character. For example: echo "AT %sched_time% /interactive "%*^"" Will echo AT 12:01 /interactive "%*" ============================================ Use the CALL command to fix variable substitution: set %variableNamePrefix%!arrayIndex!=somevalue somtimes doing this fixes problems... i don't know why call set %variableNamePrefix%!arrayIndex!=somevalue ============================================ Remember that the SET /A command uses integer math. It wraps at 2^30. For example: set /A junk="(2<<30)-1" ==> 2,147,483,647 set /A junk="(2<<30) " ==> -2,147,483,648 set /A junk="(2<<30)+1" ==> -2,147,483,647 ============================================ Delayed evaluation can cause problems: Notice this command line produced this: 1073741824 !junk! 1,073,741,824 However, without the !!, it worked like this: set /A junk="(2<<29)"&echo.&echo %junk%&echo.&parsenumberwithcomma.bat %junk% [ 2:59:16.92] C:\Util> set /A junk="(2<<29)"&echo.&echo %junk%&echo.&parsenumberwithcomma.bat %junk% 1073741824 1073741824 1,073,741,824 [ 2:59:21.41] C:\Util> set /A junk="(2<<28)"&echo.&echo %junk%&echo.&parsenumberwithcomma.bat %junk% 536870912 1073741824 1,073,741,824 Notice the delay of the evaluation of %junk% until after the whole command line had been complete processed. ============================================ multi-line statement blocks can't end a block with a "::" style comment: The following statements fails with this error ") was unexpected at this time." for %%a in (*.*) do ( @echo xcopy %1 %2 :: xcopy %1 %2 ) Change it to this: for %%a in (*.*) do ( :: xcopy %1 %2 @echo xcopy %1 %2 ) Note: Sometimes i've seen this same behavior with a blank line as the last line of a block ============================================ Tip: You can test multi-line FOR and IF commands by using the "(" then hitting ENTER it will give you a prompt like this: More? Here's an example: [ 3:10:10.41] C:\Util>@echo off [ 3:10:13.00] C:\Util>for /L %a in (1 1 2) do ( More? echo %a More? set /a junk=%a*2 More? echo !junk! More? ) 1 2!junk! 2 4!junk! ============================================ Use delayedExpansion ============================================ Make a debugger: What's cool about this is you can enter commands from the breakpoint prompt. I'm still having trouble with ending the batch file at the prompt. Bascially, I have to just run a badly formed FOR loop to exit. One bit of improvement would be to separate the debugging from tracing. Example: There is a line below with %BREAK% tracing is !tracing!: At runtime, if !TRACING! isn't ON, it will just skip it as if a comment. However, if !TRACING! is ON, it will act like this: [ 3:20:58.24] Break. Enter Command or "(Q)uit"] tracing is ON: From there, you can hit enter and run to the next breakpoint. Or, if you like, you can enter batch commands, like this: [ 3:20:58.24] Break. Enter Command or "(Q)uit"] tracing is ON:set TRAC TRACEECHO=echo !time! TRACESET=SET TRACING=ON You can even do things like this. It allows you to run the program without noise from execution until you hit the breakpoint. Then you can turn echo on at the area you're trying to debug, hit enter and run to the next breakpoint, and then enter: echo off. [ 3:20:58.24] Break. Enter Command or "(Q)uit"] tracing is ON:echo on Here's two tricks demonstrated: You can turn debugging off. Also you can see that you can call other subroutines, issue dos commands, etc. And it will turn tracing/debugging off and continue running the program [ 3:20:58.24] Break. Enter Command or "(Q)uit"] tracing is ON:set TRACING=OFF [ 3:21:04.24] Break. Enter Command or "(Q)uit"] tracing is ON:call SETUPTRACING set return=goto :EOF set return_notrace=goto :EOF :SETUPTRACING if /I {!TRACING!} == {ON} ( set TRACEECHO=echo !time! set TRACESET=SET set BREAK=call :BREAKPOINT ) ELSE ( set TRACEECHO=rem . set TRACESET=rem . set BREAK=rem . ) ::%TRACESET% %BREAK% tracing is !tracing!: :: These next lines are a little trick that allows you to comment-out :: lines of code and stuff inside for loops and other statement :: blocks set COMMENT=set comment= set //=set comment= %return_notrace% :BREAKPOINT :BREAKPOINT2 set BREAKPOINT_COMMAND= SET /P BREAKPOINT_COMMAND=[%TIME% Break. Enter Command or "(Q)uit"] %* if /I {"!BREAKPOINT_COMMAND!"} == {"q"} ( set TRACEECHO=goto :EXIT !TRACEECHO! DONE ) if /I {"!BREAKPOINT_COMMAND!"} NEQ {""} ( set BREAKPOINT_COMMAND2=!BREAKPOINT_COMMAND! !TRACEECHO! BREAKPOINT !BREAKPOINT_COMMAND2! goto BREAKPOINT2 ) else ( %return_notrace% ) %return_notrace% :EXIT IF /i ())() 9)(echo (((()()() ) EXITING WITH PREJUDICE ============================================ You can't depend on being able to parse the /? switch. This is because if you have a statement like this in your batch script: CALL xyz %1 or %* It will pass the /? to the CALL command and give you the HELP text for CALL - as if you entered CALL /? from the command line. The workaround is like this: echo abc %*|find /i "/?">nul: if %errorlevel% equ 0 goto :HELP ============================================ You can use the FOR loop expansion options (like %%~zI to get the file size) inside a CALL. You can also substitue the argumet number, like %~f0. For example: cd \ call :TEST "boot.ini" %return% :TEST echo %1, %~f1, %~d1 %return% Yielded this: "boot.ini", C:\boot.ini, C: ============================================ Here's how I parse out command line arguments. update 2007_12_15. A possible better solution would be to use the SHIFT command: pseudo code: FOR ... ( set a+=1 SET ARG!a!=%1 SHIFT ) I do this within :_ASTART :_ASTART SET RETURN=goto :RETURN SET RETURN_NOTRACE=goto :EOF SET TRACEECHO=:: setlocal enabledelayedexpansion ENABLEEXTENSIONS if errorlevel 1 ( echo ERRORLEVEL=%errorlevel%. Unable to enable delayed variable expansion. %return% ) :: CMD_ARGS will be the first argument with any quotes removed SET CMD_ARGS=%* set cmd_arg0=%~f0 SET HELP=FALSE :: To use the tracing feature, change the TRACING variable to either :: ON or OFF :SET_TRACING set TRACING=OFF CALL :SETUPTRACING CALL :SAVE_CMD_ARGS CALL :CHECK_FOR_HELP if /I {%HELP%} == {TRUE} (goto HELP) CALL :INITIALIZE GOTO :MAIN :SAVE_CMD_ARGS !TRACEECHO! :SAVE_CMD_ARGS set CMD_ARGC=0 set /A count=1 ::call :CMD_ARG_ENTRY "%0" FOR /F "usebackq tokens=1-26" %%a in ('%cmd_args%') do ( call :CMD_ARG_ENTRY "%%a" call :CMD_ARG_ENTRY "%%b" call :CMD_ARG_ENTRY "%%c" call :CMD_ARG_ENTRY "%%d" call :CMD_ARG_ENTRY "%%e" call :CMD_ARG_ENTRY "%%f" call :CMD_ARG_ENTRY "%%g" call :CMD_ARG_ENTRY "%%h" call :CMD_ARG_ENTRY "%%i" call :CMD_ARG_ENTRY "%%j" call :CMD_ARG_ENTRY "%%k" call :CMD_ARG_ENTRY "%%l" call :CMD_ARG_ENTRY "%%m" call :CMD_ARG_ENTRY "%%n" call :CMD_ARG_ENTRY "%%o" call :CMD_ARG_ENTRY "%%p" call :CMD_ARG_ENTRY "%%q" call :CMD_ARG_ENTRY "%%r" call :CMD_ARG_ENTRY "%%s" call :CMD_ARG_ENTRY "%%t" call :CMD_ARG_ENTRY "%%u" call :CMD_ARG_ENTRY "%%v" call :CMD_ARG_ENTRY "%%w" call :CMD_ARG_ENTRY "%%x" call :CMD_ARG_ENTRY "%%y" %TRACESET% cmd_arg ) %TRACESET% cmd_arg %return% :CMD_ARG_ENTRY !TRACEECHO! :CMD_ARG_ENTRY set cmd_arg_temp=%~1 IF {!cmd_arg_temp!} == {} %return% SET /A CMD_ARGC=!CMD_ARGC!+1 SET CMD_ARG!CMD_ARGC!=!cmd_arg_temp! %RETURN% ============================================ get the date: The easiest is to use %date% or %time%. For example: echo %date%, %time% yields this: Mon 03/07/2011, 10:34:55.49 Another trick is to do it this way, though I can't remember why I didn't use the first method: echo.|date The current date is: Sun 12/16/2007 Enter the new date: (mm-dd-yy) The trick here is that the echo.| presses enter for you. Can do the same with time or cd, etc. echo.|time The current time is: 0:15:15.09 Enter the new time: ============================================ If you want to answer Y, try this: echo Y|chkdsk /F ============================================ If you need "Y", and then hit "Enter", try this: echo Y>input.inp for the Y echo.>>script.txt for the ENTER type script.txt|chkdsk /F ============================================ To get system information, try using wmic.exe ============================================ To manipulate the registry, try using the REG.exe command supports add, remove, import, export, etc ============================================ to get a volume label, use the vol command: vol d: ============================================ use VER to get the windows version. WMIC will give you more. ============================================ for regular expression string searching, use FINDSTR ============================================ Sleeping or waiting. @echo off echo waiting 5 seconds... echo wscript.sleep 5000>sleep.vbs start /w wscript.exe sleep.vbs echo done del sleep.vbs also. you may just want to wait for a program by launching it differently. try this: start /w otherProgram.exe ============================================ to wait for a cdrom or floppy, try this ( can be used on USB drives too) wmic ============================================ CALL Calls one batch program from another. Can also call any executable CALL [drive:][path]filename [batch-parameters] batch-parameters Specifies any command-line information required by the batch program. If Command Extensions are enabled CALL changes as follows: CALL command now accepts labels as the target of the CALL. The syntax is: CALL :label arguments A new batch file context is created with the specified arguments and control is passed to the statement after the label specified. You must "exit" twice by reaching the end of the batch script file twice. The first time you read the end, control will return to just after the CALL statement. The second time will exit the batch script. Type GOTO /? for a description of the GOTO :EOF extension that will allow you to "return" from a batch script. In addition, expansion of batch script argument references (%0, %1, etc.) have been changed as follows: %* in a batch script refers to all the arguments (e.g. %1 %2 %3 %4 %5 ...) Substitution of batch parameters (%n) has been enhanced. You can now use the following optional syntax: %~1 - expands %1 removing any surrounding quotes (") %~f1 - expands %1 to a fully qualified path name %~d1 - expands %1 to a drive letter only %~p1 - expands %1 to a path only %~n1 - expands %1 to a file name only %~x1 - expands %1 to a file extension only %~s1 - expanded path contains short names only %~a1 - expands %1 to file attributes %~t1 - expands %1 to date/time of file %~z1 - expands %1 to size of file %~$PATH:1 - searches the directories listed in the PATH environment variable and expands %1 to the fully qualified name of the first one found. If the environment variable name is not defined or the file is not found by the search, then this modifier expands to the empty string The modifiers can be combined to get compound results: %~dp1 - expands %1 to a drive letter and path only %~nx1 - expands %1 to a file name and extension only %~dp$PATH:1 - searches the directories listed in the PATH environment variable for %1 and expands to the drive letter and path of the first one found. %~ftza1 - expands %1 to a DIR like output line In the above examples %1 and PATH can be replaced by other valid values. The %~ syntax is terminated by a valid argument number. The %~ modifiers may not be used with %* @echo 01 "%~0" @echo 02 "%~f0" @echo 03 "%~d0" @echo 04 "%~p0" @echo 05 "%~n0" @echo 06 "%~x0" @echo 07 "%~s0" @echo 08 "%~t0" @echo 09 "%~z0" @echo 10 "%~$path:0" @echo 11 "%~dp0" @echo 12 "%~nx0" @echo 13 "%~dp$path:0" @echo 14 "%~ftza0" ============================================ Reboot tricks: To REBOOT: shutdown -r -f -t 1 To SHUTDOWN: shutdown -s -f -t 1 ============================================ LOOP FOREVER for /L %%a in (0,0,0) do ( commands ) ============================================ LIST DRIVES wmic /Node:instructor51 LogicalDisk where DriveType="3" get deviceID, DriveType, filesystem, freespace,size /format:csv ============================================ Remember Reinhard Irnberger's PUSHD trick to automatically map a drive if a UNC path is specified? I use PUSHD "%~dp0" ever since, as the first command in any batch file that could be run from a UNC path. Without this PUSHD command, the start/working directory of the batch file will be changed to %windir%\System32. This change of working directory also occurs when a batch file is running in elevated mode in Windows Vista, Windows Server 2008 or Windows 7. It occurs too when a script is started with RUNAS or PSEXEC. I suppose that's because my current/working directory is saved in my "volatile environment" in my profile, and thus is not available in the elevated user's environment. So there you have it, one more reason to specify (as opposed to assume) the working directory in your batch files. I think PUSHD "%~dp0" is by far the most reliable way to do this, and it will work with RUNAS, PSEXEC, UNC paths, UAC and Explorer's "Run as..." option. ============================================ ============================================ ============================================ ============================================ ============================================ ============================================ ============================================ ============================================ ============================================ ============================================ ============================================