In this section you will find some of the more complex scripts that I have used over the years. They require a greater understanding of how to use some of the utilities and features in the Windows command line interface and how to manipulate the data. There is also a greater use of logic and a little bit of trickery in places, which I will point out where appropriate.
Doing a scan of the network
I did this script to provide a simple way of finding out which IP addresses are in use on a given subnet. The script simply pings each IP address in the specified range and reports which ones are currently alive.
@echo off setlocal ENABLEDELAYEDEXPANSION if "%1" == "" goto error if "%2" == "" ( (for /l %%a in (1,1,255) do ping -w 100 -n 1 %1.%%a|find /i "reply">nul&&echo %1.%%a is alive) ) else ( if "%3" == "" goto error if "%2" GTR "254" goto error if "%3" GTR "255" goto error (for /l %%a in (%2,1,%3) do ping -w 100 -n 1 %1.%%a|find /i "reply">nul&&echo %1.%%a is alive) ) goto end :error echo. echo The subnet prefix is required echo Command syntax is: echo. echo netscan x.x.x start stop echo. echo Where: echo x.x.x is the subnet prefix, e.g. 192.168.22 echo start is the start address (optional, must have stop and be less than 255) echo stop is the stop address (optional, must be 255 or less and must follow start) echo. :end
The output from the script is something like this:
C:\>netscan 192.168.1 192.168.1.2 is alive 192.168.1.5 is alive 192.168.1.10 is alive 192.168.1.101 is alive 192.168.1.249 is alive 192.168.1.254 is alive 192.168.1.255 is alive
Checking the time
This script simply checks the time on a list of computers. I did this script the check whether time synchronisation was working on the servers. The script simply does the ‘net time’ command on all the hosts in the SERVERS.TXT file and reports them in the window. The window is also resized with the ‘MODE CON’ function to allow all the hosts to be visible.
@echo off setlocal ENABLEDELAYEDEXPANSION :start for /f %%a in ('type servers.txt^|find /v /c ""') do set /a numservers=%%a+1 mode con lines=!numservers! echo Getting time from hosts, please wait for /f %%a in (servers.txt) do (for /f "tokens=7,8 delims= " %%b in ('net time \\%%a') do echo %%a %%b %%c) set /p cont=Enter 'X' to exit or just Enter to refresh: if !cont! == X goto end if !cont! == x goto end goto start :end
It would be very easy to customise this script to write the output to file or use the ‘NET VIEW’ command as input instead of a static file with a list of hosts. The output from this script looks something like this:
Getting time from hosts, please wait SERVER01 12:48 PM SERVER02 12:48 PM SERVER03 12:48 PM SERVER04 12:48 PM SERVER05 12:48 PM SERVER06 12:48 PM SERVER07 12:47 PM SERVER08 12:48 PM SERVER09 12:48 PM Enter 'X' to exit or just Enter to refresh:
Calculating what tomorrow’s date is
This is one of those scripts that turned out to be a little more complex than I had expected. Basically, it tells you what tomorrow’s date is, which I believe would only be useful as part of a more purposeful script.
@echo off setlocal ENABLEDELAYEDEXPANSION :: Extract the day, month and year from the date variable for /f "tokens=2,3,4 delims=/ " %%a in ("%date%") do ( set day=%%a set month=%%b set year=%%c ) :: Note - the sequence of the above will depend on your date format, this is for dd/mm/yyyy :: Remove preceding zero if !day! LSS 10 set day=!day:~1! if !month! LSS 10 set month=!month:~1! set newday= :: Calculate whether it's leap year set leap=no set /a tmpvar1=!year!/4 set /a tmpvar2=!tmpvar1!*4 if "!tmpvar2!" == "!year!" set leap=yes set /a tmpvar1=!year!/100 set /a tmpvar2=!tmpvar1!*100 if "!tmpvar2!" == "!year!" set leap=no set /a tmpvar1=!year!/400 set /a tmpvar2=!tmpvar1!*400 if "!tmpvar2!" == "!year!" set leap=yes :: Now we do the real work if "!month!" == "2" ( if "!leap!" == "yes" if !day! == 29 set newday=1&set /a month=!month!+1 if "!leap!" == "no" if !day! == 28 set newday=1&set /a month=!month!+1 ) else ( for %%a in (4 6 9 11) do if !month! == %%a if !day! == 30 set newday=1&set /a month=!month!+1 for %%a in (1 3 5 7 8 10 12) do if !month! == %%a if !day! == 31 set newday=1&set /a month=!month!+1 ) :: Increment year if the end of December if not "!newday!" == "1" set /a newday=!day!+1 if "!month!" == "13" ( set month=1 set /a year=!year!+1 ) :: Re-add preceding zero if !newday! LSS 10 set newday=0!newday! if !month! LSS 10 set month=0!month! echo Tomorrow's date is: !newday!/!month!/!year!
Do an action when an event is logged
This is a simple batch file that watches the eventlog for a specific event. When the specified event occurs, it will run a batch file. It should be noted that this script can be used to form the basis of a broader server monitoring script. It can be used to monitor all sorts of things and can also spawn just about any action. I will hopefully be adding such a monitoring script (or set of scripts) in the advanced examples sometime in the future.
All you should need to do with this is change the variables that are set at the beginning of the script.
@echo off setlocal ENABLEDELAYEDEXPANSION :: Set variables to make customisation easier set eventid=26 set source=Application Popup set log=System set targethost=localhost set pollinterval=60 set searchstring=refreshed set action=start mybatch.cmd :: Now we start the loop set firstrun=yes :start :: This FOR loop will extract any events and set a timestamp for the last one. It also stores the content in a variable. for /f "tokens=1,2,9 delims= " %%a in ('dumpel -l !log! -s \\!targethost! -d 1 -m "!source!" -e !eventid!') do ( set timestamp=%%a%%b set content=%%c ) :: Check if any new events have occurred. if not "!firstrun!" == "yes" if not "!timestamp!" == "!lastevent!" ( echo %%c|find /i "!searchstring!">nul :: If the content contains the string specified in the searchstring variable, run the batch file if errorlevel 1 !action! ) set firstrun= set lastevent=!timestamp! sleep !pollinterval! goto start
Re-organizing files
I wrote this script to help with the naming of sequential files that had become dispersed, namely pictures from a digital camera. My problem was that when taking photos, the file names where numbered sequentially, but usually one deletes some along the way leaving gaps in the sequence. Being a bit of a perfectionist I decided to find a way of renaming the files to be sequential again. The result is this script:
@echo off setlocal ENABLEDELAYEDEXPANSION :start if "%3" == "" ( cls echo Usage: echo. echo FILESORT EXT ORDER PREFIX echo. echo EXT - The extension of the files to rename echo ORDER - Order of the files where D=date and N=name use a '-' for reverse order echo PREFIX - The prefix to use for the file names echo. echo Description echo ----------- echo This script is intended for renaming files in either name or date order with a echo specified prefix and an incremental suffix in the name. A single file type is echo done only. A filter can be specified in interactive mode to narrow the scope echo of the files that are renamed. This can be useful if you only want to rename echo files that contain a particular string, like a year or month. echo. echo When run in interactive mode, the files that are to be renamed are listed echo before the operation is performed. An UNDO file is also created in case you echo need to go back a step. Beware though, it's only one level. echo. echo If run with arguments, all three arguments are required and must be in the echo order specified above. The filter feature is only available in interactive mode echo. echo Example 1 - FILESORT jpg -n My holiday ^(use a - sign for reverse order^) echo Example 2 - FILESORT log d websec99 echo. set arg1= set arg2= set arg3= set /p arg1=Which file extension? call :filesexist set /p arg2=Which order? call :setorder set /p arg3=What is the prefix? set /p arg4=Use filter? ^(Press ENTER if no filter is required^) echo. if "!arg4!" == "" set arg4=* ) else ( for /f "tokens=1,2* delims= " %%a in ('echo %*') do ( set arg1=%%a set arg2=%%b set arg3=%%c ) call :filesexist set arg4=* call :setorder ) :checkargs if "!arg2:~0,1!" == "-" ( if /i "!arg2:~1!" == "n" call :verify decending name if /i "!arg2:~1!" == "d" call :verify decending date ) else ( if /i "!arg2!" == "n" call :verify ascending name if /i "!arg2!" == "d" call :verify ascending date ) echo. if "!arg4:~0,1!" == "-" ( set search=dir /b /!order! "*.!arg1!"^|find /v "!filter!" ) else set search=dir /b /!order! "!arg4!.!arg1!" :ask2 set /p ask2=Display summary before renaming the files? (Y/N) if /i "!ask2!" == "y" ( call :getpadding set incr=0 for /f "tokens=* delims= " %%a in ('!search!') do ( set /a incr+=1 if !incr! == 10 set padding=!padding:~0,-1! if !incr! == 100 set padding=!padding:~0,-1! if !incr! == 1000 set padding=!padding:~0,-1! if !incr! == 10000 set padding=!padding:~0,-1! echo %%a --^> !arg3!!padding!!incr!.!arg1! ) ) else if /i not "!ask2!" == "n" ( goto ask2 ) :ask3 set /p tmpvar=OK to rename files? (Y/N) if /i "!tmpvar!" == "y" ( call :getpadding set incr=0 ( echo @echo off echo setlocal ENABLEDELAYEDEXPANSION echo :start echo set /p var=Are you sure you want to undo the rename? ^(Y/N^) echo if /i "^!var^!" == "n" ^( echo goto end echo ^) else if /i not "^!var^!" == "y" goto start )>undo.cmd for /f "tokens=* delims= " %%a in ('!search!') do ( set /a incr+=1 if !incr! == 10 set padding=!padding:~0,-1! if !incr! == 100 set padding=!padding:~0,-1! if !incr! == 1000 set padding=!padding:~0,-1! if !incr! == 10000 set padding=!padding:~0,-1! ren "%%a" "!arg3!!padding!!incr!.!arg1!"&&( echo %%a was renamed to !arg3!!padding!!incr!.!arg1! echo ren "!arg3!!padding!!incr!.!arg1!" "%%a"^&^&echo "!arg3!!padding!!incr!.!arg1!" was renamed back to "%%a">>undo.cmd ) ) echo :end >>undo.cmd echo set var=>>undo.cmd ) else if /i "!tmpvar!" == "n" ( goto end ) else goto ask3 goto end :verify if "!arg4!" == "*" ( echo !numfiles! files with the extension .!arg1! will be renamed to "!arg3!??.!arg1!" in %1 order by %2 ) else ( if "!arg4:~0,1!" == "-" ( for /f "tokens=1 delims=*" %%m in ('echo !arg4:~1!') do set filter=%%m for /f %%a in ('dir /b "*.!arg1!"^|find /v /c "!filter!"') do set numfiles=%%a ) else for /f %%a in ('dir /b "!arg4!.!arg1!"^|find /c "."') do set numfiles=%%a echo !numfiles! files matching the filter "!arg4!.!arg1!" will be renamed to "!arg3!??.!arg1!" in %1 order by %2 ) :ask1 echo. set /p tmpvar=Is this correct? (Y/N) if /i !tmpvar! == n ( goto start ) else if /i not !tmpvar! == y goto ask1 goto:eof :filesexist for /f %%a in ('dir /b *.!arg1!^|find /c "."') do ( set numfiles=%%a if %%a == 0 ( echo No files exist with the .!arg1! extension. Please check and try again. set /p arg1=Which file extension? goto :filesexist ) else if %%a LSS 2 ( set /p tmpvar=There is only 1 file with the .!arg1! extension, is this correct? ^(Y/N^) if /i "!tmpvar!" == "n" ( goto start ) else if /i not "!tmpvar!" == "y" goto filesexist ) ) goto:eof :setorder if /i "!arg2!" == "d" ( set order=od ) else if /i "!arg2!" == "n" ( set order=on ) else if /i "!arg2!" == "-d" ( set order=o-d ) else if /i "!arg2!" == "-n" ( set order=o-n ) else ( echo Invalid order, must be one of d, n, -d or -n. set /p arg2=Which order? goto setorder ) goto:eof :getpadding if !numfiles! LSS 100000 set padding=0000 if !numfiles! LSS 10000 set padding=000 if !numfiles! LSS 1000 set padding=00 if !numfiles! LSS 100 set padding=0 if !numfiles! LSS 10 set padding= goto:eof :end
looks like this when run without arguments:
Usage: FILESORT EXT ORDER PREFIX EXT - The extension of the files to rename ORDER - Order of the files where D=date and N=name use a '-' for reverse order PREFIX - The prefix to use for the file names Description ----------- This script is intended for renaming files in either name or date order with a specified prefix and an incremental suffix in the name. A single file type is done only. A filter can be specified in interactive mode to narrow the scope of the files that are renamed. This can be useful if you only want to rename files that contain a particular string, like a year or month. When run in interactive mode, the files that are to be renamed are listed before the operation is performed. An UNDO file is also created in case you need to go back a step. Beware though, it's only one level. If run with arguments, all three arguments are required and must be in the order specified above. The filter feature is only available in interactive mode Example 1 - FILESORT jpg -n My holiday (use a - sign for reverse order) Example 2 - FILESORT log d websec99 Which file extension? jpg Which order? d What is the prefix? My Holiday Use filter? (Press ENTER if no filter is required) 2004* 15 files matching the filter "2004*.jpg" will be renamed to "My Holiday??.jpg" in ascending order by date Is this correct? (Y/N) y Display summary before renaming the files? (Y/N) y 200403-001.jpg --> My Holiday01.jpg 200403-002.jpg --> My Holiday02.jpg 200403-003.jpg --> My Holiday03.jpg 200403-006.jpg --> My Holiday04.jpg 200403-009.jpg --> My Holiday05.jpg 200403-010.jpg --> My Holiday06.jpg 200403-011.jpg --> My Holiday07.jpg 200403-012.jpg --> My Holiday08.jpg 200403-014.jpg --> My Holiday09.jpg 200403-018.jpg --> My Holiday10.jpg 200403-019.jpg --> My Holiday11.jpg 200403-020.jpg --> My Holiday12.jpg 200403-021.jpg --> My Holiday13.jpg 200403-025.jpg --> My Holiday14.jpg 200403-026.jpg --> My Holiday15.jpg OK to rename files? (Y/N) y 200403-001.jpg was renamed to My Holiday01.jpg 200403-002.jpg was renamed to My Holiday02.jpg 200403-003.jpg was renamed to My Holiday03.jpg 200403-006.jpg was renamed to My Holiday04.jpg 200403-009.jpg was renamed to My Holiday05.jpg 200403-010.jpg was renamed to My Holiday06.jpg 200403-011.jpg was renamed to My Holiday07.jpg 200403-012.jpg was renamed to My Holiday08.jpg 200403-014.jpg was renamed to My Holiday09.jpg 200403-018.jpg was renamed to My Holiday10.jpg 200403-019.jpg was renamed to My Holiday11.jpg 200403-020.jpg was renamed to My Holiday12.jpg 200403-021.jpg was renamed to My Holiday13.jpg 200403-025.jpg was renamed to My Holiday14.jpg 200403-026.jpg was renamed to My Holiday15.jpg
As you can see, it does quite a good job. What it does is rename all the files with sequential numbers, prefixed by the string you enter. The numbers are done with leading zeros, the number of which is determined by how many files there are. While the script does prompt you a number of times to be sure, it also creates an ‘undo’ batch file called ‘UNDO.CMD’. If you don’t like the way the files were renamed, simply run the undo file and the file names will revert to what they were before. Be careful with this one though because it could cause havoc if run in the wrong place.
Service Account Password Reset Script
The basic idea of this script is to change the password on a service account in Active Directory, then query all servers in the domain to find all the services that are configured to use the account. For every instance, the service is then reconfigured with the new password and optionally restarted.
My intention was to provide a mechanism for ensuring the security of the accounts and services by allowing the account credentials to be updated without having to log in to every server that runs services of a particular account. The password is automatically generated using the PWDGEN utility and is not recorded or displayed. This is because there is not really any reason for the password to be known. This of course, is a problem when installing new servers or services, so if this happens frequently the script can be easily altered to show the generated password so that it can be stored and used for new installations.
There is also an option to only report on the service account. This might be useful for identifying all the locations in which a particular service account is used. There is also an option to restart the service after it is reconfigured.
I have not had much luck getting administrators to embrace this script, mostly because they feel uncomfortable with the paradigm of managing service accounts in this way. I will admit, it really needs an astute admin to manage this effectively, but if it is embraced in the right way, there is probably no better way to secure the accounts and the services. The password is set at 24 characters, but that can be easily adjusted in the script.
The script and PWDGEN utility can be found on the downloads page.
@echo off setlocal ENABLEDELAYEDEXPANSION if "%1" == "" ( :getaccount set /p serviceaccount=Please enter desired service account: if "!serviceaccount!" == "" ( echo You must enter something! goto getaccount ) dsquery user -name !serviceaccount!|>nul find /i "!serviceaccount!" if errorlevel 1 ( echo Account not found, please enter a valid account name goto getaccount ) :gettask set /p task=Perform Report or Change? ^(R/C^): if /i not "!task!" == "r" if /i not "!task!" == "c" echo You must enter one of R or C!&goto gettask :getsvcrestart if /i "!task!" == "c" ( set /p svcrestart=Restart services? ^(Y/N^): if /i not "!svcrestart!" == "y" if /i not "!svcrestart!" == "n" echo You must enter one of Y or N!&goto getsvcrestart ) ) for /f %%a in ('pwdgen 24 1') do set newpw=%%a if /i "!task!" == "c" ( >nul 2>nul net user !serviceaccount! !newpw! /domain if errorlevel 1 ( echo ERROR - Could not change the account password, exiting. goto end ) else echo Service account password changed successfully. ) echo. echo Checking servers.... echo. for /f "skip=1" %%a in ('dsquery * -limit 10000 -filter "(&(objectClass=computer)(operatingSystem=Windows Server*))" -attr name') do ( for /f "tokens=1,3 delims=\ " %%b in ('wmic /node:%%a service get name^,startname^|find /i "!serviceaccount!"') do ( set servicename=%%b if /i "!task!" == "c" ( echo Account !serviceaccount! runs service !servicename! on %%a - setting password wmic /node:%%a service "!servicename!" call Change StartPassword=!newpw! |>nul find /i "ReturnValue = 0" if not errorlevel 1 ( echo %%a - Service startup properties changed successfully ) else echo %%a - ERROR! - Service startup properties failed if /i "!svcrestart!" == "y" ( echo Restarting service 2>nul wmic /node:%%a service "!servicename!" call StopService |>nul find /i "ReturnValue = 0" if errorlevel 1 echo WARNING - Service did not stop correctly or was already stopped, please check server. sleep 5 2>nul wmic /node:%%a service "!servicename!" call StartService |>nul find /i "ReturnValue = 0" if errorlevel 1 echo WARNING - Service did not start correctly, please check server. ) echo. ) else echo Account !serviceaccount! runs service !servicename! on %%a ) ) echo. echo Done. :end
That’s all the intermediate scripts I have for now. Please move on to my advanced examples to see what really can be done.