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

时间:2013-04-10 20:20:57

标签: visual-studio wix windows-installer wix3.7

我在Visual Studio 2010解决方案中有许多Visual C ++项目。此解决方案中还有一个WiX项目,它为可执行文件构建安装程序,该安装程序是其中一个C ++项目的产品。

可执行文件在其项目中有一个资源文件,它将程序的版本写入可执行文件。

现在,我想对WiX构建的安装程序进行版本控制,其编号与资源文件写入可执行文件的编号相同。我在StackOverflow上搜索了与WiX相关的帖子,发现了这篇文章:

Referencing a WixVariable defined in a WiX Library Project from a WiX Setup Project

接受的答案似乎表明可能的解决方案是使用MSBuild和BeforeBuild Target中的GetAssemblyIdentity任务从另一个文件中获取版本号(在SO问题的情况下是一个DLL,在我的例子中)可执行文件)并在WiX构建安装程序之前将其暴露给WiX。

我尝试将其添加到我的.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.

我似乎无法在MSDN上找到有关此错误的任何信息,因为它与MSBuild有关。我检查了构建的可执行文件,它肯定有一个版本号(以及.rc文件中的其余信息),WiX项目依赖于输出可执行文件的项目;所以我假设它的BeforeBuild任务在它所依赖的项目完全构建之后运行。

我是否应该使用不同的任务而不是GetAssemblyIdentity来从MSBuild中的.exe中检索版本号,在GetAssemblyIdentity工作之前是否还有其他要求要满足,或者是否无法获得此类信息。 MS文件中的exe文件?

编辑:

我接受了Rob的回答,因为我误解了ProductVersion和FileVersion之间的区别,并且他建议的WiX技术按预期工作,并且是迈向我需要的解决方案的一步。

FileVersion仅是可执行文件的属性。 Msi文件本质上是数据库,ProductVersion是该数据库中的一个条目;它们没有要设置的FileVersion属性。他建议的方法正确地在.msi数据库中设置ProductVersion。

这个问题的标题现在与我实际遇到的问题没有关系,因为我当时正在寻求一个我认为我需要的解决方案。我现在已经解决了只是访问安装程序的ProductVersion的根本问题。我在网上发现了一个cscript脚本:http://kentie.net/article/wixnameversion/index.htm,它显示了如何访问.msi的ProductVersion。使用它可以让我提取ProductVersion并在其他工具中使用它。

2 个答案:

答案 0 :(得分:7)

如果您不需要MSBuild中的版本,更简单的解决方案是直接在.wxs文件中引用该文件的版本。这是一个片段,显示了该做什么:

<?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>

神奇的是,!(bind.fileVersion.Xxx)表示用File查找Id='Xxx'元素并获取其版本。这是将文件版本放入MSI包中的最简单方法。

答案 1 :(得分:2)

我需要一次文件版本,最后我写了一个自定义任务来获取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示例

<?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>