简体   繁体   English

在MSBuild中获取本机exe的文件版本

[英]Getting the file version of a native exe in MSBuild

I have a number of Visual C++ projects in a Visual Studio 2010 solution. 我在Visual Studio 2010解决方案中有许多Visual C ++项目。 Also in this solution is a WiX project that builds the installer for the executable that is the product of one of the C++ projects. 此解决方案中还有一个WiX项目,它为可执行文件构建安装程序,该安装程序是其中一个C ++项目的产品。

The executable has a resource file in its project which writes the version of the program to the executable. 可执行文件在其项目中有一个资源文件,它将程序的版本写入可执行文件。

Now I'd like to version the WiX-built installer with the same number as the one written to the executable by the resource file. 现在,我想将WiX构建的安装程序的版本与资源文件写入可执行文件的编号相同。 I've searched the WiX related posts on StackOverflow and found this post: 我在StackOverflow上搜索了WiX相关帖子,发现这篇文章:

Referencing a WixVariable defined in a WiX Library Project from a WiX Setup Project 从WiX设置项目引用WiX库项目中定义的WixVariable

The accepted answer to which, seems to indicate that a possible solution is to use MSBuild and the GetAssemblyIdentity task in the BeforeBuild Target to acquire the version number from another file (in the case of the SO question a DLL, in my case the executable) and expose it to WiX before WiX builds the installer. 接受的答案似乎表明可能的解决方案是使用MSBuild和BeforeBuild Target中的GetAssemblyIdentity任务从另一个文件中获取版本号(在SO问题的情况下是一个DLL,在我的例子中是可执行文件)并在WiX构建安装程序之前将其暴露给WiX。

I tried adding this to the MSBuild portion of my .wixproj file but when I try to build the installer I get an error returned saying: 我尝试将此添加到我的.wixproj文件的MSBuild部分,但是当我尝试构建安装程序时,我收到一条错误,返回说:

error MSB3441: Cannot get assembly name for "<ExePath>". Could not load file or assembly '<ExeName>.exe' or one of its dependencies. The module was expected to contain an assembly manifest.

I can't seem to find any information on MSDN about this error as it relates to MSBuild. 我似乎无法在MSDN上找到有关此错误的任何信息,因为它与MSBuild有关。 I've checked the built executable and it definitely has a version number on it (as well as the rest of the information from the .rc file) and the WiX project depends on the project that outputs the executable; 我检查了构建的可执行文件,它肯定有一个版本号(以及.rc文件中的其余信息),WiX项目依赖于输出可执行文件的项目; so I assume its BeforeBuild task is running after the project it depends on has been built completely. 所以我假设它的BeforeBuild任务在它所依赖的项目完全构建之后运行。

Should I be using a different task instead of GetAssemblyIdentity to retrieve the version number from the .exe in MSBuild, are there other requirements to satisfy before GetAssemblyIdentity will work, or is it just not possible to get this type of information about .exe files in MSBuild? 我应该使用不同的任务而不是GetAssemblyIdentity从MSBuild中的.exe检索版本号,在GetAssemblyIdentity工作之前是否还有其他要求要满足,或者是否无法获得有关.exe文件的此类信息的MSBuild?

EDIT : 编辑:

I accepted Rob's answer since I was misunderstanding the difference between ProductVersion and FileVersion, and the WiX technique that he suggested was working as intended and is a step towards the solution I needed. 我接受了Rob的回答,因为我误解了ProductVersion和FileVersion之间的区别,他建议的WiX技术按照预期工作,是迈向我需要的解决方案的一步。

FileVersion is an attribute of executables only. FileVersion仅是可执行文件的属性。 Msi files are essentially databases and ProductVersion is an entry in that database; Msi文件本质上是数据库,ProductVersion是该数据库中的一个条目; they have no FileVersion attribute to set. 它们没有要设置的FileVersion属性。 The method he suggests correctly sets ProductVersion in the .msi database. 他建议的方法正确地在.msi数据库中设置ProductVersion。

The title of this question is now not really related to what the problem I actually had was, since I was asking for a solution I believed I needed at the time. 这个问题的标题现在与我实际遇到的问题没有关系,因为我当时正在寻求一个我认为我需要的解决方案。 I've now solved the root problem which was simply getting access to the ProductVersion of the installer. 我现在已经解决了只是访问安装程序的ProductVersion的根本问题。 I found a cscript script posted online here: http://kentie.net/article/wixnameversion/index.htm that shows how to access the ProductVersion of the .msi. 我在网上发现了一个cscript脚本: http//kentie.net/article/wixnameversion/index.htm ,它显示了如何访问.msi的ProductVersion。 Using that has allowed me to extract the ProductVersion and use it in other tools. 使用它可以让我提取ProductVersion并在其他工具中使用它。

An easier solution, if you don't need the version in MSBuild, is to just reference the version of the file directly in your .wxs file. 如果您不需要MSBuild中的版本,更简单的解决方案是直接在.wxs文件中引用该文件的版本。 Here's a snippet that shows what to do: 这是一个片段,显示了该做什么:

<?xml version="1.0" encoding="UTF-8"?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
  <Product Version="!(bind.fileVersion.ExeWithVersion)" ...>

    ...

   <Component ...>
     <File Id="ExeWithVersion" Source="path\to\your\versioned\file.exe" />
   </Component> 

   ...

  </Product>
</Wix>

The magic is that the !(bind.fileVersion.Xxx) says to look up the File element with Id='Xxx' and get its version. 神奇的是!(bind.fileVersion.Xxx)说要查找具有Id='Xxx'File元素并获取其版本。 This is far away the easiest way to get the version of the file into your MSI package. 这是将文件版本放入MSI包的最简单方法。

I needed the file version one time, and I ended up writing a custom task to get the FileVersion because I couldn't find anything. 我需要一次文件版本,最后我写了一个自定义任务来获取FileVersion,因为我找不到任何东西。

namespace GranadaCoder.Framework.CrossDomain.MSBuild.Tasks.IO//.FileVersionTask
{
    using System;
    using System.Collections.Generic;
    using System.Collections;
    using System.Linq;
    using System.Globalization;
    using System.Diagnostics;
    using System.IO;
    using System.Text;
    using System.Security;

    using Microsoft.Build.Framework;
    using Microsoft.Build.Utilities;

    public class FileVersionTask : FileBasedTaskBase
    {
        private static readonly string ROOT_DIRECTORY = "myrootdir";
        private static readonly string FULL_PATH = "myfullpath";
        private static readonly string FILE_NAME = "myfilename";
        private static readonly string DIRECTORY = "mydirectory";
        private static readonly string EXTENSION = "myextension";
        private static readonly string VERSION = "myfileversion";

        /// <summary>
        /// Gets or sets the source files.
        /// </summary>
        /// <value>The source files.</value>
        [Required]
        public string SourceFiles { get; set; }

        /// <summary>
        /// Gets the file versions as a Task Output property.
        /// </summary>
        /// <value>The file versions.</value>
        [Output]
        public ITaskItem[] FileVersions
        { get; private set; }

        /// <summary>
        /// Task Entry Point.
        /// </summary>
        /// <returns></returns>
        protected override bool AbstractExecute()
        {
            InternalExecute();
            return !Log.HasLoggedErrors;
        }

        /// <summary>
        /// Internal Execute Wrapper.
        /// </summary>
        private void InternalExecute()
        {
            IList<string> files = null;

            if (String.IsNullOrEmpty(this.SourceFiles))
            {
                Log.LogWarning("No SourceFiles specified");
                return;
            }

            if (!String.IsNullOrEmpty(this.SourceFiles))
            {
                Console.WriteLine(this.SourceFiles);
                files = base.ConvertSourceFileStringToList(this.SourceFiles);
            }

            //List<string> fileVersions = new List<string>();

            ArrayList itemsAsStringArray = new ArrayList();

            foreach (string f in files)
            {
                FileInfoWrapper fiw = null;
                fiw = this.DetermineFileVersion(f);

                IDictionary currentMetaData = new System.Collections.Hashtable();

                currentMetaData.Add(ROOT_DIRECTORY, fiw.RootDirectory);
                currentMetaData.Add(FULL_PATH, fiw.FullPath);
                currentMetaData.Add(FILE_NAME, fiw.FileName);
                currentMetaData.Add(DIRECTORY, fiw.Directory);
                currentMetaData.Add(EXTENSION, fiw.Extension);
                currentMetaData.Add(VERSION, fiw.Version);

                itemsAsStringArray.Add(new TaskItem(fiw.Version, currentMetaData));

            }
            this.FileVersions = (ITaskItem[])itemsAsStringArray.ToArray(typeof(ITaskItem));
        }


        /// <summary>
        /// Determines the file version.
        /// </summary>
        /// <param name="fileName">Name of the file.</param>
        /// <returns>File version or 0.0.0.0 if value cannot be determined</returns>
        private FileInfoWrapper DetermineFileVersion(string fileName)
        {
            FileInfoWrapper fiw = new FileInfoWrapper();
            fiw.Directory = string.Empty;
            fiw.Extension = string.Empty;
            fiw.FileName = string.Empty;
            fiw.FullPath = string.Empty;
            fiw.RootDirectory = string.Empty;
            fiw.Version = "0.0.0.0";
            try
            {
                if (System.IO.File.Exists(fileName))
                {
                    fiw.Extension = System.IO.Path.GetExtension(fileName);
                    fiw.FileName = System.IO.Path.GetFileNameWithoutExtension(fileName);
                    fiw.FullPath = fileName;// System.IO.Path.GetFileName(fileName);
                    fiw.RootDirectory = System.IO.Path.GetPathRoot(fileName);

                    //Take the full path and remove the root directory to mimic the DotNet default behavior of '%filename'
                    fiw.Directory = System.IO.Path.GetDirectoryName(fileName).Remove(0, fiw.RootDirectory.Length);

                    FileVersionInfo fvi = FileVersionInfo.GetVersionInfo(fileName);
                    if (null != fvi)
                    {
                        if (null != fvi.FileVersion)
                        {
                            fiw.Version = fvi.FileVersion;
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                if (ex is IOException
                    || ex is UnauthorizedAccessException
                    || ex is PathTooLongException
                    || ex is DirectoryNotFoundException
                    || ex is SecurityException)
                {
                    Log.LogWarning("Error trying to determine file version " + fileName + ". " + ex.Message);
                }
                else
                {
                    Log.LogErrorFromException(ex);
                    throw;
                }
            }
            return fiw;
        }




        /// <summary>
        /// Internal wrapper class to hold file properties of interest.
        /// </summary>
        internal sealed class FileInfoWrapper
        {
            public string Directory { get; set; }
            public string Extension { get; set; }
            public string FileName { get; set; }
            public string FullPath { get; set; }
            public string RootDirectory { get; set; }
            public string Version { get; set; }
        }
    }
}

.msbuild example .msbuild示例

<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="AllTargetsWrapper" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">

  <UsingTask AssemblyFile="GranadaCoder.Framework.CrossDomain.MSBuild.dll" TaskName="FileVersionTask"/>




  <Target Name="AllTargetsWrapper">
    <CallTarget Targets="FileVersionTask1" />
    <CallTarget Targets="FileVersionTask2" />
  </Target>


  <PropertyGroup>
    <WorkingCheckout>c:\Program Files\MSBuild</WorkingCheckout>
  </PropertyGroup>


  <ItemGroup>
    <MyTask1ExcludeFiles Include="$(WorkingCheckout)\**\*.rtf" />
    <MyTask1ExcludeFiles Include="$(WorkingCheckout)\**\*.doc" />
  </ItemGroup>

  <ItemGroup>
    <MyTask1IncludeFiles Include="$(WorkingCheckout)\**\*.exe" Exclude="@(MyTask1ExcludeFiles)" />
  </ItemGroup>

  <Target Name="FileVersionTask1">
    <FileVersionTask SourceFiles="@(MyTask1IncludeFiles)" >

      <Output TaskParameter="FileVersions"  ItemName="MyFileVersionItemNames"/>

    </FileVersionTask>


    <Message Text=" MyFileVersionItemNames MetaData  "/>
    <Message Text="  ------------------------------- "/>
    <Message Text="   "/>


    <Message Text="directory: "/>
    <Message Text="@(MyFileVersionItemNames->'%(mydirectory)')"/>
    <Message Text="   "/>
    <Message Text="   "/>
    <Message Text="extension: "/>
    <Message Text="@(MyFileVersionItemNames->'%(myextension)')"/>
    <Message Text="   "/>
    <Message Text="   "/>
    <Message Text="filename: "/>
    <Message Text="@(MyFileVersionItemNames->'%(myfilename)')"/>
    <Message Text="   "/>
    <Message Text="   "/>
    <Message Text="fullpath: "/>
    <Message Text="@(MyFileVersionItemNames->'%(myfullpath)')"/>
    <Message Text="   "/>
    <Message Text="   "/>
    <Message Text="rootdir: "/>
    <Message Text="@(MyFileVersionItemNames->'%(myrootdir)')"/>
    <Message Text="   "/>
    <Message Text="   "/>

    <Message Text="fileversion: "/>
    <Message Text="@(MyFileVersionItemNames->'%(myfileversion)')"/>
    <Message Text="   "/>
    <Message Text="   "/>


    <Message Text="   "/>
    <Message Text="   "/>
    <Message Text="rootdir + directory + filename + extension: "/>
    <Message Text="@(MyFileVersionItemNames->'%(myrootdir)%(mydirectory)%(myfilename)%(myextension)')"/>
    <Message Text="   "/>
    <Message Text="   "/>



    <Message Text="List of files using special characters (carriage return)"/>
    <Message Text="@(MyFileVersionItemNames->'&quot;%(myfullpath)&quot;' , '%0D%0A')"/>
    <Message Text="   "/>
    <Message Text="   "/>



  </Target>



  <ItemGroup>
    <MyTask2IncludeFiles Include="c:\windows\notepad.exe"  />
  </ItemGroup>

  <Target Name="FileVersionTask2">
    <FileVersionTask SourceFiles="@(MyTask2IncludeFiles)" >
      <Output TaskParameter="FileVersions" PropertyName="SingleFileFileVersion"/>
    </FileVersionTask>

    <Message Text="SingleFileFileVersion = $(SingleFileFileVersion)   "/>

  </Target>


</Project>

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 MSBuild使用不正确的sgen.exe版本生成XmlSerializer dll? - MSBuild Using Incorrect Version of sgen.exe to Generate XmlSerializer dlls? 版本号 - msbuild version 项目文件包含ToolsVersion =“4.0”,此版本的MSBuild不支持 - Project file contains ToolsVersion=“4.0”, which is not supported by this version of MSBuild msbuild.exe 有效,但 msbuild API 无效 - msbuild.exe works but msbuild API does not 通过MsBuild / csproj强制评估具有锁定文件的浮动版本程序包 - Force evaluate floating version packages with lock file via MsBuild / csproj exe版本中不提供用户手册文本文件 - User manual text file not available in the exe version 通过构建应用程序的exe的自定义文件版本/产品版本 - Custom File Version/ Product Version of exe by building an application 如何使msbuild理解.csproj文件取决于另一个.csproj构建的exe的可用性 - How can I make msbuild understand that a .csproj file depends on the availability of an exe built by another .csproj VS 2017-具有版本信息(资源)的C ++ DLL的MSBuild不起作用(严重错误LNK1158:无法运行&#39;cvtres.exe&#39;) - VS 2017 - MSBuild of C++ DLL with version information (resource) is not functional (fatal error LNK1158: cannot run 'cvtres.exe') MSBuild:如何从AssmeblyInfo.cs读取程序集版本和文件版本? - MSBuild: How to read Assembly Version and FIle Version from AssmeblyInfo.cs?
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM