Find the version of a installed program from batch file

41,159

Solution 1

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

Solution 2

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.

Solution 3

and two ways without external tools 1.WMIC

WMIC DATAFILE WHERE name="C:\\install.exe" get Version /format:Textvaluelist

Pay attention to the double slashes of file name.

Ready to use script/subroutine:

@echo off
:wmicVersion pathToBinary [variableToSaveTo]
setlocal
set "item=%~1"
set "item=%item:\=\\%"


for /f "usebackq delims=" %%a in (`"WMIC DATAFILE WHERE name='%item%' get Version /format:Textvaluelist"`) do (
    for /f "delims=" %%# in ("%%a") do set "%%#"
)

if "%~2" neq "" (
    endlocal & (
        echo %version%
        set %~2=%version%
    )
) else (
    echo %version%
)
exit /b %errorlevel%

Example (it needs a full file path):

call wmicVersion.bat "C:\Windows\System32\cmd.exe" cmdver
echo %cmdver%

2.MAKECAB as the WMIC is not installed on home versions of windows here's a way with makecab that will run on every windows machine:

; @echo off
;;goto :end_help
;;setlocal DsiableDelayedExpansion
;;;
;;;
;;; fileinf /l list of full file paths separated with ;
;;; fileinf /f text file with a list of files to be processed ( one on each line )
;;; fileinf /? prints the help
;;;
;;:end_help
 
; REM Creating a Newline variable (the two blank lines are required!)
; set NLM=^
 
 
; set NL=^^^%NLM%%NLM%^%NLM%%NLM%
; if "%~1" equ "/?" type "%~f0" | find ";;;" | find /v "find" && exit /b 0
; if "%~2" equ "" type "%~f0" | find ";;;" | find /v "find" && exit /b 0
; setlocal enableDelayedExpansion
; if "%~1" equ "/l" (
;  set "_files=%~2"
;  echo !_files:;=%NL%!>"%TEMP%\file.paths"
;  set _process_file="%TEMP%\file.paths"
;  goto :get_info
; )
 
; if "%~1" equ "/f" if exist "%~2" (
;  set _process_file="%~2"
;  goto :get_info
; )
 
; echo incorect parameters & exit /b 1
; :get_info
; set "file_info="
 
; makecab /d InfFileName=%TEMP%\file.inf /d "DiskDirectory1=%TEMP%" /f "%~f0"  /f %_process_file% /v0>nul
 
; for /f "usebackq skip=4 delims=" %%f in ("%TEMP%\file.inf") do (
;  set "file_info=%%f"
;  echo !file_info:,=%nl%!
; )
 
; endlocal
;endlocal
; del /q /f %TEMP%\file.inf 2>nul
; del /q /f %TEMP%\file.path 2>nul
; exit /b 0
 
.set DoNotCopyFiles=on
.set DestinationDir=;
.set RptFileName=nul
.set InfFooter=;
.set InfHeader=;
.Set ChecksumWidth=8
.Set InfDiskLineFormat=;
.Set Cabinet=off
.Set Compress=off
.Set GenerateInf=ON
.Set InfDiskHeader=;
.Set InfFileHeader=;
.set InfCabinetHeader=;
.Set InfFileLineFormat=",file:*file*,date:*date*,size:*size*,csum:*csum*,time:*time*,vern:*ver*,vers:*vers*,lang:*lang*"

example output (it has a string version which is a small addition to wmic method :) ):

c:> fileinfo.bat /l C:\install.exe
    file:install.exe
    date:11/07/07
    size:562688
    csum:380ef239
    time:07:03:18a
    vern:9.0.21022.8
    vers:9.0.21022.8 built by: RTM
    lang:1033

3 Using shell.application and hybrid batch\jscript.Here's tooptipInfo.bat :

@if (@X)==(@Y) @end /* JScript comment
    @echo off
    
    rem :: the first argument is the script name as it will be used for proper help message
    cscript //E:JScript //nologo "%~f0" %*

    exit /b %errorlevel%
    
@if (@X)==(@Y) @end JScript comment */
 
////// 
FSOObj = new ActiveXObject("Scripting.FileSystemObject");
var ARGS = WScript.Arguments;
if (ARGS.Length < 1 ) {
 WScript.Echo("No file passed");
 WScript.Quit(1);
}
var filename=ARGS.Item(0);
var objShell=new ActiveXObject("Shell.Application");
/////


//fso
ExistsItem = function (path) {
    return FSOObj.FolderExists(path)||FSOObj.FileExists(path);
}

getFullPath = function (path) {
    return FSOObj.GetAbsolutePathName(path);
}
//

//paths
getParent = function(path){
    var splitted=path.split("\\");
    var result="";
    for (var s=0;s<splitted.length-1;s++){
        if (s==0) {
            result=splitted[s];
        } else {
            result=result+"\\"+splitted[s];
        }
    }
    return result;
}


getName = function(path){
    var splitted=path.split("\\");
    return splitted[splitted.length-1];
}
//

function main(){
    if (!ExistsItem(filename)) {
        WScript.Echo(filename + " does not exist");
        WScript.Quit(2);
    }
    var fullFilename=getFullPath(filename);
    var namespace=getParent(fullFilename);
    var name=getName(fullFilename);
    var objFolder=objShell.NameSpace(namespace);
    var objItem=objFolder.ParseName(name);
    //https://msdn.microsoft.com/en-us/library/windows/desktop/bb787870(v=vs.85).aspx
    WScript.Echo(fullFilename + " : ");
    WScript.Echo(objFolder.GetDetailsOf(objItem,-1));
    
}

main();

used against cmd.exe :

C:\Windows\System32\cmd.exe :
File description: Windows Command Processor
Company: Microsoft Corporation
File version: 6.3.9600.16384
Date created: ?22-?Aug-?13 ??13:03
Size: 347 KB
Share:
41,159
andrewmu
Author by

andrewmu

Contract C++ Developer

Updated on October 22, 2020

Comments

  • andrewmu
    andrewmu over 3 years

    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?

  • andrewmu
    andrewmu almost 10 years
    That's nice but doesn't give me the version in a variable which I can use in batch file.
  • Knuckle-Dragger
    Knuckle-Dragger almost 10 years
    you can figure out how to get it into a variable by reading this post - stackoverflow.com/questions/2955512/…
  • andrewmu
    andrewmu almost 10 years
    I now get Node - COMP009811\nERROR:\nDescription - Invalid query\nxxx\nxxx (you can't get line breaks in comments)
  • andrewmu
    andrewmu almost 10 years
    The first quote is in the wrong place - I am guessing it should be after the equality symbol. Now I get - Invalid alias verb.
  • dbenham
    dbenham almost 10 years
    @graham.reeds - Sorry, all fixed. I had tested successfully with an actual program, but when I copied in your sample fictitious program, I accidentally overwrote the critical single quotes. The double quotes are exactly where they need to be.
  • Mike Q
    Mike Q over 3 years
    should include a file example with more than one leaf so it is clear what the path should look like (e.g. c:\\path\\to\\some\\file.exe)