计划改成在那里放置构建日期,例如“应用程序创建于21/10/2009”。
我正在努力找到一种编程方式来提取构建日期作为文本字符串以供使用。
对于构建编号,我使用了:
Assembly.GetExecutingAssembly().GetName().Version.ToString()
对于编译日期(如果有时间更好),我想要类似那样的东西。
非常感谢指针(如果适用,请原谅双关语),或更加简洁的解决方案...
Assembly.GetExecutingAssembly().GetName().Version.ToString()
对于编译日期(如果有时间更好),我想要类似那样的东西。
非常感谢指针(如果适用,请原谅双关语),或更加简洁的解决方案...
Jeff Atwood在 这篇文章 中就这个问题提出了一些看法。
最可靠的方法是从可执行文件中嵌入的PE头中检索链接器时间戳 -- Jeff文章评论中的Joe Spivey提供了相应的C#代码:
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范围内的随机年份。
.AddHours()
有些繁琐,并且(我认为)不考虑DST。如果你想使用本地时间,应该使用更简洁的 dt.ToLocalTime();
。中间部分也可以通过 using()
块大大简化。 - JLRishe请在预构建事件命令行中添加以下内容:
echo %date% %time% > "$(ProjectDir)\Resources\BuildDate.txt"
将此文件作为资源添加, 现在您的资源中有“BuildDate”字符串。
要创建资源,请参见如何在.NET中创建和使用资源。
正如在 评论 中 @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属性为例如1.0.*
,Visual Studio(或C#编译器)实际上会提供自动构建和修订号。
会发生的情况是:构建将等于自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)
TimeZone.CurrentTimeZone.IsDaylightSavingTime(buildDateTime) == true
,我刚刚添加了一个小时。 - e4rthdog将以下内容添加到预构建事件命令行中:
echo %date% %time% > "$(ProjectDir)\Resources\BuildDate.txt"
将此文件作为资源添加,现在您的资源中有“BuildDate”字符串。
将文件插入资源(作为公共文本文件),我通过以下方式访问它
string strCompTime = Properties.Resources.BuildDate;
要创建资源,请参见如何在.NET中创建和使用资源。
我惊讶的发现还没有人提到的一种方法是使用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); } }
}
}
优点:
缺点:
WriteLinesToFile
MSBuild任务方法更好: https://dev59.com/D3I-5IYBdhLWcg3w8dYQ#50905092 - Artfunkel这里有许多优秀的答案,但我觉得我可以加入我的答案,因为它简单,性能好(与资源相关的解决方案相比),跨平台(也适用于Net Core)并且避免了任何第三方工具。只需将此msbuild目标添加到csproj中。
<Target Name="Date" BeforeTargets="BeforeBuild">
<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.CompileTime
,例如:
var compileTime = new DateTime(Builtin.CompileTime, DateTimeKind.Utc);
ReSharper不会喜欢这样做。你可以忽略它或向项目中添加一个部分类,但无论如何它都能正常工作。
更新:现在ReSharper在选项的第一页中有一个选项:“MSBuild访问”、“在每次编译后从MSBuild获取数据”。这有助于生成的代码的可见性。
Builtin.CompileTime
。 - Jeremy CookItemGroup
部分移出 Target
块才能使它正常工作。 - Daniel Ellis;
。 - J Scott对于 .NET Core 项目,我采用了Postlagerkarte的答案来更新程序集版权字段以反映构建日期。
以下内容可以直接添加到csproj文件的第一个PropertyGroup
中:
<Copyright>Copyright © $([System.DateTime]::UtcNow.Year) Travis Troyer ($([System.DateTime]::UtcNow.ToString("s")))</Copyright>
或在Visual Studio的项目属性的包部分中直接将内部表达式粘贴到版权字段中:
Copyright © $([System.DateTime]::UtcNow.Year) Travis Troyer ($([System.DateTime]::UtcNow.ToString("s")))
这可能有点令人困惑,因为Visual Studio会评估表达式并在窗口中显示当前值,但它也会在幕后适当地更新项目文件。
您可以将上面的<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);
关于从程序集 PE 头的字节中提取构建日期/版本信息的技术,微软在 Visual Studio 15.4 开始更改了默认的构建参数。新的默认设置包括确定性编译,使得有效时间戳和自动递增的版本号成为过去的事情。时间戳字段仍然存在,但它被填充了一个永久值,该值是某些哈希值,而不是任何构建时间的指示。
对于那些优先考虑有用时间戳的人,可以通过在感兴趣的程序集的 .csproj 文件中包含一个标记来覆盖新的默认设置,具体如下:
<PropertyGroup>
...
<Deterministic>false</Deterministic>
</PropertyGroup>
更新: 我支持在此处另一个答案中描述的T4文本模板解决方案。我使用它清洁地解决了我的问题,而不会失去确定性编译的好处。关于它的一个警告是,Visual Studio仅在保存.tt文件时运行T4编译器,而不是在构建时运行。如果你将.cs结果从源代码控制中排除(因为你希望它被生成),并且另一个开发人员检出了代码,则可能会很棘手。如果没有重新保存,他们将没有.cs文件。NuGet上有一个包(我认为称为AutoT4),可以使T4编译成为每个构建的一部分。我还没有在生产部署期间面对此解决方案,但我期望有类似的东西可以解决它。
namespace SuperDuper
{
[AttributeUsage(AttributeTargets.Assembly)]
public class BuildDateTimeAttribute : Attribute
{
public DateTime Built { get; }
public BuildDateTimeAttribute(string date)
{
this.Built = DateTime.Parse(date);
}
}
}
.csproj
文件,包含类似以下内容的内容:<ItemGroup>
<AssemblyAttribute Include="SuperDuper.BuildDateTime">
<_Parameter1>$([System.DateTime]::Now.ToString("s"))</_Parameter1>
</AssemblyAttribute>
</ItemGroup>
_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;
}
<PropertyGroup>
中设置<GenerateAssemblyInfo>
为true
或省略。如果设置为false,这将导致沮丧。 - Flydog57我只是 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资源管理器显示的日期是2012-05-23。在寻找这个差异时,我发现该文件可能是在2012-05-23创建的(如Windows资源管理器所示),但在2012-05-29被复制到当前文件夹(如File.GetCreationTime命令所示) - 为了安全起见,我使用File.GetLastWriteTime命令。LastWriteTime
,因为它准确地反映了可执行文件实际更新的时间。 - David R Tribble