Intermediate Scripting Examples

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.

Leave a Reply

Your email address will not be published. Required fields are marked *