Undefined variable error in a bat file - windows

I tried building a batch file using the commands after searching the site. I am trying to find the number/count of a process running, and then use if to execute another command if the number of such processes is more than 5 at any instance.
When I run the statements line by line in the CMD prompt, it works fine.
However, when I run it through a bat file it gives an error saying- a was unexpected at this time.
Here is the script. Also I am not sure if am using the correct If statement (I did search and use before coming to you, but still just incase):
for /f "tokens=1,*" %a in ('tasklist ^| find /I /C "iexplore.exe" ') do
#set var=%a
echo %var%
if %var% <= 5
::echo "hi"
::end if
Also, have one more syntax to do so:
wmic process where name="iexplore.exe" | find "iexplore.exe" /c
but I am not sure how to assign the output of this command to any variable and go on to compare the value of this command to 5.

You need to use double % for FOR command when used in a batch file.
#echo off
set var=0
for /f "tokens=1,*" %%a in ('tasklist ^| find /I /C "explorer.exe" ') do set var=%%a
echo %var%
if %var% leq 5 (
echo less or equal to 5
) else (
echo 5 or more
)

Related

Store a decryption result in a variable and print it command line [duplicate]

I would like to make a bat script that performs an action only if fewer than 2 occurrences of cmd.exe are running. I managed to find a solution that stores the occurrence count in a temporary file, but I find it very inelegant. So my question is how to do the same as below without a temporary file, but rather by storing the occurence count given by #TASKLIST | FIND /I /C "%_process%" in a variable. I saw that there may be a solution with a for loop, but I couldn’t get it to work and anyway I would really prefer a solution based on SET (if this is possible).
#SET _process=cmd.exe
#SET _temp_file=tempfiletodelete.txt
#TASKLIST | FIND /I /C "%_process%" > %_temp_file%
#SET /p _count=<%_temp_file%
#DEL /f %_temp_file%
#IF %_count% LSS 2 (
#ECHO action 1
) ELSE (
#ECHO action 2
)
Edit: This question is similar to Save output from FIND command to variable, but I wasn’t able to apply the solution to my problem and I wanted to know if a solution without a for loop is possible.
The command FOR with option /F and the command or command line specified as set (string inside parentheses) additionally enclosed by ' can be used for processing the output of a command or a command line with multiple commands on one line.
You can use this batch file:
#ECHO OFF
SET "_process=cmd.exe"
FOR /F %%I IN ('%SystemRoot%\System32\tasklist.exe /FI "IMAGENAME eq %_process%" ^| %SystemRoot%\System32\find.exe /I /C "%_process%"') DO SET "_count=%%I"
IF /I "%_process%" == "cmd.exe" SET /A _count-=1
IF %_count% LSS 2 (
ECHO action 1
) ELSE (
ECHO action 2
)
The command FOR runs in background without a visible console window cmd.exe /C with the command line:
C:\Windows\System32\tasklist.exe /FI "IMAGENAME eq cmd.exe" | C:\Windows\System32\find.exe /I /C "cmd.exe"
TASKLIST option /FI "IMAGENAME eq cmd.exe" filters the output of TASKLIST already to processes with image name (= name of executable) cmd.exe. This makes further processing faster and avoids that a running process with file name totalcmd.exe is counted as cmd.exe as the command line in question does.
The output of TASKLIST written to handle STDOUT is redirected to handle STDIN of command FIND which processes the lines and counts the lines containing cmd.exe anywhere in line. FIND outputs finally the number of lines containing searched string to handle STDOUT of command process running in background.
The redirection operator | must be escaped with caret character ^ on FOR command line to be interpreted as literal character when Windows command interpreter processes this command line before executing command FOR which executes the embedded command line with using a separate command process started in background.
FOR with option /F captures everything written to handle STDOUT of executed command process and processes each line. In this case just one line is captured by FOR containing just a number. Therefore no additional options are needed to assign the number assigned to loop variable I by FOR to environment variable _count using command SET.
The goal of this batch file is counting the number of cmd.exe processes already running. As the command line with TASKLIST and FIND is executed by FOR in background with using one more cmd.exe process, it is necessary to subtract the count by one to get the correct result using an arithmetic expression evaluated with SET /A _count-=1. This decrement by one is needed only for counting right the number of cmd.exe processes. It is not necessary for any other process.
For understanding the used commands and how they work, open a command prompt window, execute there the following commands, and read entirely all help pages displayed for each command very carefully.
echo /?
find /?
for /?
if /?
set /?
tasklist /?
You would do it like this, using TaskList in a For loop:
#Set "_process=cmd.exe"
#For /F %%A In ('TaskList^|Find /C /I "%_process%"'
) Do #If %%A Lss 2 (Echo Action 1) Else Echo Action 2
#Pause
You can also perform a similar thing using WMIC too:
#Set "_process=cmd.exe"
#For /F %%A In ('WMIC Process Get Name^|Find /C "%_process%"'
) Do #If %%A Lss 2 (Echo Action 1) Else Echo Action 2
#Pause
These could be refined further to ensure processes containing the string as opposed to matching it aren't counted:
#Set "_process=cmd.exe"
#For /F %%A In ('TaskList /FI "ImageName Eq "%_Process%""^|Find /C "."'
) Do #If %%A Lss 2 (Echo Action 1) Else Echo Action 2
#Pause
 
#Set "_process=cmd.exe"
#For /F %%A In (
'WMIC Process Where "Name='%_process%'" Get Name^|Find /C "."'
) Do #If %%A Lss 2 (Echo Action 1) Else Echo Action 2
#Pause
Note:If you're really checking the cmd.exe process, be aware that an the command inside the For loop parentheses is spawned within a new cmd.exe instance, (thereby increasing your count by 1)

How to make a character line across the width of the command line?

How to make a character line across the width of the command line Windows?
In Linux, I can do like this
printf '\n%*s\n\n' \"${COLUMNS:-$(tput cols)}\" '' | tr ' ' -
In Windows, I have so far only
echo -----------------------
you can get the current width of your window with the mode command.
#echo off
setlocal enabledelayedexpansion
"set width="
for /f "tokens=2 delims=:" %%a in ('mode con^|more +4') do if not defined width set /a width=%%a
for /l %%a in (1,1,%width%) do set "line=!line!-"
echo %line%
If you are on a supported version of Windows, this can easily be done using PowerShell. PowerShell also runs on Linux/*NIX and Mac.
powershell -NoLogo -NoProfile -Command "'-' * $Host.UI.RawUI.WindowSize.Width"
I only know a workaround for it by determining the width of the command line window and repeating the character ofter enough.
Since I don't know if you only want it in the command prompt oder in a batch file I post what I made while ago for me. It only works in a batch file or when you save the second part in a batch file and call it in a command prompt window.
:RepeatChar <Char> <Count> <Variable>
setlocal enabledelayedexpansion
set tempRepChar=
for /L %%l in (1,1,%~2) do (
set tempRepChar=!tempRepChar!%~1
)
if /i "%~3"=="" (
echo %tempRepChar%
) else (
set %~3=%tempRepChar%
set tempRepChar=
)
goto :EOF
exit /b
(the extra exit /b in the RepeatChar function isn't really necessary, but I just do it for myself)
You can then call it within a batch file with
for /f %%f in ('powershell.exe -command $host.UI.RawUI.WindowSize.Width') do set WindowsWidth=%%f
call :RepeatChar "-" %WindowsWidth% Stipline
echo %Stripline%
exit /b
if you don't give it the 3rd parameter then it just echos the line so if you only need it once you can just use
call :RepeatChar "-" %WindowsWidth%
Or you can also store or use it via a for loop, like
for /f %%f in ('powershell.exe -command $host.UI.RawUI.WindowSize.Width') do set WindowsWidth=%f
for /f %%f in ('call temp.bat "-" "%WindowsWidth%"') do (
echo %%f
set Stripline=%%f
)

BATCH — Store command output to variable

I would like to make a bat script that performs an action only if fewer than 2 occurrences of cmd.exe are running. I managed to find a solution that stores the occurrence count in a temporary file, but I find it very inelegant. So my question is how to do the same as below without a temporary file, but rather by storing the occurence count given by #TASKLIST | FIND /I /C "%_process%" in a variable. I saw that there may be a solution with a for loop, but I couldn’t get it to work and anyway I would really prefer a solution based on SET (if this is possible).
#SET _process=cmd.exe
#SET _temp_file=tempfiletodelete.txt
#TASKLIST | FIND /I /C "%_process%" > %_temp_file%
#SET /p _count=<%_temp_file%
#DEL /f %_temp_file%
#IF %_count% LSS 2 (
#ECHO action 1
) ELSE (
#ECHO action 2
)
Edit: This question is similar to Save output from FIND command to variable, but I wasn’t able to apply the solution to my problem and I wanted to know if a solution without a for loop is possible.
The command FOR with option /F and the command or command line specified as set (string inside parentheses) additionally enclosed by ' can be used for processing the output of a command or a command line with multiple commands on one line.
You can use this batch file:
#ECHO OFF
SET "_process=cmd.exe"
FOR /F %%I IN ('%SystemRoot%\System32\tasklist.exe /FI "IMAGENAME eq %_process%" ^| %SystemRoot%\System32\find.exe /I /C "%_process%"') DO SET "_count=%%I"
IF /I "%_process%" == "cmd.exe" SET /A _count-=1
IF %_count% LSS 2 (
ECHO action 1
) ELSE (
ECHO action 2
)
The command FOR runs in background without a visible console window cmd.exe /C with the command line:
C:\Windows\System32\tasklist.exe /FI "IMAGENAME eq cmd.exe" | C:\Windows\System32\find.exe /I /C "cmd.exe"
TASKLIST option /FI "IMAGENAME eq cmd.exe" filters the output of TASKLIST already to processes with image name (= name of executable) cmd.exe. This makes further processing faster and avoids that a running process with file name totalcmd.exe is counted as cmd.exe as the command line in question does.
The output of TASKLIST written to handle STDOUT is redirected to handle STDIN of command FIND which processes the lines and counts the lines containing cmd.exe anywhere in line. FIND outputs finally the number of lines containing searched string to handle STDOUT of command process running in background.
The redirection operator | must be escaped with caret character ^ on FOR command line to be interpreted as literal character when Windows command interpreter processes this command line before executing command FOR which executes the embedded command line with using a separate command process started in background.
FOR with option /F captures everything written to handle STDOUT of executed command process and processes each line. In this case just one line is captured by FOR containing just a number. Therefore no additional options are needed to assign the number assigned to loop variable I by FOR to environment variable _count using command SET.
The goal of this batch file is counting the number of cmd.exe processes already running. As the command line with TASKLIST and FIND is executed by FOR in background with using one more cmd.exe process, it is necessary to subtract the count by one to get the correct result using an arithmetic expression evaluated with SET /A _count-=1. This decrement by one is needed only for counting right the number of cmd.exe processes. It is not necessary for any other process.
For understanding the used commands and how they work, open a command prompt window, execute there the following commands, and read entirely all help pages displayed for each command very carefully.
echo /?
find /?
for /?
if /?
set /?
tasklist /?
You would do it like this, using TaskList in a For loop:
#Set "_process=cmd.exe"
#For /F %%A In ('TaskList^|Find /C /I "%_process%"'
) Do #If %%A Lss 2 (Echo Action 1) Else Echo Action 2
#Pause
You can also perform a similar thing using WMIC too:
#Set "_process=cmd.exe"
#For /F %%A In ('WMIC Process Get Name^|Find /C "%_process%"'
) Do #If %%A Lss 2 (Echo Action 1) Else Echo Action 2
#Pause
These could be refined further to ensure processes containing the string as opposed to matching it aren't counted:
#Set "_process=cmd.exe"
#For /F %%A In ('TaskList /FI "ImageName Eq "%_Process%""^|Find /C "."'
) Do #If %%A Lss 2 (Echo Action 1) Else Echo Action 2
#Pause
 
#Set "_process=cmd.exe"
#For /F %%A In (
'WMIC Process Where "Name='%_process%'" Get Name^|Find /C "."'
) Do #If %%A Lss 2 (Echo Action 1) Else Echo Action 2
#Pause
Note:If you're really checking the cmd.exe process, be aware that an the command inside the For loop parentheses is spawned within a new cmd.exe instance, (thereby increasing your count by 1)

Batch file how to use findstr command to find a text and make it a variable?

I want to know how to use Findstr command to find a text in a file and make it a variable here what i tried :
#echo off
for /F "delims=" %%a in ('findstr /I /m "100" Config.dat') do set "Variable=%%a"
cls
if %errorlevel% == 0 (
goto found
) else (
goto nope
)
:found
cls
echo founded ! %Variable%
pause
exit
:nope
cls
echo not found!
pause
exit
Ok i explain : In the 2nd line the number "100" is what i want to find and the "Config.dat" is the file that have in it the number 100 and some other numbers and the "Variable" in there is the name of the variable that i want to store in it 100.
The problem is when it founded number 100 it goes to the function "found" and displays "Founded! 100" but when it not founded it also goes to "found" function and only display founded! without 100. So why when it didn't founded it it goes to "found" i need it to go to "nope".
So i hope you guys explain to me if i did something wrong and thanks!
This is because for /F calls command in a standalone cmd.exe process and does not return the error level into context of the caller:
#echo off
rem drop last error level
type nul>nul
for /F "usebackq delims=" %%a in (`cmd.exe /C #exit /b 123`) do rem
echo ERRORLEVEL=%ERRORLEVEL%
-
ERRORLEVEL=0
If you want just load config values into environment variables, then there is no need to search anything. Just create standalone configuration file for that.
config.vars
# loads by cmd.exe script
aaa=111
"bbb=111 222"
/A ccc=1+1
"ddd=%bbb% & 333"
load_config.bat
#echo off
for /F "usebackq eol=# tokens=* delims=" %%i in ("config.vars") do (
call set %%i
)

Retrieve %ERRORLEVEL% with complicated call in FOR loop

In a Windows batch file I needed to retrieve the result of a program execution into a variable, when the program call was complicated with spaces and several options. After much discussion, a workaround was found by using CALL:
FOR /F "delims=" %%G IN ('CALL "C:\path with spaces\foo.bat" "blah blah='foobar' blah"') do set foo=%%G
Please see the following question for more details and to understand the context:
Retrieve command output to variable when command has spaces
In reality the batch file calls PostgreSQL 9.3, like this:
SET PSQL_EXE=C:\Program Files\Foo\Bar\PostgreSQL\9.3\bin\psql.exe
SET FOO=1
REM psql query should result in a 0 or 1 based on the mydbproperty table value
FOR /F %%G IN ('call "%PSQL_EXE%" -U pguser -d MyDB -p %PG_PORT% -c "select string_value from mydb.uri as uri, mydb.property as prop where uri.id = prop.property_uriid and uri.namespace_uri='http://example.com/foo/bar/' and uri.simplename = 'fooBar'" -q -t -w') DO SET FOO=%%G
REM next line doesn't have ERRORLEVEL set
IF !ERRORLEVEL! NEQ 0 EXIT !ERRORLEVEL!
Unfortunately it appears that this format results in a separate cmd instance, so any error that occurred in calling pgsql (e.g. the lack of a password file) does not get passed back and the %ERRORLEVEL% does not get set. Specifically pgsql prints out:
psql: fe_sendauth: no password supplied
Yet %ERRORLEVEL% (and !ERRORLEVEL!) is still 0.
See the following question for more info:
PSQL Error Level in Batch For Loop
So now the question is how to find out the %ERRORLEVEL%, now that I've succeeding in getting the response of psql. I'd prefer not to write to some temporary file---I want to do everything in memory.
(Note that, yes, the value I'm trying to query from the database and store in FOO will be either a 0 or a 1; not that it matters, but it does make things more confusing.)
I tried to test Aacini's proposed solution by simply seeing what would be returned in %%G:
FOR /F "delims=" %%G IN ('"CMD /V:ON /C CALL "%PSQL_EXE%" -U user -d MyDB -p %PG_PORT% -c "select string_value from mydb.uri as uri, mydb.database_property as prop where uri.id = prop.property_uriid and uri.namespace_uri='http://example.com/foo/bar/' and uri.simplename = 'fooBar'" -q -t -w ^& ECHO !ERRORLEVEL!"') DO (
ECHO %%G
)
Because psql could not find a password file, this prints an error to stderr (as expected) and the ECHO outputs 0---so the ERRORLEVEL is not getting sent back to the DO clause. But if I split out the command from the FOR loop and just run it directly, it shows the ERRORLEVEL (2) just fine!
"%PSQL_EXE%" -U user -d MyDB -p %PG_PORT% -c "select string_value from mydb.uri as uri, mydb.database_property as prop where uri.id = prop.property_uriid and uri.namespace_uri='http://example.com/foo/bar/' and uri.simplename = 'fooBar'" -q -t -w
ECHO !ERRORLEVEL!
Update: After being advised that I need to escape the escapes if I have delayed expansion already turned on, I took the given answer and updated it to take into account that the program I'm calling won't return a value if it generates an error. So here's what I do; first I make sure two variables are undefined:
SET "var1="
SET "var2="
Then I use the loop that #Aacini and #jeb gave, but inside the loop I do this:
if NOT DEFINED var1 (
SET "var1=%%G"
) else (
SET "var2=%%G"
)
Then outside the loop after it has finished:
if DEFINED var2 (
ECHO received value: !var1!
ECHO ERRORLEVEL: !var2!
) ELSE (
ECHO ERRORLEVEL: !var1!
)
That seems to work. (Unbelievably---what gymnastics!)
A couple tricks must be combined in order to do that. The Batch file placed in the for /F set is executed in a new cmd.exe context, so we need a method to report its errorlevel when such command ends. The only way to do that is by first executing the desired Batch file via a cmd.exe /C placed explicitly, and after it insert a command that report its errorlevel. However, the commands placed in the for /F set are executed in the command-line context, not in the Batch file context, so it is necessary to enable delayed expansion (/V:ON switch) in the explicit cmd.exe. After that, a simple echo !errorlevel! show the errorlevel returned by the Batch file.
#echo off
setlocal
set "foo="
FOR /F "delims=" %%G IN ('"CMD /V:ON /C CALL "path with spaces\foo.bat" "blah blah='foobar' blah" ^& echo !errorlevel!"') do (
if not defined foo (
set "foo=%%G"
) else (
set "errlevel=%%G"
)
)
echo String output from foo.bat: "%foo%"
echo Errorlevel returned by foo.bat: %errlevel%
This is "path with spaces\foo.bat":
#echo off
echo String from foo.bat
exit /B 12345
And this is the output of previous code:
String output from foo.bat: "String from foo.bat"
Errorlevel returned by foo.bat: 12345
If delayed expansion is already enabled before in the main batch file then a quite different syntax have to be used.
^!ERRORLEVEL^! This is necessary to avoid, that the !ERRORLEVEL! is evaluated in the context of the batch file, before the FOR line will be executed.
^^^& is necessary to bring a single ^& to the inner cmd /V:ON expression
#echo off
setlocal EnableDelayedExpansion
set "foo="
REM *** More carets must be used in the next line ****
FOR /F "delims=" %%G IN ('"CMD /V:ON /C CALL "path with spaces\foo.bat" "blah blah='foobar' blah" ^^^& echo ^!errorlevel^!"') do (
if not defined foo (
set "foo=%%G"
) else (
set "errlevel=%%G"
)
)
EDIT: Reply to OP's comments
My first solution assumes that the program always output one line, so it took the errorlevel from the second output line. However, the new specification indicate that when the program ends because an error, the first output line is not sent. It is very easy to fix this detail, and the corrected code is below.
#echo off
setlocal EnableDelayedExpansion
set "foo="
FOR /F "delims=" %%G IN ('"CMD /V:ON /C CALL "path with spaces\foo.bat" "blah blah='foobar' blah" ^^^& echo errlevel=^!errorlevel^!"') do (
set "str=%%G"
if "!str:~0,8!" equ "errlevel" (
set "errlevel=!str:~9!"
) else (
set "foo=%%G"
)
)
echo String output from foo.bat: "%foo%"
echo Errorlevel returned by foo.bat: %errlevel%
However, the original specification indicate that the executed program is a BATCH file called "C:\path with spaces\foo.bat". This code correctly works when the executed program is a Batch .BAT file as requested, but it does not work if the executed program is an .exe file, as I clearly indicated in my first comment: "Note that C:\path with spaces\foo.bat must be a .bat file! This method don't works if such file is an .exe". This way, you just need to create the "foo.bat" file with the execution of the .exe and an exit /B %errorlevel% command in the next line. Of course, if you directly test this method by directly executing the .exe program, it never will work...
2ND EDIT: Some explanations added
The purpose of this solution is that giving a Batch file "C:\path with spaces\foo.bat" that show some output and return an errorlevel value, like this one:
#echo off
echo String from foo.bat
exit /B 12345
... it may be called by another Batch file that get both the output and the errorlevel. This can be done in a very easy way using an auxiliary file:
#echo off
call "C:\path with spaces\foo.bat" > foo.txt
set errlevel=%errorlevel%
set /P "foo=" < foo.txt
echo String output from foo.bat: "%foo%"
echo Errorlevel returned by foo.bat: %errlevel%
However, the request indicate: "not to write to some temporary file---I want to do everything in memory".
The way to get the output from another command is via FOR /F, like this:
FOR /F "delims=" %%G in ('CALL "C:\path with spaces\foo.bat" "blah blah='foobar' blah"') do set foo=%%G
However, The Batch file placed in the FOR /F set is executed in a new cmd.exe context. To be more precise, the execution of foo.bat in previous FOR command is entirely equivalent to the next code:
CMD /C CALL "C:\path with spaces\foo.bat" "blah blah='foobar' blah" > tempFile & TYPE tempFile & DEL tempFile
Each line in tempFile is assigned to the %%G replaceable parameter and the set foo=%%G command is executed. Note that previous line is executed as if it was entered at the command-prompt (command-line context), so several commands does not work in this context (like goto/call to a label, setlocal/endlocal, etc) and delayed expansion is set to the initial value of cmd.exe, usually disabled.
In order to simplify the following description, we use a shorter, but similar FOR /F command that is shown with the equivalent internal code of the execution of foo.bat below it:
FOR /F %%G in ('CALL "foo.bat"') do set foo=%%G
-> CMD /C CALL "foo.bat" <- we also omit the "tempFile" parts
This way, we need a method to report the errorlevel of foo.bat when such command ends. The only way to do that is by first executing the desired Batch file via a cmd.exe /C placed explicitly, and after it insert a command that report its errorlevel. That is:
FOR /F %%G IN ('"CMD /C CALL foo.bat ^& echo !errorlevel!"') do set foo=%%G
-> CMD /C "CMD /C CALL foo.bat ^& echo !errorlevel!"
Note that the ampersand sign must be escaped this way: ^&; otherwise it will incorrectly split the commands placed in the set of the FOR /F command. In previous line both the foo.bat and the echo !errorlevel! command are executed via the nested CMD /C.
However, the commands placed in the FOR /F set are executed in the command-line context as explained before, so it is necessary to enable delayed expansion (/V:ON switch) in the explicit cmd.exe; otherwise, the echo !errorlevel! just not works:
FOR /F %%G IN ('"CMD /V:ON /C CALL foo.bat ^& echo !errorlevel!"') do set foo=%%G
-> CMD /C "CMD /V:ON /C CALL foo.bat ^& echo !errorlevel!"
Ok. Note that the previous description assumed that delayed expansion was DISABLED when the FOR /F command is executed. What are the changes if it was enabled? 1. The !errorlevel! is replaced by the current errorlevel value, and 2. Any caret that is used to escape a character is removed. These changes are done when the line is parsed, before the FOR /F command is executed. Lets see this point with detail:
SETLOCAL ENABLEDELAYEDEXPANSION
FOR /F %%G IN ('"CMD /V:ON /C CALL foo.bat ^& echo !errorlevel!"') do set foo=%%G
-> After parsed:
FOR /F %%G IN ('"CMD /V:ON /C CALL foo.bat & echo 0"') do set foo=%%G
Previous line issue an error because the unescaped &, as usual. We need to preserve both the exclamation marks and the caret of the ampersand. Preserve exclamation marks is easy: ^!errorlevel^!, but how we preserve the caret in ^&? If we use ^^& the first caret preserve the second one, but the next character to parse is & alone and the usual error appears! So, the right way is: ^^^&; the first caret preserve the second one and give ^, and the ^& preserve the ampersand:
SETLOCAL ENABLEDELAYEDEXPANSION
FOR /F %%G IN ('"CMD /V:ON /C CALL foo.bat ^^^& echo ^!errorlevel^!"') do set foo=%%G
-> After parsed:
FOR /F %%G IN ('"CMD /V:ON /C CALL foo.bat ^& echo !errorlevel!"') do set foo=%%G

Resources