显示构建日期


260

我目前有一个应用程序,在其标题窗口中显示内部版本号。那是好事,只是它对大多数想知道自己是否拥有最新版本的用户毫无意义-他们倾向于将其称为“上周四”,而不是版本1.0.8.4321。

计划是将构建日期放在那里-因此,例如“ App build on 21/10/2009”。

我正在努力寻找一种编程方式来将构建日期作为文本字符串提取出来,以供使用。

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

Assembly.GetExecutingAssembly().GetName().Version.ToString()

在定义了这些内容之后。

我想要类似的东西作为编译日期(和时间,以获得加分)。

非常感谢这里的指针(如果适当的话,请使用双关语)或更整洁的解决方案...


2
我尝试了提供的方法来获取可在简单场景中使用的程序集的构建数据,但如果将两个程序集合并在一起,则我得到的构建时间不正确,那么将来的时间是一小时..有什么建议吗?

Answers:


356

Jeff Atwood在“ 确定构建日期”这一难题上有几句话要说。

事实证明,最可靠的方法是从嵌入在可执行文件中的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;
}

用法示例:

var linkTimeLocal = Assembly.GetExecutingAssembly().GetLinkerTime();

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


8
我已经对此稍作改动,但在挖掘手动PE标头时仍要非常小心。但是据我所知,这种PE东西比使用版本号要可靠得多,除了我不会分配与构建日期分开的版本号。
约翰·莱德格伦

6
我喜欢并正在使用它,但是与的倒数第二行.AddHours()有点黑,(我认为)不会考虑DST。如果要在当地时间使用,则应改用清洁剂dt.ToLocalTime();。中间部分也可以用一个using()块大大简化。
JLRishe 2013年

6
是的,这也停止了我对.net core的工作(1940年代,1960年代等)
eoleary

7
尽管今天使用PE标头似乎是一个不错的选择,但值得注意的是,MS正在试验确定性构建(这将使此标头无用),甚至可能使其在将来的C#编译器版本中默认(出于充分的理由)。好读:blog.paranoidcoding.com/2016/04/05/...这里的回答与.NET核心(TLDR:“这是由设计”):developercommunity.visualstudio.com/content/problem/35873/...
帕维尔Bulwan

13
对于那些发现此问题不再起作用的人,该问题不是.NET Core问题。请参阅以下有关从Visual Studio 15.4开始的新构建参数默认值的答案。
汤姆(Tom)

107

将以下内容添加到预构建事件命令行中:

echo %date% %time% > "$(ProjectDir)\Resources\BuildDate.txt"

将此文件添加为资源,现在您的资源中有“ BuildDate”字符串。

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


4
向我+1,简单有效。我什至设法通过如下代码从文件中获取值:字符串buildDate = <MyClassLibraryName>
.Properties.Resources.BuildDate

11
另一个选择是创建一个类:(必须在第一次编译后包含在项目中)-> echo名称空间My.app.namespace {public static class Build {public static string Timestamp =“%DATE%%TIME%” .Substring(0,16);}}>“ $(ProjectDir)\ BuildTimestamp.cs”---->然后可以使用Build.Timestamp来调用它
FabianSilva 2014年

9
这是一个很好的解决方案。唯一的问题是%date%和%time%命令行变量已本地化,因此输出将根据用户的Windows语言而有所不同。
VS 2014年

2
+1,这是比读取PE标头更好的方法-因为在某些情况下根本无法使用(例如Windows Phone App)
Matt Whitfield

17
聪明。您还可以使用powershell对格式进行更精确的控制,例如,获取格式为ISO8601的UTC日期时间:powershell -Command“((Get-Date).ToUniversalTime())。ToString(\” s \“)|输出文件'$(ProjectDir)Resources \ BuildDate.txt'“
dbruning 2015年

90

方式

正如@ 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

旧的方式

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

将会发生的情况是,构建将等于自本地时间2000年1月1日以来的天数,而修订版将等于自本地时间午夜以来的秒数除以2。

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

例如AssemblyInfo.cs

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

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)

3
我刚刚添加了一个小时,如果TimeZone.CurrentTimeZone.IsDaylightSavingTime(buildDateTime) == true
e4rthdog

2
不幸的是,我在没有彻底审查的情况下使用了这种方法,这在生产中一直困扰着我们。问题是,当JIT编译器插入PE标头信息时,该信息就会更改。因此,下降投票。现在,我不需要进行“研究”来解释为什么我们将安装日期视为构建日期。
杰森D

8
@JasonD您的问题在什么世界上成为我的问题?您仅仅因为遇到了一个问题,而该实现未考虑到该问题,您如何证明拒绝投票的正当性。您是免费获得的,并且测试不佳。还有什么使您相信JIT编译器正在重写标头?您是从过程存储器还是从文件中读取此信息?
约翰·莱德格伦

6
我注意到,如果您在Web应用程序中运行,则.Codebase属性似乎是URL(file:// c:/path/to/binary.dll)。这将导致File.Exists调用失败。使用“ assembly.Location”代替CodeBase属性为我解决了此问题。
mdryden

2
@JohnLeidegren:不要依赖Windows PE标头。由于Windows 10和可复制的build,该IMAGE_FILE_HEADER::TimeDateStamp字段设置为随机数,不再是时间戳记。
c00000fd

51

将以下内容添加到预构建事件命令行中:

echo %date% %time% > "$(ProjectDir)\Resources\BuildDate.txt"

将此文件添加为资源,现在您的资源中有“ BuildDate”字符串。

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

string strCompTime = Properties.Resources.BuildDate;

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


1
@DavidGorsline-注释标记正确,因为它引用了另一个答案。我没有足够的声誉来回滚您的更改,否则我会自己完成。
李慧夏

1
@Wai Ha Lee-a)您引用的答案没有给出实际检索编译日期/时间的代码。b)当时我没有足够的声誉来向该答案添加评论(我会这样做),只能发表。因此c)我张贴了完整的答案,以便人们可以在一个区域中获得所有详细信息
。–

如果您看到的是%te%而不是%date%,请查看此处:developercommunity.visualstudio.com/content/problem/237752/… 简而言之,请执行以下操作:echo%25date%25%25time%25
Qodex

41

令我惊讶的是,至今尚未有人提及的一种方法是使用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); } }
    }
}

优点:

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

缺点:


1
因此,这是最好的答案。要成为最高投票答案,还需要324分:)。Stackoverflow需要一种方法来显示最快的登山者。
pauldendulk

1
@pauldendulk不会有太大帮助,因为投票最多的答案和被接受的答案几乎总是最快地获得选票。自从我发布此答案以来,该问题的可接受答案为+ 60 / -2
彼得·泰勒

我相信您需要在.Ticks中添加.ToString()(否则会出现编译错误)。就是说,我在这里遇到了困难的学习曲线,您能否也说明如何在主程序中使用它?
安迪

@Andy,您对ToString()的看法是正确的。用法只是Constants.CompilationTimestampUtc。如果VS没有使用该类生成C#文件,则需要弄清楚如何使它执行此操作,但是答案至少(至少)取决于VS的版本和csproj文件的类型,因此这篇文章的细节太多了。
彼得·泰勒,

1
如果其他人想知道,这就是在VS 2017上运行所需的工作:我不得不将其设计为Design Time T4模板(花了我一段时间才弄清楚,我首先添加了Preprocessor模板)。我还必须包括以下程序集:Microsoft.VisualStudio.TextTemplating.Interfaces.10.0作为对该项目的引用。最后,我的模板必须包含“ using System”;在名称空间之前,否则对DateTime的引用将失败。
安迪

20

关于从程序集PE标头的字节中提取构建日期/版本信息的技术,Microsoft更改了从Visual Studio 15.4开始的默认构建参数。新的默认设置包括确定性编译,该编译使有效的时间戳记和自动递增的版本号成为过去。timestamp字段仍然存在,但是它填充有一个永久性值,该值是某种东西或其他东西的哈希,但不表示生成时间。

这里有一些详细的背景

对于那些优先使用确定性时间戳而不是确定性编译的用户,有一种方法可以覆盖新的默认值。您可以在目标程序集的.csproj文件中包括一个标签,如下所示:

  <PropertyGroup>
      ...
      <Deterministic>false</Deterministic>
  </PropertyGroup>

更新:我支持此处另一个答案中所述的T4文本模板解决方案。我用它来干净地解决我的问题,而不会失去确定性编译的好处。关于此的一种警告是,Visual Studio仅在保存.tt文件时运行T4编译器,而不在生成时运行。如果您从源代码管理中排除.cs结果(因为您希望生成该结果),并且另一个开发人员检出了代码,这可能会很尴尬。如果不重新保存,他们将没有.cs文件。在nuget上有一个软件包(我认为是AutoT4),它使T4编译成为每个构建的一部分。在生产部署期间,我还没有遇到过解决方案,但是我希望有类似的解决方案。


这在使用最旧答案的sln中解决了我的问题。
pauldendulk

您对T4的警告是完全公平的,但是请注意,我的回答中已经包含了该警告。
彼得·泰勒

15

我只是C#新手,所以我的回答听起来很傻-我显示从可执行文件最后写入日期起的构建日期:

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());

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

扎莱克


4
我不确定这是否可以跨驱动器/计算机/网络复制可执行文件。
Stealth Rabbi

这是第一件事,但是您知道它不可靠,有许多软件用于通过网络移动文件,下载后它们不会更新属性,我会使用@Abdurrahim的回答。
Mubashar 2014年

我知道这很旧,但是我发现使用一些类似的代码,INSTALL进程(至少在使用clickonce时)会更新汇编文件时间。不太有用。但是,不确定它是否适用于此解决方案。
bobwki

您可能真的想要LastWriteTime,因为它可以准确反映可执行文件实际更新的时间。
David R Tribble

对不起,但是可执行文件的写入时间并不是构建时间的可靠指示。由于影响范围之外的各种因素,可以重写文件时间戳。
汤姆

15

这里有很多很棒的答案,但是我觉得我可以添加自己的内容,因为它简单,性能(与资源相关的解决方案相比),跨平台(也可以与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>

现在你有Builtin.CompileTimenew DateTime(Builtin.CompileTime, DateTimeKind.Utc)是否需要这种方式。

ReSharper不会喜欢它。您可以忽略他,也可以将部分类添加到项目中,但是仍然可以。


我可以使用它进行构建,并在ASP.NET Core 2.1中本地开发(运行网站),但是VS 2017的Web部署发布失败,并显示错误“名称'Builtin'在当前上下文中不存在”。补充:如果我是Builtin.CompileTime从Razor视图访问的。
杰里米·库克

在这种情况下,我认为您只需要BeforeTargets="RazorCoreCompile"但仅在同一项目中时
Dmitry Gusarov

很酷,但是我们如何引用生成的对象呢?在我看来,答案缺少关键部分了……
Matteo,

1
@Matteo,如答案中所述,您可以使用“ Builtin.CompileTime”或“新的DateTime(Builtin.CompileTime,DateTimeKind.Utc)”。Visual Studio IntelliSense可以立即看到此情况。旧的ReSharper可能会在设计时抱怨,但是看起来他们在新版本中已解决此问题。clip2net.com/s/46rgaaO
德米特里·

我使用了此版本,因此不需要其他代码即可获取日期。Resharper也不抱怨其最新版本。<WriteLinesToFile File =“ $(IntermediateOutputPath)BuildInfo.cs” Lines =“使用系统%3B内部静态局部类BuildInfo {public static long DateBuiltTicks = $([[System.DateTime] :: UtcNow.Ticks)%3B public static DateTime DateBuilt =>新的DateTime(DateBuiltTicks,DateTimeKind.Utc)%3B}“ Overwrite =” true“ />
Softlion

13

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

直接编辑csproj

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

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

替代方案:Visual Studio项目属性

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

Copyright © $([System.DateTime]::UtcNow.Year) Travis Troyer ($([System.DateTime]::UtcNow.ToString("s")))

这可能有点令人困惑,因为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>

Directory.Build.props:自定义构建

输出量

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

Copyright © 2018 Travis Troyer (2018-05-30T14:46:23)

恢复

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

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

Console.WriteLine(version.LegalCopyright);

11

通过使用内存中的文件映像(与从存储中重新读取文件相反),可以针对已经在进程中加载​​的程序集调整上述方法:

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;
    }
}

即使对于框架4.7,此方法也很好用:Utils.GetLinkerDateTime(Assembly.GetExecutingAssembly(),null))
real_yggdrasil

很棒!谢谢!
bobwki '18

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);
            }
        }
    }

对于需要在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;
    }

注意:在所有情况下,您都在沙盒中运行,因此,您将只能获得与应用程序一起部署的程序集的编译时间。(即,这对GAC中的任何内容均无效)。


这里是你如何在WP 8.1获得大会:var assembly = typeof (AnyTypeInYourAssembly).GetTypeInfo().Assembly;
安德烈·菲德勒

如果要在两个系统上运行代码怎么办?-这些方法之一是否适用于两个平台?
bvdb

10

在2018年,上述某些解决方案不再起作用或不适用于.NET Core。

我使用以下简单且适用于.NET Core 2.0项目的方法。

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

    <Today>$([System.DateTime]::Now)</Today>

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

您的预构建看起来像这样

echo $(today) > $(ProjectDir)BuildTimeStamp.txt

将BuildTimeStamp.txt的属性设置为嵌入式资源。

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

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();
            }
        }
    }

使用批处理脚本命令从预构建事件生成BuildTimeStamp.txt 也是可以的。请注意,您在此处犯了一个错误:您应该将目标括在引号中(例如"$(ProjectDir)BuildTimeStamp.txt"),否则,文件夹名称中如果有空格,它将被破坏。
Nyerguds

使用文化不变的时间格式也许很有意义。像这样:$([System.DateTime]::Now.tostring("MM/dd/yyyy HH:mm:ss"))代替$([System.DateTime]::Now)
Ivan Kochurkin

9

这里未讨论的选项是将您自己的数据插入AssemblyInfo.cs,“ AssemblyInformationalVersion”字段似乎合适-我们有几个项目在进行类似于构建步骤的操作(但是,我并不完全满意这样行得通,这样就不必真正复制我们所拥有的东西)。

关于代码项目,有一篇文章:http : //www.codeproject.com/KB/dotnet/Customizing_csproj_files.aspx


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

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

powershell -File $(ProjectDir)GenBuildDate.ps1 -outputFile $(ProjectDir)BuildDate.cs

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


1
您也可以通过svn命令行工具使用它来获取SVN修订版号。我已经做了类似的事情。
user169771'4

5

我只是做:

File.GetCreationTime(GetType().Assembly.Location)

1
有趣的是,如果从调试运行,则“真实”日期为GetLastAccessTime()
balint


3

另一种与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>

在目标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>

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

public class BuildMetadata
{
    public static string BuildDate => "This can be any arbitrary string";
}

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


2

您可以使用项目生成后事件将文本文件与当前日期时间一起写入目标目录。然后,您可以在运行时读取该值。这有点hacky,但应该可以。



2

Jhon对“ New Way”答案的一个小更新。

使用ASP.NET/MVC时,您需要构建路径而不是使用CodeBase字符串

    var codeBase = assembly.GetName().CodeBase;
    UriBuilder uri = new UriBuilder(codeBase);
    string path = Uri.UnescapeDataString(uri.Path);

1

您可以在构建过程中启动一个额外的步骤,该步骤将日期戳写入可以显示的文件。

在项目属性选项卡上,查看构建事件选项卡。有一个选项可以执行前或后构建命令。


1

我采纳了阿卜杜拉希姆的建议。但是,它似乎给出了一种奇怪的时间格式,并且在构建日期的一部分中还增加了一天的缩写。例如:Sun 12/24/2017 13:21:05.43。我只需要日期,所以我不得不使用子字符串消除其余部分。

在将echo %date% %time% > "$(ProjectDir)\Resources\BuildDate.txt"pre 添加到预构建事件后,我执行了以下操作:

string strBuildDate = YourNamespace.Properties.Resources.BuildDate;
string strTrimBuildDate = strBuildDate.Substring(4).Remove(10);

好消息是它奏效了。


非常简单的解决方案。我喜欢。而且,如果格式很麻烦,则有多种方法可以从命令行获取更好的格式
Nyerguds

0

如果这是Windows应用程序,则可以使用应用程序可执行路径:new System.IO.FileInfo(Application.ExecutablePath).LastWriteTime.ToString(“ yyyy.MM.dd”)


2
已经在使用此功能并做出回答了,而且也不是完全防弹。
crashmstr 2015年

0

它可能是 Assembly execAssembly = Assembly.GetExecutingAssembly(); var creationTime = new FileInfo(execAssembly.Location).CreationTime; // "2019-09-08T14:29:12.2286642-04:00"


这和其他答案不一样吗?
李慧夏
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.