I'm struggling to read the value of a registry key into a variable. The registry value may, or may not, contain spaces. In this case I'm trying to look up SDK paths.
It's easy to get the value with reg query, but it's returned in a remarkably unhelpful format:
C:\Users\Administrator\Desktop>reg query "HKLM\SOFTWARE\Microsoft\Microsoft SDKs\Windows\v7.1\WinSDKTools" /v InstallationFolder
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Microsoft SDKs\Windows\v7.1\WinSDKTools
InstallationFolder REG_SZ C:\Program Files\Microsoft SDKs\Windows\v7.1\bin\
The key name, key type, and key value are separated by a series of spaces, not tabs.
You'd think you could use something like:
FOR /F "usebackq tokens=3* skip=2" %%L IN (
`reg query "HKLM\SOFTWARE\Microsoft\Microsoft SDKs\Windows\v7.1\WinSDKTools" /v InstallationFolder`
) DO SET sdkpath=%%L
... but if the key contains spaces, as in this case, the result emitted on the command line is:
C:\Users\Administrator\Desktop>SET sdkpath=C:\Program
Which isn't helpful. There doesn't seem to be a wildcard variable to say "All matches". And you can't just write:
DO SET sdkpath=%%L %%M
... because if there are not spaces in the path, that will produce a literal %M (and would also produce a trailing space).
So. Is there any way to do this simple thing in a batch file?
I've written most of my tooling in Powershell (and some Perl), but I need to use a batch file for the "simple" task of calling the visual studio / windows sdk environment script and then invoking the code in sane languages once the environment is set up.
You'd think that after 10+ years of cmd.exe's existence, and command.com before it, this would be easy. Help?
(I can use Perl and the Win32:: packages to query the registry, but it doesn't help me get it into a batch var...)
Using Win2k12.
I've read:
Read registry value that contains spaces using batch file
Shell script reading windows registry
and many others, but none handle the general case of correctly reading a value from a key without knowing whether or not it contains spaces / how many.
Ah this is one of the annoying details about the for /f command. In order to read all the characters including the spaces with the * the previous token needs to be declared. tokens=2,* The functional reason is stated in the documentation.
If the last character in the tokens= string is an asterisk (*), an additional variable is allocated and receives the remaining text on the line after the last token that is parsed.
FOR Documentation
FOR /F "usebackq tokens=2,* skip=2" %%L IN (
`reg query "HKLM\SOFTWARE\Microsoft\Microsoft SDKs\Windows\v7.1\WinSDKTools" /v InstallationFolder`
) DO SET sdkpath=%%M
Use Quotation marks
SET "sdkpath=%%M"
instead SET sdkpath=%%M
This will avoid problems with spaces.
Related
I want to make a script so i could use Win+R, "venv (some path)" and add it in path env variable.
Problem is that i'm not really a batch coder and idk language.
I wrote sth that "should look like working code" but it's not.
Could you help me to make this script working?
echo %venvPath%
#set PATH=%PATH%;%venvPath
If you simply need the variable to be updated for the duration of a specific cmd.exe instance, then you should change your second line to:
#Set "PATH=%PATH%%venvPath%;"
If you need it to be more permamently defined, (for future cmd.exe instances), and as there are both a User, and a System, environment, (and the PATH variable is a joining of both), below is one method you could employ from a Windows batch/command script.
User Environment, (recommended):
#For /F "EOL=H Tokens=2,*" %%G In ('%SystemRoot%\System32\reg.exe Query "HKCU\Environment" /V Path 2^>NUL') Do #%SystemRoot%\System32\setx.exe Path "%%H%venvPath%;"
System Environment, (will need to be Run as administrator):
#For /F "EOL=H Tokens=2,*" %%G In ('%SystemRoot%\System32\reg.exe Query "HKLM\SYSTEM\CurrentControlset\Control\Session Manager\Environment" /V Path 2^>NUL') Do #%SystemRoot%\System32\setx.exe Path "%%H%venvPath%;" /M
If, of course, you need it for both the current cmd.exe instance, and all future instances, you'd need to implement both methods.
Notes
There is a 1024 character string length limitation when using setx.exe. This means that for safety, the expanded content of %%H plus the expanded value of %venvPath%, should not exceed that character length. It may therefore be prudent to save the content of %%H%venvPath% as a variable, then perform a character length check on it, before invoking the setx.exe command.
In addition, it would also be prudent to determine whether the expanded content of %venvPath%, is already listed within the existing %PATH% variable value before you attempt to add it. Whilst duplicating it may not immediately or seriously impact the variable, it would unnecessarily increase the character length, and therefore increase the likelihood of the forementioned limitation in subsequent setx.exe commands, with PATH.
I am running a batch file on Windows 7 and running into this error (I have narrowed down the error to the following line):
FOR /F "delims=" %%I in ('echo %RegVal%') do set sasroot=%%~sI
Where Regval is the file path of a given software, which in this case (on my Win7 machine) is:
RegVal = C:\Program Files\SAS 9.2_M3_10w37\SASFoundation\9.2(32-bit)
This same script used to work on Windows Vista, although I suspect it may be that there a parenthesis in RegVal now as it was previousy C :\Program Files\SAS 9.2_M3_10w37\SASFoundation\ on my previous Vista machine.
You suspection is correct.
To get around it, enclose your variable into doublequotes (You remove them again with the ~ in the setcommand)
FOR /F "delims=" %%I in ('echo "%RegVal%"') do set sasroot=%%~sI
I suggest you create a file with the value of RegVal in it, then parse it using the FOR loop:
echo %RegVal%>C:\SomeFile.txt
FOR /F "delims=" %%I in (C:\SomeFile.txt) do set sasroot=%%~sI
This should help you get around your problem.
Stephan's solution is much simpler, but I'll explain my solution anyway, which might prove useful in some cases.
When the FOR command parses the data specified in the IN part using a command, it replaces the command with the result of the command, then runs the FOR command. For example, with the question above, the FOR command that will be executed after expanding echo %RegVal% is:
FOR /F "delims=" %%I in (C:\Program Files\SAS 9.2_M3_10w37\SASFoundation\9.2(32-bit)) do set sasroot=%%~sI
Thus, when the parser hits the first closing parenthesis, it will stop, thinking that everything it read before is the text to work on. However, in this case this is wrong, as the first closing parenthesis is part of the string to read; it doesn't indicate the end of the string.
When parsing a file with the FOR command, it will read each line, assign the predefined tokens with the correct values, then execute the code block that follows. Rinse and repeat for every line in the file. But in this case, it will not replace the IN part with each line; it will only parse it and assign values to the tokens. This is the reason why special characters (such as parenthesis) do not create parsing errors in this case.
I don't think this is possible, but I'd like to be able to do this, or possibly use an alternative method...
I have a batch file;
for /f "usebackq tokens=*" %%a in (`wmic process get description, commandline`) do (
*Some Code*
)
I need to be able to take the two answers from each line, and use them individually (basically, use the description to check if a process is running, then after I've killed the process and done some file clean-up work, reload the original process including any command line parameters.
One example of the output for a process I may need to end/re-open might be;
"C:\some folder\some other folder\some_application" -cmd_parameter process_name.exe
Note that the descrption is clearly defined by multiple spaces..
So is there a way of saying
for /f "tokens=* delims= " <--(The delims is TWO spaces, not space OR space)
Another way that may be better could be to replcae all instances of multiple spaces with a special character (i.e. one that is never used in a proces or path), and then use that as my delimeter... Though I don't know if that is even possible..
I'm also open to any alternative methods, as long as I can get the process name (to check against a pre-defined list of processes, and the full path to the exe, plus any command line paramteres given.
Thanks all
In direct answer to you question: No, you cannot specify 2 spaces as a delimiter. You can use SET search and replace to change 2 spaces into some unique character, but determining a unique character that will never appear in your description or command line is easier said then done.
A better alternative is to change the output format of WMIC to LIST - one value per line in the form of propertyName=Value. Each propery value can be stored in a variable, and then when the last property for a process is recorded you can take action using the variable values. WMIC output uses Unicode, and that results in a CarriageReturn character being appended to the end of each variable assignment. The CarriageReturn must be stripped to get the correct results.
#echo off
setlocal
for /f "tokens=1* delims==" %%A in ('"wmic process get description, commandline /format:list"') do (
if "%%A"=="CommandLine" (
set "cmd=%%B"
) else if "%%A"=="Description" (
set "desc=%%B"
setlocal enableDelayedExpansion
set "desc=!desc:~0,-1!"
set "cmd=!cmd:~0,-1!"
echo(
echo Do whatever you need to do with the description and command line.
echo description=!desc!
echo command line=!cmd!
endlocal
)
)
There are a few things you need to be careful of.
1) You could have multiple processes for the same image name. If you kill a process via the image name (description), then you will delete all of them. If you also restart it it based on the command line, then it will be killed again when the next process with the same name is killed. It is probably better to kill the process via the process ID.
2) If you know the image name (description) of the process, then you can restrict your output using the WMIC WHERE clause.
3) The command line reported by WMIC is not always reliable. The process is able to modify the value that is reported as the command line.
Here is a solution that retrieves the process ID and command line for a specific description.
EDIT - I fixed the code below
#echo off
setlocal
for /f "tokens=1* delims==" %%A in ('"wmic process where description='MyApp.exe' get processId, commandline /format:list"') do (
if "%%A"=="CommandLine" (
set "cmd=%%B"
) else if "%%A"=="ProcessId" (
set "id=%%B"
setlocal enableDelayedExpansion
set "id=!id:~0,-1!"
set "cmd=!cmd:~0,-1!"
echo(
echo Do whatever you need to do with the process id and command line.
echo process Id=!id!
echo command line=!cmd!
endlocal
)
)
Note - the WMIC WHERE clause uses SQL syntax. It can be made complex using AND and OR conditions, and it supports the LIKE operator using % and _ as wildcards. I believe the entire expression needs to be enclosed in double quotes when it becomes complex.
Foreword
I'd just like to add this for future readers, because I had this problem, solved it myself and I think it'll be useful just to show how to do this simply. Firstly, dbenham is absolutely correct in his answer that "No, you cannot specify 2 spaces as a delimiter.". Since you can't do it directly using the batch for loop, you can simply make your own that does the job. Again dbenham is correct in saying
"You can use SET search and replace to change 2 spaces into some unique character"
And thats somewhat similar to what I did (with some differences) but for completeness sake I think its good to have it on record. The thing is, simply setting all occurences of double spaces to some other character doesn't always solve the problem. Sometimes we have more than two spaces and what we really want is to delimit strings by more than one space. The problem I was trying to solve here is more like this (from the OP) Ricky Payne
"Another way that may be better could be to replcae all instances of multiple spaces with a special character (i.e. one that is never used
in a proces or path), and then use that as my delimeter... Though I
don't know if that is even possible.."
The answer to that is that It IS possible, and not hard at all. All you need is to be able to
A. loop over each character of the string
B. differentiate single from double (or more) spaces
C. turn a flag on when you encounter a double space
D. turn the double (or more) spaces into a special character or sequence of characters that you can delimit by.
the code
To do Exactly this, I coded this for my own use (edited for clarity):
FOR /F "tokens=* delims=*" %%G IN ('<command with one line output>') DO (SET
"LineString=%%G")
SET /A "tempindex=0"
:LineStringFOR
SET "currchar=!LineString:~%tempindex%,1!"
IF "!currchar!"=="" (goto :LineStringFOREND)
SET /A "tempindex=!tempindex!+1"
SET /A "BeforeSpacePosition=!tempindex!"
SET /A "AfterSpacePosition=!tempindex!+1"
IF NOT "!LineString:~%BeforeSpacePosition%,2!"==" " (goto :LineStringFOR)
:LineStringSUBFOR
IF "!LineString:~%BeforeSpacePosition%,2!"==" " (
SET LineString=!LineString:~0,%BeforeSpacePosition%!!LineString:~%AfterSpacePosition%!
GOTO :LineStringSUBFOR
) ELSE (
SET LineString=!LineString:~0,%BeforeSpacePosition%!;!LineString:~%AfterSpacePosition%!
GOTO :LineStringSUBFOREND
)
:LineStringSUBFOREND
GOTO :LineStringFOR
:LineStringFOREND
ECHO Final Result is "!LineString!"
So if your input (output of the command in the FOR or you can change that FOR loop to take in a string) was:
"a b c a b c"
The output should be in this format:
"a;b;c;a b c"
I have tested this on my own code. However, for my answer here I removed all of my comments and changed some variable names for clarity. If this code doesn't work after putting in your commands feel free to let me know and I'll update it but it SHOULD be working. Formatting on here might prevent a direct copy paste.
Just to show whats actually going on
The program flow is basically like this:
FOR each character
:TOP
grab the next character
set a variable to the current index
set another variable to the next index
IF this or the next character are not spaces, goto the TOP
:Check for 2 spaces again
IF this and the next character are both spaces then
get the string up to (but not including) the current index AS A
get the string after the current index AS B
set the string to A+B
goto Check for 2 spaces again
ELSE we have turned the double or more space into one space
get the string up to (but not including) the current index AS A
get the string after the current index AS B
set the string to A + <char sequence of choice for delimiting> + B
goto TOP to grab the next character
After all characters are looped over
RETURN the string here (or echo it out like I did)
Extra
dbenham says in his answer on this type of method that:
"You can use SET search and replace to change 2 spaces into some
unique character, but determining a unique character that will never
appear in your description or command line is easier said then done."
While this may have been true in the past, my yeilded that (at least for my method correct me if I'm wrong for other cases) you can in fact use a delimiter that definitely WON'T appear in your input. this is accomplished by using multicharacter delimiters. This doesn't allow you to use the standard FOR loop, however you can quite easily do this manually. This is described much more in depth here:
"delims=#+#" - more then 1 character as delimiter
Great thread!
This got me thinking and I came up with a slightly sideways solution that may work well for someone as it did for me.
As the original questions was for the WMIC command, and the output can be CSV format, why not just circumvent the space handling by using the /format:csv switch and setting a comma as the delimiter, and incorporating 'usebackq'?
Of course, this might not work if the data itself from WMIC has commas but waqs perfect in my instance where I wanted only the BootOptionOnWatchDog status
would look something like this:
FOR /F "usebackq skip=1 tokens=1-31 delims=," %a IN (`%windir%\system32\wbem\wmic computersystem list /format:csv`) DO echo %f
which returns:
BootOptionOnWatchDog
Normal boot
I ended using 'skip=2' which would return "Normal Boot"
btw, dont post here often hence posting as a guest, but thought it prudent to put this here as it was this post that helped me come to the answer above.
cheers
-steve (NZ)
I have searched this across the net and found many codes for retrieving the entire line from a text or replacing the text with another but not for what i was looking for.
Using the For loop with the tokens would return on the set (word) separated with spaces.
I want to pull only a few characters from the line.
Eg: 12345qwerty67890
If this on in a text file i want to pull only '12345' and assign it to a variable.
Any help is greatly appreciated.
set HELP SET and try the following to get you started
#echo off
setlocal enabledelayedexpansion
for /f "tokens=*" %%a in (sample.txt) do (
set line=%%a
set chars=!line:~0,5!
echo !chars! --- !line!
)
At the command prompt, do help set. There you will find more information about the set command than you would ever want to know. The part you are interested in says:
May also specify substrings for an expansion.
%PATH:~10,5%
would expand the PATH environment variable, and then use only the 5
characters that begin at the 11th (offset 10) character of the expanded
result. If the length is not specified, then it defaults to the
remainder of the variable value. If either number (offset or length) is
negative, then the number used is the length of the environment variable
value added to the offset or length specified.
Of course, in order to use that kind of stuff with the for loop variable, you need to first get acquainted with the very peculiar way in which variables are expanded in Windows NT batch files. Let me know if you have problems with that, and I can add more information.
Over the past few years I've really found Stackoverflow to be very helpful, and decided to create an account - this is my first post.
Example situation - I have a fair few of these images, of different subjects -
AAA_BBB_randomDigits_front.jpg
AAA_BBB_randomDigits_left.jpg
AAA_BBB_randomDigits_right.jpg
ZZZ_EEE_randomDigits_front.jpg
ZZZ_EEE_randomDigits_left.jpg
ZZZ_EEE_randomDigits_right.jpg
I would like them to all be grouped up in folders as -
AAA_BBB_randomDigits\(contains left, front and right)
ZZZ_EEE_randomDigits\(contains left, front and right)
The code I currently have works -
#echo off
for /f "tokens=1-3 delims=_" %%a in ('dir /b /a-d *_*_*_*.*') do (
md "%%a_%%b_%%c" 2>nul
move "%%a_%%b_%%c*" "%%a_%%b_%%c"
)
pause
However, I would love it if someone could explain to me -
What's %%a?
What's dir /b /a-d and why do I need it?
Is it neccessary to have #echo off and pause?
Thanks guys, I really appreciate it.
For documentation, see commandname /? from the prompt.
dir /b /a-d filemask performs a directory listing /b specifies filenames only - no size, date, header or footer. The /a-d excludes directorynames.
You need it to provide the names to the for /f command.
for /f reads the "filename" in parentheses (it can be a real filename or a single-quoted command (like dir) or a double-quoted literal string) and assigns values to the metavariable (in this case, %%a) according to the specified options (the part in quotes directly following the /f).
The delims option specifies which set of characters is used for parsing the line of data arriving from the "file" specified. The line is then interpreted as a series of tokens, separated by delimiter-sequences. By default, delims is Space and Tab. It's common to turn delims off entirely using "...delims=" in which case, there is but one token (the entire line). Note that any characters between delims= and " are equally-ranking and case-sensitive - it is a set of delimiters which replaces Space and Tab, not a delimiter-string.
The tokens option specifies which tokens are selected, by number, starting at 1. The special token * means "the remainder of the line following the highest-number token specified (including any delimiter characters)". By default, tokens=1.
%%a is a metavariable. It is the variable that holds the first token number selected from the tokens= list. Each selected token number is assigned to the next metavariable name in alphabetical sequence, hence in your example, since you have tokens=1-3 then %%a is the first token, %%b the second and %%c the third. Metavariables are always one letter (some other characters are sometimes used - but numerics are definitely not allowed) and the name is case-sensitive (normally, batch is case-insensitive). %%a, %%A and %a% are all different variables. %a% and %A% are the same variable.
A metavariable is only valid within the for loop where it was created. When the for loop ends, the variable disappears.
#echo off simply turns off the command-echoing that batch would otherwise produce (show the command on the console, then execute it). It's used to reduce clutter on the display. When debugging a section of code, it's normal to set echo to on (echo on) and then off again (echo off) to show precisely what instructions are being executed. The # means "don't report this command to the console"
The pause simply waits until a response is received from the keyboard. It's used to allow the display to be read rather than simply continuing directly to the next instruction. It's often used in debugging and also to allow the result of a batch to be held for the user if the batch is executed by using point-click-and-giggle.