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

计划是把构建日期放在那里,例如“App构建于21/10/2009”。

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

对于版本号,我使用:

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

在定义了这些是怎么来的之后。

我希望编译日期(和时间,为了加分)也像这样。

非常感谢这里的指示(如果合适的话,借口双关语),或者更整洁的解决方案……


当前回答

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

编辑:

通过将所有逻辑移动到SetBuildDate来简化。目标文件,并使用Regex而不是简单的字符串替换,这样文件可以在每次构建时修改而不“重置”。

MSBuild内联任务定义(保存在SetBuildDate中。目标文件本地到Xamarin。本例中的表单项目):

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

在Xamarin中调用上述内联任务。在目标BeforeBuild中形成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属性被设置为Xamarin中的BuildMetadata.cs文件。表单项目,包含一个简单的类,具有字符串属性BuildDate,构建时间将被替换为:

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

将此文件BuildMetadata.cs添加到项目中。它将在每次构建时被修改,但是以一种允许重复构建的方式(重复替换),因此您可以根据需要在源代码控制中包含或省略它。

其他回答

如果这是一个windows应用程序,你可以只使用应用程序的可执行路径: 新System.IO.FileInfo (Application.ExecutablePath) .LastWriteTime.ToString(“yyyy.MM.dd”)

我需要一个通用的解决方案,可以在任何平台(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,并添加到你的项目。最后,添加以下行到您的预构建事件:

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

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

的方式

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

我刚刚添加了预构建事件命令:

powershell -Command Get-Date -Format 'yyyy-MM-ddTHH:mm:sszzz' > Resources\BuildDateTime.txt

在项目属性中生成一个易于从代码中读取的资源文件。

如果将程序集复制到另一个位置,则不会更改GetLastWriteTime。

public static class AssemblyExtensions
{
    public static DateTime GetLinkerTime(this Assembly assembly)
    {
        return File.GetLastWriteTime(assembly.Location).ToLocalTime();
    }
}