CMD Batch - Find literal string - batch-file

Got an interesting problem. I'm pulling a query on a registry to see if it exists - if it does, I go down one path. If it doesn't, I go down a seperate path. I'll put a code example at the bottom. Here's the question:
FIND doesn't have an /L switch I can use for literal interpretation. One of my settings is 4, and the other is 14. So 14 works great, 4 finds both 4 and 14.
Is there a way to have it find "4" rather than just 4? Using escape characters before the quotations or double quoting just makes it miss entirely.
This is just an example of what I mean. Names and values changed to protect the innocent. This is part of a much larger script, so 'enabledelayedexpansion' is necessary.
setlocal enabledelayedexpansion
set ValueOne=4
set ValueTwo=14
rem This section determines if additional specific concept settings are needed.
:ConceptCheck
rem DEBUG
echo CONCEPT CHECK
for /f "tokens=1" %%G in ('reg query hklm\software\SomeRegistry\ /v "Value" ^| find "!ValueTwo!"') do (
echo Value Two Found!
call :ValueTwoSetup)
for /f "tokens=1" %%G in ('reg query hklm\software\SomeRegistry\ /v "Value" ^| find "!ValueOne!"') do (
echo Value One Found!
call :ValueOneSetup)
goto :eof
When run, if the entry is ValueTwo, it works great. ValueOne calls both ValueTwo and itself, due to the fact that 14 has the number 4 in it, and find catches it. I've tried ""!ValueOne!"", but then it fails utterly. I've tried "^"!ValueOne!^"" to the same result. I CAN stick an 'goto :eof' at the end of the do call, but then I lose expandability, and I still have to monitor which value shows up first, which makes things more complicated and clumsy. I'll be dealing with around two hundred 'values' by the time I'm done, and they aren't sequential.
Thoughts?

findstr has \< and \> special characters to indicate word boundaries:
findstr /r /c:"\<!ValueOne!\>"
Alternatively you can instruct reg query to use exact match by adding /e, so no need for find:
reg query hklm\software\SomeRegistry /s /v "Value" /d /e /f "!ValueOne!" >nul
if not errorlevel 1 echo ValueOne Found! & call :ValueOneSetup

Related

Correct syntax for nested for loops in batch file

I am trying to query the registry value of an installed application on many servers. I could hardcode the servers in, and have a lot of repetition and it seems to work. But sometimes a list of servers may change, and I'd like to take a more sensible modular approach.
So I can provide a test file with a list of server names, and then query the registry values of all of these servers, outputting them in a logfile.
I think the issue is definitely with my loops/variable storing. Not sure exactly what is going on, but it seems like once I get one set of Registry values, these are outputted for every single server, regardless of their respective registry values.
Not too experience with this kind of scripting, so I have been sort of mashing this together through trial and error, below is what I have so far. Please point out any woeful syntax errors I've made.
So essentially this is to find a McAfee scan engine version installed to each server. The reporting from the McAfee console itself is not always reliable, and I want to check that the contents are successfully pulled down to each server.
There are two registry values required to create a full engine version, so I am required to pull both of these and then combine them into a single string.
At the moment I do get an output that looks correct, but it does not represent what is actually installed to each server. It is like one value is being picked up and then replicated.
On an additional note, this only seems to work when executed in command line. And the first 1-2 times it is ran, no values are pulled up. Get the feeling I am quite far away from a solution.
SET LOGFILE=C:\LogFile.log
For /f %%i in (servers.txt) DO (
for /f "tokens=3" %%a in ('reg query \\%%i\HKLM\SOFTWARE\McAfee\AVEngine /v EngineVersion32Major ^| find /i "EngineVersion32Major"') do set /a major=%%a
for /f "tokens=3" %%b in ('reg query \\%%i\HKLM\SOFTWARE\McAfee\AVEngine /v EngineVersion32Minor ^| find /i "EngineVersion32Minor"') do set /a minor=%%b
echo %%i: %major%.%minor% >> %LOGFILE%
)
Would expect an output like this:
server1 : 5900.5647
server2 : 6000.4589
server3 : 5900.5647
server4 : 5900.5647
Cheers for any help you can provide.
It has nothing to do with for syntax, but how cmd parses scripts. See How does the Windows Command Interpreter (CMD.EXE) parse scripts?
When cmd parses a block (anything within parens), processes it as a single line. So it expands any %var% to its actual content. Changes made to it, are not taken into account until the block is exited. To retrieve new content within a block, you must enable delayed expansion, which forces the parser to evaluate it for every iteration. Also, syntax must be changed from %var% to !var!
Here, also, removed the /a switch from set command, as you are not doing calculations, and you may get results you won't expect (imagine a minor version equal to 0080 that will be treated as octal (invalid) and would break the script). Also, note both var name and var content enclosed in quotes (set "minor=%%b"), to avoid undesired trailing/leading spaces.
More also, I think you don't need the ^| find /i "EngineVersion32Major part, as possibly the key named EngineVersion32Major will content only what you are looking for. And again, enclose data in quotes (never know when a space may appear). You may also change "tokens=3" by "skip=1 tokens=3" to avoid process the heading reg echoes.
Thus
#echo off
SetLocal EnableDelayedExpansion
SET LOGFILE=C:\LogFile.log
for /f %%i in (servers.txt) DO (
for /f "tokens=3" %%a in ('reg query "\\%%i\HKLM\SOFTWARE\McAfee\AVEngine" /v "EngineVersion32Major"') do set "major=%%a"
for /f "tokens=3" %%b in ('reg query "\\%%i\HKLM\SOFTWARE\McAfee\AVEngine" /v "EngineVersion32Minor"') do set "minor=%%b"
echo %%i: !major!.!minor!>>%LOGFILE%
)
EndLocal
Also, this works within a block regardless delayed expansion is enabled or not (note double percent sign)
call echo %%i: %%major%%.%%minor%%>>%LOGFILE%
Another issue, any time a redirection is used within a block, the system opens and closes the file (or stream).
But
#echo off
SetLocal EnableDelayedExpansion
SET LOGFILE=C:\LogFile.log
>>"%LOGFILE%" (
for /f %%i in (servers.txt) DO (
for /f "tokens=3" %%a in ('reg query "\\%%i\HKLM\SOFTWARE\McAfee\AVEngine" /v "EngineVersion32Major"') do set "major=%%a"
for /f "tokens=3" %%b in ('reg query "\\%%i\HKLM\SOFTWARE\McAfee\AVEngine" /v "EngineVersion32Minor"') do set "minor=%%b"
echo %%i: !major!.!minor!
)
)
EndLocal
Processes all commands in the block, so the file is opened and closed only once. This may improve performance, specially with large files.
BTW,
>>"%LOGFILE%" (
...
...
)
Is the same as
(
...
...
)>>"%LOGFILE%"
Please use the search facility to locate many SO articles on delayed expansion.
The easy way in your case is to use
call echo %%i: %%major%%.%%minor%% >> %LOGFILE%

CMD Variable for registry

Alright so I was trying to delete the shell data in the registry. I can get to it and get all of the information right, but I want to automate it for all users. The one I can use right now only targets a specific file.
reg delete "HKEY_USERS\S-1-5-21-3793956547-500355711-2568367668-1002\Software\Classes\Local Settings\Software\Microsoft\Windows\Shell\Bags" /f
What I wanted to do was skip the input for S-1-5-21 and have it target all of the keys within HKEY_USERS. This way I can get all of the shell data deleted with the press of a button.
I am not sure if there is a variable for this, or maybe I am going in the wrong direction here. Any input is appreciated and I will attempt to answer any questions I can.
To enumerate the HKEY_USERS you can Reg Query within a For /f
#Echo off
Set "Hive=HKEY_USERS"
For /F "delims=" %%A in (
'Reg Query "%Hive%" ^|findstr "%Hive%\S-1-5-21" '
) Do Echo %%A
Replace Echo with any cmd you like to execute.
Sample scrambled output:
> SO_41773670.cmd
HKEY_USERS\S-1-5-21-2140113576-3579786329-1990256020-1001
HKEY_USERS\S-1-5-21-2140113576-3579786329-1990256020-1001_Classes
HKEY_USERS\S-1-5-21-2140113576-3579786329-1990256020-1005
HKEY_USERS\S-1-5-21-2140113576-3579786329-1990256020-1006
Something like this may very well suit your needs, it is very likely language dependent, and blind automated removal of registry subkeys is not my recommendation.
#Echo Off
For /F "EOL=E Delims=" %%A In ('Reg Query HKU /S /F Bags /K'
) Do Echo=Reg Delete "%%A" /F&&Echo=Reg Add "%%A"
Timeout -1
remove the two instances of Echo= and the last line if you're happy with the output and wish to continue.

Get registry key value and change accordingly from multiple PCs

First thank you for this great site! I've learned lots of batch scripting from here, but finally got stuck. I was tasked to write a script that will go out and check a specific registry keyword and change the ones that are not correct, on all PCs on the network.
#echo off
SetLocal EnableDelayedExpansion
FOR /F %%a in (C:\batchFiles\computers.txt) DO (
FOR /F "tokens=3" %%b in (reg query "\\%%a\HKLM\SOFTWARE\some\any" /v "Forms Path") do set "var=%%b"
if "%var%" == "\\server\folder\forms\path"
echo %%a was correct
pause
if "%var%" NEQ "\\server\folder\forms\path"
echo %%a was not correct
pause
)
My boss tasked me with this not to long ago and its a little above my head, so i'm trying to learn on the fly. I tried with %errorlevel% and couldn't get it to do what I wanted either.
I had all of my PC names listed in C:\batchFiles\computers.txt. The REG_SZ key from "Forms Path" is a folder located on a network drive. Right now it says that the syntax is incorrect.
If you can understand what i'm trying to do, and have a better suggestion, I'm all ears! Oh and I'd like to output ALL of the results to a text file so I know which PCs were changed, which ones had it correct, and which ones the script couldn't reach.
Thank you so much for your time!
You enabled delayed environment variable expansion, but do not use it. %var% must be written as !var! to make use of delayed expansion as required here.
The syntax used on both if conditions is also not correct.
The registry query output by reg.exe on my computer running Windows XP is:
! REG.EXE VERSION 3.0
HKEY_LOCAL_MACHINE\SOFTWARE\some\any
Forms Path REG_SZ \\server\folder\forms\path
There is first a blank line, next a line with version of reg.exe, one more blank line, a line with registry key and finally on fifth line the data of interest. Therefore I used in the batch code below skip=4 to speed it up. However, the inner loop would produce the right result also without skip=4 and therefore parsing all 5 lines.
Important is the last line. The inner loop separates by spaces. As the name of the registry value contains also a space character, the first two tokens are for Forms and Path. And the third token is REG_SZ.
The rest of the line after the spaces after REG_SZ is of real interest, but could contain also a space character. So I used in batch code below not tokens=4, but instead tokens=3* and ignored %b which holds REG_SZ. Instead %c is assigned to environment variable var resulting in getting really entire string value even if the string contains 1 or more spaces.
And the environment variable var is deleted before a new query on next computer is executed in case of a computer does not contain the registry value at all. The error message written by reg.exe to stderr is redirected to device nul for this case. The value of var would be unchanged from previous computer if not deleted before running the next query.
#echo off
setlocal EnableDelayedExpansion
for /F %%a in (C:\batchFiles\computers.txt) do (
set var=
for /F "skip=4 tokens=3*" %%b in ('%SystemRoot%\System32\reg.exe query "\\%%a\HKLM\SOFTWARE\some\any" /v "Forms Path" 2^>nul') do set "var=%%c"
if "!var!" == "\\server\folder\forms\path" (
echo %%a has correct value.
) else if "!var!" == "" (
echo %%a does not have the value at all.
) else (
echo %%a has wrong value.
)
pause
)
endlocal

Windows 2003 batch script with pipe

I'm trying to write a batch script that will run every 15 minutes, check number of files in a directory and if that number is bigger then set limit, move all files to another directory. I would easily do this in bash script, but this is my first batch script.
I split this task in several steps:
Find number of files in directory.
I managed to do this with this command:
dir/b/a-d d:\test\test2 | find /v /c "::"
Next thing is to assign output of this command to some variable so I can compare it with my desired limit. This is where problem starts.
ECHO OFF
setlocal enableextensions
FOR /F "tokens=* USEBACKQ" %%a IN (`dir/b/a-d d:\test\test2 ^| find /v /c "::"`)
DO (#SET NUMFIL=%%a)
ECHO %NUMFIL%
endlocal
I'm getting: "| was unexpected at this time". Obviously, pipe is getting in the way. I found that it is special character and as such must be escaped with caret. After doing so, I'm getting: "The syntax of the command was incorrect." This is Windows server 2003.
3.After getting this problem solved, I plan to insert something like this:
IF %%NUMFIL%% > 20
(move "d:\test\test2\ti*" "d:\test\test2\dir\")
That would move all that files (all of them starts with "ti") to desired directory.
So my questions would be: what to do with #2 issue and will #3 work in this case?
Not sure ":: will work in your first case, since :: is unlikely to appear in a DIR output. A single colon would suffice, since the /c option of find counts the number of LINES in which the target string occurs.
The secret to the second problem is that the do keyword must occur on the same line as the closing-parenthesis of the IN clause. It is possible to break the structure into
FOR /F "tokens=* USEBACKQ" %%a IN (
' dir/b/a-d d:\test\test2 ^| find /v /c ":" '
) DO (SET NUMFIL=%%a)
Note the # is not required - it suppresses the command's being echoed, which is turned off by the initial #echo off (the # there suppresses the echoing of the ECHO OFF
Also, the parentheses around this set are not required. IF they are used, the open-parenthesis must occur on the same physical line as the do.
You also don't need to use usebackq since you have no need here to change the interpretation of quotes.
Third item - > is a redirector. For a comparison operator, use one of EQU GEQ LSS LEQ GTR NEQ depending on comparison required.
And again, the open-parenthesis must be on the same line as the if. With an else, the close-parenthsis before the ELSE , the ELSE keyword and the open-parenthesis after must all be on the same physical line.
I think this should work. Note that it does not use backticks.
ECHO OFF
setlocal enableextensions
FOR /F %%a IN (' dir /b /a-d "d:\test\test2" ^| find /c /v "" ') DO SET NUMFIL=%%a
ECHO %NUMFIL%
IF %NUMFIL% GTR 20 (move "d:\test\test2\ti*" "d:\test\test2\dir\")

BATCH Iterate FOR /F variables

I have simple files that look like:
Submit.log
Submitting job(s)..
2 job(s) submitted to cluster 528.
Submitting job(s)..
2 job(s) submitted to cluster 529.
I need then to output only the last number from the second line (the cluster), in this case the desired output would be:
528
529
I tried using the script below but I dont know why it is not working. I need to do it using batch, it is a restriction of our system.
#ECHO OFF
FOR /F "tokens=6" %%g IN (Submit.log) DO (
set Job=%%g
echo %Job:.=%
)
An elegant solution would definitively be a plus, but if someone could help me out with a simple solution I would appreciate.
Here is a one liner that works. The FINDSTR search can easily be adapted to make the search very precise.
#echo off
for /f "tokens=6 delims=. " %%N in ('findstr /c:"submitted to cluster" "submit.log"') do echo %%N
The paxdiablo solution can be simplified as well so that it does not need to modify the value therefor it doesn't need delayed expansion. There is no need to capture all the tokens, just the ones you want to use. The delimiter is set to look for dot and space (the default was space and tab). As paxdiablo said, more tokens can be tested to make the search more precise, but this is not as convenient as FINDSTR.
#echo off
for /f "tokens=5,6 delims=. " %%A in (submit.log) do if %%A==cluster echo %%B
The following script will do the job:
#setlocal enableextensions enabledelayedexpansion
#echo off
for /f "tokens=1-6" %%a IN (submit.log) do (
if %%c==submitted (
set job=%%f
echo !job:.=!
)
)
endlocal
And, since I'm basically the modest type, I'll stop short of contending that it's both elegant and simple :-)
It basically only looks at those lines where the third word is submitted (you can also use additional rules if this proves problematic) and then modifies and outputs the sixth word.
Note the use of delayed expansion (using ! instead of %) That's needed to ensure the variables aren't evaluated before the loop even begins.

Resources