Visualizzazione della data di costruzione


260

Al momento ho un'app che mostra il numero di build nella finestra del titolo. Va bene e va bene tranne per il fatto che non significa nulla per la maggior parte degli utenti, che vogliono sapere se hanno l'ultima build - tendono a chiamarla "giovedì scorso" piuttosto che costruire 1.0.8.4321.

Il piano è quello di inserire lì la data di costruzione, ad esempio "App costruita il 21/10/2009".

Sto lottando per trovare un modo programmatico per estrarre la data di compilazione come una stringa di testo da utilizzare in questo modo.

Per il numero di build, ho usato:

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

dopo aver definito come sono venuti fuori.

Vorrei qualcosa del genere per la data di compilazione (e l'ora, per i punti bonus).

Puntatori qui molto apprezzati (scusate il gioco di parole se appropriato) o soluzioni più ordinate ...


2
Ho provato i modi forniti per ottenere i dati di build degli assembly che funzionano in scenari semplici ma se due assembly vengono uniti non ottengo il tempo di build corretto, è un'ora in futuro .. qualche suggerimento?

Risposte:


356

Jeff Atwood aveva alcune cose da dire su questo problema nel Determinare la data di costruzione nel modo più duro .

Il metodo più affidabile risulta essere il recupero del timestamp del linker dall'intestazione PE incorporata nel file eseguibile - un codice C # (di Joe Spivey) per quello dai commenti all'articolo di 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;
}

Esempio di utilizzo:

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

AGGIORNAMENTO: Il metodo funzionava per .Net Core 1.0, ma ha smesso di funzionare dopo il rilascio .Net Core 1.1 (fornisce anni casuali nell'intervallo 1900-2020)


8
Ho cambiato il mio tono su questo in qualche modo, sarei ancora molto attento quando scavo nell'intestazione PE acuta. Ma per quanto posso dire, questa roba PE è molto più affidabile rispetto all'utilizzo dei numeri di versione, inoltre non voglio assegnare i numeri di versione separati dalla data di costruzione.
John Leidegren,

6
Mi piace questo e lo sto usando, ma quella seconda all'ultima riga .AddHours()è piuttosto hackish e (penso) non terrà conto dell'ora legale. Se lo desideri nell'ora locale, dovresti invece utilizzare il detergente dt.ToLocalTime();. La parte centrale potrebbe anche essere notevolmente semplificata con un using()blocco.
JLRishe

6
Sì, questo ha smesso di funzionare anche con me .net core (anni '40, '60, ecc.)
eoleary,

7
Mentre l'uso dell'intestazione PE potrebbe sembrare una buona opzione oggi, vale la pena notare che MS sta sperimentando build deterministiche (che renderebbero inutile questa intestazione) e forse lo sta addirittura rendendo predefinito nelle future versioni del compilatore di C # (per buoni motivi). Buona lettura: blog.paranoidcoding.com/2016/04/05/… ed ecco la risposta relativa a .NET Core (TLDR: "è di progettazione"): developercommunity.visualstudio.com/content/problem/35873/…
Paweł Bulwan

13
Per coloro che lo trovano non funziona più, il problema non è un problema di .NET Core. Vedi la mia risposta di seguito sui valori predefiniti dei nuovi parametri di build che iniziano con Visual Studio 15.4.
Tom,

107

Aggiungi sotto alla riga di comando dell'evento pre-build:

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

Aggiungi questo file come risorsa, ora hai la stringa 'BuildDate' nelle tue risorse.

Per creare risorse, vedere Come creare e utilizzare risorse in .NET .


4
+1 da parte mia, semplice ed efficace. Sono anche riuscito a ottenere il valore dal file con una riga di codice come questa: String buildDate = <MyClassLibraryName> .Properties.Resources.BuildDate
davidfrancis

11
un'altra opzione è creare una classe: (deve essere incluso nel progetto dopo la prima volta che lo compili) -> echo namespace My.app.namespace {public static class Build {public static string Timestamp = "% DATE%% TIME%" .Substring (0,16);}}> "$ (ProjectDir) \ BuildTimestamp.cs" - - - -> quindi puoi chiamarlo con Build.Timestamp
FabianSilva

9
Questa è un'ottima soluzione L'unico problema è che le variabili della riga di comando% date% e% time% sono localizzate, quindi l'output varierà a seconda della lingua di Windows dell'utente.
VS

2
+1, questo è un metodo migliore rispetto alla lettura delle intestazioni PE - perché ci sono diversi scenari in cui non funzionerà affatto (ad esempio l'app Windows Phone)
Matt Whitfield

17
Intelligente. È inoltre possibile utilizzare powershell per ottenere un controllo più preciso sul formato, ad esempio per ottenere un datetime UTC formattato come ISO8601: powershell -Command "((Get-Date) .ToUniversalTime ()). ToString (\" s \ ") | Out-File '$ (ProjectDir) Resources \ BuildDate.txt' "
dbruning

90

La via

Come sottolineato da @ c00000fd nei commenti . Microsoft sta cambiando questo. E mentre molte persone non usano l'ultima versione del loro compilatore, sospetto che questa modifica renda indiscutibilmente questo approccio. E mentre è un esercizio divertente, consiglierei alle persone di incorporare semplicemente una data di costruzione nel loro binario con qualsiasi altro mezzo necessario se è importante tenere traccia della data di costruzione del binario stesso.

Questo può essere fatto con una banale generazione di codice che probabilmente è già il primo passo nello script di compilazione. Questo e il fatto che gli strumenti ALM / Build / DevOps aiutano molto in questo e dovrebbero essere preferiti a qualsiasi altra cosa.

Lascio il resto di questa risposta qui solo per scopi storici.

Il nuovo modo

Ho cambiato idea su questo e attualmente uso questo trucco per ottenere la data di costruzione corretta.

#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

Alla vecchia maniera

Bene, come si generano i numeri di build? Visual Studio (o il compilatore C #) fornisce effettivamente numeri di build e revisione automatici se si modifica l'attributo AssemblyVersion in es1.0.*

Ciò che accadrà è che la build sarà uguale al numero di giorni dall'1 gennaio 2000 ora locale e che la revisione sarà uguale al numero di secondi dall'ora locale di mezzanotte, diviso per 2.

vedere il contenuto della community, i numeri di build e revisione automatici

ad es. 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)

3
Ho appena aggiunto un'ora seTimeZone.CurrentTimeZone.IsDaylightSavingTime(buildDateTime) == true
e4rthdog

2
Sfortunatamente ho usato questo approccio senza esaminarlo a fondo, e ci sta mordendo nella produzione. Il problema è che quando il compilatore JIT prende a calci nelle informazioni dell'intestazione PE viene modificato. Da qui il downvote. Ora sto per fare una 'ricerca' non necessaria per spiegare perché vediamo la data di installazione come la data di costruzione.
Jason D,

8
@JasonD In quale universo il tuo problema diventa in qualche modo il mio problema? Come si giustifica un downvote semplicemente perché si è verificato un problema che questa implementazione non ha preso in considerazione. L'hai ricevuto gratuitamente e l'hai testato male. Inoltre, cosa ti fa credere che l'intestazione sia stata riscritta dal compilatore JIT? Stai leggendo queste informazioni dalla memoria di processo o dal file?
John Leidegren,

6
Ho notato che se si esegue in un'applicazione Web, la proprietà .Codebase sembra essere un URL (file: // c: /path/to/binary.dll). Ciò causa l'esito negativo della chiamata File.Exists. L'uso di "assembly.Location" invece della proprietà CodeBase ha risolto il problema per me.
mdryden,

2
@JohnLeidegren: non fare affidamento sull'intestazione di Windows PE per questo. Da Windows 10 e build riproducibili , il IMAGE_FILE_HEADER::TimeDateStampcampo è impostato su un numero casuale e non è più un timestamp.
c00000fd,

51

Aggiungi sotto alla riga di comando dell'evento pre-build:

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

Aggiungi questo file come risorsa, ora hai la stringa 'BuildDate' nelle tue risorse.

Dopo aver inserito il file nella risorsa (come file di testo pubblico), ho effettuato l'accesso tramite

string strCompTime = Properties.Resources.BuildDate;

Per creare risorse, vedere Come creare e utilizzare risorse in .NET .


1
@DavidGorsline - il markdown del commento è stato corretto poiché sta citando questa altra risposta . Ho una reputazione insufficiente per ripristinare il tuo cambiamento, altrimenti l'avrei fatto da solo.
Wai Ha Lee,

1
@Wai Ha Lee - a) la risposta citata non fornisce il codice per recuperare effettivamente la data / ora della compilazione. b) al momento non avevo abbastanza reputazione per aggiungere commenti a quella risposta (cosa che avrei fatto), solo per pubblicare. quindi c) ho postato dando una risposta completa in modo che le persone potessero ottenere tutti i dettagli in una zona ..
brewmanz

Se vedi Úte% anziché% date%, controlla qui: developercommunity.visualstudio.com/content/problem/237752/… In poche parole, procedi nel seguente modo: echo% 25date% 25% 25time% 25
Qodex

41

Un approccio che non mi ha ancora stupito nessuno è stato quello di utilizzare i modelli di testo T4 per la generazione del codice.

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

Professionisti:

  • Locale indipendente
  • Permette molto di più del semplice momento della compilazione

Contro:


1
Quindi, questa è ora la risposta migliore. 324 punti prima che diventi la risposta più votata :). Stackoverflow ha bisogno di un modo per mostrare lo scalatore più veloce.
pauldendulk,

1
@pauldendulk, non aiuterebbe molto, perché la risposta più votata e la risposta accettata raccolgono quasi sempre i voti più velocemente. La risposta accettata a questa domanda ha + 60 / -2 da quando ho pubblicato questa risposta.
Peter Taylor,

Credo che tu debba aggiungere un .ToString () ai tuoi tick (altrimenti ottengo un errore di compilazione). Detto questo, sto incontrando una ripida curva di apprendimento qui, potresti mostrare come USARLO anche nel programma principale?
Andy,

@Andy, hai ragione sul ToString (). L'utilizzo è giusto Constants.CompilationTimestampUtc. Se VS non sta generando un file C # con la classe, devi capire come farlo per farlo, ma la risposta dipende (almeno) dalla versione di VS e dal tipo di file csproj, quindi è troppi dettagli per questo post.
Peter Taylor,

1
Nel caso altri si stiano chiedendo, questo è quello che ci è voluto per farlo funzionare su VS 2017: ho dovuto fare questo un modello T4 di Design Time (mi ci è voluto un po 'per capire, ho aggiunto prima un modello di preprocessore). Ho anche dovuto includere questo assembly: Microsoft.VisualStudio.TextTemplating.Interfaces.10.0 come riferimento al progetto. Alla fine il mio modello ha dovuto includere "using System;" prima dello spazio dei nomi, altrimenti il ​​riferimento a DateTime non è riuscito.
Andy,

20

Per quanto riguarda la tecnica di estrazione delle informazioni sulla data / versione di build dai byte di un'intestazione PE di assembly, Microsoft ha modificato i parametri di build predefiniti a partire da Visual Studio 15.4. La nuova impostazione predefinita include la compilazione deterministica, che rende un timestamp valido e numeri di versione incrementati automaticamente un ricordo del passato. Il campo timestamp è ancora presente ma viene riempito con un valore permanente che è un hash di qualcosa o altro, ma non alcuna indicazione del tempo di compilazione.

Alcuni retroscena dettagliati qui

Per coloro che danno la priorità a un utile timestamp rispetto alla compilazione deterministica, esiste un modo per sovrascrivere il nuovo valore predefinito. È possibile includere un tag nel file .csproj dell'assembly di interesse come segue:

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

Aggiornamento: approvo la soluzione del modello di testo T4 descritta in un'altra risposta qui. L'ho usato per risolvere il mio problema in modo pulito senza perdere il beneficio della compilazione deterministica. Un avvertimento a riguardo è che Visual Studio esegue il compilatore T4 solo quando il file .tt viene salvato, non al momento della compilazione. Questo può essere imbarazzante se si esclude il risultato .cs dal controllo del codice sorgente (poiché si prevede che venga generato) e un altro sviluppatore verifica il codice. Senza salvare, non avranno il file .cs. C'è un pacchetto su nuget (penso che si chiama AutoT4) che rende la compilazione T4 parte di ogni build. Non ho ancora affrontato la soluzione a questo durante l'implementazione della produzione, ma mi aspetto qualcosa di simile per farlo bene.


Ciò ha risolto il mio problema in uno sln che utilizza la risposta più vecchia.
pauldendulk,

La tua attenzione su T4 è perfettamente giusta, ma nota che è già presente nella mia risposta.
Peter Taylor,

15

Sono solo un principiante di C #, quindi forse la mia risposta sembra sciocca: visualizzo la data di costruzione dalla data in cui il file eseguibile è stato scritto l'ultima volta:

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

Ho provato a utilizzare il metodo File.GetCreationTime ma ho ottenuto risultati strani: la data dal comando era 29/05/2012, ma la data dall'Esploratore finestre mostrava 23/05/2012. Dopo aver cercato questa discrepanza ho scoperto che il file è stato probabilmente creato il 23/05/2012 (come mostrato da Esplora risorse), ma copiato nella cartella corrente il 29/05/2012 (come mostrato dal comando File.GetCreationTime) - così per essere al sicuro sto usando il comando File.GetLastWriteTime.

Zalek


4
Non sono sicuro se questo è a prova di proiettile dalla copia dell'eseguibile su unità / computer / reti.
Rabbino

questa è la prima cosa che viene in mente ma sai che non è affidabile, ci sono molti software usati per spostare i file sulla rete che non aggiornano gli attributi dopo il download, vorrei andare con la risposta di @ Abdurrahim.
Mubashar,

So che questo è vecchio, ma ho appena scoperto con un codice simile che il processo INSTALL (almeno quando si utilizza clickonce) aggiorna il tempo del file assembly. Non molto utile Non sono sicuro che si applicherebbe a questa soluzione, però.
bobwki,

Probabilmente vuoi davvero il LastWriteTime, poiché ciò riflette accuratamente il tempo in cui il file eseguibile è stato effettivamente aggiornato.
David R Tribble,

Siamo spiacenti ma un tempo di scrittura del file eseguibile non è un'indicazione affidabile del tempo di compilazione. Il timestamp del file può essere riscritto a causa di tutti i tipi di cose al di fuori della sfera di influenza.
Tom,

15

Molte ottime risposte qui, ma mi sento di poter aggiungere le mie per semplicità, prestazioni (rispetto alle soluzioni relative alle risorse) multipiattaforma (funziona anche con Net Core) ed evitamento di qualsiasi strumento di terze parti. Aggiungi questo target msbuild a 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 ora hai Builtin.CompileTimeo new DateTime(Builtin.CompileTime, DateTimeKind.Utc)se ne hai bisogno in quel modo.

A ReSharper non piacerà. Puoi ignorarlo o aggiungere anche una classe parziale al progetto ma funziona comunque.


Posso costruire con questo e sviluppare localmente (eseguire siti Web) in ASP.NET Core 2.1 ma la pubblicazione di distribuzione Web da VS 2017 fallisce con l'errore "Il nome 'Builtin' non esiste nel contesto attuale". AGGIUNTA: se accedo Builtin.CompileTimeda una vista Rasoio.
Jeremy Cook,

In questo caso penso che tu abbia solo bisogno, BeforeTargets="RazorCoreCompile"ma solo mentre è nello stesso progetto
Dmitry Gusarov

bello, ma come ci riferiamo all'oggetto generato? mi sembra che alla risposta manchi una parte fondamentale ...
Matteo

1
@Matteo, come indicato nella risposta, è possibile utilizzare "Builtin.CompileTime" o "new DateTime (Builtin.CompileTime, DateTimeKind.Utc)". Visual Studio IntelliSense è in grado di vederlo subito. Un vecchio ReSharper può lamentarsi in fase di progettazione, ma sembra che sia stato risolto in nuove versioni. clip2net.com/s/46rgaaO
Dmitry Gusarov

Ho usato questa versione, quindi non è necessario un codice aggiuntivo per ottenere la data. Anche resharper non si lamenta dell'ultima versione. <WriteLinesToFile File = "$ (IntermediateOutputPath) BuildInfo.cs" Lines = "utilizzando System% 3B classe parziale statica interna BuildInfo {public static long DateBuiltTicks = $ ([System.DateTime] :: UtcNow.Ticks)% 3B public static DateTime DateBuilt => new DateTime (DateBuiltTicks, DateTimeKind.Utc)% 3B} "Overwrite =" true "/>
Softlion

13

Per i progetti .NET Core, ho adattato la risposta di Postlagerkarte per aggiornare il campo Copyright dell'assieme con la data di costruzione.

Modifica direttamente csproj

Quanto segue può essere aggiunto direttamente al primo PropertyGroupnel csproj:

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

Alternativa: proprietà del progetto Visual Studio

Oppure incollare l'espressione interna direttamente nel campo Copyright nella sezione Pacchetto delle proprietà del progetto in Visual Studio:

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

Questo può essere un po 'confuso, perché Visual Studio valuterà l'espressione e visualizzerà il valore corrente nella finestra, ma aggiornerà anche il file di progetto in modo appropriato dietro le quinte.

A livello di soluzione tramite Directory.Build.props

È possibile inserire l' <Copyright>elemento sopra in un Directory.Build.propsfile nella radice della soluzione e applicarlo automaticamente a tutti i progetti all'interno della directory, presupponendo che ciascun progetto non fornisca il proprio valore di Copyright.

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

Directory.Build.props: personalizza la tua build

Produzione

L'espressione di esempio ti darà un copyright come questo:

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

Recupero

È possibile visualizzare le informazioni sul copyright dalle proprietà del file in Windows o acquisirle in fase di esecuzione:

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

Console.WriteLine(version.LegalCopyright);

11

Il metodo sopra può essere ottimizzato per gli assembly già caricati all'interno del processo usando l'immagine del file in memoria (invece di rileggerlo dalla memoria):

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

Questo funziona alla grande, anche per l'utilizzo del framework 4.7: Utils.GetLinkerDateTime (Assembly.GetExecutingAssembly (), null))
real_yggdrasil

Funziona alla grande! Grazie!
bobwki,

10

Per chiunque abbia bisogno di ottenere il tempo di compilazione in 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);
            }
        }
    }

Per chiunque abbia bisogno di ottenere il tempo di compilazione in 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: in tutti i casi in cui si esegue una sandbox, quindi sarà possibile ottenere solo il tempo di compilazione degli assembly distribuiti con l'app. (cioè questo non funzionerà su nulla nel GAC).


Ecco come si ottiene l'Assemblea in WP 8.1:var assembly = typeof (AnyTypeInYourAssembly).GetTypeInfo().Assembly;
André Fiedler

Cosa succede se si desidera eseguire il codice su entrambi i sistemi? - uno di questi metodi è applicabile per entrambe le piattaforme?
bvdb,

10

Nel 2018 alcune delle soluzioni di cui sopra non funzionano più o non funzionano con .NET Core.

Uso il seguente approccio che è semplice e funziona per il mio progetto .NET Core 2.0.

Aggiungi quanto segue al tuo .csproj all'interno del gruppo di proprietà:

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

Ciò definisce un PropertyFunction a cui è possibile accedere nel comando pre-build.

La tua pre-build è simile a questa

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

Impostare la proprietà di BuildTimeStamp.txt su risorsa incorporata.

Ora puoi leggere il timestamp in questo modo

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

Anche la generazione di tale BuildTimeStamp.txt dagli eventi pre-build mediante i comandi di script batch funziona. Nota che hai fatto un errore lì: dovresti racchiudere il tuo obiettivo tra virgolette (ad esempio "$(ProjectDir)BuildTimeStamp.txt") o si romperà quando ci sono spazi nei nomi delle cartelle.
Nyerguds,

Forse ha senso usare il formato temporale invariante della cultura. In questo modo: $([System.DateTime]::Now.tostring("MM/dd/yyyy HH:mm:ss"))invece di$([System.DateTime]::Now)
Ivan Kochurkin il

9

L'opzione non discussa qui è quella di inserire i tuoi dati in AssemblyInfo.cs, il campo "AssemblyInformationalVersion" sembra appropriato - abbiamo un paio di progetti in cui stavamo facendo qualcosa di simile come un passo di costruzione (tuttavia non sono del tutto soddisfatto del funziona così non voglio davvero riprodurre quello che abbiamo).

C'è un articolo sull'argomento su codeproject: http://www.codeproject.com/KB/dotnet/Customizing_csproj_files.aspx


6

Avevo bisogno di una soluzione universale che funzionasse con un progetto NETStandard su qualsiasi piattaforma (iOS, Android e Windows.) A tale scopo, ho deciso di generare automaticamente un file CS tramite uno script PowerShell. Ecco lo script di 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

Salvare il file PowerScript come GenBuildDate.ps1 e aggiungerlo al progetto. Infine, aggiungi la seguente riga al tuo evento pre-build:

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

Assicurarsi che BuildDate.cs sia incluso nel progetto. Funziona come un campione su qualsiasi sistema operativo!


1
Puoi anche usarlo per ottenere il numero di revisione SVN usando lo strumento da riga di comando svn. Ho fatto qualcosa di simile a questo con quello.
user169771

5

Faccio solo:

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

1
È interessante notare che, se in esecuzione dal debug, la data 'vera' è GetLastAccessTime ()
balint

4

È possibile utilizzare questo progetto: https://github.com/dwcullop/BuildInfo

Sfrutta T4 per automatizzare il timestamp della data di costruzione. Esistono diverse versioni (diversi rami) tra cui una che ti dà il Git Hash del ramo attualmente estratto, se ti piace quel genere di cose.

Divulgazione: ho scritto il modulo.


3

Un approccio diverso, compatibile con PCL, sarebbe quello di utilizzare un'attività in linea di MSBuild per sostituire il tempo di compilazione in una stringa restituita da una proprietà sull'app. Stiamo usando questo approccio con successo in un'app che ha progetti Xamarin.Forms, Xamarin.Android e Xamarin.iOS.

MODIFICARE:

Semplificato spostando tutta la logica nel SetBuildDate.targetsfile e usando al Regexposto della semplice stringa sostituisci in modo che il file possa essere modificato da ogni build senza un "reset".

La definizione dell'attività inline MSBuild (salvata in un file SetBuildDate.targets locale nel progetto Xamarin.Forms per questo esempio):

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

Richiamare l'attività incorporata sopra nel file csproj Xamarin.Forms nella destinazione 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>

La FilePathproprietà è impostata su un BuildMetadata.csfile nel progetto Xamarin.Forms che contiene una classe semplice con una proprietà stringa BuildDate, in cui verrà sostituito il tempo di compilazione:

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

Aggiungi questo file BuildMetadata.csal progetto. Verrà modificato da ogni build, ma in modo da consentire build ripetute (sostituzioni ripetute), quindi è possibile includerlo o ometterlo nel controllo del codice sorgente come desiderato.


2

È possibile utilizzare un evento post-build del progetto per scrivere un file di testo nella directory di destinazione con il datetime corrente. È quindi possibile leggere il valore in fase di esecuzione. È un po 'confuso, ma dovrebbe funzionare.



2

Un piccolo aggiornamento sulla risposta "New Way" di Jhon.

È necessario creare il percorso invece di utilizzare la stringa CodeBase quando si lavora con ASP.NET/MVC

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

1

È possibile avviare un passaggio aggiuntivo nel processo di creazione che scrive un timbro data in un file che può quindi essere visualizzato.

Nella scheda delle proprietà dei progetti guarda la scheda degli eventi di compilazione. C'è un'opzione per eseguire un comando pre o post build.


1

Ho usato il suggerimento di Abdurrahim. Tuttavia, sembrava dare un formato orario strano e ha anche aggiunto l'abbreviazione del giorno come parte della data di costruzione; esempio: dom 24/12/2017 13: 21: 05.43. Avevo solo bisogno della data, quindi ho dovuto eliminare il resto usando la sottostringa.

Dopo aver aggiunto l' echo %date% %time% > "$(ProjectDir)\Resources\BuildDate.txt"evento pre-build, ho appena fatto quanto segue:

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

La buona notizia qui è che ha funzionato.


Soluzione molto semplice. Mi piace. E se il formato è un problema, ci sono modi per ottenere un formato migliore dalla riga di comando.
Nyerguds,

0

Se questa è un'app di Windows, puoi semplicemente usare il percorso eseguibile dell'applicazione: new System.IO.FileInfo (Application.ExecutablePath) .LastWriteTime.ToString ("yyyy.MM.dd")


2
Già e rispondi usando questo, e anche non esattamente a prova di proiettile.
crashmstr,

0

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


Non sta facendo lo stesso di questa altra risposta ?
Wai Ha Lee,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.