6

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

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

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

从 WiX 设置项目引用在 WiX 库项目中定义的 WixVariable

接受的答案似乎表明一个可能的解决方案是在 BeforeBuild 目标中使用 MSBuild 和 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 工作之前是否需要满足其他要求,或者只是无法获取有关 .exe 文件的此类信息微软构建?

编辑 :

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

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

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

4

2 回答 2

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 包的最简单方法。

于 2013-04-10T21:13:06.053 回答
3

有一次我需要文件版本,最后我编写了一个自定义任务来获取 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>
于 2013-04-10T20:42:46.777 回答