Exibindo a data de construção

260

Atualmente, tenho um aplicativo exibindo o número da compilação em sua janela de título. Isso é bom, exceto que isso não significa nada para a maioria dos usuários, que querem saber se possuem a versão mais recente - eles tendem a se referir a ela como "da última quinta-feira" em vez da versão 1.0.8.4321.

O plano é colocar a data da compilação lá - então "Aplicativo criado em 21/10/2009", por exemplo.

Estou lutando para encontrar uma maneira programática de extrair a data de compilação como uma sequência de texto para uso como este.

Para o número da compilação, usei:

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

depois de definir como eles surgiram.

Eu gostaria de algo assim para a data de compilação (e hora, para pontos de bônus).

Os ponteiros aqui são muito apreciados (desculpe trocadilhos, se apropriado), ou soluções mais limpas ...

Mark Mayo
fonte
2
Eu tentei as formas fornecidas para obter os dados de compilação dos assemblies que funcionam em cenários simples, mas se dois assemblies forem mesclados, não recebo o tempo de compilação correto, será uma hora no futuro .. alguma sugestão?

Respostas:

356

Jeff Atwood tinha algumas coisas a dizer sobre esse problema em Determinando a data da compilação da maneira mais difícil .

O método mais confiável é recuperar o registro de data e hora do vinculador do cabeçalho PE incorporado no arquivo executável - algum código C # (de Joe Spivey) para isso 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 (fornece anos aleatórios no intervalo de 1900-2020)

mdb
fonte
8
Eu mudei meu tom sobre isso um pouco, ainda assim eu teria muito cuidado ao cavar o cabeçalho do PE brutal. Mas, até onde eu sei, esse material de PE é muito mais confiável do que usar os números de versão, além de não querer atribuir os números de versão separados a partir da data de criação.
John Leidegren
6
Eu gosto disso e estou usando, mas a penúltima linha do .AddHours()é bastante hackish e (acho) não levará em consideração o horário de verão. Se você quiser na hora local, use o limpador dt.ToLocalTime();. A parte do meio também pode ser bastante simplificada com um using()bloco.
JLRishe #
6
Sim, isso parou de funcionar para mim também com o núcleo .net (décadas de 1940, 1960, etc.) #
315 eoleary
7
Embora o uso do cabeçalho PE possa parecer uma boa opção hoje, vale a pena observar que o MS está experimentando compilações determinísticas (o que tornaria esse cabeçalho inútil) e talvez até o torne padrão nas futuras versões do C # do compilador (por boas razões). Boa leitura: blog.paranoidcoding.com/2016/04/05/… e aqui está a resposta relacionada ao .NET Core (TLDR: "é por design"): developercommunity.visualstudio.com/content/problem/35873/…
Paweł Bulwan
13
Para quem acha que isso não funciona mais, o problema não é do .NET Core. Veja minha resposta abaixo sobre novos padrões de parâmetro de compilação, começando com o Visual Studio 15.4.
Tom
107

Adicione abaixo à linha de comando do evento de pré-construção:

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

Adicione este arquivo como recurso, agora você tem a string 'BuildDate' em seus recursos.

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

Abdurrahim
fonte
4
+1 de mim, simples e eficaz. Eu ainda conseguiu obter o valor a partir do arquivo com uma linha de código como este: String buildDate = <MyClassLibraryName> .Properties.Resources.BuildDate
davidfrancis
11
outra opção é criar uma classe: (precisa incluir no projeto após a primeira compilação) -> echo namespace My.app.namespace {classe estática pública Build (string estática pública Timestamp = "% DATE%% TIME%" .Substring (0,16);}}> "$ (ProjectDir) \ BuildTimestamp.cs" - - - ->, em seguida, pode chamá-lo com Build.Timestamp
FabianSilva
9
Esta é uma excelente solução. O único problema é que as variáveis ​​de linha de comando% date% e% time% estão localizadas, portanto a saída varia de acordo com o idioma do usuário do Windows.
VS
2
+1, este é um método melhor do que ler cabeçalhos PE - porque há vários cenários em que não vai funcionar em tudo (Windows Phone App por exemplo)
Matt Whitfield
17
Inteligente. Você também pode usar o powershell para obter um controle mais preciso sobre o formato, por exemplo, para obter um datetime UTC formatado como ISO8601: powershell -Command "((Get-Date) .ToUniversalTime ()). ToString (\" s \ ") | Arquivo de saída '$ (ProjectDir) Resources \ BuildDate.txt' "
dbruning
90

O caminho

Como apontado por @ c00000fd nos comentários . A Microsoft está mudando isso. E enquanto muitas pessoas não usam a versão mais recente de seu compilador, suspeito que essa mudança torne essa abordagem inquestionavelmente ruim. E, embora seja um exercício divertido, eu recomendaria que as pessoas simplesmente incorporassem uma data de construção em seu binário por qualquer outro meio necessário, se for importante rastrear a data de construção do próprio binário.

Isso pode ser feito com alguma geração trivial de código, que provavelmente já é a primeira etapa do seu script de construção. Isso e o fato de as ferramentas ALM / Build / DevOps ajudarem muito com isso e devem ser preferidas a qualquer outra coisa.

Deixo o restante desta resposta aqui apenas para fins históricos.

O novo caminho

Mudei de idéia sobre isso e, atualmente, use esse truque para obter a data de compilação 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 compilação? O Visual Studio (ou o compilador C #) realmente fornece números de compilação e revisão automáticos se você alterar o atributo AssemblyVersion para, por exemplo,1.0.*

O que acontecerá é que a compilação será igual ao número de dias desde 1º de janeiro de 2000, horário local, e a revisão será igual ao número de segundos desde o horário local da meia-noite, dividido por 2.

consulte Conteúdo da comunidade, números de compilação 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)
John Leidegren
fonte
3
Acabei de adicionar uma hora seTimeZone.CurrentTimeZone.IsDaylightSavingTime(buildDateTime) == true
e4rthdog
2
Infelizmente, eu usei essa abordagem sem examiná-la completamente, e está nos mordendo na produção. O problema é que, quando o compilador JIT entra em ação, as informações do cabeçalho do PE são alteradas. Daí o voto negativo. Agora estou fazendo uma 'pesquisa' desnecessária para explicar por que vemos a data da instalação como a data da compilação.
Jason D
8
@ JasonD Em que universo seu problema se torna, de alguma forma, meu problema? Como você justifica um voto negativo simplesmente porque encontrou um problema que esta implementação não levou em consideração. Você conseguiu isso de graça e o testou mal. Além disso, o que faz você acreditar que o cabeçalho está sendo reescrito pelo compilador JIT? Você está lendo essas informações da memória do processo ou do arquivo?
John Leidegren
6
Percebi que, se você estiver executando em um aplicativo Web, a propriedade .Codebase parece ser uma URL (arquivo: // c: /path/to/binary.dll). Isso faz com que a chamada File.Exists falhe. O uso de "assembly.Location" em vez da propriedade CodeBase resolveu o problema para mim.
Mdryden
2
@ JohnLeidegren: Não confie no cabeçalho do Windows PE para isso. Desde o Windows 10 e versões reproduzíveis , o IMAGE_FILE_HEADER::TimeDateStampcampo é definido como um número aleatório e não é mais um carimbo de data / hora.
C00000fd
51

Adicione abaixo à linha de comando do evento de pré-construção:

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

Adicione este arquivo como recurso, agora você tem a string 'BuildDate' em seus recursos.

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

string strCompTime = Properties.Resources.BuildDate;

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

brewmanz
fonte
1
@DavidGorsline - a marcação do comentário estava correta, pois está citando essa outra resposta . Não tenho reputação suficiente para reverter sua alteração, caso contrário, eu mesma teria feito.
Wai Ha Lee
1
@Wai Ha Lee - a) a resposta que você cita não fornece código para recuperar a data / hora da compilação. b) na época eu não tinha reputação suficiente para adicionar comentários a essa resposta (o que eu teria feito), apenas para postar. assim c) Eu postei dando resposta completa para que as pessoas poderiam obter todos os detalhes em uma área ..
brewmanz
Se você vir Úte% em vez de% date%, verifique aqui: developercommunity.visualstudio.com/content/problem/237752/… Em poucas palavras, faça o seguinte: eco% 25date% 25% 25time% 25
Qodex
41

Uma abordagem que me surpreende que ninguém tenha mencionado ainda é usar modelos de texto T4 para geração de código.

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

Prós:

  • Independente da localidade
  • Permite muito mais do que apenas o tempo de compilação

Contras:

Peter Taylor
fonte
1
Então, agora é a melhor resposta. Faltam 324 pontos para que se torne a resposta mais votada :). O Stackoverflow precisa de uma maneira de mostrar o escalador mais rápido.
pauldendulk
1
@pauldendulk, não ajudaria muito, porque a resposta mais votada e a resposta aceita quase sempre recebem os votos mais rapidamente. A resposta aceita para esta pergunta tem + 60 / -2 desde que eu publiquei esta resposta.
Peter Taylor
Eu acredito que você precisa adicionar um .ToString () aos seus tiques (caso contrário, recebo um erro de compilação). Dito isso, estou enfrentando uma curva acentuada de aprendizado aqui, você poderia mostrar como USAR isso também no programa principal?
Andy Andy
@ Andy, você está certo sobre o ToString (). O uso é justo Constants.CompilationTimestampUtc. Se o VS não estiver gerando um arquivo C # com a classe, você precisará descobrir como fazê-lo, mas a resposta depende (pelo menos) da versão do VS e do tipo de arquivo csproj, por isso é muitos detalhes para este post.
Peter Taylor
1
No caso de outras pessoas estarem se perguntando, foi o que foi necessário para fazê-lo funcionar no VS 2017: tive que criar um modelo Design Time T4 (demorei um pouco para descobrir, adicionei primeiro um modelo de pré-processador). Eu também tive que incluir este assembly: Microsoft.VisualStudio.TextTemplating.Interfaces.10.0 como uma referência ao projeto. Finalmente, meu modelo teve que incluir "using System"; antes do espaço para nome, ou a referência ao DateTime falhou.
Andy Andy
20

Em relação à técnica de extrair informações de data / versão da compilação dos bytes de um cabeçalho PE do assembly, a Microsoft alterou os parâmetros de compilação padrão começando no Visual Studio 15.4. O novo padrão inclui compilação determinística, que torna um carimbo de data / hora válido e os números de versão incrementados automaticamente 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 uma coisa ou outra, mas não indica o tempo de construção.

Alguns antecedentes detalhados aqui

Para aqueles que priorizam um carimbo de data / hora útil sobre a compilação determinística, há uma maneira de substituir o novo padrão. Você pode incluir uma tag no arquivo .csproj do assembly de interesse da seguinte maneira:

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

Atualização: eu apoio a solução de modelo de texto T4 descrita em outra resposta aqui. Usei-o para resolver meu problema de maneira limpa, sem perder o benefício da compilação determinística. Um cuidado é que o Visual Studio só executa o compilador T4 quando o arquivo .tt é salvo, não no tempo de compilação. Isso pode ser estranho se você excluir o resultado .cs do controle de origem (já que você espera que seja gerado) e outro desenvolvedor verificar o código. Sem salvar novamente, eles não terão o arquivo .cs. Existe um pacote no nuget (eu acho chamado AutoT4) que faz a compilação do T4 parte de cada compilação. Ainda não enfrentei a solução para isso durante a implantação da produção, mas espero que algo semelhante o ajude.

Tom
fonte
Isso resolveu meu problema em um sln que usa a resposta mais antiga.
precisa saber é o seguinte
Sua cautela sobre o T4 é perfeitamente justa, mas observe que ele já está presente na minha resposta.
Peter Taylor
15

Eu sou apenas novato em C #, talvez minha resposta pareça boba - eu exibo a data de compilaçã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());

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

Zalek

Zalek Bloom
fonte
4
Não tenho certeza se isso é à prova de balas ao copiar o executável em unidades / computadores / redes.
Rabino Stealth
essa é a primeira coisa que vem à mente, mas você sabe que não é confiável. Existem muitos softwares usados ​​para mover os arquivos pela rede que não atualizam os atributos após o download. Eu usaria a resposta de @ Abdurrahim.
Mubashar
Eu sei que isso é antigo, mas acabei de descobrir com um código semelhante que o processo de instalação (pelo menos ao usar clickonce) atualiza o tempo do arquivo de montagem. Não é muito útil. Não tenho certeza se isso se aplicaria a esta solução.
bobwki
Você provavelmente realmente deseja o LastWriteTimearquivo, pois isso reflete com precisão a hora em que o arquivo executável foi realmente atualizado.
David R Tribble
Desculpe, mas um tempo de gravação de arquivo executável não é uma indicação confiável do tempo de compilação. O registro de data e hora do arquivo pode ser reescrito devido a todos os tipos de coisas que estão fora da sua esfera de influência.
Tom
15

Muitas ótimas respostas aqui, mas acho que posso adicionar as minhas por causa da simplicidade, desempenho (em comparação com soluções relacionadas a recursos) entre plataformas (funciona também com o Net Core) e evita qualquer ferramenta de terceiros. Basta adicionar este destino msbuild ao 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>

e agora você tem Builtin.CompileTimeou new DateTime(Builtin.CompileTime, DateTimeKind.Utc)se precisar dessa maneira.

ReSharper não vai gostar. Você pode ignorá-lo ou adicionar uma classe parcial ao projeto também, mas funciona assim mesmo.

Dmitry Gusarov
fonte
Posso criar isso e desenvolver localmente (executar sites) no ASP.NET Core 2.1, mas a publicação na implantação na Web do VS 2017 falha com o erro "O nome 'Builtin' não existe no contexto atual". ADIÇÃO: Se eu estiver acessando a Builtin.CompileTimepartir de uma visualização do Razor.
21418 Jeremy Cook
Neste caso, eu acho que você só precisa BeforeTargets="RazorCoreCompile", mas somente enquanto este está no mesmo projeto
Dmitry Gusarov
legal, mas como nos referimos ao objeto gerado? parece-me que a resposta está faltando uma parte fundamental ...
Matteo
1
@ Matteo, como mencionado na resposta, você pode usar "Builtin.CompileTime" ou "novo DateTime (Builtin.CompileTime, DateTimeKind.Utc)". O Visual Studio IntelliSense é capaz de ver isso imediatamente. Um ReSharper antigo pode reclamar em tempo de design, mas parece que o corrigiu em novas versões. clip2net.com/s/46rgaaO
Dmitry Gusarov
Eu usei esta versão, então não há necessidade de código adicional para obter a data. Também o re-afiador não se queixa com sua versão mais recente. <WriteLinesToFile File = "$ (IntermediateOutputPath) BuildInfo.cs" Lines = "usando a classe parcial estática interna System% 3B BuildInfo {public static long DateBuiltTicks = $ ([System.DateTime] :: UtcNow.Ticks)% 3B public static DateTime DateBuilt => new DateTime (DateBuiltTicks, DateTimeKind.Utc)% 3B} "Substituir =" true "/>
Softlion
13

Para projetos do .NET Core, adaptei a resposta do Postlagerkarte para atualizar o campo Copyright da montagem com a data da compilação.

Editar diretamente csproj

O seguinte pode ser adicionado diretamente ao primeiro PropertyGroupno csproj:

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

Alternativa: Propriedades do Projeto 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 do projeto adequadamente nos bastidores.

Em toda a solução via Directory.Build.props

Você pode colocar o <Copyright>elemento acima em um Directory.Build.propsarquivo na raiz da solução e aplicá-lo automaticamente a todos os projetos no diretório, assumindo que cada projeto não forneça seu próprio valor de Copyright.

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

Directory.Build.props: personalize sua compilação

Resultado

A expressão de exemplo fornecerá 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 obtê-las em tempo de execução:

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

Console.WriteLine(version.LegalCopyright);
Travis Troyer
fonte
11

O método acima pode ser ajustado para montagens já carregadas no processo , usando a imagem do arquivo na memória (em vez de relê-lo 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;
    }
}
tcostina
fonte
Isso funciona muito bem, mesmo para quadro 4.7 Usage: Utils.GetLinkerDateTime (Assembly.GetExecutingAssembly (), null))
real_yggdrasil
Funciona bem! Obrigado!
bobwki
10

Para quem precisa obter o tempo de compilação no 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);
            }
        }
    }

Para quem precisa obter o tempo de compilação no 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;
    }

OBSERVAÇÃO: em todos os casos, você está executando em uma sandbox, para poder obter o tempo de compilação dos assemblies implantados com o aplicativo. (ou seja, isso não funcionará em nada no GAC).

Matt Dotson
fonte
Veja como você obtém a Assembléia no WP 8.1:var assembly = typeof (AnyTypeInYourAssembly).GetTypeInfo().Assembly;
André Fiedler,
E se você quiser executar seu código nos dois sistemas? - um desses métodos é aplicável às duas plataformas?
bvdb
10

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:

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

Isso define uma PropertyFunction que você pode acessar no seu comando de pré-construção.

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();
            }
        }
    }
Postlagerkarte
fonte
Apenas gerar esse BuildTimeStamp.txt a partir dos eventos de pré-construção usando comandos de script em lote também funciona. Observe que você cometeu um erro aqui: você deve colocar seu alvo entre aspas (por exemplo "$(ProjectDir)BuildTimeStamp.txt") ou ele será quebrado quando houver espaços nos nomes das pastas.
Nyerguds
Talvez faça sentido usar o formato de tempo invariável da cultura. Assim: em $([System.DateTime]::Now.tostring("MM/dd/yyyy HH:mm:ss"))vez de$([System.DateTime]::Now)
Ivan Kochurkin 07/08
9

A opção não discutida aqui é inserir seus próprios dados no AssemblyInfo.cs, o campo "AssemblyInformationalVersion" parece apropriado - temos alguns projetos em que estávamos fazendo algo semelhante como uma etapa de construção (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 no codeproject: http://www.codeproject.com/KB/dotnet/Customizing_csproj_files.aspx

Murph
fonte
6

Eu precisava de uma solução universal que funcionasse com um projeto NETStandard em qualquer plataforma (iOS, Android e Windows.) Para fazer 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 do PowerScript como GenBuildDate.ps1 e adicione-o ao seu projeto. Por fim, adicione a seguinte linha ao seu evento Pre-Build:

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

Verifique se BuildDate.cs está incluído no seu projeto. Funciona como um campeão em qualquer sistema operacional!

David Tosi
fonte
1
Você também pode usar isso para obter o número de revisão do SVN usando a ferramenta de linha de comando svn. Eu fiz algo semelhante a isso com isso.
user169771
5

Eu só faço:

File.GetCreationTime(GetType().Assembly.Location)
Rui Santos
fonte
1
Curiosamente, se correndo de depuração, a data de 'verdadeiro' é o GetLastAccessTime ()
balint
4

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

Ele utiliza o T4 para automatizar o registro de data e hora da construção. Existem várias versões (ramificações diferentes), incluindo uma que fornece o Git Hash da ramificação com check-out atualmente, se você gosta desse tipo de coisa.

Divulgação: escrevi o módulo.

Darrin Cullop
fonte
3

Uma abordagem diferente, compatível com PCL, seria usar uma tarefa embutida do MSBuild para substituir o tempo de compilação em uma sequência retornada por uma propriedade no aplicativo. Estamos usando essa abordagem com êxito em um aplicativo que possui projetos Xamarin.Forms, Xamarin.Android e Xamarin.iOS.

EDITAR:

Simplificado movendo toda a lógica para o SetBuildDate.targetsarquivo e usando em Regexvez de uma sequência simples, substitua para que o arquivo possa ser modificado por cada construção sem uma "redefinição".

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

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

Chamando a tarefa embutida acima no arquivo Xamarin.Forms csproj no destino BeforeBuild:

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

A FilePathpropriedade é configurada para um BuildMetadata.csarquivo no projeto Xamarin.Forms que contém uma classe simples com uma propriedade string BuildDate, na qual o tempo de compilação será substituído:

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

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

Mark Larter
fonte
2

Você pode usar um evento de pós-construção do projeto para gravar um arquivo de texto no diretório de destino com a data e hora atuais. Você pode então ler o valor no tempo de execução. É um pouco hacky, mas deve funcionar.

MikeWyatt
fonte
2

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

Você precisa criar 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);
Thadeu Antonio Ferreira Melo
fonte
1

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

Na guia de propriedades do projeto, observe a guia de eventos de construção. Existe uma opção para executar um comando de pré ou pós-compilação.

Guy van den Berg
fonte
1

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

Depois de adicionar o echo %date% %time% > "$(ProjectDir)\Resources\BuildDate.txt"evento pré-compilação, fiz o seguinte:

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

A boa notícia aqui é que funcionou.

DemarcPoint
fonte
Solução muito simples. Eu gosto disso. E se o formato for um incômodo, existem maneiras de obter um formato melhor na linha de comando.
Nyerguds
0

Se esse for um aplicativo do Windows, você pode apenas usar o caminho executável do aplicativo: new System.IO.FileInfo (Application.ExecutablePath) .LastWriteTime.ToString ("aaaa.MM.dd")

John Clark
fonte
2
Já e responda usando isso, e também não exatamente à prova de balas.
Crash -str
0

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

Sasha Bond
fonte
Isso não está fazendo o mesmo que esta outra resposta ?
Wai Ha Lee