Batchfile: cmd.exe closes if variable has semicolon? - windows

Here is a part of my batch...
for /f "delims=" %%i in ('C:\pathto\dig.exe www.example.com #8.8.8.8 +short') do set RIP=%%i
If %RIP%==93.184.216.119 (
ECHO - PASS >> results-file.txt
) else (
ECHO - FAIL >> results-file.txt
)
...this works fine - except, let's say the DNS server 8.8.8.8 is unavailable or broken (so for testing replace it with a bad IP) and then, normally from the command-line dig.exe will timeout and eventually return...
;; connection timed out; no servers could be reached
...followed by a return to the command prompt. So I would expect that string to get set in %RIP% and thus FAIL would be printed to the file, but what happens instead is dig.exe runs, timeout, and then cmd.exe just closes, and nothing gets written to the file, and the rest of the batch does not continue.
Any idea where I've gone wrong?

There is at least one reason why the RIP variable is not set - the default FOR /F EOL value is semicolon, meaning all lines that begin with ; are ignored. You need to set EOL to something you know the line cannot begin with, or disable it entirely (tricky).
It is also possible that the error message is sent to stderr instead of stdout, and FOR /F only captures stdout. If this is happening, then you must also redirect stderr to stdout using 2>&1. The special characters must be escaped (or quoted) when in the FOR /F IN() clause.
There is an awkward syntax to disable both DELIMS and EOL that can solve your problem:
for /f delims^=^ eol^= %%i in ('C:\pathto\dig.exe www.example.com #8.8.8.8 +short 2^>^&1') do set RIP=%%i
The reason your IF fails if RIP is not set is the left side is completely empty, causing the parser to fail. You can fix this by adding quotes (or any character) to both sides. But you should also explicitly clear the RIP value before the loop, just in case a prior command previously set RIP.
set "RIP="
for /f "delims=" %%i in ('C:\pathto\dig.exe www.example.com #8.8.8.8 +short') do set RIP=%%i
If "%RIP%"=="93.184.216.119" (
ECHO - PASS >> results-file.txt
) else (
ECHO - FAIL >> results-file.txt
)

Related

Windows 10 - Clearing Event Logs in CMD batch script with all output running on same line?

I have a batch script which part of it is clearing the Event logs of Windows 10.
I would like to see the output of the following wevtutil.exe command, but output just on a single line (overwriting each line) instead of many multiple lines.
I know about the ANSI Escape Sequences ESC[1FESC[0J and ESC[1A which can overwrite a previous line (The ESC char is ALT+027 and can be seen in Notepad++), but I haven't been able to figure out how to do that with the wevtutil.exe el command. It still outputs each line one after the other.
Here's what I tried (running in CMD with admin rights):
#echo off
SET OverwriteLine=ESC[1FESC[0J
echo Making sure the variable OverwriteLine actually works. This is line 1.
echo %OverwriteLine%If you see this line instead of line 1, OverwriteLine works.
echo Great, now let^'s see if it works for the "wevtutil.exe cl" command
pause
echo Clearing Event logs...
#echo on
for /F "tokens=*" %%E in ('wevtutil.exe el') DO echo %OverwriteLine% | wevtutil.exe cl "%%E"
pause
I know this can be done via Powershell's $host.ui.RawUI.CursorPosition, but I need a solution for CMD/BAT.
As we deal with a single specific issue per question, and your main one appears to be with the implementation of the VT100 sequences, here is a commented example using a completely different for loop just for demonstration of the technique.
#Echo Off
SetLocal EnableExtensions
Rem Create a variable to use as the escape character
For /F %%G In ('Echo Prompt $E ^| %SystemRoot%\System32\cmd.exe') Do Set "\x1b=%%G"
Rem This is a static line included to demonstrate that it is unaffected
Echo(Output
Rem Turns off the cursor and removes a stray new line caused by Echo
Echo(%\x1b%[?25l%\x1b%[1A
Rem For loop example
For /L %%G In (1,1,8) Do (
Rem Clears to the beginning of the line, prints the output, and moves up a line
Echo(%\x1b%[1KLine %%G%\x1b%[1A
Rem Creates a short delay between output lines to see the effect
%SystemRoot%\System32\PATHPING.EXE 127.0.0.1 -n -q 1 -p 650 1>NUL
)
Rem Turns on the cursor again
Echo(%\x1b%[?25h
Pause
OK, found the solution:
for /F "tokens=*" %%E in ('wevtutil.exe el') DO (wevtutil.exe cl "%%E" | echo <ESC>[1F<ESC>[0KClearing %%E)
Explanation/Notes:
< ESC> means the special ESC escape code sequence. You can generate this char by typing ALT+027 in Notepad++ if you're editing your code in there, or generate it at runtime using the FOR loop that Compo mentioned.
We move the cursor to the beginning of the previous line with ESC[1F.
We then clear from the cursor to the end of the line with ESC[0K.
Clearing the Windows event logs requires running the CMD script with Administrator rights.
Expect some event logs to fail. The failed ones will remain on the screen, each on a new line (which might become handy). If you don't want to see any failures, just add 2>nul : DO (wevtutil.exe cl "%%E" 2>nul | echo <ESC>[1F<ESC>[0KClearing %%E)
You can learn more about escape codes here.

Echoing output of command from variable

I'm trying to create a simple batch script that stores the output of a command to tmp file, stores the content of the tmp file to a variable, and outputs the variable:
#setlocal enableextensions enabledelayedexpansion
#echo off
ping -n 1 10.1.0.2 > tmp
SET #var= < tmp
ECHO %#var%
del tmp
I would expect the above to work, but it outpus:
C:\Documents and Settings\Administrator\Desktop>pinger.bat
ECHO is off.
(Nb: taking the #echo off just outputs ECHO is on, including echoing all the lines of code)
The question pointed by #rostok (my answer here), shows the reason to not use the received packets to determine if the host is or not online. On the same subnet, over ipv4, with an offline host, you will get a "unreachable" error, but the packets are received.
For a simple test in ipv4, the linked answer can handle your problem. For a more robust test over ipv4 or ipv6 (that show different behaviour), this could help.
Anyway, the code to get the required data directly from the ping command,
set "received="
for /f "skip=6 tokens=5 delims==, " %%a in ('ping -n 1 10.1.0.2') do (
if not defined received set "received=%%a"
)
or from the file
set "received="
for /f "usebackq skip=6 tokens=5 delims==, " %%a in ("tmpFile") do (
if not defined received set "received=%%a"
)
Where the skip clause indicates that the first six lines should be skipped, and the tokens and delims select the required element inside the line
Packets: Send = 1, Received = 1,
^ ^^^ ^^ ^^^ ^ Delimiter position
1 2 3 4 5 Token number
But as said, this is not a reliable way to solve the problem

for loop doesn't iterate through the lines of a text file

I have a for loop that is supposed to print each line of a text file. Instead it's printing the logPath.
This is the code:
set enabledelayedexpansion
for %%G in (C:\ExecutionSDKTest_10.2.2\*.properties) DO (
Set fileName=%%~nxG
...
set logPath="C:/ExecutionSDKTest_10.2.2/Logs/!fileName!.log"
...
For /f "tokens=*" %%B in (!logPath!) Do (
echo Inside the for loop for printing each line!!
set logLine=%%B
print !logLine! REM this prints the logPath instead of each logLine and jumps out of this for loop after the 1st iteration!
)
)
Any help?
echo off
For %%G in (C:\ExecutionSDKTest_10.2.2\*.properties) DO (
FOR /F "tokens=*" %%i in (%%G) do #echo %%i
)
Use backslashes instead of forward slashes.
set "logPath=C:\ExecutionSDKTest_10.2.2\Logs\!fileName!.log"
While usually you can use them interchangeably in Windows, cmd is a special case as the forward slash is used for switches and options to built-in commands. And its parser often stumbles over forward slashes. You usually can safely pass such paths to external commands, though.
you don't tell us which line is issuing the "invalid switch" error message, but I see several potential problems:
to use !variables! you need to enable delayed expansion
SetLocal EnableDelayedExpansion
don't use '/' in filenames, change to '\'
set logPath="C:\ExecutionSDKTest_10.2.2\Logs\!fileName!.log"
print command sends a text file to the printer. Change it to echo
echo !logLine!

Why does this batch variable never change even when set?

#echo off
SET first=0
FOR %%N IN (hello bye) DO (
SET first=1
echo %first%
echo %%N
)
It seems that the variable "first" is always 0. Why?
With batch files, variables are expanded when their command is read - so that would be as soon as the for executes. At that point, it no longer says echo %first%, it literally says echo 0, because that was the value at the point of expansion.
To get around that, you need to use delayed expansion by surrounding your variable name with ! instead of % - so that would be echo !first!. This may require you to start cmd.exe with the /V parameter, or use setlocal enabledelayedexpansion in the beginning of your batch file (just after echo off).
If you type set /?, you'll see a much more detailed explanation of this at the end of the output.

How do you strip quotes out of an ECHO'ed string in a Windows batch file?

I have a Windows batch file I'm creating, but I have to ECHO a large complex string, so I'm having to put double quotes on either end. The problem is that the quotes are also being ECHOed to the file I'm writing it to. How do you ECHO a string like that and strip the quotes off?
UPDATE:
I've spent the last two days working on this and finally was able to kludge something together. Richard's answer worked to strip the quotes, but even when I put the ECHO in the subroutine and directly outputted the string, Windows still got hung up on the chars in the string. I'll accept Richard's answer since it answers the question asked.
I ended up using Greg's sed solution, but had to modify it because of sed/windows bugs/features (it didn't help that it came with no documentation). There are a few caveats to using sed in Windows: you have to use double quotes instead of single quotes, you can't escape the double quotes in the string directly, you have to endquote the string, escape using the ^ (so ^") then beqin quote for the next section. Also, someone pointed out that if you pipe input to sed, there's a bug with a pipe being in the string (I didn't get to verify this since in my final solution, I just found a way not to have all quotes in the middle of the string, and just removed all quotes, I never could get the endquote to be removed by itself.) Thanks for all the help.
The call command has this functionality built in. To quote the help for call:
Substitution of batch parameters (%n) has been enhanced. You can
now use the following optional syntax:
%~1 - expands %1 removing any surrounding quotes (")
Here is a primitive example:
#echo off
setlocal
set mystring="this is some quoted text"
echo mystring=%mystring%
call :dequote %mystring%
echo ret=%ret%
endlocal
goto :eof
:dequote
setlocal
rem The tilde in the next line is the really important bit.
set thestring=%~1
endlocal&set ret=%thestring%
goto :eof
Output:
C:\>dequote
mystring="this is some quoted text"
ret=this is some quoted text
I should credit the 'environment variable tunneling' technique (endlocal&set ret=%thestring%) to Tim Hill, 'Windows NT Shell Scripting'. This is the only book I have ever found that addresses batch files with any depth.
The following approach can be used to print a string without quotes:
echo|set /p="<h1>Hello</h1>"
pushing this string into file:
echo|set /p="<h1>Hello</h1>" > test.txt
pushing this string into file and appending a CR/LF:
echo|(set /p="<h1>Hello</h1>" & echo.) > test.txt`
To check:
type test.txt
You can use the %var:x=y% construction that replaces all x with y.
See this example what it can do:
set I="Text in quotes"
rem next line replaces " with blanks
set J=%I:"=%
echo original %I%
rem next line replaces the string 'in' with the string 'without'
echo stripped %J:in=without%
To remove all quotation marks from a set variable, you need Delayed Variable Expansion to securely expand the variable and process it. Expansion using percent signs (i.e. %VAR% and %1) are inherently unsafe (they are vulnerable to command injection; read this for details).
SETLOCAL EnableDelayedExpansion
SET VAR=A ^"quoted^" text.
REM This strips all quotes from VAR:
ECHO !VAR:^"=!
REM Really that's it.
To strip quotes from a text file or a command output, things will get complicated because with Delayed Expansion, string like !VAR! within the text document will get expanded (within the %%i expansion in FOR /F) when it shouldn't. (This is another vulnerability—information disclosure—that's not documented elsewhere.)
To safely parse the document, a switch between delayed-expansion-enabled and -disabled environment is needed.
REM Suppose we fetch the text from text.txt
SETLOCAL DisableDelayedExpansion
REM The FOR options here employs a trick to disable both "delims"
REM characters (i.e. field separators) and "eol" character (i.e. comment
REM character).
FOR /F delims^=^ eol^= %%L IN (text.txt) DO (
REM This expansion is safe because cmd.exe expands %%L after quotes
REM parsing as long as DelayedExpansion is Disabled. Even when %%L
REM can contain quotes, carets and exclamation marks.
SET "line=%%L"
CALL :strip_quotes
REM Print out the result. (We can't use !line! here without delayed
REM expansion, so do so in a subroutine.)
CALL :print_line
)
ENDLOCAL
GOTO :EOF
REM Reads !line! variable and strips quotes from it.
:strip_quotes
SETLOCAL EnableDelayedExpansion
SET line=!line:^"=!
REM Make the variable out of SETLOCAL
REM I'm expecting you know how this works:
REM (You may use ampersand instead:
REM `ENDLOCAL & SET "line=%line%"`
REM I just present another way that works.)
(
ENDLOCAL
SET "line=%line%"
)
GOTO :EOF
:print_line
SETLOCAL EnableDelayedExpansion
ECHO !line!
ENDLOCAL
GOTO :EOF
The delims^=^ eol^= in the code above probably needs explanation:
This effectively disables both "delims" characters (i.e. field separators) and "eol" character (i.e. comment character). Without it, the "delims" will default to tab and space and "eol" defaults to a semicolon.
The eol= token always read whichever the next character it is after the equal sign. To disable it this token has to be in the end of the options string so that no character may be used for "eol", effectively disabling it. If the options string is quoted, it might use quotation mark (") as the "eol", so we must not quote the options string.
The delims= option, when it's not the last option in the options string, will be terminated by a space. (To include space in "delims" it has to be the last option of FOR /F options.) So delims= followed by a space and then another option disables the "delims".
I know that it is not actually for the author, but if you need to send some text to the file without quotes - the solution below works for me. You do not need to use quotes in the echo command, just surround the complete command with brackets.
(
echo first very long line
echo second very long line with %lots% %of% %values%
) >"%filename%"
This worked for me:
SET "SOMETHING=Complex (String) (of stuff!)"
echo !SOMETHING! >> file.txt
This will turn "C:\Program Files\somefile.txt" into C:\Program Files\somefile.txt
while still preserving cases such as Height=5'6" and Symbols="!##
:DeQuote
SET _DeQuoteVar=%1
CALL SET _DeQuoteString=%%!_DeQuoteVar!%%
IF [!_DeQuoteString:~0^,1!]==[^"] (
IF [!_DeQuoteString:~-1!]==[^"] (
SET _DeQuoteString=!_DeQuoteString:~1,-1!
) ELSE (GOTO :EOF)
) ELSE (GOTO :EOF)
SET !_DeQuoteVar!=!_DeQuoteString!
SET _DeQuoteVar=
SET _DeQuoteString=
GOTO :EOF
Example
SetLocal EnableDelayedExpansion
set _MyVariable = "C:\Program Files\ss64\"
CALL :dequote _MyVariable
echo %_MyVariable%
The above answer (starting with :DeQuote) assumes delayed environment variable expansion is set to on. From cmd /?:
Delayed environment variable expansion is NOT enabled by default. You
can enable or disable delayed environment variable expansion for a
particular invocation of CMD.EXE with the /V:ON or /V:OFF switch. You
can enable or disable completion for all invocations of CMD.EXE on a
machine and/or user logon session by setting either or both of the
following REG_DWORD values in the registry using REGEDT32.EXE:
HKEY_LOCAL_MACHINE\Software\Microsoft\Command Processor\DelayedExpansion
and/or
HKEY_CURRENT_USER\Software\Microsoft\Command Processor\DelayedExpansion
to either 0x1 or 0x0. The user specific setting takes precedence over
the machine setting. The command line switches take precedence over the
registry settings.
If delayed environment variable expansion is enabled, then the exclamation
character can be used to substitute the value of an environment variable
at execution time.
The following batch file starts a series of programs with a delay after each one.
The problem is to pass a command line with parameters for each program. This requires quotes around the program argument, which are removed when the call is made. This illustrates a few techniques in batch file processing.
Look in the local subroutine :mystart for how an argument in quotes is passed in, and the quotes are removed.
#echo off
rem http://www.microsoft.com/resources/documentation/windows/xp/all/proddocs/en-us/if.mspx?mfr=true
rem Start programs with delay
rem Wait n seconds
rem n number retries to communicate with the IP address
rem 1000 milliseconds between the retries
rem 127.0.0.1 is the LocalHost
rem start /b (silent) /min (minimized) /belownormal (lower priority)
rem /normal provides a no-op switch to hold the place of argument 1
rem start /normal "Opinions" %SystemRoot%\explorer.exe /e,d:\agar\jobs\opinion
rem ping 127.0.0.1 -n 8 -w 1000 > nul
rem Remove quotes in Batch
rem http://ss64.com/nt/syntax-dequote.html
rem String manipulation in Batch
rem http://www.dostips.com/DtTipsStringManipulation.php
rem ^ line continuation
rem
rem set p="One Two" p has the exact value "One Two" including the quotes
rem set p=%p:~1,-1% Removes the first and last characters
rem set p=%p:"=% Removes all double-quotes
rem set p=%p:cat=mouse% Replaces cat with mouse
rem ping 127.0.0.1 -n 12 -w 1000 > nul
rem 1 2 3 4
#echo on
call :mystart /b/min "Opinions" "%SystemRoot%\explorer.exe /e,d:\agar\jobs\opinion" 8
#echo on
call :mystart /b/min "Notepad++" D:\Prog_D\Notepad++\notepad++.exe 14
#echo on
call :mystart /normal "Firefox" D:\Prog_D\Firefox\firefox.exe 20
#rem call :mystart /b/min "ProcessExplorer" D:\Prog_D\AntiVirus\SysInternals\procexp.exe 8
#echo on
call :mystart /b/min/belownormal "Outlook" D:\Prog_D\MSOffice\OFFICE11\outlook.exe 2
#echo off
goto:eof
:mystart
#echo off
rem %3 is "program-path arguments" with the quotes. We remove the quotes
rem %4 is seconds to wait after starting that program
set p=%3
set p=%p:"=%
start %1 %2 %p%
ping 127.0.0.1 -n %4 -w 1000 > nul
goto:eof
Using the FOR command to strip the surrounding quotation marks is the most efficient way I've found to do this. In the compact form (Example 2) it's a one-liner.
Example 1: The 5-line (commented) solution.
REM Set your string
SET STR=" <output file> (Optional) If specified this is the name of your edited file"
REM Echo your string into the FOR loop
FOR /F "usebackq tokens=*" %%A IN (`ECHO %STR%`) DO (
REM Use the "~" syntax modifier to strip the surrounding quotation marks
ECHO %%~A
)
Example 2: The 1-liner real-world example.
SET STR=" <output file> (Optional) If specified this is the name of your edited file"
FOR /F "usebackq tokens=*" %%A IN (`ECHO %STR%`) DO #ECHO %%~A
I find it interesting that the inner echo ignores the redirection characters '<' and '>'.
If you execute ECHO asdfsd>asdfasd you will write file out instead of std out.
Hope this helps :)
Edit:
I thought about it and realized there is an even easier (and less hacky) way of accomplishing the same thing. Use the enhanced variable substitution/expansion (see HELP SET) like this:
SET STR=" <output file> (Optional) If specified this is the name of your edited file"
ECHO %STR:~1,-1%
That will print all but the first and last characters (your quotation marks). I would recommend using SETLOCAL ENABLEDELAYEDEXPANSION too. If you need to figure out where quotation marks are located in the string you can use FINDSTR to get the character #s.
Daniel Budzyński's response is brilliant. It works even in situations where there are special characters in the output. For example:
C:\> for /f "usebackq tokens=2 delims=:" %i in (`%comspec%\..\ping -n 1 -w 200 10.200.1.1 ^| \
findstr /c:"TTL="`) do echo|set /p="%i"
bytes=32 time<1ms TTL=255
If you try tried a simple echo without the quotes, you get a error, due to the "<" in the variable:
C:\> set "output=bytes=32 time<1ms TTL=255"
C:\> echo %output%
The system cannot find the file specified.
C:\> echo|set /p="%output%"
bytes=32 time<1ms TTL=255
Brute force method:
echo "foo <3 bar" | sed -e 's/\(^"\|"$\)//g'
This requires finding a suitable Win32 version of sed, of course.
http://unxutils.sourceforge.net/ is a native win32 port of a bunch of GNU utilities including sed, gawk, grep and wget. (sorry that I don't have enough rep to post this as a comment!)

Resources