batch script still holding the file after execution - windows

hc.bat
set num=0
setlocal ENABLEDELAYEDEXPANSION
for /f "tokens=1-3 delims=;" %%a in (server.conf) do (
findstr /b "^#" %%a >nul 2>&1
if errorlevel 1 start /B check.bat %%a %%b %num% > check!num!.log
set /A num=num+1
)
)
endlocal
exit /b 2
check.bat
set svr=%1
set log=%2
set num=%3
echo %svr% %log% !num!
setlocal ENABLEDELAYEDEXPANSION
copy /y NUL output!num!.tmp >NUL
powershell -command "& {Get-Content %log% | Select-Object -last 1}">> output!num!.tmp
type output!num!.tmp | findstr /m "STUCK" >nul 2>&1
if %errorlevel%==0 (goto action)
type output!num!.tmp | findstr /m "shut" >nul 2>&1
if %errorlevel%==0 (goto action)
:action
type output!num!.tmp
del output!num!.tmp
exit /b 2
Two problems with the script
1) the process keeps holding to the log file check0.log
2) In the hc.bat, I'm trying to use the findstr to ignore lines that start with #, but it doesn't seem to be working

hc.bat:
Expand num like !num! (delayed expansion).
I modified the set /A statement to avoid (immediate) expansion right to =.
The redirection > means to overwrite data. Hence I changed it to >> in order to append data.
Insert cmd /C into start /B argument.
At findstr, the /b switch and ^ being the first char. in the search string are redudant. I gave the /r switch to force regular expression mode.
The 3rd for /F token was not used, so I simply removed it.
set num=0
setlocal ENABLEDELAYEDEXPANSION
for /f "tokens=1,2 delims=;" %%a in (server.conf) do (
findstr /r "^#" %%a >nul 2>&1
if errorlevel 1 start /B cmd /C "check.bat %%a %%b !num! >> check!num!.log"
set /A num=+1
)
)
endlocal
exit /b 2
check.bat:
Actually you do not need delayed expansion here (although it does no harm).
set svr=%1
set log=%2
set num=%3
echo %svr% %log% %num%
copy /y NUL output%num%.tmp >NUL
powershell -command "& {Get-Content %log% | Select-Object -last 1}">> output%num%.tmp
type output%num%.tmp | findstr /m "STUCK" >nul 2>&1
if %errorlevel%==0 (goto action)
type %output%num%.tmp | findstr /m "shut" >nul 2>&1
if %errorlevel%==0 (goto action)
:action
type output%num%.tmp
del output%num%.tmp
exit /b 2

Related

Single line nested loop

I am experimenting with a single line cmd /c to get an inner loop without branching. (Actually i have the showLines routine which performs the loop.) I know its worst for performance but i want to know if its possible to get it run without quotes. Currently it raises "%G was unexpected at this time" error. So, it needs some correct escaping or expansion of variables.
#echo off
setlocal enableDelayedExpansion
set "param=%~1"
netstat -aonb | findstr /n $ > tmpFile_Content
for /F "tokens=*" %%A in ('type tmpFile_Content ^| findstr /r /c:"%param%" /i') do (
SET line=%%A
for /F "tokens=1 delims=:" %%I in ("!line!") DO (
set /a LineNum=%%I
rem set /a NextLineNum=LineNum+1
)
set /a lineNum=!LineNum!-1
if !lineNum!==0 ( set param="tokens=*" ) else ( set param="tokens=* skip=!lineNum!" )
rem FOLLOWING WORKS FINE in quotes
cmd /q /v:on /c "#echo off && setlocal enableDelayedExpansion && set cnt=2 && for /F %%param%% %%B in (tmpFile_Content) do ( echo %%B && set /a cnt-=1 >nul && if ^!cnt^!==0 exit /b )"
rem Following does not work even though cmd should take the rest of arguments after /c
cmd /q /v:on /c setlocal enableDelayedExpansion && FOR /F "tokens=*" %%C IN ('echo !param!') DO ( for /F %%C %%G in (tmpFile_Content) do ( echo %%G && set /a cnt-^=1 >nul && if ^!cnt^!==0 exit /b ))
rem call :showLines !LineNum!
)
del tmpFile_Content
goto :eof
:showLines
set /a lineNum=%1-1
set cnt=2
for /F "tokens=* skip=%lineNum%" %%B in (tmpFile_Content) do (
echo %%B
set /a cnt-=1
if !cnt!==0 goto exitLoop
)
:exitLoop
exit /b
to construct for loops with variable parameters, you essentially need to define and execute them as a macro. Eg:
#Echo Off & Setlocal ENABLEdelayedExpasnion
Set param="tokens=* delims="
Set "test=string line"
Set For=For /F %param% %%G in ("^!test^!") Do echo %%G
%For%
Of course you could go even further, and build the entire for loop with another for loop macro on the fly.
UPDATE:
Method for defining conditional concatenation of commands now exampled
Syntax simplified to allow the same usage form for regular expansion and within codeblocks by having the constructor macro call a subroutine to expand the new for loop once it's constructed.
delayed concatenation variable usage simplified to avoid the escaping requirement
#Echo off
::: { Macro Definition
Setlocal DisabledelayedExpansion
(set \n=^^^
%= This creates an escaped Line Feed - DO NOT ALTER =%
)
::: [ For Loop Constructor macro. ] For advanced programmers who need to use dynamic for loop options during code blocks.
::: - usage: %n.For%{For loop options}{variable set}{For Metavariable}{commands to execute}
::: - use delayed !and! variable to construct concatenated commands in the new for loop.
Set n.For=For %%n in (1 2) Do If %%n==2 (%\n%
Set FOR=%\n%
For /F "tokens=1,2,3,4 Delims={}" %%1 in ("!mac.in!") Do (%\n%
Set "FOR=For /F %%1 %%3 in ("!%%2!") Do (%%~4)"%\n%
)%\n%
Call :Exc.For%\n%
)Else Set mac.in=
Set "and.=&&"
Set "and=!and.!"
::: } End macro definition.
Setlocal EnableDelayedExpansion& rem // required to expand n.For constructor macro
::: - Usage examples:
Set "example=is a string line"
%n.For%{"tokens=* delims="}{example}{%%G}{Echo/%%~G}
%n.For%{"tokens=1,2,3,4 delims= "}{example}{%%G}{"Echo/%%~J %%~G %%~H %%~I !and! Echo/%%~I %%~G %%~H %%~J"}
Set "example2=Code block example"
For %%a in (1 2 3) do (
%n.For%{"Tokens=%%a Delims= "}{example2}{%%I}{"For /L %%# in (1 1 4) Do (Set %%I[%%#]=%%a%%#) !and! Set %%I[%%#]"}
)
Pause > Nul
Goto :EOF
:Exc.For
%FOR%
Exit /B
Example output:
is a string line
line is a string
string is a line
Code[1]=11
Code[2]=12
Code[3]=13
Code[4]=14
block[1]=21
block[2]=22
block[3]=23
block[4]=24
example[1]=31
example[2]=32
example[3]=33
example[4]=34
Finally, i came up with following to execute code given in string for anyone interested experimentally and may be for some insight about escaping and expansion.
I use macro instead of cmd which will be much more faster i think(not sure because its said that "call" also causes launch of cmd).
So, it is a simple one-liner without a lot of extra code. But things easily become complicated and when extra escaping and special characters used then #T3RR0R's macro routine would be a necessity.
#echo off
setlocal EnableDelayedExpansion
set "param=%~1"
netstat -aonb | findstr /n $ > tmpFile_Content
for /F "tokens=*" %%A in ('type tmpFile_Content ^| findstr /r /c:"%param%" /i') do (
SET line=%%A
for /F "tokens=1 delims=:" %%I in ("!line!") DO (
set /a LineNum=%%I
rem set /a NextLineNum=LineNum+1
)
set /a lineNum=!LineNum!-1
rem CORRECT QUOTING
if !lineNum!==0 ( set "param="tokens=*"" ) else ( set "param="tokens=* skip=!lineNum!"" )
rem FOLLOWING WORKS FINE in quotes
rem cmd /q /v:on /c "set cnt=2 && for /F ^!param^! %%B in (tmpFile_Content) do ( echo %%B && set /a cnt-=1 >nul && if ^!cnt^!==0 exit /b )"
rem For reading !cnt! use !x!cnt!x!.
rem Only one extra variable used, and in routine its replaced with !(exclamation) for our "cnt" variable.
set "x=^!x^!"
call :ExecCode "set cnt=2 && for /F ^!param^! %%%%B in (tmpFile_Content) do (echo %%%%B && set /a cnt=!x!cnt!x!-1 >nul && if !x!cnt!x!==0 (exit /b) )"
rem call :showLines !LineNum!
)
del tmpFile_Content
goto :eof
:ExecCode
setlocal
rem Replace with exclamation for variable
set "x=^!"
set "s=%~1"
%s%
endlocal
exit /b
:showLines
set /a lineNum=%1-1
set cnt=2
for /F "tokens=* skip=%lineNum%" %%B in (tmpFile_Content) do (
echo %%B
set /a cnt-=1
if !cnt!==0 goto exitLoop
)
:exitLoop
exit /b

Windows Batch: findstr not setting ERRORLEVEL within a for loop

Can anyone explain to me why the following snippet prints 0:
#echo off
setlocal
for /f %%i in ('cmd /c echo blah') do (
echo %%i | findstr bin > NUL
echo %ERRORLEVEL%
)
While adding another, equivalent statement outside of the for-loop makes it print 1 1:
#echo off
setlocal
echo blah | findstr bin > NUL
echo %ERRORLEVEL%
for /f %%i in ('cmd /c echo blah') do (
echo %%i | findstr bin > NUL
echo %ERRORLEVEL%
)
I'm a bit of a newbie to Batch, so this is kinda mysterious to me since the two statements seem to be unrelated. Any help with this would be appreciated, thanks!
The issue is that within a code block (parenthesised series of statements) any %var%will be replaced by the actual value of the variable at parse time.
Hin your first example, %errorlevel% 0 and echoed as such. In second example, it is 1 when the for is encountered, hence it it replaced by 1.
If you want to display a value of an environment variable that may be changed within a loop, then you need to do one of three things:
Invoke setlocal enabledelayedexpansion and echo !var! instead of %var% - noting that the number of nested setlocal instructions you can have active is limited.
Call a subroutine
Employ a syntax-exploit.
There are many, many articles about delayedexpansion on SO.
Crudely, you could simply use (note - case is largely irrelevant in batch, except for the case of the loop-control variable (metavariable - %%i in this instance)
#echo off
setlocal ENABLEDELAYEDEXPANSION
echo blah | findstr bin > NUL
echo %ERRORLEVEL% or !errorlevel!
for /f %%i in ('cmd /c echo blah') do (
echo %%i | findstr bin > NUL
echo %ERRORLEVEL% or !errorlevel!
)
Another way is to dynamically invoke setlocal
#echo off
setlocal
echo blah | findstr bin > NUL
echo %ERRORLEVEL% or !errorlevel!
for /f %%i in ('cmd /c echo blah') do (
echo %%i | findstr bin > NUL
setlocal ENABLEDELAYEDEXPANSION
echo %ERRORLEVEL% or !errorlevel!
endlocal
)
The disadvantage of this is that the endlocal backs out any changes made to the environment since that last setlocal. Also note that if delayedexpansion is not in effect, ! is no longer a special character.
Or, you can use errorlevel in its traditional manner:
#echo off
setlocal
echo blah | findstr bin > NUL
echo %ERRORLEVEL% or !errorlevel!
for /f %%i in ('cmd /c echo blah') do (
echo %%i | findstr bin > NUL
if errorlevel 1 (echo errorlevel is 1 or greater
) else (echo errorlevel is 0
)
)
Note that this looks at the run-time value of errorlevel and if errorlevel n means "if errorlevel is n or greater than n"
Or - call a subroutine:
#echo off
setlocal
echo blah | findstr bin > NUL
echo %ERRORLEVEL% or !errorlevel!
for /f %%i in ('cmd /c echo blah') do (
echo %%i | findstr bin > NUL
call :show
)
goto :eof
:show
echo %ERRORLEVEL%
goto :eof
Note here that goto :eof (here the colon is important - this means "go to the physical end-of-file"
Or, a special version of using a subroutine - a syntax-exploit
#echo off
setlocal
echo blah | findstr bin > NUL
echo %ERRORLEVEL% or !errorlevel!
for /f %%i in ('cmd /c echo blah') do (
echo %%i | findstr bin > NUL
call echo %%errorlevel%%
)
UPDATED:
If you put your echo %ERRORLEVEL% statement after the closing ) of the for statement it works as expected.
for /f %%i in ('cmd /c echo blah') do (
echo %%i | findstr bin > NUL
)
echo %ERRORLEVEL%
I'm kind of guessing here, for the exact 'definitive' reason, but setlocal is definitely causing it. I'm assuming the value of the ERRORLEVEL within the for loop is the result of the cmd /c ... statement. It seems like a strange behavior, but I've seen worse in batch files.
If you remove the setlocal at the beginning it works as one would normally expect it to..
ORIGINAL:
The findstr sets the errorlevel when it runs. So, echo %errorlevel% will always output 0.
For example the following findstr statement is successful, so it outputs the line with the match (the only line passed to it):
C:\>echo blahbin | findstr bin
blahbin
C:\>echo %errorlevel%
0 ; success / found it..
While this statement is not successful (findstr doesn't output anything) and the errorlevel is set to 1:
C:\>echo blah_in | findstr bin
C:\>echo %errorlevel%
1 ; failed
#echo off
setlocal ENABLEDELAYEDEXPANSION
for /f %%i in ('cmd /c echo blah') do (
echo %%i | findstr bin > NUL
echo !ERRORLEVEL!
)
this should work.
A for loop in cmd is technically one line, so the variable gets expanded only once. Using exclamation marks instead of percent and "ENABLEDELAYEDEXPANSION" should fix it.
#echo off
setlocal enabledelayedexpansion
:::This part is not exactly necessary but it creates the file dictionary.txt and the var - this
echo.word>dictionary.txt
set this=notaword
:::So instead of checking for errorlevel just check for a space in the output
:middle
for /f "tokens=*" %%a in ('findstr /n /b /c:%this% dictionary.txt') do (
set "output=%%a"
goto :yeah
)
:yeah
if exist %output% (""," "
goto :oops
) else (
goto :nice
)
:oops
echo.%this% is NOT a word
pause & set this=word & goto :middle
:nice
echo.%output%
pause

Batch script help, script exiting after parentheses

I wrote a very simple script to output the host machine's MAC addresses to a text file.
The script is exiting right after line 3 - 'IF DEFINED WRITEOK ('.
#echo off
cls
copy /Y NUL "%CD%\.writable" > NUL 2>&1 && set WRITEOK=1
IF DEFINED WRITEOK (
rem ---- we have write access ----
set DIR=%CD%\interfaces
set FILE=%DIR%\%USERNAME%.txt
IF NOT EXIST "%DIR%" (
MKDIR "%DIR%"
echo DIR '%DIR%' was created
) else (
echo DIR '%DIR%' already exists
) for /f "tokens=2 delims=:" %%i in ('ipconfig /all ^| findstr /i "Physical Host"') do (
echo %%i >> "%FILE%"
echo OUTPUT written to '%FILE%'
)
) else (
rem ---- we don't ----
echo DIR '%DIR%' is not writable
)
echo.
echo DONE!
pause
Try to put the FOR one line after the closing parenthesis :
...)
for /f "tokens=2 delims=:" %%i in ('ipconfig /all ^| findstr /i "Physical Host"') do (...
you can't start a FOR with a closing parenthesis in front :
This will not work :
(echo 1
) for /l %%a in (1,1,10) do echo %%a
and this will work :
(echo 1
)
for /l %%a in (1,1,10) do echo %%a
EDIT 1 :
For the path variables containing space use double quote :
"%cd%"
when using it.

Using IF condition

I want to query a remote machine and if the KB22334358 is found in the systeminfo output then exit, otherwise write hostname to failed.txt.
Why doesn't this work?
systeminfo /s "remotenamemachine" | find "KB22334358"
if %errorlevel% equ 1 goto nome else goto exit
:nome
systeminfo | find "Nome host" > C:\failed.txt
:exit
exit
Seems like you might need to use the /s switch as well when you connect the next time. Also, you might want to make the find command not case sensitive by using /i. You might also want to append to the failed.txt file, like so:
systeminfo /s "remotenamemachine" | find "KB22334358"
if %errorlevel% equ 1 goto nome else goto exit
:nome
systeminfo /s "remotenamemachine" | find /i "Nome host" >> C:\failed.txt
:exit
exit
Or you can use this triple for /f loop that gets it all in one shot:
KB_Checker.bat
#echo off
setlocal ENABLEDELAYEDEXPANSION
set comp_name=%*
for /f "usebackq tokens=1* delims=" %%I in (`
systeminfo /s "%comp_name%"
`) do (
for /f "usebackq tokens=2 delims=:" %%k in (`
echo %%I ^| findstr /v "^)" ^| findstr "KB22334358"
`) do (
for /f "tokens=1* delims=" %%f in ('
echo %%k ^| findstr /v "UTC"
') do (
set temp_KB=%%f
)
)
for /f "usebackq tokens=2 delims=:" %%h in (`
echo %%I ^| findstr /i /C:"Nome host"
`) do (
for /f "tokens=1* delims=" %%f in ('
echo %%h ^| findstr /v "UTC"
') do (
set hm=%%f
)
)
) 2> nul
set KB=!temp_KB!
if not defined KB echo !hm! >> C:\failed.txt
exit
UPDATE
I updated the script above to set the comp_name environment var. You can pass the computer name into the script like so: KB_Checker.bat computer_1 and it will process that

Syntax issue in batch

I have been working on this script for a little and I am new to writing batch files. I know my syntaxs is wrong and need some help.
#echo off
setlocal enabledelayedexpansion
set "ports=HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Ports"
for /f %%I in ( 'reg query "%ports%"')
do (
echo %%I | findstr /i "c:\\convertdoc\\output\\silentprinttemp\\.*\.ps" >NUL
IF ERRORLEVEL 1 reg delete "%ports%" /v "%%I" /f
)
#echo off
setlocal
set "ports=HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Ports"
for /f %%I in (
'reg query "%ports%"'
) do (
echo %%I | findstr /i "c:\\convertdoc\\output\\silentprinttemp\\.*\.ps" >NUL
IF ERRORLEVEL 1 reg delete "%ports%" /v "%%I" /f
)
As the comments also point out, cmd wants "do" on the same line as the closing parenthesis of the previous expression. You're not using delayed expansion. It's not an error, but I see no reason to turn it on.
#echo off
setlocal
set "ports=HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Ports"
for /f %%I in ( 'reg query "%ports%"' ) do (
echo %%I | findstr /i "c:\\convertdoc\\output\\silentprinttemp\\.*\.ps" >NUL
IF ERRORLEVEL 1 reg delete "%ports%" /v "%%I" /f
)
Curiously, this experiment worked:
FOR /F %%i IN (
'dir /b'
) DO (
#ECHO %%i
)

Resources