显示构建日期

Mar*_*ayo 249 c# time compilation date

我目前有一个应用程序在其标题窗口中显示内部版本号.这很好,除非对大多数用户没有任何意义,他们想知道他们是否拥有最新版本 - 他们倾向于将其称为"上周四",而不是构建1.0.8.4321.

计划是将构建日期放在那里 - 所以"应用程序构建于2009年10月21日".

我正在努力寻找一种程序化的方法来将构建日期作为文本字符串拉出来像这样使用.

对于内部版本号,我使用了:

Assembly.GetExecutingAssembly().GetName().Version.ToString()
Run Code Online (Sandbox Code Playgroud)

在确定了那些如何出现之后.

我想在编译日期(和时间,奖励积分)这样的东西.

这里的指针非常赞赏(如果合适的话,请原谅双关语),或更整洁的解决方案......

mdb*_*mdb 354

杰夫阿特伍德在确定构建日期方面有一些关于这个问题的说法.

最可靠的方法是从可执行文件中嵌入的PE头中检索链接器时间戳- 一些C#代码(由Joe Spivey提供),从评论到Jeff的文章:

public static DateTime GetLinkerTime(this Assembly assembly, TimeZoneInfo target = null)
{
    var filePath = assembly.Location;
    const int c_PeHeaderOffset = 60;
    const int c_LinkerTimestampOffset = 8;

    var buffer = new byte[2048];

    using (var stream = new FileStream(filePath, FileMode.Open, FileAccess.Read))
        stream.Read(buffer, 0, 2048);

    var offset = BitConverter.ToInt32(buffer, c_PeHeaderOffset);
    var secondsSince1970 = BitConverter.ToInt32(buffer, offset + c_LinkerTimestampOffset);
    var epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);

    var linkTimeUtc = epoch.AddSeconds(secondsSince1970);

    var tz = target ?? TimeZoneInfo.Local;
    var localTime = TimeZoneInfo.ConvertTimeFromUtc(linkTimeUtc, tz);

    return localTime;
}
Run Code Online (Sandbox Code Playgroud)

用法示例:

var linkTimeLocal = Assembly.GetExecutingAssembly().GetLinkerTime();
Run Code Online (Sandbox Code Playgroud)

更新:该方法适用于.Net Core 1.0,但在.Net Core 1.1发布后停止工作(在1900-2020范围内提供随机年份)

  • 对于那些发现不再有效的人来说,问题不是.NET Core问题.请参阅下面的答案,了解从Visual Studio 15.4开始的新构建参数默认值. (12认同)
  • 我已经改变了我的语气,在挖掘真正的PE标题时我仍然非常小心.但据我所知,这个PE的东西比使用版本号更可靠,除了我不想从构建日期分配版本号. (8认同)
  • 虽然今天使用PE头似乎是一个不错的选择,但值得注意的是MS正在试验确定性构建(这会使这个头无用),甚至可能在未来的C#编译器版本中使其默认(出于好的理由).好读:http://blog.paranoidcoding.com/2016/04/05/deterministic-builds-in-roslyn.html这里有与.NET Core相关的答案(TLDR:"它是设计的"):https:// developercommunity.visualstudio.com/content/problem/35873/invalid-timestamp-in-pe-header-of-compiled-net-cor.html (7认同)
  • 我喜欢这个并且正在使用它,但是使用`.AddHours()`的倒数第二行是相当hackish(我认为)不会考虑DST.如果你想在当地时间,你应该使用更清洁的`dt.ToLocalTime();`.使用`using()`块也可以大大简化中间部分. (6认同)
  • 是的,这也停止了.net核心(20世纪40年代,60年代等)为我工作 (4认同)
  • 我永远不会以这种方式深入研究PE头,只是为了得到汇编版本信息.我从未遇到过构建号没有更新到目前为止的问题,这个问题已成为过去.由于您将可执行文件视为原始字节,因此无法保证PE标头将来不会更改或者根本不是Windows PE标头(这是单声道工作吗?可能是的).这是你应该需要的唯一原因.除了格式之外,XBOX360上的endian可能会出现问题,当有人试图移植此代码时,您会遇到这种问题. (3认同)
  • 我不知道它是否有意,但从.net核心1.1.1开始,这似乎无效(虽然在以前版本的.net核心中工作过).这是假设我没有时间旅行到20世纪60年代(获得负面时代). (3认同)
  • 一个很好的解决方案,也适用于Mono,竖起大拇指! (2认同)
  • 现代化这个答案 (2认同)
  • 由于这不再适用于最新的.net核心,我现在使用MSBUILD任务将当前的UTCNow写入builddate.txt.然后我的代码加载此文件中的文本 (2认同)

Abd*_*him 95

在下面添加到预构建事件命令行:

echo %date% %time% > "$(ProjectDir)\Resources\BuildDate.txt"
Run Code Online (Sandbox Code Playgroud)

将此文件添加为资源,现在您的资源中包含"BuildDate"字符串.

要创建资源,请参阅如何在.NET中创建和使用资源.

  • 聪明.您还可以使用powershell来更精确地控制格式,例如,获取格式为ISO8601的UTC日期时间:powershell -Command"((Get-Date).ToUniversalTime()).ToString(\"s \")| Out-File'$(ProjectDir)Resources\BuildDate.txt'" (12认同)
  • 另一种选择是创建一个类:(必须在第一次编译后包含在项目中) - > echo namespace My.app.namespace {public static class Build {public static string Timestamp ="%DATE %% TIME%" .substring(0,16);}}>"$(ProjectDir)\ BuildTimestamp.cs" - - - - >然后可以用Build.Timestamp调用它 (9认同)
  • 这是一个很好的解决方案.唯一的问题是%date%和%time%命令行变量已本地化,因此输出将根据用户的Windows语言而有所不同. (8认同)
  • 来自我的+1,简单而有效.我甚至设法通过一行代码从文件中获取值,如下所示:String buildDate = <MyClassLibraryName> .Properties.Resources.BuildDate (3认同)
  • +1,这是比读取PE头更好的方法 - 因为有几种情况根本不起作用(例如Windows Phone App) (2认同)
  • http://stackoverflow.com/questions/90697/how-to-create-and-use-resources-in-net (2认同)

Joh*_*ren 87

方式

正如@ c00000fd在评论中指出的那样.微软正在改变这一点.虽然许多人不使用他们编译器的最新版本,但我怀疑这种改变使得这种方法毫无疑问是糟糕的.虽然这是一个有趣的练习,但我建议人们通过任何其他必要的方法将构建日期嵌入到二进制文件中,如果跟踪二进制文件本身的构建日期很重要.

这可以通过一些简单的代码生成来完成,这可能是您的构建脚本中的第一步.那个,以及ALM/Build/DevOps工具对此有很大帮助的事实应该是任何人都喜欢的.

我将这个答案的其余部分仅用于历史目的.

新的方式

我改变了主意,目前使用这个技巧来获得正确的构建日期.

#region Gets the build date and time (by reading the COFF header)

// http://msdn.microsoft.com/en-us/library/ms680313

struct _IMAGE_FILE_HEADER
{
    public ushort Machine;
    public ushort NumberOfSections;
    public uint TimeDateStamp;
    public uint PointerToSymbolTable;
    public uint NumberOfSymbols;
    public ushort SizeOfOptionalHeader;
    public ushort Characteristics;
};

static DateTime GetBuildDateTime(Assembly assembly)
{
    var path = assembly.GetName().CodeBase;
    if (File.Exists(path))
    {
        var buffer = new byte[Math.Max(Marshal.SizeOf(typeof(_IMAGE_FILE_HEADER)), 4)];
        using (var fileStream = new FileStream(path, FileMode.Open, FileAccess.Read))
        {
            fileStream.Position = 0x3C;
            fileStream.Read(buffer, 0, 4);
            fileStream.Position = BitConverter.ToUInt32(buffer, 0); // COFF header offset
            fileStream.Read(buffer, 0, 4); // "PE\0\0"
            fileStream.Read(buffer, 0, buffer.Length);
        }
        var pinnedBuffer = GCHandle.Alloc(buffer, GCHandleType.Pinned);
        try
        {
            var coffHeader = (_IMAGE_FILE_HEADER)Marshal.PtrToStructure(pinnedBuffer.AddrOfPinnedObject(), typeof(_IMAGE_FILE_HEADER));

            return TimeZone.CurrentTimeZone.ToLocalTime(new DateTime(1970, 1, 1) + new TimeSpan(coffHeader.TimeDateStamp * TimeSpan.TicksPerSecond));
        }
        finally
        {
            pinnedBuffer.Free();
        }
    }
    return new DateTime();
}

#endregion
Run Code Online (Sandbox Code Playgroud)

旧的方式

那么,你如何生成构建数字?如果将AssemblyVersion属性更改为例如,Visual Studio(或C#编译器)实际上提供自动构建和修订号1.0.*

将会发生的是,构建将等于自2000年1月1日当地时间以来的天数,并且修订将等于自当地时间午夜以来的秒数除以2.

请参阅社区内容,自动构建和修订号

例如AssemblyInfo.cs

[assembly: AssemblyVersion("1.0.*")] // important: use wildcard for build and revision numbers!
Run Code Online (Sandbox Code Playgroud)

SampleCode.cs

var version = Assembly.GetEntryAssembly().GetName().Version;
var buildDateTime = new DateTime(2000, 1, 1).Add(new TimeSpan(
TimeSpan.TicksPerDay * version.Build + // days since 1 January 2000
TimeSpan.TicksPerSecond * 2 * version.Revision)); // seconds since midnight, (multiply by 2 to get original)
Run Code Online (Sandbox Code Playgroud)

  • @JasonD在什么宇宙中你的问题不知何故成为我的问题?你如何证明一个downvote是因为你遇到了一个这个实现没有考虑到的问题.你得到这个免费的,你测试得很差.是什么让你相信JIT编译器正在重写头?您是从过程存储器还是从文件中读取此信息? (7认同)
  • 我注意到,如果您在Web应用程序中运行,.Codebase属性似乎是一个URL(file:// c:/path/to/binary.dll).这会导致File.Exists调用失败.使用"assembly.Location"而不是CodeBase属性解决了我的问题. (6认同)
  • 如果`TimeZone.CurrentTimeZone.IsDaylightSavingTime(buildDateTime)== true,我刚加了一个小时 (3认同)
  • 不幸的是,我使用这种方法而没有彻底审查它,它在生产中咬我们.问题是,当JIT编译器启动时,PE头信息会发生变化.因此,downvote.现在我开始做不必要的"研究"来解释为什么我们将安装日期视为构建日期. (2认同)
  • @JohnLeidegren:不要依赖Windows PE标头。[自Windows 10起](https://blogs.msdn.microsoft.com/oldnewthing/20180103-00/?p=97705)和[可复制的版本](https://reproducible-builds.org/),`IMAGE_FILE_HEADER :: TimeDateStamp`字段设置为随机数,不再是时间戳记。 (2认同)

bre*_*anz 46

在下面添加到预构建事件命令行:

echo %date% %time% > "$(ProjectDir)\Resources\BuildDate.txt"
Run Code Online (Sandbox Code Playgroud)

将此文件添加为资源,现在您的资源中包含"BuildDate"字符串.

将文件插入资源(作为公共文本文件)后,我通过访问它

string strCompTime = Properties.Resources.BuildDate;
Run Code Online (Sandbox Code Playgroud)

要创建资源,请参阅如何在.NET中创建和使用资源.

  • @Wai Ha Lee - a) 您引用的答案没有给出实际检索编译日期/时间的代码。b)当时我没有足够的声誉来为该答案添加评论(我会这样做),只能发布。所以 c) 我发布了完整的答案,这样人们就可以得到一个区域的所有详细信息.. (2认同)

Pet*_*lor 29

我很惊讶的一种方法是使用T4文本模板进行代码生成.

<#@ template debug="false" hostspecific="true" language="C#" #>
<#@ assembly name="System.Core" #>
<#@ import namespace="System" #>
<#@ output extension=".g.cs" #>
using System;
namespace Foo.Bar
{
    public static partial class Constants
    {
        public static DateTime CompilationTimestampUtc { get { return new DateTime(<# Write(DateTime.UtcNow.Ticks.ToString()); #>L, DateTimeKind.Utc); } }
    }
}
Run Code Online (Sandbox Code Playgroud)

优点:

  • 语言环境无关
  • 允许的不仅仅是编译时间

缺点:

  • @pauldendulk,不会有太大帮助,因为获得最多支持的答案和接受的答案几乎总是最快获得选票。自从我发布这个答案以来,这个问题的公认答案有[+60/-2](http://data.stackexchange.com/stackoverflow/query/834729)。 (2认同)
  • 万一其他人想知道,这就是让它在 VS 2017 上工作所需要的:我必须把它做成一个设计时 T4 模板(我花了一段时间才弄清楚,我首先添加了一个预处理器模板)。我还必须包含这个程序集:Microsoft.VisualStudio.TextTemplating.Interfaces.10.0 作为对项目的引用。最后,我的模板必须包含“使用系统;” 在命名空间之前,否则对 DateTime 的引用失败。 (2认同)
  • 成功了。确保安装答案中链接到的 Clarius.TransformOnBuild NuGet 包(废话),并且模板毕竟应该使用 TextTemplatePreProcessor 自定义工具...现在可以使用。 (2认同)

Jin*_*lye 24

对于.NET Core(.NET 5+)上的项目,可以这样完成。优点是无需添加或嵌入文件,无需 T4,也无需预构建脚本。

将这样的类添加到您的项目中:

namespace SuperDuper
{
    [AttributeUsage(AttributeTargets.Assembly)]
    public class BuildDateTimeAttribute : Attribute
    {
        public DateTime Built { get; }
        public BuildDateTimeAttribute(string date)
        {
            this.Built = DateTime.Parse(date);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

更新.csproj您的项目以包含如下内容:

<ItemGroup>
    <AssemblyAttribute Include="SuperDuper.BuildDateTime">
        <_Parameter1>$([System.DateTime]::Now.ToString("s"))</_Parameter1>
    </AssemblyAttribute>
</ItemGroup>
Run Code Online (Sandbox Code Playgroud)

请注意,这_Parameter1是一个神奇的名称 - 它意味着我们属性类的构造函数的第一个(也是唯一的)参数BuildDateTime。默认情况下,它期望它的类型为string

这就是在程序集中记录构建日期时间所需的全部内容。

然后要读取程序集的构建日期时间,请执行以下操作:

private static DateTime? getAssemblyBuildDateTime()
{
    var assembly = System.Reflection.Assembly.GetExecutingAssembly();
    var attr = Attribute.GetCustomAttribute(assembly, typeof(BuildDateTimeAttribute)) as BuildDateTimeAttribute;
    return attr?.Built;
}
Run Code Online (Sandbox Code Playgroud)

注意 1(根据评论中的Flydog57 ):如果您在其中列出了.csproj属性并将其设置为false,则构建将不会生成程序集信息,并且您将不会在程序集中获得任何 BuildDateTime 信息。因此,要么不要在您的中提及(这是新项目的默认行为,如果没有专门设置为false ,则默认为true),或者明确将其设置为trueGenerateAssemblyInfoGenerateAssemblyInfo.csprojGenerateAssemblyInfo

注 2(根据评论中的Teddy ):在给出的_Parameter1示例中,我们使用::NowDateTime.Now它是您计算机上的本地日期和时间,受夏令时(如果适用)和您当地时区的影响。如果您想使用,则可以::UtcNowDateTime.UtcNow构建日期和时间记录为 UTC/GMT。

  • 这就像一个魅力,并且应该是它所工作的平台的公认答案(在 .NET 6 上进行了测试)。但是,重要的是“&lt;GenerateAssemblyInfo&gt;”(在“&lt;PropertyGroup&gt;”中)要么设置为“true”,要么被省略。如果设置为 false,这会导致挫败感 (4认同)

Tom*_*Tom 19

关于从程序集PE头的字节中提取构建日期/版本信息的技术,Microsoft已更改从Visual Studio 15.4开始的默认构建参数.新的默认值包括确定性编译,它使有效的时间戳和自动递增的版本号成为过去.时间戳字段仍然存在,但它会填充永久值,该值是某个或其他内容的散列,但不是任何构建时间的指示.

http://blog.paranoidcoding.com/2016/04/05/deterministic-builds-in-roslyn.html 这里有一些详细的背景

对于那些优先考虑确定性编译的有用时间戳的人,有一种方法可以覆盖新的默认值.您可以在感兴趣的程序集的.csproj文件中包含一个标记,如下所示:

  <PropertyGroup>
      ...
      <Deterministic>false</Deterministic>
  </PropertyGroup>
Run Code Online (Sandbox Code Playgroud)

更新:我赞同此处另一个答案中描述的T4文本模板解决方案.我用它来干净地解决我的问题而不会失去确定性编译的好处.有一点需要注意的是,Visual Studio只在保存.tt文件时运行T4编译器,而不是在构建时运行.如果从源代码控制中排除.cs结果(因为您希望生成它)并且另一个开发人员检出代码,这可能会很麻烦.没有重新保存,他们将没有.cs文件.nuget上有一个包(我认为叫做AutoT4),它使T4编译成为每个构建的一部分.我还没有在生产部署期间面对这个问题的解决方案,但我希望类似的东西可以做到.


Zal*_*oom 13

我只是C#newbie所以也许我的答案听起来很傻 - 我显示了从可执行文件最后写入日期开始的构建日期:

string w_file = "MyProgram.exe"; 
string w_directory = Directory.GetCurrentDirectory();

DateTime c3 =  File.GetLastWriteTime(System.IO.Path.Combine(w_directory, w_file));
RTB_info.AppendText("Program created at: " + c3.ToString());
Run Code Online (Sandbox Code Playgroud)

我尝试使用File.GetCreationTime方法但得到了奇怪的结果:命令的日期是2012-05-29,但是Window Explorer的日期显示为2012-05-23.在搜索到这种差异后,我发现该文件可能是在2012-05-23创建的(如Windows资源管理器所示),但在2012-05-29复制到当前文件夹(如File.GetCreationTime命令所示) - 所以为了安全起见我正在使用File.GetLastWriteTime命令.

Zalek

  • 我不确定这是否是通过驱动器/计算机/网络复制可执行文件的防弹. (4认同)

Dmi*_*rov 11

这里有很多很棒的答案,但我觉得我可以添加自己的,因为简单,性能(与资源相关的解决方案相比)跨平台(也适用于Net Core)和避免使用任何第三方工具.只需将此msbuild目标添加到csproj即可.

<Target Name="Date" BeforeTargets="CoreCompile">
    <WriteLinesToFile File="$(IntermediateOutputPath)gen.cs" Lines="static partial class Builtin { public static long CompileTime = $([System.DateTime]::UtcNow.Ticks) %3B }" Overwrite="true" />
    <ItemGroup>
        <Compile Include="$(IntermediateOutputPath)gen.cs" />
    </ItemGroup>
</Target>
Run Code Online (Sandbox Code Playgroud)

现在你已经Builtin.CompileTime或者new DateTime(Builtin.CompileTime, DateTimeKind.Utc)如果你需要那样的话.

ReSharper不会喜欢它.您可以忽略他或者将部分类添加到项目中,但无论如何它都可以工作.

  • 在 Visual Studio 2022 上,我必须将“ItemGroup”部分移出“Target”块才能正常工作。 (11认同)
  • 对于任何想知道 %3B 作用的人来说,它是一个 url 编码的分号 `;` (3认同)
  • 我可以使用此构建并在 ASP.NET Core 2.1 中进行本地开发(运行网站),但从 VS 2017 进行 Web 部署发布失败,并出现错误“名称‘Builtin’在当前上下文中不存在”。添加:如果我从 Razor 视图访问“Builtin.CompileTime”。 (2认同)
  • 我使用了这个版本,因此不需要额外的代码来获取日期。resharper 也不会抱怨其最新版本。&lt;WriteLinesToFile File="$(IntermediateOutputPath)BuildInfo.cs" Lines="using System %3B 内部静态部分类 BuildInfo { public static long DateBuiltTicks = $([System.DateTime]::UtcNow.Ticks) %3B public static DateTime DateBuilt =&gt; 新的 DateTime(DateBuiltTicks, DateTimeKind.Utc) %3B }" Overwrite="true" /&gt; (2认同)

Mat*_*son 10

对于需要在Windows 8/Windows Phone 8中获得编译时间的任何人:

    public static async Task<DateTimeOffset?> RetrieveLinkerTimestamp(Assembly assembly)
    {
        var pkg = Windows.ApplicationModel.Package.Current;
        if (null == pkg)
        {
            return null;
        }

        var assemblyFile = await pkg.InstalledLocation.GetFileAsync(assembly.ManifestModule.Name);
        if (null == assemblyFile)
        {
            return null;
        }

        using (var stream = await assemblyFile.OpenSequentialReadAsync())
        {
            using (var reader = new DataReader(stream))
            {
                const int PeHeaderOffset = 60;
                const int LinkerTimestampOffset = 8;

                //read first 2048 bytes from the assembly file.
                byte[] b = new byte[2048];
                await reader.LoadAsync((uint)b.Length);
                reader.ReadBytes(b);
                reader.DetachStream();

                //get the pe header offset
                int i = System.BitConverter.ToInt32(b, PeHeaderOffset);

                //read the linker timestamp from the PE header
                int secondsSince1970 = System.BitConverter.ToInt32(b, i + LinkerTimestampOffset);

                var dt = new DateTimeOffset(1970, 1, 1, 0, 0, 0, DateTimeOffset.Now.Offset) + DateTimeOffset.Now.Offset;
                return dt.AddSeconds(secondsSince1970);
            }
        }
    }
Run Code Online (Sandbox Code Playgroud)

对于需要在Windows Phone 7中获取编译时间的任何人:

    public static async Task<DateTimeOffset?> RetrieveLinkerTimestampAsync(Assembly assembly)
    {
        const int PeHeaderOffset = 60;
        const int LinkerTimestampOffset = 8;            
        byte[] b = new byte[2048];

        try
        {
            var rs = Application.GetResourceStream(new Uri(assembly.ManifestModule.Name, UriKind.Relative));
            using (var s = rs.Stream)
            {
                var asyncResult = s.BeginRead(b, 0, b.Length, null, null);
                int bytesRead = await Task.Factory.FromAsync<int>(asyncResult, s.EndRead);
            }
        }
        catch (System.IO.IOException)
        {
            return null;
        }

        int i = System.BitConverter.ToInt32(b, PeHeaderOffset);
        int secondsSince1970 = System.BitConverter.ToInt32(b, i + LinkerTimestampOffset);
        var dt = new DateTimeOffset(1970, 1, 1, 0, 0, 0, DateTimeOffset.Now.Offset) + DateTimeOffset.Now.Offset;
        dt = dt.AddSeconds(secondsSince1970);
        return dt;
    }
Run Code Online (Sandbox Code Playgroud)

注意:在所有情况下,您都在沙箱中运行,因此您只能获得使用应用程序部署的程序集的编译时间.(即这不适用于GAC中的任何内容).


tco*_*tin 10

通过在内存中使用文件的映像(而不是从存储中重新读取它),可以调整上面的方法,以便已经在进程中加载​​的程序集:

using System;
using System.Runtime.InteropServices;
using Assembly = System.Reflection.Assembly;

static class Utils
{
    public static DateTime GetLinkerDateTime(this Assembly assembly, TimeZoneInfo tzi = null)
    {
        // Constants related to the Windows PE file format.
        const int PE_HEADER_OFFSET = 60;
        const int LINKER_TIMESTAMP_OFFSET = 8;

        // Discover the base memory address where our assembly is loaded
        var entryModule = assembly.ManifestModule;
        var hMod = Marshal.GetHINSTANCE(entryModule);
        if (hMod == IntPtr.Zero - 1) throw new Exception("Failed to get HINSTANCE.");

        // Read the linker timestamp
        var offset = Marshal.ReadInt32(hMod, PE_HEADER_OFFSET);
        var secondsSince1970 = Marshal.ReadInt32(hMod, offset + LINKER_TIMESTAMP_OFFSET);

        // Convert the timestamp to a DateTime
        var epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
        var linkTimeUtc = epoch.AddSeconds(secondsSince1970);
        var dt = TimeZoneInfo.ConvertTimeFromUtc(linkTimeUtc, tzi ?? TimeZoneInfo.Local);
        return dt;
    }
}
Run Code Online (Sandbox Code Playgroud)


Mur*_*rph 9

这里没有讨论的选项是将你自己的数据插入到AssemblyInfo.cs中,"AssemblyInformationalVersion"字段似乎是合适的 - 我们有几个项目,我们正在做一些类似于构建步骤的事情(但是我对它并不完全满意)这样做的方式并不是真的想要重现我们所拥有的东西.

在codeproject上有一篇关于这个主题的文章:http://www.codeproject.com/KB/dotnet/Customizing_csproj_files.aspx


Tra*_*yer 9

对于.NET Core项目,我改编了Postlagerkarte的答案,用构建日期更新程序集版权字段.

直接编辑csproj

以下内容可以直接添加到PropertyGroupcsproj中的第一个:

<Copyright>Copyright © $([System.DateTime]::UtcNow.Year) Travis Troyer ($([System.DateTime]::UtcNow.ToString("s")))</Copyright>
Run Code Online (Sandbox Code Playgroud)

替代方案:Visual Studio项目属性

或者将内部表达式直接粘贴到Visual Studio中项目属性的"包"部分中的"版权"字段中:

Copyright © $([System.DateTime]::UtcNow.Year) Travis Troyer ($([System.DateTime]::UtcNow.ToString("s")))
Run Code Online (Sandbox Code Playgroud)

这可能有点令人困惑,因为Visual Studio将评估表达式并在窗口中显示当前值,但它也将在后台适当地更新项目文件.

通过Directory.Build.props解决方案范围

您可以将<Copyright>上面的元素放入Directory.Build.props解决方案根目录中的文件中,并将其自动应用于目录中的所有项目,假设每个项目都不提供自己的版权值.

<Project>
 <PropertyGroup>
   <Copyright>Copyright © $([System.DateTime]::UtcNow.Year) Travis Troyer ($([System.DateTime]::UtcNow.ToString("s")))</Copyright>
 </PropertyGroup>
</Project>
Run Code Online (Sandbox Code Playgroud)

Directory.Build.props:自定义您的构建

产量

示例表达式将为您提供如下版权:

Copyright © 2018 Travis Troyer (2018-05-30T14:46:23)
Run Code Online (Sandbox Code Playgroud)

恢复

您可以从Windows中的文件属性查看版权信息,也可以在运行时获取它:

var version = FileVersionInfo.GetVersionInfo(Assembly.GetEntryAssembly().Location);

Console.WriteLine(version.LegalCopyright);
Run Code Online (Sandbox Code Playgroud)


小智 9

我只是做:

File.GetCreationTime(GetType().Assembly.Location)
Run Code Online (Sandbox Code Playgroud)

  • 有趣的是,如果从调试运行,“真实”日期是 GetLastAccessTime() (2认同)

Pos*_*rte 8

在2018年,上述某些解决方案不再起作用或不能与.NET Core一起使用.

我使用以下方法,这很简单,适用于我的.NET Core 2.0项目.

将以下内容添加到PropertyGroup内的.csproj中:

    <Today>$([System.DateTime]::Now)</Today>
Run Code Online (Sandbox Code Playgroud)

这定义了一个PropertyFunction,您可以在预构建命令中访问该PropertyFunction.

您的预构建看起来像这样

echo $(today) > $(ProjectDir)BuildTimeStamp.txt
Run Code Online (Sandbox Code Playgroud)

将BuildTimeStamp.txt的属性设置为Embedded资源.

现在你可以像这样读取时间戳了

public static class BuildTimeStamp
    {
        public static string GetTimestamp()
        {
            var assembly = Assembly.GetEntryAssembly(); 

            var stream = assembly.GetManifestResourceStream("NamespaceGoesHere.BuildTimeStamp.txt");

            using (var reader = new StreamReader(stream))
            {
                return reader.ReadToEnd();
            }
        }
    }
Run Code Online (Sandbox Code Playgroud)


Dav*_*osi 6

我需要一个可在任何平台(iOS,Android和Windows)上与NETStandard项目一起使用的通用解决方案。为实现此目的,我决定通过PowerShell脚本自动生成CS文件。这是PowerShell脚本:

param($outputFile="BuildDate.cs")

$buildDate = Get-Date -date (Get-Date).ToUniversalTime() -Format o
$class = 
"using System;
using System.Globalization;

namespace MyNamespace
{
    public static class BuildDate
    {
        public const string BuildDateString = `"$buildDate`";
        public static readonly DateTime BuildDateUtc = DateTime.Parse(BuildDateString, null, DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal);
    }
}"

Set-Content -Path $outputFile -Value $class
Run Code Online (Sandbox Code Playgroud)

将PowerScript文件另存为GenBuildDate.ps1并将其添加到您的项目中。最后,将以下行添加到您的Pre-Build事件中:

powershell -File $(ProjectDir)GenBuildDate.ps1 -outputFile $(ProjectDir)BuildDate.cs
Run Code Online (Sandbox Code Playgroud)

确保BuildDate.cs包含在您的项目中。在任何操作系统上都像冠军!


Mar*_*ter 5

另一种对 PCL 友好的方法是使用 MSBuild 内联任务将构建时间替换为应用程序属性返回的字符串。我们在具有 Xamarin.Forms、Xamarin.Android 和 Xamarin.iOS 项目的应用中成功地使用了这种方法。

编辑:

通过将所有逻辑移动到SetBuildDate.targets文件中并使用Regex而不是简单的字符串替换来简化,以便每次构建都可以修改文件而无需“重置”。

MSBuild 内联任务定义(保存在本示例的 Xamarin.Forms 项目本地的 SetBuildDate.targets 文件中):

<Project xmlns='http://schemas.microsoft.com/developer/msbuild/2003' ToolsVersion="12.0">

  <UsingTask TaskName="SetBuildDate" TaskFactory="CodeTaskFactory" 
    AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.v12.0.dll">
    <ParameterGroup>
      <FilePath ParameterType="System.String" Required="true" />
    </ParameterGroup>
    <Task>
      <Code Type="Fragment" Language="cs"><![CDATA[

        DateTime now = DateTime.UtcNow;
        string buildDate = now.ToString("F");
        string replacement = string.Format("BuildDate => \"{0}\"", buildDate);
        string pattern = @"BuildDate => ""([^""]*)""";
        string content = File.ReadAllText(FilePath);
        System.Text.RegularExpressions.Regex rgx = new System.Text.RegularExpressions.Regex(pattern);
        content = rgx.Replace(content, replacement);
        File.WriteAllText(FilePath, content);
        File.SetLastWriteTimeUtc(FilePath, now);

   ]]></Code>
    </Task>
  </UsingTask>

</Project>
Run Code Online (Sandbox Code Playgroud)

在目标 BeforeBuild 中的 Xamarin.Forms csproj 文件中调用上述内联任务:

  <!-- To modify your build process, add your task inside one of the targets below and uncomment it. 
       Other similar extension points exist, see Microsoft.Common.targets.  -->
  <Import Project="SetBuildDate.targets" />
  <Target Name="BeforeBuild">
    <SetBuildDate FilePath="$(MSBuildProjectDirectory)\BuildMetadata.cs" />
  </Target>
Run Code Online (Sandbox Code Playgroud)

FilePath属性设置为BuildMetadata.csXamarin.Forms 项目中的一个文件,该文件包含一个带有字符串 property 的简单类BuildDate,生成时间将被替换到其中:

public class BuildMetadata
{
    public static string BuildDate => "This can be any arbitrary string";
}
Run Code Online (Sandbox Code Playgroud)

将此文件添加BuildMetadata.cs到项目中。每次构建都会修改它,但以允许重复构建(重复替换)的方式进行修改,因此您可以根据需要在源代码管理中包含或省略它。


Dar*_*lop 5

您可以使用这个项目: https: //github.com/dwcullop/BuildInfo

它利用 T4 自动生成构建日期时间戳。有几个版本(不同的分支),其中一个版本可以为您提供当前签出分支的 Git 哈希(如果您喜欢这类事情)。

披露:我编写了该模块。