【问题标题】:Find the version of a installed program from batch file从批处理文件中查找已安装程序的版本
【发布时间】:2014-08-06 13:58:20
【问题描述】:

作为开发人员设置的一部分,我们有一个安装多个程序的批处理文件。当我们获得使用组件的新版本时,它会定期运行。所以如果版本不同,最好只安装。

在命令提示符下,我可以运行它并取回安装的版本:

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

输出Version=12.1.369.0

但是,当我将它放入这样的批处理文件并尝试提取版本时:

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%

我收到回复\\Common was unexpected at this time.

有些部分可能是多余的,因为我一直在尝试从网络上解决这个问题。

我错过了什么?

【问题讨论】:

    标签: batch-file wmic


    【解决方案1】:

    你有一组错位的双引号,还有一个额外的(

    WMIC 使用 SQL 语法,字符串用单引号括起来。内部的单引号不会干扰带单引号的命令。

    您可以在 WHERE 子句(不包括 WHERE 关键字)周围加上双引号,以避免 FOR DO() 子句中出现一些转义问题。

    @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%
    

    但这可能不是完整的解决方案。上面的代码看不到它,但是 RESULT 实际上包含一个尾随回车符(0x0D)。这是由于 FOR /F 如何处理 WMIC unicode 输出的怪癖。 WMIC 输出的每一行都会有额外的尾随回车。

    只要您始终使用%RESULT%(正常扩展)访问RESULT,那么您不会有任何问题。但是,如果您需要延迟扩展,那么您可能会遇到问题,如下所示。

    @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
    

    去除不需要的回车的一种方便方法是使用额外级别的 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
    

    【讨论】:

    • 我现在得到Node - COMP009811\nERROR:\nDescription - Invalid query\nxxx\nxxx(你不能在 cmets 中获得换行符)
    • 第一个引号放错了地方——我猜它应该在等号之后。现在我得到- Invalid alias verb.
    • @graham.reeds - 抱歉,已修复。我已经使用实际程序成功测试过,但是当我复制您的示例虚构程序时,我不小心覆盖了关键的单引号。双引号正是它们需要的位置。
    【解决方案2】:

    这是我在自己的软件更新批处理脚本中使用的子例程:

    :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
    

    它可以获取wmic datafile get支持的任何文件属性。例如,您可以通过以下方式获取当前安装的 Adob​​e Reader 的文件版本:

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

    完成后,环境变量version 将包含请求的版本字符串。如果:getfattr 失败,version 保证不会被设置。

    该示例的测试执行跟踪如下所示(延迟扩展已启用,尽管 :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"
    

    正如您所看到的,它非常直接,而且不会太在意。然而,它确实会小心翼翼地穿过cmdwmic 的雷区。

    首先,您要获取的属性的名称也是您希望结果结束的变量的名称(上面测试中的version)。在子例程中,该名称为%1,因此set %1= 将其清除。

    您传入的文件名需要一些预处理才能安全地交给wmic,并且需要一个shell变量,因此发出setlocal以避免踩踏调用者的变量。

    set "name=%~f2" 在去掉所有周围的双引号并将其扩展为完整路径名后,将名称复制到环境变量中。双引号将整个 set 参数括起来,以防止路径名中的与号或括号引起的麻烦。

    wmic 查询使用类似 SQL 的语法,其中字符串值被单引号 ' 字符包围,\ 是一种转义,可抑制后面字符的任何特殊含义。由于这两个在 Windows 路径名中都是合法的,因此所有出现的任何一个都需要一个 \ 前缀。 set "name=%name:\=\\%" 转义嵌入的反斜杠,wmic 命令行中的 '%name:'=\'%' 构造转义嵌入的单引号并添加所需的周围。

    cmd 的解析器不会关闭单引号之间的特殊处理,并且名称周围不再有任何双引号,因此嵌入的空格、括号或 & 符号可能会破坏事物。为了防止这种情况,wmic 的整个 name= 参数被双引号引起来。不需要对名称中已有的双引号进行特殊处理,因为双引号 在 Windows 文件名中被禁止,因此不能有。

    包含wmic 命令的for 命令行以@^ 序列结尾。 ^ 用于附加下一行作为外部for 命令的有效负载; @ 防止该有效负载在执行跟踪中回显,即使 ECHO 已打开。

    之所以进行回声抑制,主要是因为内部for 的存在只是为了摆脱cmdwmic 的输出从Unicode 转换为ASCII 时注入的虚假CR 字符(使用相同的技术)在@dbenham 的回答中),如果允许回显,那些 CR 只会用令人困惑的覆盖弄脏跟踪。附带的好处是,当内部for 从外部for 传递的行包含一个CR,其中wmic 的版本依赖编号时,内部for 不会执行其自己的有效负载坚持发射。如果 ECHO 开启,内部 for 的有效负载确实会得到回显,因此跟踪仍会捕获所有有用的事件。

    该有效负载由三个 & 分隔的命令组成,for 将在 cmd 处理各个命令之前将其扩展为单个命令行。特别是,这意味着set "%%B"endlocal 运行之前被扩展,这会将set 创建的变量放在setlocal/endlocal 范围之外,并使其可供调用者使用。

    %%B 将始终以 name=value 格式扩展,因为 /format:list 开关传递给 wmic;该名称将与get 动词指定的名称相同,这就是您传入的名称最终选择您返回的变量的方式。 set 的整个 name=value 参数被引用以防请求的属性包含 shell 特殊字符。这使得 :getfattr 本身是安全的,但您可能想要使用 !delayed!扩展而不是 %premature% 扩展,无论您实际使用它返回给您的变量。

    同一行上的& goto :eof 会从两个for 循环中断开,并在内部循环真正执行任何操作后立即返回到:getfattr 的调用者,以防万一你传入一些奇怪的名称并且wmic get 产生更多一个非空行输出。

    最后三行只有在wmic 完全没有输出时才会运行,这就是它失败时发生的情况。

    【讨论】:

      【解决方案3】:

      以及无需外部工具的两种方式 1.WMIC

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

      注意文件名的双斜线。

      准备使用脚本/子程序:

      @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%
      

      示例(需要完整的文件路径):

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

      2.MAKECAB 由于 WMIC 未安装在家庭版本的 Windows 上,因此使用 makecab 的方法可以在每台 Windows 机器上运行:

      ; @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*"
      

      示例输出(它有一个字符串版本,是 wmic 方法的一个小补充 :)):

      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 使用 shell.application 和混合批处理\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();
      

      用于 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
      

      【讨论】:

      • 应该包含一个包含多个叶子的文件示例,以便清楚路径应该是什么样子(例如 c:\\path\\to\\some\\file.exe)
      【解决方案4】:

      这是我的 filever bat 文件。

      @echo off
      
      If "%~1"=="" goto help
      If "%~1"=="/?" goto help
      If /i "%~1"=="/h" goto help
      If "%~1"=="-?" goto help
      If /i "%~1"=="-h" goto help
      
      set filepath=%~f1
      set  file=%filepath:\=\\%
      wmic datafile where name^="%file%" get version|findstr /i /v /c:"version"
      echo %errorlevel%
      goto finish
      
      :help
      Echo.
      Echo. FileVer
      Echo. -------
      Echo.
      Echo. Purpose:
      Echo.
      Echo.    Reports the version number for an exe, dll, and similar files.
      Echo.
      Echo. Usage:
      Echo.
      Echo.    filever ^<executable file^>
      Echo.
      Echo.    filever [/h ^| -h ^| /? ^| -?]    Starts this help
      Echo.
      echo. Examples:
      Echo.
      Echo.    filever c:\windows\explorer.exe
      Echo.    filever "C:\Program Files\Windows NT\Accessories\wordpad.exe"
      Echo.    filever shell32.dll
      
      Echo.
      Echo.    For Help
      Echo.
      Echo.    filever 
      
      Echo.    filever /?
      Echo.
      
      :finish
      rem Pause if command double clicked
      If /i "%cmdcmdline:~0,6%"=="cmd /c" pause
      

      你似乎在整个命令周围有一组额外的引号

      for 循环的一个问题是 wmic 在版本之后输出一个空行。

      set filepath=%~f1
      set  file=%filepath:\=\\%
      for /f  "tokens=1 eol= " %%A in ('wmic datafile where name^="%file%" get version^|findstr /i /v /c:"version"') do set a=%%A & echo %%A
      

      把它放在一个文件中。虽然文件有两个空行。

      set filepath=%~f1
      set  file=%filepath:\=\\%
      wmic datafile where name^="%file%" get version|findstr /i /v /c:"version">test.txt
      

      也许这可行

      set filepath=%~f1
      set  file=%filepath:\=\\%
      for /f  "tokens=1 eol= " %%A in ('wmic datafile where name^="%file%" get version^|findstr /i /v /c:"version"') do if not ""==%%A set a=%%A & echo %%A
      

      或者调用另一个批处理文件,不要返回。

      这是一种去除空行的方法。

      set filepath=%~f1
      set  file=%filepath:\=\\%
      for /f  "tokens=1 eol= " %%A in ('wmic datafile where name^="%file%" get version^|findstr /i /v /c:"version"^|findstr /i /v /r "^$"') do set a=%%A & echo %A%
      wmic datafile where name^="%file%" get version|findstr /i /v /c:"version"|findstr /i /v /r "^$">test.txt
      

      【讨论】:

      • 这很好,但没有提供我可以在批处理文件中使用的变量中的版本。
      【解决方案5】:

      这是另一种方法,绕过WMIC for powershell Get-WmiObject。

      @ECHO OFF
      start /b powershell.exe -command "Get-WmiObject -Class CIM_DataFile -Filter \"Name='C:\\Program Files (x86)\\Knuckle.dll'\" | Select-Object Version"
      PAUSE
      

      双击 .cmd 批处理文件时返回此结果。

      Version
      -------
      0.0.0.0
      

      【讨论】:

      猜你喜欢
      • 2014-12-18
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-12-18
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多