AIMS:
Learn how to use empty variables in a FOR LOOP routine in a batch file.
PROBLEM:
I am using whether a variable is defined or not to determine the appropriate subroutine in my script; 2 variables defined == go do something. One is defined but not the other == do something else. Both not defined == something else again.
The script checks for C$ access and the presence of a certain file within the C$ of a text file list of networked PC's. If both criteria are met by 2 variables having data set, a simple xcopy updates the file in question by going to a certain subroutine and then the LOOP moves to the next PC on the network to try and do the same.
If there is an issue I want to use the variables in an empty state to then do something else; for example report to text file that c$ was not accessible or the file was missing meaning bad install etc.
However on my 2 test machines I am breaking the folder paths to trip the error reporting routines and finding something strange I can't fix without writing more lines of code. In my text file I have 2 PC's listed a few times e.g.
PC1
PC2
PC1
PC2
PC1
PC2
PC1 has a broken file path to test error logging
PC2 All fine to test file update process
When I run the script PC1 gets reported as having a problem and logs correctly.
PC2 all is fine and the update happens fine. BUT THEN it hits PC1 again, but seems to think [even though file path still broken] that it is OKAY --as if the variable is remembered from the previous loop and of course tries to update and has problems.
Here is the code I was trying to get to work using empty variable
#echo off
color 0E
pushd %~dp0
setlocal EnableDelayedExpansion
for /f "usebackq tokens=*" %%i in ("%~dp0hostnames.txt") do (
rem Test Access Admin Shares C$
if exist "\\%%i\C$\Windows\System32" set dollar=yes
rem Test Installation Integrity
if exist "\\%%i\C$\ProgramData\config.cfg" set install=ok
echo %%i
echo !dollar!
echo !install!
pause
IF !dollar!==yes IF !install!==ok (call :updatecfg %%i)
IF !dollar!==yes IF [!install!]==[] (call :installerror %%i)
IF [!dollar!]==[] (call :errorshare %%i)
)
echo THE END
pause
exit
:updatecfg
CLS
XCOPY "%~dp0config.cfg" /Y "\\%1\C$\ProgramData" & echo %1 Update Config.cfg Succeeded! & echo %1 Update Succeeded! >>"%~dp0logpass.txt"
ping 127.0.0.1 -n 3 >nul
goto :eof
:errorshare
CLS
echo.
echo %1 Has C$ Access Issues [Logging] & echo %1 Has C$ Access Issues >>"%~dp0logfail.txt"
ping 127.0.0.1 -n 3 >nul
goto :eof
:installerror
CLS
echo.
echo %1 Cannot Find Config.cfg^!^! [Logging] & echo %1 Cannot Find Config.cfg^!^! Not Installed or Configured^? >>"%~dp0logfail.txt"
ping 127.0.0.1 -n 4 >nul
goto :eof
If I add if not exist entried to the 2 at the start and have them set something when there is a problem then this works fine. But I'd like to know if this the right way to do this or should I also be able to use empty variables. I am nearly there it's just that they are not clearing properly per loop.
Many thanks.
....
for /f "usebackq delims=" %%i in ("%~dp0hostnames.txt") do (
rem Clear variables for each iteration
set "dollar="
set "install="
rem Test Access Admin Shares C$
if exist "\\%%i\C$\Windows\System32" set "dollar=yes"
rem Test Installation Integrity
if exist "\\%%i\C$\ProgramData\config.cfg" set "install=ok"
if defined dollar (
if defined install (
call :updatecfg %%i
) else (
call :installerror %%i
)
) else (
call :errorshare %%i
)
)
....
or
....
for /f "usebackq delims=" %%i in ("%~dp0hostnames.txt") do (
rem Test Access Admin Shares C$
if exist "\\%%i\C$\Windows\System32" ( set "dollar=yes" ) else ( set "dollar=" )
rem Test Installation Integrity
if exist "\\%%i\C$\ProgramData\config.cfg" ( set "install=ok" ) else ( set "install=" )
if defined dollar (
if defined install (
call :updatecfg %%i
) else (
call :installerror %%i
)
) else (
call :errorshare %%i
)
)
....
In any case, you should ensure the variables have the adecuated value before taking a decision based on their content.
Related
Okay here is what I got so far.
This is meant to add websites to block in the hosts file, as well as allow the user to delete the entries when they want to. When trying to add a website to block sometimes it creates a new line then puts the entry on the line before it. This is not what I want. I want it to create a new line then add the entry on that line. For some reason it works sometimes and other times it don't work at all. I get an error message that says Find parameter is incorrect. I am using the Find command to see if the entries is already in the hosts file. If it is I want it to avoid adding it. If it is not then I want to add it. When I try to delete a entry the batch just crashes, so I am not really sure what I am doing wrong here. I am trying to find the entry and replace it with nothing. What I really want to do is delete the entire line so that I don't end up with a lot of blank lines.
Any help is greatly appreciated. Thanks!
#echo off
TITLE Modifying your HOSTS file
COLOR F0
:LOOP
cls
SET "CHOICE="
ECHO Choose 1 to block a website
ECHO Choose 2 remove a blocked website
ECHO Choose 3 to exit
SET /P CHOICE= selection %CHOICE%
GOTO %CHOICE%
:1
cls
SET /P WEBSITE=Enter the name of the website to add:
SET HOME= 127.0.0.1
SET NEWLINE=^& echo.
SET BLOCKEDSITE=%HOME% %WEBSITE%
FIND /C /I %BLOCKEDSITE% %WINDIR%\system32\drivers\etc\hosts
IF %ERRORLEVEL% NEQ 0 ECHO %NEWLINE%^%BLOCKEDSITE%>>%WINDIR%\System32\drivers\etc\hosts
ECHO Website blocked
ping 127.0.0.1 -n 5 > nul
GOTO LOOP
:2
cls
SET /P WEBSITE=Enter the name of the website to remove:
SETLOCAL ENABLEEXTENTIONS DISABLEDELAYEDEXPANSION
SET "HOME= 127.0.0.1 "
SET "BLOCKEDSITE=%HOME% %WEBSITE%"
SET "REPLACE="
SET "HOSTSFILE=%WINDIR%\system32\drivers\etc\hosts"
FOR /F "DELIMS=" %%i IN ('TYPE "%HOSTSFILE%" ^& BREAK ^> "%HOSTSFILE%" ')
DO
(
SET "LINE=%%i"
SETLOCAL ENABLEDELAYEDEXPANSION
>>"%HOSTSFILE%" echo(!LINE:%BLOCKEDSITE%=%REPLACE%!
ENDLOCAL
)
ECHO Website unblocked
GOTO LOOP
:3
EXIT
Please note that the term website is misleading when referring to the entries of the hosts file. The entries of hosts file are used for custom mappings of DNS host names to IP addresses, and any host name that is present in the file does not necessarily hosts a website. Using the term website may lead to the false impression that something like http://www.example.com can be added to hosts file which is not true.
Skipping a host if it is already present in the hosts file:
The problem with your usage of find is that %BLOCKEDSITE% has embedded spaces so you should enclose it quotes and use:
FIND /C /I "%BLOCKEDSITE%" %WINDIR%\system32\drivers\etc\hosts
But it has another problem: Because of its dependency on the exact spacing between the IP address and host name which is mandated by %BLOCKEDSITE% It only works for the entries that are added by your batch file. Additionally the user may have commented out (disabled) an entry by placing # in the begging of the line that contains the entry, and your batch code will skip adding the host even if the entry is disabled.
This can be resolved by using findstr with its regex syntax. for example:
findstr /IRC:"^ *127\.0\.0\.1 *example\.com *$" "%WINDIR%\system32\drivers\etc\hosts"
Removing an entry from the hosts file:
In the FOR loop you just have to skip writing the lines that contains the specified entry:
if "!Line!"=="!LINE:%BLOCKEDSITE%=!" echo(!Line!>>"%HOSTSFILE%"
But again it is not accurate and is suffering from the same problems that are mentioned earlier for skipping adding the entry. Again By using findstr you can easily remove the lines that contain the unwanted entry:
findstr /VIRC:"^ *127\.0\.0\.1 *example\.com *$" "%HOSTSFILE%" > "%HOSTSFILE%.tmp"
del "%HOSTSFILE%"
ren "%HOSTSFILE%.tmp" "hosts"
With above mentioned points the script can be rewritten like this:
#echo off
setlocal EnableExtensions DisableDelayedExpansion
title Modifying your HOSTS file
color F0
set "HOSTSFILE=%WINDIR%\system32\drivers\etc\hosts"
set "HOME=127.0.0.1"
set "PROMPT_TEXT=Enter the host name to"
set "ACTION_TEXT[1]=add"
set "ACTION_TEXT[2]=remove"
set "FindEmptyLine=^ *$"
set "NewLineAppended="
cls
setlocal EnableDelayedExpansion
:LOOP
echo,
echo 1. Block a host
echo 2. Remove a blocked host
echo 3. Exit
choice /C "123" /N /M "Choose an item [1, 2, 3]: "
set "Item=%errorlevel%"
goto choice%Item%
:choice0 // User Pressed CTRL-C
:choice3
exit /b
:choice1
call :Common
set "HostEntry=!HOME! !HOST!"
findstr /IRC:"!FindEntry!" "!HOSTSFILE!"> nul && (
echo The host !HOST! is already blocked, No action taken.
) || (
if not defined NewLineAppended (
REM This will append a new line ONLY if the file does not end by LF character
type "!HOSTSFILE!" | findstr $ > "!HOSTSFILE!.tmp" && (
del "!HOSTSFILE!"
ren "!HOSTSFILE!.tmp" "hosts"
set "NewLineAppended=1"
)
)
echo !HostEntry!>>"!HOSTSFILE!"
echo The host !HOST! blocked
)
goto LOOP
:choice2
call :Common
findstr /VIR /C:"!FindEntry!" /C:"!FindEmptyLine!" "!HOSTSFILE!">"!HOSTSFILE!.tmp" && (
del "!HOSTSFILE!"
ren "!HOSTSFILE!.tmp" "hosts"
echo The host !HOST! unblocked
)
goto LOOP
:Common
set "HOST="
set /P "HOST=!PROMPT_TEXT! !ACTION_TEXT[%Item%]! (e.g. example.com): "
if not defined HOST (
(goto)2>nul
goto LOOP
)
set "FindEntry=^^ *!HOME! *!HOST! *$"
set "FindEntry=!FindEntry:.=\.!"
exit /b
I am trying to loop through a file which contains few names and search the same with two patterns in another file and echo some statements.
Like I have two files :
1.Installers.txt
2.Progress.txt
Installers.txt
abc.jar
def.jar
tef.jar
....
....
Progress.txt
abc.jar deployment started
abc.jar deployed successfully
def.jar deployment started
So my requirement is to read the Installers.txt file one line at a time and search for the 2 patterns "abc.jar deployment started" and "abc.jar deployed successfully" and report successful or else if both patterns are yet to be found to show as still in progress.
I have tried writing below but its failing at many things while doing pattern and the logic also does not look good. can someone help here.
for /F "usebackq delims=" %%a in ("Installer.txt") do (
set /A i+=1
call echo installing %%i%% : %%a
:NOTVALID
findstr /I "%%k\ in\ progress" %1%\progress.txt
If errorlevel 1 (
echo "installation still in progress.."
PING 127.0.0.1 -n 1 >NUL 2>&1 || PING ::1 -n 1 >NUL 2>&1
goto NOTVALID
) else (
set /A i+=1
echo "installation completed.."
call set array[%%i%%]=%%a
call set n=%%i%%
)
Try the below code which suite your requirement :
cls
#echo off
for /f %%g in (Installers.txt) do type Progress.txt|findstr "%%g" || echo %%g is Yet to start , still in progress
Above code will read a single line from installer and then search for that string in file Progress.txt and then print the output if it is found or not.
Been wrecking my brain all night trying to figure out why this isn't working, but one of my variables isn't releasing on the next iteration of my loop and I can't figure out why... The first pass of the loop seems to work fine, but the next iteration, the first variable gets locked and the script connects to the system that's already been configured.
I've been staring at this for a while now and no matter how I approach it, it still behaves badly. :/ The purpose is to read a text-string of a given file, and use it to modify (via Find and Replace (fnr.exe)) another file with several instances of the required data. I didn't have alot of luck with 'findstr' replacing so many instances of the text required so I went with a tool I've used before that seemed to work really well in it's previous scripting application...
Truth be told, I find myself stumbling with even the most basic code a lot of times, so any kind soul willing to impart some wisdom/assistance would be greatly appreciated!
Thanks in advance...
#ECHO ON
setlocal enabledelayedexpansion
> "%~dp0report.log" ECHO Batch Script executed on %DATE% at %TIME%
rem read computer list line by line and do
FOR /F %%A in (%~dp0workstations.txt) do (
SET lwn=
SET WKSTN=%%A
rem connect to workstation and read lwn.txt file
pushd "\\%WKSTN%\c$\"
IF ERRORLEVEL 0 (
FOR /F %%I in (\\%wkstn%\c$\support\lwn.txt) DO (
SET LWN=%%I
%~dp0fnr.exe --cl --dir "\\%WKSTN%\c$\support\folder\config" --fileMask "file.xml" --find "21XXXX" --replace "%%I"
IF ERRORLEVEL 0 ECHO Station %LWN%,Workstation %WKSTN%,Completed Successfully >> %~dp0report.log
IF ERRORLEVEL 1 ECHO Station %LWN%,Workstation %WKSTN%, A READ/WRITE ERROR OCCURRED >> %~dp0report.log
echo logwrite error 1 complete
popd
)
)
IF ERRORLEVEL 1 (
ECHO ,,SYSTEM IS OFFLINE >> %~dp0report.log
)
popd
set wkstn=
set lwn=
echo pop d complete
)
msg %username% Script run complete...
eof
The ! notation must be used on all variables that are changed inside the loop.
C:>type looptest.bat
#ECHO OFF
setlocal enabledelayedexpansion
rem read computer list line by line and do
FOR /F %%A in (%~dp0workstations.txt) do (
SET WKSTN=%%A
ECHO WKSTN is set to %WKSTN%
ECHO WKSTN is set to !WKSTN!
pushd "\\!WKSTN!\c$\"
ECHO After PUSHD, ERRORLEVEL is set to %ERRORLEVEL%
ECHO After PUSHD, ERRORLEVEL is set to !ERRORLEVEL!
IF !ERRORLEVEL! NEQ 0 (
ECHO ,,SYSTEM IS OFFLINE
) ELSE (
ECHO Host !WKSTN! is available
)
popd
)
EXIT /B 0
The workstations.txt file contained the following. (I should not give out actual host names.)
LIVEHOST1
DEADHOST1
LIVEHOST2
The output is...
C:>call looptest.bat
WKSTN is set to
WKSTN is set to LIVEHOST1
After PUSHD, ERRORLEVEL is set to 0
After PUSHD, ERRORLEVEL is set to 0
Host LIVEHOST1 is available
WKSTN is set to
WKSTN is set to DEADHOST1
The network path was not found.
After PUSHD, ERRORLEVEL is set to 0
After PUSHD, ERRORLEVEL is set to 1
,,SYSTEM IS OFFLINE
WKSTN is set to
WKSTN is set to LIVEHOST2
After PUSHD, ERRORLEVEL is set to 0
After PUSHD, ERRORLEVEL is set to 0
Host LIVEHOST2 is available
Although your code have several issues, the main one is the use of % instead of ! when you access the value of variables modified inside a for loop (although you already have the "enabledelayedexpansion" part in setlocal command). However, I noted that you sometimes use the FOR replaceable parameter (like in --replace "%%I") and sometimes you use the variable with the same value (%LWN%), so a simpler solution in your case would be to replace every %VAR% with its corresponding %%A for parameter.
I inserted this modification in your code besides a couple small changes that make the code simpler and clearer.
#ECHO ON
setlocal
> "%~dp0report.log" ECHO Batch Script executed on %DATE% at %TIME%
rem Read computer list line by line and do
FOR /F %%A in (%~dp0workstations.txt) do (
rem Connect to workstation and read lwn.txt file
pushd "\\%%A\c$\"
IF NOT ERRORLEVEL 1 (
FOR /F "usebackq" %%I in ("\\%%A\c$\support\lwn.txt") DO (
%~dp0fnr.exe --cl --dir "\\%%A\c$\support\folder\config" --fileMask "file.xml" --find "21XXXX" --replace "%%I"
IF NOT ERRORLEVEL 1 (
ECHO Station %%I,Workstation %%A,Completed Successfully >> %~dp0report.log
) ELSE (
ECHO Station %%I,Workstation %%A, A READ/WRITE ERROR OCCURRED >> %~dp0report.log
echo logwrite error 1 complete
)
)
) ELSE (
ECHO ,,SYSTEM IS OFFLINE >> %~dp0report.log
)
popd
echo pop d complete
)
msg %username% Script run complete...
I'm trying to write a script to check if the remote workstations have 64bit or 32bit operating system. The script simple should check if the directory "Program Files (x86)" exists and then echo the results to the output.txt file. For some reason or another the script doesn't even create the output.txt file. Any suggestions?
#echo off
SET output=D:\output.txt
IF EXIST "%output%" DEL "%output%"
FOR /f %%a IN (computers.txt) DO (
CALL :ping %%a
)
GOTO :EOF
:ping
SET pingtest=false
ping -n 1 %1 | find "approximate round trip time" >NUL || SET pingtest=true
set directorytest=true
set directory="\\%1%\c$\Program Files (x86)\"
IF pingtest==true (
dir %directory% >nul 2>nul
if errorlevel 1 (
set directorytest=false
)
IF directorytest==true ( echo "%1;64bit" >> "%output%" ) ELSE ( ECHO "%1;32bit" >> "%output%" )
)
IF pingtest==false (
ECHO "%1;offline" >> "%output%"
)
:EOF
There are a couple of things wrong with your code.
When assigning your %directory% variable you use \\%1%\c$\Program Files (x86). Here %1% should be %1
You are using the c$ share, which is mostly not available anymore (or at least not without administative privilidges)
It could work like this:
#echo off
setlocal enabledelayedexpansion
echo Welcome
call :ping 192.168.1.1
echo.
echo done with everything
goto eof
:ping
echo Probing %1 ...
set pingtest=false
ping -n 1 %1 | find "Approximate round trip" > nul
IF %ERRORLEVEL% == 0 (
set tdir="\\%1\c$\Program Files (x86)"
echo Looking up !tdir!
dir !tdir!
if !ERRORLEVEL! == 0 (
set arch=64bit
) else (
set arch=32bit
)
echo Architecutre of %1 is: !arch! >> output.txt
) else (
echo Remote host unreachable: %1 >> output.exe
)
goto eof
BUT:
This will return 64bit for every machine that runs the microsoft sharing service or samba and that does not share C$ to just anyone, as dir will only return a non zero ERRORLEVEL when the target does not provide that service or generally is unavailable.
On the other hand every machine that does not provide the shareing service at all, will be marked as 32bit
For any machine that does provide access to it's root drive (which none should) the script does work.
Here is a more reliably method of checking for 32/64 bit Windows OS
#echo off
set RemoteHost=192.168.1.100
set RegQry=HKLM\Hardware\Description\System\CentralProcessor\0
reg \\%RemoteHost%\%RegQry% > checkOS.txt
find /i "x86" < CheckOS.txt > StringCheck.txt
IF %ERRORLEVEL% == 0 (
echo "This is 32 Bit Operating system"
) ELSE (
echo "This is 64 Bit Operating System"
)
[source]
This one utilizes the reg.exe that can query the registry of remote Windows NT machines.
There is also the possiblity of utilizing wmic: Example
IF pingtest==true (
will never be true because the string pingtest and true are not the same.
You need
IF %pingtest%==true (
Even when you've fixed that, there's a further gotcha:
Within a block statement (a parenthesised series of statements), the entire block is parsed and then executed. Any %var% within the block will be replaced by that variable's value at the time the block is parsed - before the block is executed - the same thing applies to a FOR ... DO (block).
Hence, IF (something) else (somethingelse) will be executed using the values of %variables% at the time the IF is encountered.
Two common ways to overcome this are 1) to use setlocal enabledelayedexpansion and use !var! in place of %var% to access the changed value of var or 2) to call a subroutine to perform further processing using the changed values.
Search for 'delayed expansion' on SO for many, many, many examples.
I'm trying to create a batch file for an automated testing script.
Everytime the batch for the master script is started up, it should open an existing .txt-file in the same directory containing something like this:
10.200.6.111 inactive
10.200.6.112 inactive
10.200.6.113 inactive
10.200.6.114 inactive
etc...
It should then navigate to the line with it's own IP (which is specified in the batch) and replace the 'inactive' tag with 'active' (indicating that this system has now started testing). Ideally it would also append a timestamp, maybe with something along these lines:
for /f "tokens=1-5 delims=:" %%d in ("%time%") //and then add %%d-%%e at the end?
I've tried to do something similar before and also looked through existing topics but everything seems to be very specific to one situation and I lack the skills to adapt them myself. Ultimately I would need a different .bat to also read from this file and delay any action until all 'active' tabs are gone. But since as of now there is only one vm doing the testing, it would really be a weight off my shoulder if I could just get this to work. Thanks in advance for any help and I apologize if this is a duplicate, I really did try to find something I could use!
PS: I'm really terrible at 'getting' generic code sometimes, it's perfectly possible this has been clearly answered before and I'm just not capable of understanding the solution.
Edit: Just to clarify, it should look roughly like this:
10.200.6.111 inactive
10.200.6.112 inactive
10.200.6.113 active 14:20
10.200.6.114 inactive
etc...
Edit 2: Actually, after thinking about it some more, I've concluded that I can probably do it better by just using my scripts and keeping the batch files simple.
#echo off
setlocal enableextensions disabledelayedexpansion
:: define the log file
set "file=%~nx0.test.txt"
:: generate some lines in log file to test
if not exist "%file%" (
echo 10.200.6.111 inactive
echo 10.200.6.112 inactive
echo 10.200.6.113 inactive
echo 10.200.6.114 inactive
) > "%file%"
:: define the ip of the current node
set "ip=10.200.6.114"
:: change status in log file to active and shows sucess/failure of operation
call :changeStatus "%file%" "%ip%" "active %time:~0,5%"
if errorlevel 1 (
echo failed to change status
) else (
echo status changed
)
:: dump file to see changes
call :dumpFile "%file%"
:: change status in log file to inactive
call :changeStatus "%file%" "%ip%" "inactive"
:: dump file to see changes
call :dumpFile "%file%"
exit /B
:dumpFile file
echo(
echo(----------------------------------
type "%~1"
echo(----------------------------------
echo(
exit /b
:changeStatus file ip status
setlocal enableextensions
set "file=%~1"
set "ip=%~2"
set "status=%~3"
set "retries=0123456789"
set "exitCode=0"
:changeStatusRetry
(>"%file%.lock" (
set "reset=1"
for /f "usebackq tokens=1*" %%a in ("%file%") do (
if defined reset ( set "reset=" & >"%file%" break )
(if "%%a"=="%ip%" (
echo(%%a %status%
) else (
echo(%%a %%b
)) >> "%file%"
)
) && set "exitCode=0" || set "exitCode=1") >nul 2>nul
if "%exitCode%"=="1" (
ping -n 2 localhost >nul 2>nul
set "retries=%retries:~0,-1%"
if defined retries ( set "exitCode=0" & goto changeStatusRetry )
)
del /q "%file%.lock" >nul 2>nul
endlocal & exit /b %exitCode%
This uses a subroutine to handle the changes to log file. It also handles locking on the file from multiple processes. Log file access is locked while changing data and if at the time of changing the file it is locked, it retries up to 10 times to write changed data. On return from the subroutine, errorlevel indicates the result of the operation.