Exibindo a data de construção

Atualmente, tenho um aplicativo que exibe o número da compilation na janela de título. Isso é bom, exceto que não significa nada para a maioria dos usuários, que querem saber se eles têm a compilation mais recente – eles tendem a se referir a ela como “última quinta-feira” em vez de construir 1.0.8.4321.

O plano é colocar a data de construção lá em vez disso – Então “App construído em 21/10/2009” por exemplo.

Estou com dificuldades para encontrar uma maneira programática de extrair a data de construção como uma string de texto para uso desse tipo.

Para o número da compilation, usei:

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

depois de definir como isso surgiu.

Eu gostaria de algo assim para a data de compilation (e tempo, para pontos de bônus).

Ponteiros aqui muito apreciado (desculpe trocadilho se for o caso), ou soluções mais simples …

Jeff Atwood tinha algumas coisas a dizer sobre esse problema em Determinando o Build Date da maneira mais difícil .

O método mais confiável é recuperar o timestamp do vinculador do header PE embutido no arquivo executável – algum código C # (de Joe Spivey) para aquele dos comentários ao artigo de 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; } 

Exemplo de uso:

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

ATUALIZAÇÃO: O método estava funcionando para o .Net Core 1.0, mas parou de funcionar após o lançamento do .Net Core 1.1 (dá anos randoms no intervalo 1900-2020)

O novo caminho

Eu mudei de idéia sobre isso e atualmente uso este truque para obter a data de compilation correta.

 #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 

O jeito antigo

Bem, como você gera números de compilation? O Visual Studio (ou o compilador C #) fornece números de compilation e revisão automáticos se você alterar o atributo AssemblyVersion para, por exemplo, 1.0.*

O que acontecerá é que a compilation será igual ao número de dias desde 1º de janeiro de 2000, hora local, e que a revisão seja igual ao número de segundos desde a meia-noite, hora local, dividida por 2.

veja o conteúdo da comunidade, os números de compilation e revisão automáticos

por exemplo, 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) 

Adicione abaixo para pré-construir a linha de comando do evento:

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

Adicione este arquivo como recurso, agora você tem a string ‘BuildDate’ em seus resources.

Para criar resources, consulte Como criar e usar resources no .NET .

Adicione abaixo para pré-construir a linha de comando do evento:

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

Adicione este arquivo como recurso, agora você tem a string ‘BuildDate’ em seus resources.

Depois de inserir o arquivo no Recurso (como arquivo de texto público), eu o acessei via

 string strCompTime = Properties.Resources.BuildDate; 

Para criar resources, consulte Como criar e usar resources no .NET .

Eu sou apenas novato em C #, então talvez minha resposta pareça boba – eu exibo a data de construção a partir da data em que o arquivo executável foi gravado pela última vez:

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

Eu tentei usar o método File.GetCreationTime, mas tenho resultados estranhos: a data do comando foi 2012-05-29, mas a data do Windows Explorer mostrou 2012-05-23. Depois de procurar por essa discrepância, descobri que o arquivo foi provavelmente criado em 2012-05-23 (como mostrado pelo Windows Explorer), mas copiado para a pasta atual em 2012-05-29 (como mostrado pelo comando File.GetCreationTime) – assim para estar no lado seguro, estou usando o comando File.GetLastWriteTime.

Zalek

Uma abordagem que eu estou espantado que ninguém mencionou ainda é usar T4 Text Templates para geração de código.

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

Prós:

  • Independente de localidade
  • Permite muito mais que apenas o tempo de compilation

Contras:

  • Aplicável apenas a bibliotecas nas quais você controla a origem
  • Requer configurar seu projeto (e construir o servidor, se isso não for necessário) para executar o modelo em uma etapa de pré-compilation . (Veja também T4 sem VS ).

A opção não discutida aqui é inserir seus próprios dados em AssemblyInfo.cs, o campo “AssemblyInformationalVersion” parece apropriado – temos alguns projetos em que estávamos fazendo algo semelhante como uma etapa de compilation (no entanto, não estou totalmente satisfeito com o maneira que funciona, então não quero realmente reproduzir o que temos).

Há um artigo sobre o assunto em codeproject: http://www.codeproject.com/KB/dotnet/Customizing_csproj_files.aspx

Para qualquer pessoa que precise obter o tempo de compilation no Windows 8 / Windows Phone 8:

  public static async Task 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); } } } 

Para qualquer um que precise obter o tempo de compilation no Windows Phone 7:

  public static async Task 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(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; } 

OBSERVAÇÃO: em todos os casos em que você está executando em uma sandbox, você só poderá obter o tempo de compilation dos assemblies implantados com seu aplicativo. (isto é, isso não funcionará em nada no GAC).

O método acima pode ser ajustado para montagens já carregadas dentro do processo usando a imagem do arquivo na memory (ao invés de releá-lo a partir do armazenamento):

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

Em relação à técnica de extrair informações de data / versão de compilation dos bytes de um header PE de assembly, a Microsoft alterou os parâmetros de compilation padrão a partir do Visual Studio 15.4. O novo padrão inclui a compilation determinística, que torna um registro de data e hora válido e incrementa automaticamente os números de versão de uma coisa do passado. O campo de registro de data e hora ainda está presente, mas é preenchido com um valor permanente que é um hash de algo ou outro, mas não indica o tempo de compilation.

http://blog.paranoidcoding.com/2016/04/05/deterministic-builds-in-roslyn.html Alguns detalhes detalhados aqui

Para aqueles que priorizam um timestamp útil sobre a compilation determinística, existe uma maneira de replace o novo padrão. Você pode include uma tag no arquivo .csproj da assembly de interesse da seguinte maneira:

   ... false  

Atualização: Eu endosso a solução de modelo de texto T4 descrita em outra resposta aqui. Eu usei para resolver meu problema de forma limpa sem perder o benefício da compilation determinista. Um cuidado é que o Visual Studio só executa o compilador T4 quando o arquivo .tt é salvo, não no tempo de compilation. Isso pode ser complicado se você excluir o resultado .cs do controle de origem (desde que você espera que ele seja gerado) e outro desenvolvedor verifica o código. Sem salvamento, eles não terão o arquivo .cs. Existe um pacote no nuget (eu acho chamado AutoT4) que faz parte da compilation T4 de cada compilation. Eu ainda não enfrentei a solução para isso durante a implantação da produção, mas espero algo semelhante para torná-lo correto.

Você poderia usar um evento de pós-compilation do projeto para gravar um arquivo de texto em seu diretório de destino com o datetime atual. Você poderia então ler o valor em tempo de execução. É um pouco hacky, mas deve funcionar.

Não tenho certeza, mas talvez o Build Incrementer ajude.

Uma abordagem diferente, compatível com PCL, seria usar uma tarefa em linha do MSBuild para replace o tempo de compilation por uma cadeia de caracteres retornada por uma propriedade no aplicativo. Estamos usando essa abordagem com sucesso em um aplicativo que possui projetos Xamarin.Forms, Xamarin.Android e Xamarin.iOS.

EDITAR:

Simplificado, movendo toda a lógica para o arquivo SetBuildDate.targets , e usando o Regex invés da simples substituição de strings para que o arquivo possa ser modificado por cada build sem um “reset”.

A definição de tarefa em linha do MSBuild (salva em um arquivo SetBuildDate.targets local para o projeto Xamarin.Forms para este exemplo):

       < ![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); ]]>    

Invocando a tarefa inline acima no arquivo csproj Xamarin.Forms no BeforeBuild de destino:

       

A propriedade FilePath é definida como um arquivo BuildMetadata.cs no projeto Xamarin.Forms que contém uma class simples com uma propriedade de cadeia de caracteres BuildDate , na qual o tempo de compilation será substituído:

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

Adicione este arquivo BuildMetadata.cs ao projeto. Ele será modificado por cada compilation, mas de uma maneira que permita compilações repetidas (substituições repetidas), para que você possa incluí-las ou omiti-las no controle de origem, conforme desejado.

Em 2018, algumas das soluções acima não funcionam mais ou não funcionam com o .NET Core.

Eu uso a seguinte abordagem, que é simples e funciona para o meu projeto .NET Core 2.0.

Adicione o seguinte ao seu .csproj dentro do PropertyGroup:

  $([System.DateTime]::Now) 

Isso define uma PropertyFunction que você pode acessar no seu comando pre build.

Sua pré-construção se parece com isso

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

Defina a propriedade do BuildTimeStamp.txt como Recurso Incorporado.

Agora você pode ler o carimbo de hora como este

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

Eu precisava de uma solução universal que funcionasse com um projeto NETStandard em qualquer plataforma (iOS, Android e Windows). Para conseguir isso, decidi gerar automaticamente um arquivo CS por meio de um script do PowerShell. Aqui está o script do 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 

Salve o arquivo PowerScript como GenBuildDate.ps1 e adicione seu projeto. Por fim, adicione a seguinte linha ao seu evento de pré-compilation:

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

Certifique-se de que BuildDate.cs esteja incluído no seu projeto. Funciona como um campeão em qualquer sistema operacional!

Muitas das grandes respostas aqui, mas eu sinto que posso adicionar o meu próprio por causa da simplicidade, desempenho (em comparação com soluções relacionadas a resources) cross platform (funciona com o Net Core também) e evitar qualquer ferramenta de terceiros. Basta adicionar esse destino msbuild ao csproj.

       

e agora você tem o Builtin.CompileTime ou o new DateTime(Builtin.CompileTime, DateTimeKind.Utc) se você precisar dessa forma.

ReSharper não vai gostar. Você pode ignorá-lo ou adicionar uma class parcial ao projeto também, mas funciona de qualquer maneira.

Você pode iniciar uma etapa extra no processo de criação que grava um registro de data em um arquivo que pode ser exibido.

Na guia de propriedades do projeto, veja a guia de events de construção. Existe uma opção para executar um comando pré ou pós build.

Eu usei a sugestão de Abdurrahim. No entanto, parecia dar um formato de hora estranho e também adicionou a abreviação do dia como parte da data de compilation; exemplo: dom 12/24/2017 13: 21: 05.43. Eu só precisava da data, então eu tive que eliminar o resto usando substring.

Depois de adicionar o echo %date% %time% > "$(ProjectDir)\Resources\BuildDate.txt" ao evento de pré-compilation, acabei de fazer o seguinte:

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

A boa notícia aqui é que funcionou.

Uma pequena atualização sobre a resposta “New Way” de Jhon.

Você precisa construir o caminho em vez de usar a string CodeBase ao trabalhar com o ASP.NET/MVC

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

Para projetos .NET Core, adaptei a resposta do Postlagerkarte para atualizar o campo Copyright de assembly com a data de construção.

Diretamente Editar csproj

O seguinte pode ser adicionado diretamente ao primeiro PropertyGroup no csproj:

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

Alternativa: propriedades do projeto do Visual Studio

Ou cole a expressão interna diretamente no campo Copyright na seção Package das propriedades do projeto no Visual Studio:

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

Isso pode ser um pouco confuso, porque o Visual Studio avaliará a expressão e exibirá o valor atual na janela, mas também atualizará o arquivo de projeto apropriadamente nos bastidores.

Solução ampla via Directory.Build.props

Você pode plopar o elemento acima em um arquivo Directory.Build.props na raiz da sua solução e aplicá-lo automaticamente a todos os projetos dentro do diretório, supondo que cada projeto não forneça seu próprio valor de Copyright.

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

Directory.Build.props: Personalize sua compilation

Saída

A expressão de exemplo lhe dará direitos autorais como este:

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

Recuperação

Você pode visualizar as informações de direitos autorais das propriedades do arquivo no Windows ou agarrá-las no tempo de execução:

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

Se este é um aplicativo do windows, você pode apenas usar o caminho do executável do aplicativo: new System.IO.FileInfo (Application.ExecutablePath) .LastWriteTime.ToString (“yyyy.MM.dd”)

Você pode usar este projeto: https://github.com/dwcullop/BuildInfo

Ele aproveita o T4 para automatizar o registro de data e hora da data de criação. Existem várias versões (diferentes ramos), incluindo uma que lhe dá o Git Hash da ramificação atualmente retirada, se você estiver nesse tipo de coisa.

Divulgação: eu escrevi o módulo.