我在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并在其他工具中使用它。
答案 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->'"%(myfullpath)"' , '%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>