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.