Anzeigen des Erstellungsdatums

260

Ich habe derzeit eine App, die die Build-Nummer in ihrem Titelfenster anzeigt. Das ist gut und schön, außer dass es für die meisten Benutzer, die wissen möchten, ob sie den neuesten Build haben, nichts bedeutet. Sie bezeichnen ihn eher als "letzten Donnerstag" als als Build 1.0.8.4321.

Es ist geplant, stattdessen das Erstellungsdatum dort anzugeben - also zum Beispiel "App erstellt am 21.10.2009".

Ich habe Probleme, einen programmatischen Weg zu finden, um das Erstellungsdatum als Textzeichenfolge für eine solche Verwendung herauszuholen.

Für die Build-Nummer habe ich verwendet:

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

nachdem definiert wurde, wie diese entstanden sind.

Ich möchte so etwas für das Kompilierungsdatum (und die Uhrzeit für Bonuspunkte).

Zeiger hier sehr geschätzt (entschuldigen Sie Wortspiel, falls zutreffend) oder sauberere Lösungen ...

Mark Mayo
quelle
2
Ich habe die bereitgestellten Methoden ausprobiert, um die Build-Daten von Assemblys abzurufen, was in einfachen Szenarien funktioniert. Wenn jedoch zwei Assemblys zusammengeführt werden, erhalte ich nicht die richtige Build-Zeit. Es ist eine Stunde in der Zukunft. Irgendwelche Vorschläge?

Antworten:

356

Jeff Atwood hatte ein paar Dinge zu diesem Problem zu sagen, indem er auf die harte Tour das Erstellungsdatum festlegte .

Die zuverlässigste Methode ist das Abrufen des Linker-Zeitstempels aus dem in die ausführbare Datei eingebetteten PE-Header - ein C # -Code (von Joe Spivey) dafür aus den Kommentaren zu Jeffs Artikel:

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

Anwendungsbeispiel:

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

UPDATE: Die Methode funktionierte für .Net Core 1.0, funktionierte jedoch nach der Veröffentlichung von .Net Core 1.1 nicht mehr (gibt zufällige Jahre im Bereich von 1900 bis 2020 an).

mdb
quelle
8
Ich habe meinen Ton etwas geändert, ich wäre immer noch sehr vorsichtig, wenn ich in den akuten PE-Header grabe. Aber soweit ich das beurteilen kann, ist dieses PE-Zeug viel zuverlässiger als die Verwendung der Versionsnummern, außerdem möchte ich die Versionsnummern nicht separat vom Erstellungsdatum zuweisen.
John Leidegren
6
Ich mag das und benutze es, aber diese vorletzte Zeile mit dem .AddHours()ist ziemlich hackisch und (glaube ich) wird die Sommerzeit nicht berücksichtigen. Wenn Sie es in der Ortszeit möchten, sollten Sie dt.ToLocalTime();stattdessen den Reiniger verwenden. Der Mittelteil könnte auch mit einem using()Block stark vereinfacht werden .
JLRishe
6
Ja, das hat bei mir auch mit .net Core nicht mehr funktioniert (1940er, 1960er usw.)
eoleary
7
Obwohl die Verwendung von PE-Headern heutzutage eine gute Option zu sein scheint, ist anzumerken, dass MS mit deterministischen Builds experimentiert (was diesen Header unbrauchbar machen würde) und ihn möglicherweise sogar in zukünftigen Compiler-Versionen von C # als Standard festlegt (aus guten Gründen). Gut gelesen: blog.paranoidcoding.com/2016/04/05/… und hier ist die Antwort zu .NET Core (TLDR: "It's by Design"): Developercommunity.visualstudio.com/content/problem/35873/…
Paweł Bulwan
13
Für diejenigen, die feststellen, dass dies nicht mehr funktioniert, ist das Problem kein .NET Core-Problem. Siehe meine Antwort unten zu den Standardeinstellungen für neue Build-Parameter ab Visual Studio 15.4.
Tom
107

Fügen Sie der Befehlszeile für vorgefertigte Ereignisse Folgendes hinzu:

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

Fügen Sie diese Datei als Ressource hinzu. Jetzt haben Sie die Zeichenfolge 'BuildDate' in Ihren Ressourcen.

Informationen zum Erstellen von Ressourcen finden Sie unter Erstellen und Verwenden von Ressourcen in .NET .

Abdurrahim
quelle
4
+1 von mir, einfach und effektiv. Ich habe es sogar geschafft, den Wert aus der Datei mit einer Codezeile wie dieser zu erhalten: String buildDate = <MyClassLibraryName> .Properties.Resources.BuildDate
davidfrancis
11
Eine andere Option ist das Erstellen einer Klasse: (muss nach dem ersten Kompilieren in das Projekt aufgenommen werden) -> Echo-Namespace My.app.namespace {öffentliche statische Klasse Build {öffentliche statische Zeichenfolge Timestamp = "% DATE %% TIME%" .Substring (0,16);}}> "$ (ProjectDir) \ BuildTimestamp.cs" - - - -> kann dann mit Build.Timestamp
FabianSilva
9
Dies ist eine ausgezeichnete Lösung. Das einzige Problem besteht darin, dass die Befehlszeilenvariablen% date% und% time% lokalisiert sind, sodass die Ausgabe abhängig von der Windows-Sprache des Benutzers variiert.
VS
2
+1, dies ist eine bessere Methode als das Lesen von PE-Headern - da es mehrere Szenarien gibt, in denen dies überhaupt nicht funktioniert (z. B. Windows Phone App)
Matt Whitfield
17
Klug. Sie können Powershell auch verwenden, um eine genauere Kontrolle über das Format zu erhalten, z. B. um die UTC-Datumszeit zu erhalten, die als ISO8601 formatiert ist: Powershell -Command "((Get-Date) .ToUniversalTime ()). ToString (\" s \ ") | Out-File '$ (ProjectDir) Resources \ BuildDate.txt' "
Dbruning
90

Der Weg

Wie von @ c00000fd in den Kommentaren hervorgehoben . Microsoft ändert dies. Und obwohl viele Leute nicht die neueste Version ihres Compilers verwenden, vermute ich, dass diese Änderung diesen Ansatz zweifellos schlecht macht. Und während es eine lustige Übung ist, würde ich Leuten empfehlen, einfach ein Erstellungsdatum in ihre Binärdatei einzubetten, wenn es wichtig ist, das Erstellungsdatum der Binärdatei selbst zu verfolgen.

Dies kann mit einer trivialen Codegenerierung erfolgen, die wahrscheinlich bereits der erste Schritt in Ihrem Build-Skript ist. Das und die Tatsache, dass ALM / Build / DevOps-Tools dabei sehr hilfreich sind und allen anderen vorgezogen werden sollten.

Den Rest dieser Antwort lasse ich hier nur zu historischen Zwecken.

Der neue Weg

Ich habe es mir anders überlegt und verwende derzeit diesen Trick, um das richtige Erstellungsdatum zu erhalten.

#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

Der alte Weg

Wie generieren Sie Build-Nummern? Visual Studio (oder der C # -Compiler) bietet tatsächlich automatische Build- und Revisionsnummern, wenn Sie das AssemblyVersion-Attribut in z1.0.*

Was passieren wird, ist, dass der Build der Anzahl der Tage seit dem 1. Januar 2000 Ortszeit entspricht und dass die Revision der Anzahl der Sekunden seit Mitternacht Ortszeit entspricht, geteilt durch 2.

Siehe Community-Inhalt, automatische Build- und Revisionsnummern

zB 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
quelle
3
Ich habe gerade eine Stunde hinzugefügt, wennTimeZone.CurrentTimeZone.IsDaylightSavingTime(buildDateTime) == true
e4rthdog
2
Leider habe ich diesen Ansatz verwendet, ohne ihn gründlich zu überprüfen, und er beißt uns in der Produktion. Das Problem ist, dass beim Starten des JIT-Compilers die PE-Header-Informationen geändert werden. Daher die Ablehnung. Jetzt muss ich unnötige Nachforschungen anstellen, um zu erklären, warum wir das Installationsdatum als Erstellungsdatum sehen.
Jason D
8
@ JasonD In welchem ​​Universum wird dein Problem irgendwie zu meinem Problem? Wie rechtfertigen Sie eine Ablehnung, nur weil Sie auf ein Problem gestoßen sind, das bei dieser Implementierung nicht berücksichtigt wurde? Du hast das kostenlos bekommen und es schlecht getestet. Was lässt Sie auch glauben, dass der Header vom JIT-Compiler neu geschrieben wird? Lesen Sie diese Informationen aus dem Prozessspeicher oder aus der Datei?
John Leidegren
6
Ich habe festgestellt, dass die .Codebase-Eigenschaft, wenn Sie in einer Webanwendung ausgeführt werden, eine URL zu sein scheint (Datei: // c: /path/to/binary.dll). Dies führt dazu, dass der File.Exists-Aufruf fehlschlägt. Die Verwendung von "Assembly.Location" anstelle der CodeBase-Eigenschaft hat das Problem für mich behoben.
Mdryden
2
@ JohnLeidegren: Verlassen Sie sich dafür nicht auf den Windows PE-Header. Seit Windows 10 und reproduzierbaren Builds wird das IMAGE_FILE_HEADER::TimeDateStampFeld auf eine Zufallszahl gesetzt und ist kein Zeitstempel mehr.
c00000fd
51

Fügen Sie der Befehlszeile für vorgefertigte Ereignisse Folgendes hinzu:

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

Fügen Sie diese Datei als Ressource hinzu. Jetzt haben Sie die Zeichenfolge 'BuildDate' in Ihren Ressourcen.

Nachdem ich die Datei in die Ressource eingefügt hatte (als öffentliche Textdatei), habe ich über darauf zugegriffen

string strCompTime = Properties.Resources.BuildDate;

Informationen zum Erstellen von Ressourcen finden Sie unter Erstellen und Verwenden von Ressourcen in .NET .

Brewmanz
quelle
1
@DavidGorsline - Der Kommentar-Markdown war korrekt, da er diese andere Antwort zitiert . Ich habe nicht genügend Ruf, um Ihr Wechselgeld zurückzusetzen, sonst hätte ich es selbst getan.
Wai Ha Lee
1
@Wai Ha Lee - a) Die Antwort, die Sie zitieren, enthält keinen Code zum tatsächlichen Abrufen des Kompilierungsdatums / der Kompilierungszeit. b) Zu der Zeit hatte ich nicht genug Ruf, um dieser Antwort einen Kommentar hinzuzufügen (was ich getan hätte), nur um sie zu posten.
Also
Wenn Sie Úte% anstelle von% date% sehen, überprüfen Sie dies hier: Developercommunity.visualstudio.com/content/problem/237752/… Kurz gesagt: Echo% 25date% 25% 25time% 25
Qodex
41

Ein Ansatz, von dem ich erstaunt bin, dass noch niemand ihn erwähnt hat, ist die Verwendung von T4-Textvorlagen zur Codegenerierung.

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

Vorteile:

  • Gebietsschemaunabhängig
  • Ermöglicht viel mehr als nur die Zeit der Kompilierung

Nachteile:

Peter Taylor
quelle
1
Das ist jetzt die beste Antwort. Noch 324 Punkte, bevor es die am besten gewählte Antwort wird :). Stackoverflow braucht einen Weg, um den schnellsten Kletterer zu zeigen.
Pauldendulk
1
@pauldendulk, würde nicht viel helfen, weil die am besten bewertete Antwort und die akzeptierte Antwort fast immer am schnellsten Stimmen erhalten. Die akzeptierte Antwort auf diese Frage hat + 60 / -2, seit ich diese Antwort gepostet habe.
Peter Taylor
Ich glaube, Sie müssen Ihren Ticks einen .ToString () hinzufügen (ansonsten wird ein Kompilierungsfehler angezeigt). Trotzdem stoße ich hier auf eine steile Lernkurve. Können Sie zeigen, wie Sie dies auch im Hauptprogramm verwenden können?
Andy
@Andy, du hast Recht mit dem ToString (). Nutzung ist gerecht Constants.CompilationTimestampUtc. Wenn VS keine C # -Datei mit der Klasse generiert, müssen Sie herausfinden, wie Sie dies erreichen können. Die Antwort hängt jedoch (zumindest) von der Version von VS und dem Typ der csproj-Datei ab zu viele Details für diesen Beitrag.
Peter Taylor
1
Für den Fall, dass sich andere fragen, war dies erforderlich, um VS 2017 zum Laufen zu bringen: Ich musste dies zu einer Design Time T4-Vorlage machen (es dauerte eine Weile, bis ich herausgefunden hatte, dass ich zuerst eine Präprozessor-Vorlage hinzugefügt hatte). Ich musste auch diese Assembly einfügen: Microsoft.VisualStudio.TextTemplating.Interfaces.10.0 als Referenz auf das Projekt. Schließlich musste meine Vorlage "using System" enthalten; vor dem Namespace oder der Verweis auf DateTime ist fehlgeschlagen.
Andy
20

In Bezug auf die Technik zum Abrufen von Build-Datums- / Versionsinformationen aus den Bytes eines Assembly-PE-Headers hat Microsoft die Standard-Build-Parameter ab Visual Studio 15.4 geändert. Die neue Standardeinstellung umfasst die deterministische Kompilierung, wodurch ein gültiger Zeitstempel und automatisch inkrementierte Versionsnummern der Vergangenheit angehören. Das Zeitstempelfeld ist immer noch vorhanden, wird jedoch mit einem permanenten Wert gefüllt, der ein Hash von irgendetwas ist, aber keinen Hinweis auf die Erstellungszeit.

Einige detaillierte Hintergrundinformationen hier

Für diejenigen, die einen nützlichen Zeitstempel vor der deterministischen Kompilierung priorisieren, gibt es eine Möglichkeit, den neuen Standard zu überschreiben. Sie können ein Tag wie folgt in die .csproj-Datei der interessierenden Assembly aufnehmen:

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

Update: Ich unterstütze die in einer anderen Antwort hier beschriebene T4-Textvorlagenlösung. Ich habe es verwendet, um mein Problem sauber zu lösen, ohne den Vorteil der deterministischen Kompilierung zu verlieren. Eine Warnung ist, dass Visual Studio den T4-Compiler nur ausführt, wenn die .tt-Datei gespeichert wird, nicht zur Erstellungszeit. Dies kann unangenehm sein, wenn Sie das CS-Ergebnis von der Quellcodeverwaltung ausschließen (da Sie erwarten, dass es generiert wird) und ein anderer Entwickler den Code auscheckt. Ohne erneutes Speichern verfügen sie nicht über die CS-Datei. Auf nuget gibt es ein Paket (ich glaube AutoT4), das die T4-Kompilierung zu einem Teil jedes Builds macht. Ich habe die Lösung für dieses Problem während der Produktionsbereitstellung noch nicht konfrontiert, aber ich erwarte, dass etwas Ähnliches es richtig macht.

Tom
quelle
Dies löste mein Problem in einem SLN, der die älteste Antwort verwendet.
Pauldendulk
Ihre Vorsicht bezüglich T4 ist vollkommen fair, aber beachten Sie, dass sie bereits in meiner Antwort enthalten ist.
Peter Taylor
15

Ich bin nur ein C # -Neuling, daher klingt meine Antwort vielleicht albern - ich zeige das Erstellungsdatum ab dem Datum an, an dem die ausführbare Datei zuletzt geschrieben wurde:

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

Ich habe versucht, die File.GetCreationTime-Methode zu verwenden, habe aber seltsame Ergebnisse erzielt: Das Datum aus dem Befehl war 2012-05-29, aber das Datum aus dem Fenster-Explorer zeigte 2012-05-23 an. Nachdem ich nach dieser Diskrepanz gesucht hatte, stellte ich fest, dass die Datei wahrscheinlich am 23.05.2012 erstellt wurde (wie vom Windows Explorer angezeigt), aber am 29.05.2012 in den aktuellen Ordner kopiert wurde (wie vom Befehl File.GetCreationTime angezeigt) Um auf der sicheren Seite zu sein, verwende ich den Befehl File.GetLastWriteTime.

Zalek

Zalek Bloom
quelle
4
Ich bin nicht sicher, ob dies durch das Kopieren der ausführbaren Datei über Laufwerke / Computer / Netzwerke kugelsicher ist.
Stealth Rabbi
Dies ist das erste, was mir in den Sinn kommt, aber Sie wissen, dass es nicht zuverlässig ist. Es gibt viele Software, die zum Verschieben der Dateien über das Netzwerk verwendet wird und die Attribute nach dem Herunterladen nicht aktualisiert. Ich würde mich an die Antwort von @ Abdurrahim halten.
Mubashar
Ich weiß, dass dies alt ist, aber ich habe gerade mit einem ähnlichen Code festgestellt, dass der INSTALL-Prozess (zumindest bei Verwendung von clickonce) die Zeit der Assemblydatei aktualisiert. Nicht sehr nützlich. Ich bin mir jedoch nicht sicher, ob dies für diese Lösung gelten würde.
Bobwki
Sie möchten das wahrscheinlich wirklich LastWriteTime, da dies genau die Zeit widerspiegelt, zu der die ausführbare Datei tatsächlich aktualisiert wurde.
David R Tribble
Entschuldigung, aber eine Schreibzeit für ausführbare Dateien ist kein verlässlicher Hinweis auf die Erstellungszeit. Der Datei-Zeitstempel kann aufgrund aller möglichen Dinge, die außerhalb Ihres Einflussbereichs liegen, neu geschrieben werden.
Tom
15

Viele gute Antworten hier, aber ich habe das Gefühl, dass ich aufgrund der Einfachheit, der Leistung (im Vergleich zu ressourcenbezogenen Lösungen) plattformübergreifend (funktioniert auch mit Net Core) und der Vermeidung von Tools von Drittanbietern meine eigenen hinzufügen kann. Fügen Sie einfach dieses msbuild-Ziel zum csproj hinzu.

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

und jetzt hast du Builtin.CompileTimeoder new DateTime(Builtin.CompileTime, DateTimeKind.Utc)wenn du es so brauchst.

ReSharper wird es nicht mögen. Sie können ihn ignorieren oder dem Projekt auch eine Teilklasse hinzufügen, aber es funktioniert trotzdem.

Dmitry Gusarov
quelle
Ich kann damit erstellen und lokal in ASP.NET Core 2.1 entwickeln (Websites ausführen), aber das Veröffentlichen von Web Deployments aus VS 2017 schlägt mit dem Fehler "Der Name 'Builtin' ist im aktuellen Kontext nicht vorhanden" fehl. ZUSATZ: Wenn ich Builtin.CompileTimevon einer Rasiermesseransicht aus zugreife .
Jeremy Cook
In diesem Fall denke ich, dass Sie nur brauchen, BeforeTargets="RazorCoreCompile"aber nur, während dies im selben Projekt ist
Dmitry Gusarov
cool, aber wie verweisen wir auf das generierte Objekt? Mir scheint, der Antwort fehlt ein Schlüsselteil ...
Matteo
1
@Matteo, wie in der Antwort erwähnt, können Sie "Builtin.CompileTime" oder "new DateTime (Builtin.CompileTime, DateTimeKind.Utc)" verwenden. Visual Studio IntelliSense kann dies sofort erkennen. Ein alter ReSharper kann sich in der Entwurfszeit beschweren, sieht aber so aus, als hätten sie dies in neuen Versionen behoben. clip2net.com/s/46rgaaO
Dmitry Gusarov
Ich habe diese Version verwendet, sodass kein zusätzlicher Code erforderlich ist, um das Datum abzurufen. Auch resharper beschwert sich nicht mit seiner neuesten Version. <WriteLinesToFile File = "$ (IntermediateOutputPath) BuildInfo.cs" Lines = "unter Verwendung der internen statischen Teilklasse von System% 3B BuildInfo {public static long DateBuiltTicks = $ ([System.DateTime] :: UtcNow.Ticks)% 3B public static DateTime DateBuilt => new DateTime (DateBuiltTicks, DateTimeKind.Utc)% 3B} "Overwrite =" true "/>
Softlion
13

Für .NET Core-Projekte habe ich die Antwort von Postlagerkarte angepasst, um das Feld für das Assembly-Copyright mit dem Erstellungsdatum zu aktualisieren.

Bearbeiten Sie csproj direkt

Folgendes kann direkt zum ersten PropertyGroupim csproj hinzugefügt werden:

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

Alternative: Visual Studio-Projekteigenschaften

Oder fügen Sie den inneren Ausdruck direkt in das Feld Copyright im Abschnitt Package der Projekteigenschaften in Visual Studio ein:

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

Dies kann etwas verwirrend sein, da Visual Studio den Ausdruck auswertet und den aktuellen Wert im Fenster anzeigt, aber auch die Projektdatei hinter den Kulissen entsprechend aktualisiert.

Lösungsweit über Directory.Build.props

Sie können das obige <Copyright>Element in eine Directory.Build.propsDatei in Ihrem Lösungsstamm einfügen und es automatisch auf alle Projekte im Verzeichnis anwenden lassen, vorausgesetzt, jedes Projekt liefert nicht seinen eigenen Copyright-Wert.

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

Directory.Build.props: Passen Sie Ihren Build an

Ausgabe

Der Beispielausdruck gibt Ihnen ein Copyright wie folgt:

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

Abruf

Sie können die Copyright-Informationen in den Dateieigenschaften in Windows anzeigen oder zur Laufzeit abrufen:

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

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

Die obige Methode kann für bereits im Prozess geladene Assemblys optimiert werden, indem das Image der Datei im Speicher verwendet wird (im Gegensatz zum erneuten Lesen aus dem Speicher):

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;
    }
}
tcostin
quelle
Dieser funktioniert hervorragend, auch für Framework 4.7. Verwendung: Utils.GetLinkerDateTime (Assembly.GetExecutingAssembly (), null))
real_yggdrasil
Funktioniert super! Vielen Dank!
Bobwki
10

Für alle, die die Kompilierungszeit in Windows 8 / Windows Phone 8 benötigen:

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

Für alle, die die Kompilierungszeit in Windows Phone 7 benötigen:

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

ANMERKUNG: In allen Fällen werden Sie in einer Sandbox ausgeführt, sodass Sie nur die Kompilierungszeit von Assemblys abrufen können, die Sie mit Ihrer App bereitstellen. (dh dies funktioniert bei nichts im GAC).

Matt Dotson
quelle
So erhalten Sie die Versammlung in WP 8.1:var assembly = typeof (AnyTypeInYourAssembly).GetTypeInfo().Assembly;
André Fiedler
Was ist, wenn Sie Ihren Code auf beiden Systemen ausführen möchten? - Ist eine dieser Methoden für beide Plattformen anwendbar?
Bvdb
10

Im Jahr 2018 funktionieren einige der oben genannten Lösungen nicht mehr oder nicht mit .NET Core.

Ich verwende den folgenden Ansatz, der einfach ist und für mein .NET Core 2.0-Projekt funktioniert.

Fügen Sie Ihrer .csproj in der PropertyGroup Folgendes hinzu:

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

Dies definiert eine PropertyFunction die Sie in Ihrem Pre-Build-Befehl zugreifen können.

Ihr Pre-Build sieht so aus

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

Setzen Sie die Eigenschaft der Datei BuildTimeStamp.txt auf Eingebettete Ressource.

Jetzt können Sie den Zeitstempel so lesen

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
quelle
Das einfache Generieren dieser BuildTimeStamp.txt aus den Pre-Build-Ereignissen mithilfe von Batch-Skriptbefehlen funktioniert ebenfalls. Beachten Sie, dass Sie dort einen Fehler gemacht haben: Sie sollten Ihr Ziel in Anführungszeichen setzen (z. B. "$(ProjectDir)BuildTimeStamp.txt"), da es sonst unterbrochen wird, wenn die Ordnernamen Leerzeichen enthalten.
Nyerguds
Vielleicht ist es sinnvoll, ein kulturinvariantes Zeitformat zu verwenden. So: $([System.DateTime]::Now.tostring("MM/dd/yyyy HH:mm:ss"))statt$([System.DateTime]::Now)
Ivan Kochurkin
9

Die hier nicht diskutierte Option besteht darin, Ihre eigenen Daten in AssemblyInfo.cs einzufügen. Das Feld "AssemblyInformationalVersion" erscheint angemessen. Wir haben einige Projekte, in denen wir etwas Ähnliches als Build-Schritt ausgeführt haben (mit dem bin ich jedoch nicht ganz zufrieden) So funktioniert das, also will ich nicht wirklich reproduzieren, was wir haben.

Es gibt einen Artikel zu diesem Thema auf Codeproject: http://www.codeproject.com/KB/dotnet/Customizing_csproj_files.aspx

Murph
quelle
6

Ich brauchte eine universelle Lösung, die mit einem NETStandard-Projekt auf jeder Plattform (iOS, Android und Windows) funktioniert. Um dies zu erreichen, habe ich beschlossen, automatisch eine CS-Datei über ein PowerShell-Skript zu generieren. Hier ist das PowerShell-Skript:

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

Speichern Sie die PowerScript-Datei als GenBuildDate.ps1 und fügen Sie sie Ihrem Projekt hinzu. Fügen Sie abschließend Ihrem Pre-Build-Ereignis die folgende Zeile hinzu:

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

Stellen Sie sicher, dass BuildDate.cs in Ihrem Projekt enthalten ist. Funktioniert wie ein Champion auf jedem Betriebssystem!

David Tosi
quelle
1
Sie können dies auch verwenden, um die SVN-Versionsnummer mit dem Befehlszeilentool svn abzurufen. Ich habe damit etwas Ähnliches gemacht.
user169771
5

Ich mache einfach:

File.GetCreationTime(GetType().Assembly.Location)
Rui Santos
quelle
1
Interessanterweise , wenn von Debug ausgeführt wird , das ‚wahre‘ Datum ist der GetLastAccessTime ()
balint
4

Sie können dieses Projekt verwenden: https://github.com/dwcullop/BuildInfo

Es nutzt T4, um den Zeitstempel für das Erstellungsdatum zu automatisieren. Es gibt mehrere Versionen (verschiedene Zweige), darunter eine, die Ihnen den Git-Hash des aktuell ausgecheckten Zweigs gibt, wenn Sie sich für solche Dinge interessieren.

Offenlegung: Ich habe das Modul geschrieben.

Darrin Cullop
quelle
3

Ein anderer, PCL-freundlicher Ansatz wäre die Verwendung einer MSBuild-Inline-Task, um die Erstellungszeit durch eine Zeichenfolge zu ersetzen, die von einer Eigenschaft in der App zurückgegeben wird. Wir verwenden diesen Ansatz erfolgreich in einer App mit Xamarin.Forms-, Xamarin.Android- und Xamarin.iOS-Projekten.

BEARBEITEN:

Vereinfacht durch Verschieben der gesamten Logik in die SetBuildDate.targetsDatei und Verwenden von Regexanstelle eines einfachen String-Ersetzens, sodass die Datei von jedem Build ohne "Zurücksetzen" geändert werden kann.

Die MSBuild-Inline-Taskdefinition (gespeichert in einer SetBuildDate.targets-Datei, die für dieses Beispiel lokal im Xamarin.Forms-Projekt gespeichert ist):

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

Aufrufen der obigen Inline-Aufgabe in der Datei Xamarin.Forms csproj im Ziel 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>

Die FilePathEigenschaft wird BuildMetadata.csim Xamarin.Forms-Projekt auf eine Datei festgelegt, die eine einfache Klasse mit einer Zeichenfolgeeigenschaft enthält BuildDate, in die die Erstellungszeit eingesetzt wird:

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

Fügen Sie diese Datei BuildMetadata.cszum Projekt hinzu. Es wird von jedem Build geändert, jedoch auf eine Weise, die wiederholte Builds (wiederholtes Ersetzen) ermöglicht, sodass Sie es nach Bedarf in die Quellcodeverwaltung aufnehmen oder weglassen können.

Mark Larter
quelle
2

Sie können ein Projektereignis nach dem Erstellen verwenden, um eine Textdatei mit der aktuellen Datums- und Uhrzeitangabe in Ihr Zielverzeichnis zu schreiben. Sie können den Wert dann zur Laufzeit lesen. Es ist ein bisschen hacky, aber es sollte funktionieren.

MikeWyatt
quelle
2

Ein kleines Update zur "New Way" -Antwort von Jhon.

Sie müssen den Pfad erstellen, anstatt die CodeBase-Zeichenfolge zu verwenden, wenn Sie mit ASP.NET/MVC arbeiten

    var codeBase = assembly.GetName().CodeBase;
    UriBuilder uri = new UriBuilder(codeBase);
    string path = Uri.UnescapeDataString(uri.Path);
Thadeu Antonio Ferreira Melo
quelle
1

Sie können einen zusätzlichen Schritt im Erstellungsprozess starten, bei dem ein Datumsstempel in eine Datei geschrieben wird, die dann angezeigt werden kann.

Sehen Sie sich auf der Registerkarte Projekteigenschaften die Registerkarte Build-Ereignisse an. Es besteht die Möglichkeit, einen Befehl vor oder nach dem Erstellen auszuführen.

Guy van den Berg
quelle
1

Ich habe Abdurrahims Vorschlag verwendet. Es schien jedoch ein seltsames Zeitformat zu geben und fügte auch die Abkürzung für den Tag als Teil des Erstellungsdatums hinzu. Beispiel: So 24.12.2017 13: 21: 05.43 Uhr. Ich brauchte nur das Datum, also musste ich den Rest mit Teilzeichenfolgen entfernen.

Nachdem ich das echo %date% %time% > "$(ProjectDir)\Resources\BuildDate.txt"zum Pre-Build-Ereignis hinzugefügt habe , habe ich Folgendes getan:

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

Die gute Nachricht hier ist, dass es funktioniert hat.

DemarcPoint
quelle
Sehr einfache Lösung. Ich mag das. Und wenn das Format störend ist, gibt es Möglichkeiten, ein besseres Format über die Befehlszeile zu erhalten.
Nyerguds
0

Wenn dies eine Windows-App ist, können Sie einfach den ausführbaren Pfad der Anwendung verwenden: new System.IO.FileInfo (Application.ExecutablePath) .LastWriteTime.ToString ("yyyy.MM.dd")

John Clark
quelle
2
Schon und antworte damit und auch nicht gerade kugelsicher.
Crashmstr
0

es könnte sein Assembly execAssembly = Assembly.GetExecutingAssembly(); var creationTime = new FileInfo(execAssembly.Location).CreationTime; // "2019-09-08T14:29:12.2286642-04:00"

Sasha Bond
quelle
Tut das nicht dasselbe wie diese andere Antwort ?
Wai Ha Lee