Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Find the version of a installed program from batch file

We have a batch file that installs several programs as part of the developers setup. This is ran periodically when we get new versions of used components. So it would be nice only to install if the versions are different.

At the command prompt I can run this and get back the version installed:

wmic datafile where name='C:\\Program Files (x86)\\Common Files\\Company\\Product\\Version12\\Product.exe' get version /format:list

Which gives the output Version=12.1.369.0.

However when I put this into a batch file like this and try to extract the version:

echo off
FOR /F "tokens=2 delims==" %%I in ('"wmic datafile where^(name^="C:\\Program Files (x86)\\Common Files\\Company\\Product\\Version12\\Product.exe" get version /format:list"') DO (SET "RESULT=%%I")
ECHO %RESULT%

I get the response \\Common was unexpected at this time.

Some parts may be redundant as I've been trying stuff off the 'Net to correct this.

What have I missed?

like image 792
graham.reeds Avatar asked Aug 06 '14 13:08

graham.reeds


2 Answers

You have a set of misplaced double quotes, as well as an extra (.

WMIC uses SQL syntax, and strings are enclosed in single quotes.The internal single quotes do not interfere with the command enclosing single quotes.

You can put double quotes around the WHERE clause (not including the WHERE keyword) to avoid some escape issues within the FOR DO() clause.

@echo off
FOR /F "tokens=2 delims==" %%I IN (
  'wmic datafile where "name='C:\\Program Files (x86)\\Common Files\\Company\\Product\\Version12\\Product.exe'" get version /format:list'
) DO SET "RESULT=%%I"
ECHO %RESULT%

But this may not quite be the whole solution. You can't see it with the above code, but RESULT actually contains a trailing carriage return (0x0D). This is due to a quirk with how FOR /F handles WMIC unicode output. Every line of WMIC output will have the extra trailing carriage return.

As long as you always access RESULT using %RESULT% (normal expansion), then you will not have any problems. But if you should ever need delayed expansion, then you can have problems, as demonstrated below.

@echo off
setlocal enableDelayedExpansion
FOR /F "tokens=2 delims==" %%I IN (
  'wmic datafile where "name='C:\\Program Files (x86)\\Common Files\\Company\\Product\\Version12\\Product.exe'" get version /format:list'
) DO SET "RESULT=%%I"
ECHO %RESULT%xxx
ECHO !RESULT!xxx

One convenient method to strip the unwanted carriage return is to use an extra level of FOR.

@echo off
setlocal enableDelayedExpansion
FOR /F "tokens=2 delims==" %%I IN (
  'wmic datafile where "name='C:\\Program Files (x86)\\Common Files\\Company\\Product\\Version12\\Product.exe'" get version /format:list'
) DO FOR /F "delims=" %%A IN ("%%I") DO SET "RESULT=%%A"
ECHO %RESULT%xxx
ECHO !RESULT!xxx
like image 101
dbenham Avatar answered Oct 10 '22 15:10

dbenham


Here's the subroutine I use for this in my own software update batch script:

:getfattr
set %1=
setlocal
set "name=%~f2"
set "name=%name:\=\\%"
for /f "delims=" %%A in ('wmic datafile where "name='%name:'=\'%'" get %1 /format:list') do @^
for /f "delims=" %%B in ("%%A") do endlocal & set "%%B" & goto :eof
echo>&2 getfattr failed
endlocal
goto :eof

It can get any file attribute supported by wmic datafile get. For example, here's how you might get the file version for the currently installed Adobe Reader:

call :getfattr version "%ProgramFiles(x86)%\Adobe\Reader 11.0\Reader\AcroRd32.exe"
echo "!version!"

After doing that, environment variable version will contain the requested version string. If :getfattr fails, version is guaranteed to be unset.

A test execution trace for that example looks like this (delayed expansion was already enabled, though this is not assumed by :getfattr):

>call :getfattr version "C:\Program Files (x86)\Adobe\Reader 11.0\Reader\AcroRd32.exe"

>set version=

>setlocal

>set "name=C:\Program Files (x86)\Adobe\Reader 11.0\Reader\AcroRd32.exe"

>set "name=C:\\Program Files (x86)\\Adobe\\Reader 11.0\\Reader\\AcroRd32.exe"

>for /F "delims=" %A in ('wmic datafile where "name='C:\\Program Files (x86)\\Adobe\\Reader 11.0\\Reader\\AcroRd32.exe'" get version /format:list') do @for /F "delims=" %B in ("%A") do endlocal   & set "%B"   & goto :eof

>endlocal   & set "Version=11.0.18.21"   & goto :eof

>echo "!version!"
"11.0.18.21"

As you can see, it's pretty direct and doesn't faff about too much. It does, however, tiptoe through a minefield of cmd and wmic gotchas.

First, the name of the attribute you want to get is also the name used for the variable you want the result to end up in (version in the test above). Inside the subroutine, that name is %1, so set %1= clears it.

The filename you pass in needs a bit of preprocessing before it can be safely handed to wmic and a shell variable is required for that, so setlocal is issued to avoid stomping the caller's variables.

set "name=%~f2" copies the name to an environment variable after stripping off any surrounding double-quotes and expanding it to a full pathname. Double quotes surround the entire set argument to prevent grief caused by ampersands or parentheses in pathnames.

wmic queries use a SQL-like syntax, where string values are surrounded by single quote ' characters and \ is an escape that suppresses any special meaning of the following character. Since both of these are legal in Windows pathnames, all occurrences of either need a \ prefix. set "name=%name:\=\\%" escapes embedded backslashes, and the '%name:'=\'%' construct in the wmic command line escapes embedded single quotes and adds the required surrounding ones.

cmd's parser doesn't turn off special processing between single quotes, and the name no longer has any surrounding double quotes, so embedded spaces, parentheses or ampersands could potentially break things. To guard against that, wmic's entire name= argument gets double quoted. There's no need for special handling for double quotes already inside the name, because double quotes are prohibited in Windows filenames so there can't be any.

The for command line containing the wmic command ends with a @^ sequence. The ^ serves to attach the next line as the payload of the outer for command; the @ prevents that payload being echoed in an execution trace even if ECHO is on.

That echo suppression is done mainly because the inner for exists only to get rid of the spurious CR characters injected by cmd's buggy conversion of wmic's output from Unicode to ASCII (the same technique used in @dbenham's answer) and if it's allowed to echo, those CRs just filthy up the trace with confusing overwrites. As a side benefit, the inner for won't execute its own payload when the line it's handed from the outer for contains only a CR, a version-dependent number of which wmic insists on emitting. The inner for's payload does get echoed if ECHO is on, so tracing still captures all the useful happenings.

That payload consists of three &-separated commands, which for will expand as a single command line before cmd gets to process the individual commands. In particular, this means that set "%%B" gets expanded before endlocal runs, which puts the variable created by that set outside the setlocal/endlocal scope and makes it available to the caller.

%%B will always expand in the format name=value because of the /format:list switch passed to wmic; the name will be the same as that specified with the get verb, and this is how the name you pass in ends up choosing the variable you get back. The entire name=value argument to set is quoted in case the requested attribute contains shell-special characters. This makes :getfattr itself safe, but you might want to use !delayed! expansion rather than %premature% expansion wherever you actually use the variable it hands back to you.

The & goto :eof on that same line breaks from both for loops and returns to :getfattr's caller as soon as the inner one actually does anything, just in case you pass in some weird name and wmic get produces more than one non-blank line of output.

The last three lines only ever run if wmic produces no output at all, which is what happens when it fails.

like image 44
flabdablet Avatar answered Oct 10 '22 17:10

flabdablet