Pregunta

Actualmente tengo una aplicación que muestra el número de compilación en la ventana de título.Eso está muy bien, excepto que no significa nada para la mayoría de los usuarios, que quieren saber si tienen la última versión; tienden a referirse a ella como "la del jueves pasado" en lugar de la versión 1.0.8.4321.

El plan es colocar la fecha de compilación allí; por ejemplo, "Aplicación creada el 21/10/2009".

Estoy luchando por encontrar una forma programática de extraer la fecha de compilación como una cadena de texto para usarla de esta manera.

Para el número de compilación, utilicé:

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

después de definir cómo surgieron.

Me gustaría algo así para la fecha de compilación (y la hora, para obtener puntos de bonificación).

Se agradecen mucho los consejos aquí (disculpe el juego de palabras si corresponde), o soluciones más interesantes...

¿Fue útil?

Solución

Jeff Atwood dijo algunas cosas sobre este problema en Determinación Fecha de compilación de la manera difícil .

El método más confiable es recuperar la marca de tiempo del enlazador desde encabezado PE incrustado en el archivo ejecutable - algún código C # (por Joe Spivey) para eso de los comentarios al artículo 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;
}

Ejemplo de uso:

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

ACTUALIZACIÓN: El método estaba funcionando para .Net Core 1.0, pero dejó de funcionar después de la publicación de .Net Core 1.1 (da años aleatorios en el rango 1900-2020)

Otros consejos

Agregue a continuación a la línea de comando del evento previo a la construcción:

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

Agregue este archivo como recurso, ahora tiene la cadena 'BuildDate' en sus recursos.

Para crear recursos, consulte Cómo crear y usar recursos. NET .

El camino

Como señaló @ c00000fd en los comentarios . Microsoft está cambiando esto. Y aunque muchas personas no usan la última versión de su compilador, sospecho que este cambio hace que este enfoque sea indudablemente malo. Y aunque es un ejercicio divertido, recomendaría a las personas que simplemente incorporen una fecha de compilación en su binario a través de cualquier otro medio necesario si es importante rastrear la fecha de compilación del propio binario.

Esto se puede hacer con una generación de código trivial que probablemente ya sea el primer paso en su script de compilación. Eso y el hecho de que las herramientas ALM / Build / DevOps ayudan mucho con esto y deberían preferirse a cualquier otra cosa.

Dejo el resto de esta respuesta aquí solo con fines históricos.

La nueva forma

Cambié de opinión sobre esto, y actualmente uso este truco para obtener la fecha de compilación correcta.

#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

A la antigua usanza

Bueno, ¿cómo generas números de compilación? Visual Studio (o el compilador de C #) en realidad proporciona números de compilación y revisión automáticos si cambia el atributo AssemblyVersion a, p. 1.0.*

Lo que sucederá es que la compilación será igual a la cantidad de días desde el 1 de enero de 2000, hora local, y que la revisión sea igual a la cantidad de segundos desde la medianoche, hora local, dividida por 2.

vea Contenido de la comunidad, Números automáticos de compilación y revisión

p. 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)

Agregue a continuación a la línea de comando del evento previo a la construcción:

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

Agregue este archivo como recurso, ahora tiene la cadena 'BuildDate' en sus recursos.

Después de insertar el archivo en el Recurso (como archivo de texto público), accedí a él a través de

string strCompTime = Properties.Resources.BuildDate;

Para crear recursos, consulte Cómo crear y usar recursos. NET .

Un enfoque que me sorprende que nadie haya mencionado todavía es usar Plantillas de texto T4 para la generación 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); } }
    }
}

Pros:

  • Independiente de la localidad
  • Permite mucho más que solo el momento de la compilación

Contras:

Con respecto a la técnica de extraer información de la fecha / versión de compilación de los bytes de un encabezado PE de ensamblaje, Microsoft ha cambiado los parámetros de compilación predeterminados a partir de Visual Studio 15.4. El nuevo valor predeterminado incluye una compilación determinista, que hace que una marca de tiempo válida y los números de versión incrementados automáticamente sean cosa del pasado. El campo de marca de tiempo todavía está presente, pero se llena con un valor permanente que es un hash de algo u otro, pero no es una indicación del tiempo de construcción.

Algunos antecedentes detallados aquí

Para aquellos que priorizan una marca de tiempo útil sobre la compilación determinista, hay una manera de anular el nuevo valor predeterminado. Puede incluir una etiqueta en el archivo .csproj del conjunto de interés de la siguiente manera:

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

Actualización: Apoyo la solución de plantilla de texto T4 descrita en otra respuesta aquí. Lo usé para resolver mi problema limpiamente sin perder el beneficio de la compilación determinista. Una advertencia al respecto es que Visual Studio solo ejecuta el compilador T4 cuando se guarda el archivo .tt, no en el momento de la compilación. Esto puede ser incómodo si excluye el resultado .cs del control de código fuente (ya que espera que se genere) y otro desarrollador comprueba el código. Sin volver a guardar, no tendrán el archivo .cs. Hay un paquete en nuget (creo que se llama AutoT4) que hace que la compilación T4 sea parte de cada compilación. Todavía no he confrontado la solución a esto durante el despliegue de producción, pero espero que algo similar lo haga bien.

Solo soy un novato de C #, así que tal vez mi respuesta suene tonta: muestro la fecha de compilación desde la fecha en que se escribió el archivo ejecutable por ú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());

Intenté usar el método File.GetCreationTime pero obtuve resultados extraños: la fecha del comando fue el 2012-05-29, pero la fecha del Explorador de ventanas mostró 2012-05-23. Después de buscar esta discrepancia, encontré que el archivo probablemente se creó el 2012-05-23 (como se muestra en el Explorador de Windows), pero se copió a la carpeta actual el 2012-05-29 (como se muestra en el comando File.GetCreationTime). para estar seguro, estoy usando el comando File.GetLastWriteTime.

Zalek

Para cualquiera que necesite obtener el tiempo de compilación en 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 cualquiera que necesite obtener el tiempo de compilación en 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;
    }

NOTA: en todos los casos, se está ejecutando en un entorno limitado, por lo que solo podrá obtener el tiempo de compilación de los ensamblados que implementa con su aplicación. (es decir, esto no funcionará en nada del GAC).

El método anterior se puede modificar para ensamblajes ya cargados dentro del proceso utilizando la imagen del archivo en la memoria (en lugar de volver a leerlo desde el almacenamiento):

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

La opción que no se trata aquí es insertar sus propios datos en AssemblyInfo.cs, " AssemblyInformationalVersion " El campo parece apropiado: tenemos un par de proyectos en los que estábamos haciendo algo similar como un paso de compilación (sin embargo, no estoy completamente satisfecho con la forma en que funciona, así que realmente no quiero reproducir lo que tenemos).

Hay un artículo sobre el tema en codeproject: http://www.codeproject.com /KB/dotnet/Customizing_csproj_files.aspx

Para los proyectos .NET Core, adapté la respuesta de Postlagerkarte para actualizar el campo Copyright de ensamblado con la fecha de compilación.

Editar directamente csproj

Lo siguiente se puede agregar directamente al primer PropertyGroup en el csproj:

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

Alternativa: Propiedades del proyecto de Visual Studio

O pegue la expresión interna directamente en el campo Copyright en la sección Paquete de las propiedades del proyecto en Visual Studio:

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

Esto puede ser un poco confuso, porque Visual Studio evaluará la expresión y mostrará el valor actual en la ventana, pero también actualizará el archivo del proyecto adecuadamente detrás de escena.

Toda la solución a través de Directory.Build.props

Puede colocar el elemento <Copyright> anterior en un archivo Directory.Build.props en la raíz de su solución y aplicarlo automáticamente a todos los proyectos dentro del directorio, suponiendo que cada proyecto no proporcione su propio valor de Copyright.

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

Directory.Build.props: Personalice su compilación

Salida

La expresión de ejemplo le dará un copyright como este:

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

Recuperación

Puede ver la información de copyright de las propiedades del archivo en Windows, o tomarla en tiempo de ejecución:

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

Console.WriteLine(version.LegalCopyright);

Muchas respuestas geniales aquí, pero siento que puedo agregar las mías debido a la simplicidad, el rendimiento (en comparación con las soluciones relacionadas con los recursos) multiplataforma (también funciona con Net Core) y evitar cualquier herramienta de terceros. Simplemente agregue este objetivo de msbuild al 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>

y ahora tiene Builtin.CompileTime o new DateTime(Builtin.CompileTime, DateTimeKind.Utc) si lo necesita de esa manera.

A ReSharper no le va a gustar. Puede ignorarlo o agregar una clase parcial al proyecto también, pero funciona de todos modos.

En 2018, algunas de las soluciones anteriores ya no funcionan o no funcionan con .NET Core.

Utilizo el siguiente enfoque, que es simple y funciona para mi proyecto .NET Core 2.0.

Agregue lo siguiente a su .csproj dentro del PropertyGroup:

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

Esto define una PropertyFunction a la que puede acceder en su comando pre build.

Su precompilación se ve así

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

Establezca la propiedad de BuildTimeStamp.txt como recurso incorporado.

Ahora puedes leer la marca de tiempo como esta

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

Necesitaba una solución universal que funcionara con un proyecto NETStandard en cualquier plataforma (iOS, Android y Windows). Para lograr esto, decidí generar automáticamente un archivo CS a través de un script de PowerShell. Aquí está el script de 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

Guarde el archivo PowerScript como GenBuildDate.ps1 y agréguelo a su proyecto. Finalmente, agregue la siguiente línea a su evento Pre-Build:

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

Asegúrese de que BuildDate.cs esté incluido en su proyecto. ¡Funciona como un campeón en cualquier sistema operativo!

Acabo de hacer:

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

Puede usar este proyecto: https://github.com/dwcullop/BuildInfo

Aprovecha T4 para automatizar la marca de tiempo de la fecha de compilación. Hay varias versiones (diferentes ramas), incluida una que le proporciona el Git Hash de la rama actualmente desprotegida, si le gusta ese tipo de cosas.

Divulgación: escribí el módulo.

Puede usar un evento posterior a la compilación del proyecto para escribir un archivo de texto en su directorio de destino con la fecha y hora actual. Luego puede leer el valor en tiempo de ejecución. Es un poco hacky, pero debería funcionar.

No estoy seguro, pero tal vez el Build Incrementer ayude.

Un enfoque diferente, compatible con PCL, sería utilizar una tarea en línea de MSBuild para sustituir el tiempo de compilación en una cadena devuelta por una propiedad de la aplicación.Estamos utilizando este enfoque con éxito en una aplicación que tiene proyectos Xamarin.Forms, Xamarin.Android y Xamarin.iOS.

EDITAR:

Simplificado moviendo toda la lógica al SetBuildDate.targets archivo y usando Regex en lugar de un simple reemplazo de cadena para que el archivo pueda modificarse en cada compilación sin un "reinicio".

La definición de tarea en línea de MSBuild (guardada en un archivo SetBuildDate.targets local en el proyecto Xamarin.Forms para este ejemplo):

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

Invocando la tarea en línea anterior en el archivo csproj de Xamarin.Forms en el 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>

El FilePath La propiedad se establece en un BuildMetadata.cs archivo en el proyecto Xamarin.Forms que contiene una clase simple con una propiedad de cadena BuildDate, en el que se sustituirá el tiempo de construcción:

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

Añade este archivo BuildMetadata.cs para proyectar.Será modificado por cada compilación, pero de una manera que permita compilaciones repetidas (reemplazos repetidos), por lo que puede incluirlo u omitirlo en el control de código fuente según lo desee.

Una pequeña actualización en " New Way " respuesta de Jhon.

Necesita construir la ruta en lugar de usar la cadena CodeBase cuando trabaje con ASP.NET/MVC

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

Puede iniciar un paso adicional en el proceso de compilación que escribe una marca de fecha en un archivo que luego se puede mostrar.

En la pestaña de propiedades del proyecto, mire la pestaña de eventos de compilación. Hay una opción para ejecutar un comando de compilación previa o posterior.

Usé la sugerencia de Abdurrahim. Sin embargo, parecía dar un formato de tiempo extraño y también agregó la abreviatura del día como parte de la fecha de compilación; ejemplo: dom 24/12/2017 13: 21: 05.43. Solo necesitaba la fecha, así que tuve que eliminar el resto usando la subcadena.

Después de agregar el echo %date% %time% > "$(ProjectDir)\Resources\BuildDate.txt" al evento previo a la compilación, acabo de hacer lo siguiente:

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

La buena noticia aquí es que funcionó.

Si se trata de una aplicación de Windows, puede usar la ruta ejecutable de la aplicación: nuevo System.IO.FileInfo (Application.ExecutablePath) .LastWriteTime.ToString (" aaaa.MM.dd ")

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top