在后置构建事件期间确定程序集版本

67

假设我想创建一个随着每个版本发布一起运送的静态文本文件。我希望该文件能使用版本号来更新(在AssemblyInfo.cs中指定),但是我不想手动进行操作。

我希望可以使用一个构建后事件并将版本号传递给像这样的批处理文件:

call foo.bat $(AssemblyVersion)

然而我找不到任何合适的变量或宏来使用。

我是否错过了实现这一点的方法?


阅读所有答案,选择最适合您的答案。“PostBuildEventDependsOn”是得票最高且最简单的答案。(粘贴时要注意特殊的“25” Unicode技巧) - OzBob
14个回答

105

如果 (1) 你不想下载或创建检索程序集版本的自定义可执行文件,且 (2) 你不介意编辑Visual Studio项目文件,那么有一个简单的解决方案可以让你使用以下宏:

@(Targets->'%(Version)')

@(VersionNumber)
为了完成这个任务,请卸载你的项目。如果项目中某处定义了<PostBuildEvent>属性,请将其从项目中剪切,并暂时保存在其他地方(例如记事本)。然后在项目的最后,就在结束标签之前,放置以下内容:
<Target Name="PostBuildMacros">
  <GetAssemblyIdentity AssemblyFiles="$(TargetPath)">
    <Output TaskParameter="Assemblies" ItemName="Targets" />
  </GetAssemblyIdentity>
  <ItemGroup>
    <VersionNumber Include="@(Targets->'%(Version)')"/>
  </ItemGroup>
</Target>
<PropertyGroup>
  <PostBuildEventDependsOn>
    $(PostBuildEventDependsOn);
    PostBuildMacros;
  </PostBuildEventDependsOn>    
  <PostBuildEvent>echo HELLO, THE ASSEMBLY VERSION IS: @(VersionNumber)</PostBuildEvent>
</PropertyGroup>

这段代码中已经包含了一个<PostBuildEvent>的示例。不用担心,您可以在重新加载项目后将其重置为实际的后期构建事件。

现在,如承诺所述,使用此宏可使程序集版本在您的后期构建事件中可用:

@(VersionNumber)

完成!


1
它为我输出了汇编版本为:(版本号) - Poul K. Sørensen
1
对我也起作用了。问题是“25”通过Visual Studio的后构建编辑器悄悄地进入了,如果你在文本编辑器中编辑csproj @(Targets->'%25(Version)'),你会看到它。当25被移除时,它就像魔法一样工作... - jmelhus
1
@MikeDevenney ... 如果 "$(ConfigurationName)" == "Release" 则跳转到 :release {new line} :debug {仅适用于调试模式的一些代码} 跳转到 :end {new line} :release {仅适用于发布模式的一些代码} :end {一些最终化代码} - Riegardt Steyn
1
我正在尝试使用这个来仅获取Major.Minor,或者分别获取MajorMinor。但是似乎无论我尝试什么都不起作用。我该如何分别获取这些属性? - Ehryk
4
这应该成为被接受的答案,因为它不会破坏外部(自动化)构建系统如Azure DevOps。事实上,任何人都可以在不必先安装一些工具的情况下进行构建。 - Kornelis
显示剩余12条评论

17

如果您喜欢使用脚本,则这些方法也可能适用于您:

如果您正在使用后生成事件,您可以使用filever.exe工具从已构建的程序集中抓取它:

for /F "tokens=4" %%F in ('filever.exe /B /A /D bin\debug\myapp.exe') do (
  set VERSION=%%F
)
echo The version is %VERSION%

从这里获取filever.exe:http://support.microsoft.com/kb/913111

如果你正在使用预构建事件,可以按照以下方法从AssemblyInfo.cs文件中删除它:

set ASMINFO=Properties\AssemblyInfo.cs
FINDSTR /C:"[assembly: AssemblyVersion(" %ASMINFO% | sed.exe "s/\[assembly: AssemblyVersion(\"/SET CURRENT_VERSION=/g;s/\")\]//g;s/\.\*//g" >SetCurrVer.cmd
CALL SetCurrVer.cmd
DEL SetCurrVer.cmd
echo Current version is %CURRENT_VERSION%

这个使用了Unix命令行工具sed,你可以从许多地方下载,比如这里:http://unxutils.sourceforge.net/ - 如果我没记错的话,那个工具还不错。


我使用Pre-Build事件。现在,我有Properties\AssemblyInfo.cs文件或链接到%SolutionDir%\GlobalAssemblyInfo.cs。我如何检测程序集版本号是在Properties\AssemblyInfo.cs还是在%SolutionDir%\GlobalAssemblyInfo.cs中,并获得该版本号…? - Kiquenet
也许您可以使用“IF [NOT] EXIST 文件名命令”函数?在命令提示符上键入“if /?”以获取文档和一些示例。 - Tuinstoelen

12

作为一种解决方法,我编写了一个托管控制台应用程序,它以目标作为参数并返回版本号。

如果有更简单的解决方案,我仍然很感兴趣 - 但我发布这篇文章是为了让其他人发现它的用处。

using System;
using System.IO;
using System.Diagnostics;
using System.Reflection;

namespace Version
{
    class GetVersion
    {
        static void Main(string[] args)
        {
            if (args.Length == 0 || args.Length > 1) { ShowUsage(); return; }

            string target = args[0];

            string path = Path.IsPathRooted(target) 
                                ? target 
                                : Path.GetDirectoryName(Process.GetCurrentProcess().MainModule.FileName) + Path.DirectorySeparatorChar + target;

            Console.Write( Assembly.LoadFile(path).GetName().Version.ToString(2) );
        }

        static void ShowUsage()
        {
            Console.WriteLine("Usage: version.exe <target>");
        }
    }
}

是的,我发现这很有用,谢谢。我不想使用Unix工具,也不想使用MSBuild,所以我使用了Winston和Rohan提供的类似代码。在项目的postbuild事件中,我使用两个参数调用此代码,即程序集的TargetPath和打包程序集的安装程序路径。在设置的post build事件中,我调用msiinfo将版本数据移动到MSI中,以便消费者可以看到他们正在加载的版本。谢谢。 - TonyG
这可能是被接受的解决方案,因为filever似乎获取的是文件版本而不是内部程序集版本。我正在使用程序集版本来管理我的应用程序,因为它很容易自动递增。 - transistor1
这个命令行非常方便。我可以使用它来更新安装程序的版本号,以匹配正在安装的应用程序的版本。一行代码可以将版本号放入环境变量中。 - Chris Miller
绝对没有必要去搞输入路径。相对路径就是这么简单,你知道的。如果他们选择在不在exe文件所在文件夹时使用相对目录,那应该按预期工作。无论如何,我需要版本号以便自动解析readme文件,然后我就用了一个小的外部应用程序来获取版本号。最好的部分是,我可以直接从我的主程序中复制版本格式化代码到这个应用程序中 :) - Nyerguds

12

这个答案是Brent Arias的答案的一个小修改。他的PostBuildMacro在我使用的Nuget.exe版本更新后表现良好。

在最近的版本中,Nuget会剪裁包版本号中不重要的部分,以获得类似于“1.2.3”的语义版本。例如,程序集版本“1.2.3.0”会被Nuget.exe格式化为“1.2.3”,而“1.2.3.1”则按预期格式化为“1.2.3.1”。

由于我需要推断出Nuget.exe生成的确切包文件名,因此我现在使用这个修改后的宏(在VS2015中测试通过):

<Target Name="PostBuildMacros">
  <GetAssemblyIdentity AssemblyFiles="$(TargetPath)">
    <Output TaskParameter="Assemblies" ItemName="Targets" />
  </GetAssemblyIdentity>
  <ItemGroup>
    <VersionNumber Include="$([System.Text.RegularExpressions.Regex]::Replace(&quot;%(Targets.Version)&quot;, &quot;^(.+?)(\.0+)$&quot;, &quot;$1&quot;))" />
  </ItemGroup>
</Target>
<PropertyGroup>
  <PostBuildEventDependsOn>
    $(PostBuildEventDependsOn);
    PostBuildMacros;
  </PostBuildEventDependsOn>    
  <PostBuildEvent>echo HELLO, THE ASSEMBLY VERSION IS: @(VersionNumber)</PostBuildEvent>
</PropertyGroup>

更新于2017-05-24:我以这种方式更正了正则表达式:“1.2.0.0”将被翻译为“1.2.0”,而不是之前编码的“1.2”。


为了回答Ehryk Apr的评论,您可以调整正则表达式以仅保留版本号的某些部分。例如,要保留“Major.Minor”,请替换:

<VersionNumber Include="$([System.Text.RegularExpressions.Regex]::Replace(&quot;%(Targets.Version)&quot;, &quot;^(.+?)(\.0+)$&quot;, &quot;$1&quot;))" />

通过

<VersionNumber Include="$([System.Text.RegularExpressions.Regex]::Replace(&quot;%(Targets.Version)&quot;, &quot;^([^\.]+)\.([^\.]+)(.*)$&quot;, &quot;$1.$2&quot;))" />

1
Nuget也有同样的问题。帮了我很大忙。谢谢。 - Satabol

4

我不知道为什么,但Brent Arias的宏对我不起作用(@(VersionNumber)总是为空):(。.Net6 VS2022。最终我使用了稍微修改过的版本:

<Target Name="GetVersion" AfterTargets="PostBuildEvent">
    <GetAssemblyIdentity AssemblyFiles="$(TargetPath)">
        <Output TaskParameter="Assemblies" ItemName="AssemblyInfo" />
    </GetAssemblyIdentity>
    <PropertyGroup>
        <VersionInfo>%(AssemblyInfo.Version)</VersionInfo>
    </PropertyGroup>
    <!--And use it after like any other variable:-->
    <Message Text="VersionInfo = $(VersionInfo)" Importance="high" />
</Target>

2

除非我漏掉了什么,否则这个问题很简单。将以下内容放入您的预构建或后构建脚本中:

FOR /F delims^=^"^ tokens^=2 %%i in ('findstr /b /c:"[assembly: AssemblyVersion(" $(ProjectDir)\Properties\AssemblyInfo.cs') do (set version=%%i)
echo Version: %version%

1
已点赞,但是在脚本的 '$(ProjectDir)' 之前、闭合引号后面缺少一个重要的空格字符。(我无法自行编辑,因为系统说我必须至少编辑6个字符。) - Lynax
恭喜你,我的博学朋友!你刚刚赢得了2023年最勤奋的“缺失空格发现者”奖项。你已经超越了同龄人沉闷乏味的日常琐事。有这样的技能,你一定会走得更远! - stigzler

1

我认为你可以做的最好的事情就是查看 MSBuild 和 MsBuild 扩展包,你应该能够编辑解决方案文件以便在后期构建事件发生时写入到你的测试文件中。

如果这太复杂,那么你可以简单地创建一个小程序,检查你输出目录中的所有程序集,并在后期构建时执行它,你可以使用变量名传递输出目录... 例如,在后期构建事件中...

AssemblyInspector.exe "$(TargetPath)"

class Program
{
    static void Main(string[] args)
    {
        var assemblyFilename = args.FirstOrDefault();
        if(assemblyFilename != null && File.Exists(assemblyFilename))
        {
            try
            {
                var assembly = Assembly.ReflectionOnlyLoadFrom(assemblyFilename);
                var name = assembly.GetName();

                using(var file = File.AppendText("C:\\AssemblyInfo.txt"))
                {
                    file.WriteLine("{0} - {1}", name.FullName, name.Version);
                }
            }
            catch (Exception ex)
            {
                throw;
            }
        }
    }
}

你也可以传递文本文件的位置...


1

根据我的理解...

你需要一个用于构建后事件的生成器。

1. 步骤:编写生成器

/*
* Author: Amen RA
* # Timestamp: 2013.01.24_02:08:03-UTC-ANKH
* Licence: General Public License
*/
using System;
using System.IO;

namespace AppCast
{
    class Program
    {
        public static void Main(string[] args)
        {
            // We are using two parameters.

            // The first one is the path of a build exe, i.e.: C:\pathto\nin\release\myapp.exe
            string exePath = args[0];

            // The second one is for a file we are going to generate with that information
            string castPath = args[1];

            // Now we use the methods below
            WriteAppCastFile(castPath, VersionInfo(exePath));
        }


        public static string VersionInfo(string filePath)
        {
            System.Diagnostics.FileVersionInfo myFileVersionInfo = System.Diagnostics.FileVersionInfo.GetVersionInfo(filePath);
            return myFileVersionInfo.FileVersion;
        }


        public static void WriteAppCastFile(string castPath, string exeVersion)
        {
            TextWriter tw = new StreamWriter(castPath);
            tw.WriteLine(@"<?xml version=""1.0"" encoding=""utf-8""?>");
            tw.WriteLine(@"<item>");
            tw.WriteLine(@"<title>MyApp - New version! Release " + exeVersion + " is available.</title>");
            tw.WriteLine(@"<version>" + exeVersion + "</version>");
            tw.WriteLine(@"<url>http://www.example.com/pathto/updates/MyApp.exe</url>");
            tw.WriteLine(@"<changelog>http://www.example.com/pathto/updates/MyApp_release_notes.html</changelog>");
            tw.WriteLine(@"</item>");
            tw.Close();
        }
    }
}

第二步:将其用作IDE中的后构建命令

在应用程序令您满意后:

在开发IDE中,使用以下命令行进行后构建事件。

C:\Projects\pathto\bin\Release\AppCast.exe "C:\Projects\pathto\bin\Release\MyApp.exe" "c:\pathto\www.example.com\root\pathto\updates\AppCast.xml"

1

需要注意的是,使用现代化的 (VS2017+) .csproj 格式和 VS2022 版本,可以直接使用原始帖子中的 $(AssemblyVersion)


嗯,在我这里使用的是VS 2022社区版,但它对我不起作用。而且,在“生成事件”窗口的宏列表中也找不到它... - stigzler

1

我已经开始添加一个单独的项目来构建最后版本,并在该项目中添加一个后期构建事件来运行自身。然后我只需在其中以编程方式执行我的后期构建步骤。

这样做可以使这些工作变得更加容易。然后,您只需检查您想要的任何程序集的程序集属性即可。到目前为止,它的工作非常出色。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接