Wyświetlanie daty kompilacji


260

Obecnie mam aplikację wyświetlającą numer kompilacji w oknie tytułu. To dobrze, ale nic nie znaczy dla większości użytkowników, którzy chcą wiedzieć, czy mają najnowszą kompilację - zwykle nazywają ją „ostatnim czwartkiem”, a nie kompilacją 1.0.8.4321.

Zamiast tego planuje się tam podać datę kompilacji - na przykład „Aplikacja zbudowana 21/10/2009”.

Usiłuję znaleźć programowy sposób na wyciągnięcie daty kompilacji jako ciągu tekstowego do użycia w ten sposób.

Do numeru kompilacji użyłem:

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

po zdefiniowaniu, jak się pojawiły.

Chciałbym coś takiego na datę kompilacji (i czas, dla punktów bonusowych).

Wskaźniki tutaj bardzo doceniane (w razie potrzeby usprawiedliwiaj grę słów) lub bardziej odpowiednie rozwiązania ...


2
Wypróbowałem dostarczone sposoby uzyskania danych kompilacji zestawów, które działają w prostych scenariuszach, ale jeśli dwa zestawy zostaną połączone razem, nie otrzymam poprawnego czasu kompilacji, to jest godzina w przyszłości ... jakieś sugestie?

Odpowiedzi:


356

Jeff Atwood miał kilka rzeczy do powiedzenia na temat tego problemu w Określanie daty kompilacji na własnej skórze .

Okazuje się, że najbardziej niezawodną metodą jest pobranie znacznika czasowego linkera z nagłówka PE osadzonego w pliku wykonywalnym - trochę kodu C # (autorstwa Joe Spivey'a) z komentarzy do artykułu Jeffa:

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

Przykład użycia:

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

AKTUALIZACJA: Metoda działała dla .Net Core 1.0, ale przestała działać po wydaniu .Net Core 1.1 (daje losowe lata w przedziale 1900-2020)


8
Zmieniłem nieco swój ton na ten temat, nadal będę bardzo ostrożny, gdy zagłębię się w ostry nagłówek PE. Ale o ile wiem, te elementy PE są o wiele bardziej niezawodne niż używanie numerów wersji, poza tym nie chcę przypisywać numerów wersji oddzielnie od daty kompilacji.
John Leidegren,

6
Lubię to i używam tego, ale ta ostatnia do ostatniej linii z .AddHours()jest raczej hackerska i (myślę) nie weźmie pod uwagę DST. Jeśli chcesz w czasie lokalnym, dt.ToLocalTime();zamiast tego powinieneś użyć odkurzacza . Środkową część można również znacznie uprościć using()blokiem.
JLRishe,

6
Tak, to przestało działać również dla mnie z rdzeniem .net (lata 40., 60., itp.)
eoleary

7
Chociaż użycie nagłówka PE może dziś wydawać się dobrą opcją, warto zauważyć, że MS eksperymentuje z deterministycznymi kompilacjami (które czynią ten nagłówek bezużytecznym), a może nawet ustawi go jako domyślny w przyszłych wersjach C # kompilatora (z dobrych powodów). Dobra lektura: blog.paranoidcoding.com/2016/04/05/... i oto odpowiedź związana z .NET Core (TLDR: „jest z założenia”): developercommunity.visualstudio.com/content/problem/35873/…
Paweł Bulwan

13
Dla tych, którzy stwierdzą, że to już nie działa, problemem nie jest .NET Core. Zobacz moją odpowiedź poniżej na temat domyślnych parametrów nowego kompilacji, począwszy od Visual Studio 15.4.
Tom

107

Dodaj poniżej, aby wiersza polecenia zdarzenia przed kompilacją:

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

Dodaj ten plik jako zasób, teraz masz ciąg „BuildDate” w swoich zasobach.

Aby utworzyć zasoby, zobacz Jak tworzyć i wykorzystywać zasoby w .NET .


4
+1 ode mnie, proste i skuteczne. Udało mi się nawet uzyskać wartość z pliku za pomocą takiego wiersza kodu: String buildDate = <MyClassLibraryName>
.Properties.Resources.BuildDate

11
inną opcją jest utworzenie klasy: (trzeba dołączyć do projektu po pierwszym skompilowaniu) -> przestrzeń nazw echa My.app.namespace {public static class Build {public static string Datownik = "% DATE%% TIME%" .Substring (0,16);}}> "$ (ProjectDir) \ BuildTimestamp.cs" - - - -> następnie można wywołać go za pomocą Build.Timestamp
FabianSilva

9
To doskonałe rozwiązanie. Jedyny problem polega na tym, że zmienne wiersza poleceń% date% i% time% są zlokalizowane, więc dane wyjściowe będą się różnić w zależności od języka systemu Windows użytkownika.
VS

2
+1, jest to lepsza metoda niż czytanie nagłówków PE - ponieważ istnieje kilka scenariuszy, w których to w ogóle nie zadziała (na przykład aplikacja Windows Phone)
Matt Whitfield

17
Sprytny. Możesz także użyć programu PowerShell, aby uzyskać bardziej precyzyjną kontrolę nad formatem, np. Aby uzyskać datę i godzinę UTC sformatowaną jako ISO8601: powershell -Command "((Get-Date) .ToUniversalTime ()). ToString (\" s \ ") | Out-File '$ (ProjectDir) Resources \ BuildDate.txt' "
dbruning

90

Droga

Jak wskazał @ c00000fd w komentarzach . Microsoft to zmienia. I chociaż wiele osób nie korzysta z najnowszej wersji swojego kompilatora, podejrzewam, że ta zmiana czyni to podejście niewątpliwie złym. I chociaż jest to zabawne ćwiczenie, polecam ludziom, aby po prostu osadzili datę kompilacji w swoim pliku binarnym za pomocą wszelkich innych niezbędnych środków, jeśli ważne jest śledzenie daty kompilacji samego pliku binarnego.

Można to zrobić za pomocą trywialnego generowania kodu, który prawdopodobnie jest już pierwszym krokiem w skrypcie kompilacji. To oraz fakt, że narzędzia ALM / Build / DevOps bardzo w tym pomagają i powinny być preferowane w stosunku do wszystkiego innego.

Pozostałą część tej odpowiedzi pozostawiam tutaj wyłącznie do celów historycznych.

Nowy sposób

Zmieniłem zdanie na ten temat i obecnie używam tej sztuczki, aby uzyskać prawidłową datę kompilacji.

#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

Po staremu

Jak generujesz liczby kompilacji? Visual Studio (lub kompilator C #) faktycznie zapewnia automatyczne numery kompilacji i wersji, jeśli zmienisz atrybut AssemblyVersion na np1.0.*

Stanie się tak, że kompilacja będzie równa liczbie dni od 1 stycznia 2000 czasu lokalnego, a korekta będzie równa liczbie sekund od północy czasu lokalnego, podzielonej przez 2.

zobacz zawartość społeczności, automatyczne kompilacje i numery wersji

np. 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
Właśnie dodałem godzinę, jeśliTimeZone.CurrentTimeZone.IsDaylightSavingTime(buildDateTime) == true
e4rthdog

2
Niestety zastosowałem to podejście bez dokładnej weryfikacji, a to nas gryzie. Problem polega na tym, że gdy kompilator JIT kopie informacje nagłówka PE, zmienia się. Stąd opinia. Teraz przeprowadzam niepotrzebne „badania”, aby wyjaśnić, dlaczego widzimy datę instalacji jako datę kompilacji.
Jason D

8
@JasonD W jakim wszechświecie twój problem w jakiś sposób staje się moim problemem? Jak usprawiedliwić głosowanie negatywne po prostu dlatego, że napotkałeś problem, którego ta implementacja nie wzięła pod uwagę. Dostałeś to za darmo i źle go przetestowałeś. Co również sprawia, że ​​uważasz, że nagłówek jest przepisywany przez kompilator JIT? Czy czytasz te informacje z pamięci procesu lub z pliku?
John Leidegren,

6
Zauważyłem, że jeśli działasz w aplikacji internetowej, właściwość .Codebase wydaje się być adresem URL (plik: // c: /path/to/binary.dll). Powoduje to niepowodzenie wywołania File.Exists. Użycie „assembly.Location” zamiast właściwości CodeBase rozwiązało problem.
mdryden

2
@JohnLeidegren: Nie polegaj na nagłówku Windows PE. Ponieważ system Windows 10 i powtarzalne buduje The IMAGE_FILE_HEADER::TimeDateStamppole jest ustawione na liczbę losową i nie jest już czas-pieczęć.
c00000fd

51

Dodaj poniżej, aby wiersza polecenia zdarzenia przed kompilacją:

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

Dodaj ten plik jako zasób, teraz masz ciąg „BuildDate” w swoich zasobach.

Po wstawieniu pliku do zasobu (jako publicznego pliku tekstowego) uzyskałem do niego dostęp za pośrednictwem

string strCompTime = Properties.Resources.BuildDate;

Aby utworzyć zasoby, zobacz Jak tworzyć i wykorzystywać zasoby w .NET .


1
@DavidGorsline - przecena komentarza była poprawna, ponieważ cytuje inną odpowiedź . Nie mam wystarczającej reputacji, aby cofnąć twoją zmianę, w przeciwnym razie zrobiłbym to sam.
Wai Ha Lee

1
@Wai Ha Lee - a) podana przez ciebie odpowiedź nie zawiera kodu umożliwiającego pobranie daty / godziny kompilacji. b) w tym czasie nie miałem wystarczającej reputacji, aby dodać komentarz do tej odpowiedzi (co bym zrobił), tylko aby opublikować. więc c)
wysłałem

Jeśli widzisz Úte% zamiast% date%, sprawdź tutaj: developercommunity.visualstudio.com/content/problem/237752/... W skrócie, zrób to: echo% 25date% 25% 25time% 25
Qodex

41

Jednym z podejść, które mnie zadziwia, o którym nikt jeszcze nie wspomniał, jest użycie szablonów tekstowych T4 do generowania kodu.

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

Plusy:

  • Niezależny od ustawień regionalnych
  • Pozwala na znacznie więcej niż tylko czas kompilacji

Cons:


1
To jest teraz najlepsza odpowiedź. Do zdobycia 324 punktów :). Stackoverflow potrzebuje sposobu, aby pokazać najszybszego wspinacza.
pauldendulk

1
@pauldendulk, niewiele by pomógł, ponieważ najbardziej pozytywna odpowiedź i zaakceptowana odpowiedź prawie zawsze zbierają głosy najszybciej. Zaakceptowana odpowiedź na to pytanie ma + 60 / -2, odkąd opublikowałem tę odpowiedź.
Peter Taylor,

Uważam, że musisz dodać .ToString () do swoich Ticks (w przeciwnym razie pojawia się błąd kompilacji). To powiedziawszy, wpadam tutaj na stromą krzywą uczenia się, czy możesz pokazać, jak UŻYWAĆ tego w głównym programie?
Andy

@Andy, masz rację co do ToString (). Zastosowanie jest po prostu Constants.CompilationTimestampUtc. Jeśli VS nie generuje pliku C # z klasą, musisz dowiedzieć się, jak to zrobić, ale odpowiedź zależy (przynajmniej) od wersji VS i typu pliku csproj, więc jest to za dużo szczegółów dla tego postu.
Peter Taylor

1
W przypadku, gdy inni się zastanawiają, oto, co trzeba było, aby działał na VS 2017: Musiałem uczynić to szablonem Design Time T4 (zajęło mi trochę czasu, aby się domyślić, najpierw dodałem szablon Preprocessor). Musiałem również dołączyć ten zestaw: Microsoft.VisualStudio.TextTemplating.Interfaces.10.0 jako odniesienie do projektu. Wreszcie mój szablon musiał zawierać „using System;” przed przestrzenią nazw, w przeciwnym razie odwołanie do DateTime nie powiodło się.
Andy

20

Jeśli chodzi o technikę pobierania informacji o dacie kompilacji / wersji z bajtów nagłówka PE zestawu, Microsoft zmienił domyślne parametry kompilacji, zaczynając od Visual Studio 15.4. Nowe ustawienie domyślne obejmuje kompilację deterministyczną, dzięki czemu ważny znacznik czasu i automatycznie zwiększane numery wersji należą już do przeszłości. Pole znacznika czasu jest nadal obecne, ale zostaje wypełnione stałą wartością, która jest skrótem czegoś lub innego, ale nie wskazuje na czas kompilacji.

Kilka szczegółowych informacji tutaj

Dla tych, którzy nadają priorytet użytecznemu znacznikowi czasu przed kompilacją deterministyczną, istnieje sposób na zastąpienie nowego ustawienia domyślnego. Możesz dołączyć znacznik do pliku .csproj zespołu będącego przedmiotem zainteresowania w następujący sposób:

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

Aktualizacja: Popieram rozwiązanie szablonu tekstowego T4 opisane w innej odpowiedzi tutaj. Użyłem go do czystego rozwiązania problemu, nie tracąc korzyści z kompilacji deterministycznej. Jedną ostrożnością jest to, że Visual Studio uruchamia kompilator T4 tylko wtedy, gdy plik .tt jest zapisywany, a nie podczas kompilacji. Może to być niewygodne, jeśli wykluczysz wynik .cs z kontroli źródła (ponieważ spodziewasz się, że zostanie wygenerowany), a inny programista sprawdzi kod. Bez ponownego zapisywania nie będą mieli pliku .cs. W nugecie znajduje się pakiet (myślę, że nazywa się AutoT4), który sprawia, że ​​kompilacja T4 jest częścią każdej kompilacji. Nie spotkałem się jeszcze z rozwiązaniem tego problemu podczas wdrażania produkcyjnego, ale spodziewam się czegoś podobnego, aby to naprawić.


To rozwiązało mój problem w sln, który używa najstarszej odpowiedzi.
pauldendulk

Twoja ostrożność wobec T4 jest całkowicie uczciwa, ale zauważ, że jest już obecna w mojej odpowiedzi.
Peter Taylor,

15

Jestem tylko nowicjuszem w C #, więc może moja odpowiedź brzmi głupio - wyświetlam datę kompilacji od daty ostatniego zapisania pliku wykonywalnego:

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

Próbowałem użyć metody File.GetCreationTime, ale otrzymałem dziwne wyniki: data z polecenia to 29.05.2012, ale data z Eksploratora Windows pokazała 23.05.2012. Po wyszukaniu tej rozbieżności odkryłem, że plik prawdopodobnie został utworzony w dniu 23.05.2012 r. (Jak pokazano w Eksploratorze Windows), ale został skopiowany do bieżącego folderu w dniu 29.05.2012 r. (Jak pokazano w poleceniu File.GetCreationTime) - więc dla bezpieczeństwa Używam polecenia File.GetLastWriteTime.

Zalek


4
Nie jestem pewien, czy jest to kuloodporne kopiowanie pliku wykonywalnego między dyskami / komputerami / sieciami.
Stealth Rabbi,

jest to pierwsza rzecz, o której chodzi, ale wiesz, że nie jest niezawodna, istnieje wiele programów służących do przenoszenia plików w sieci, które nie aktualizują atrybutów po pobraniu, wybrałbym odpowiedź @ Abdurrahim.
Mubashar,

Wiem, że to jest stare, ale właśnie znalazłem z jakimś podobnym kodem, że proces INSTALACJI (przynajmniej przy użyciu clickonce) aktualizuje czas pliku zespołu. Niezbyt przydatne. Nie jestem jednak pewien, czy miałoby to zastosowanie do tego rozwiązania.
bobwki,

Prawdopodobnie naprawdę tego potrzebujesz LastWriteTime, ponieważ dokładnie odzwierciedla on czas faktycznej aktualizacji pliku wykonywalnego.
David R Tribble

Przepraszamy, ale czas zapisu pliku wykonywalnego nie jest wiarygodnym wskaźnikiem czasu kompilacji. Znacznik czasu pliku można przepisać z powodu różnego rodzaju rzeczy, które znajdują się poza twoją strefą wpływów.
Tom

15

Wiele świetnych odpowiedzi tutaj, ale wydaje mi się, że mogę dodać własne ze względu na prostotę, wydajność (w porównaniu z rozwiązaniami związanymi z zasobami) między platformami (działa również z Net Core) i unikanie jakiegokolwiek narzędzia innej firmy. Wystarczy dodać ten cel msbuild do 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>

a teraz masz Builtin.CompileTimelubnew DateTime(Builtin.CompileTime, DateTimeKind.Utc) jeśli potrzebujesz tego w ten sposób.

ReSharper się nie spodoba. Możesz go zignorować lub dodać częściową klasę do projektu, ale i tak działa.


Mogę dzięki temu tworzyć i programować lokalnie (uruchamiać strony internetowe) w programie ASP.NET Core 2.1, ale publikowanie przy wdrażaniu sieci od VS 2017 kończy się niepowodzeniem z błędem „Nazwa„ Builtin ”nie istnieje w bieżącym kontekście”. DODATEK: Jeśli korzystam Builtin.CompileTimez widoku Razor.
Jeremy Cook

W tym przypadku myślę, że potrzebujesz, BeforeTargets="RazorCoreCompile"ale tylko wtedy, gdy jest to ten sam projekt
Dmitrij Gusarow

fajnie, ale jak odnosimy się do generowanego obiektu? wydaje mi się, że w odpowiedzi brakuje kluczowej części ...
Matteo

1
@Matteo, jak wspomniano w odpowiedzi, można użyć „Builtin.CompileTime” lub „new DateTime (Builtin.CompileTime, DateTimeKind.Utc)”. Visual Studio IntelliSense jest w stanie to zobaczyć od razu. Stary ReSharper może narzekać w czasie projektowania, ale wygląda na to, że naprawili to w nowych wersjach. clip2net.com/s/46rgaaO
Dmitry Gusarov

Użyłem tej wersji, więc nie potrzebuję dodatkowego kodu, aby uzyskać datę. Również resharper nie narzeka na swoją najnowszą wersję. <WriteLinesToFile File = "$ (IntermediateOutputPath) BuildInfo.cs" Lines = "przy użyciu wewnętrznej statycznej klasy częściowej System% 3B BuildInfo {public static long DateBuiltTicks = $ ([System.DateTime] :: UtcNow.Ticks)% 3B public static DateTime DateBuilt => new DateTime (DateBuiltTicks, DateTimeKind.Utc)% 3B} „Zastąp = = prawda” />
Softlion

13

W przypadku projektów .NET Core dostosowałem odpowiedź Postlagerkarte, aby zaktualizować pole Prawa autorskie zestawu o datę kompilacji.

Bezpośrednio edytuj csproj

Następujące można dodać bezpośrednio do pierwszego PropertyGroupw csproj:

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

Alternatywa: właściwości projektu Visual Studio

Lub wklej wyrażenie wewnętrzne bezpośrednio w polu Copyright w sekcji Package właściwości projektu w Visual Studio:

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

Może to być nieco mylące, ponieważ Visual Studio oceni wyrażenie i wyświetli bieżącą wartość w oknie, ale odpowiednio zaktualizuje plik projektu za scenami.

W całym rozwiązaniu za pośrednictwem Directory.Build.props

Możesz <Copyright>umieścić element powyżej w Directory.Build.propspliku w katalogu głównym rozwiązania i automatycznie zastosować go do wszystkich projektów w katalogu, zakładając, że każdy projekt nie podaje własnej wartości autorskiej.

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

Directory.Build.props: Dostosuj swoją kompilację

Wynik

Przykładowe wyrażenie da ci prawa autorskie takie jak to:

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

Wyszukiwanie

Możesz wyświetlić informacje o prawach autorskich z właściwości pliku w systemie Windows lub pobrać je w czasie wykonywania:

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

Console.WriteLine(version.LegalCopyright);

11

Powyższą metodę można ulepszyć dla zestawów już załadowanych w procesie , używając obrazu pliku w pamięci (w przeciwieństwie do ponownego odczytu go z pamięci):

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

Ten działa świetnie, nawet w środowisku 4.7. Zastosowanie: Utils.GetLinkerDateTime (Assembly.GetExecutingAssembly (), null))
real_yggdrasil

Działa świetnie! Dzięki!
bobwki

10

Dla każdego, kto musi uzyskać czas kompilacji w systemie 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);
            }
        }
    }

Dla każdego, kto musi uzyskać czas kompilacji w 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;
    }

UWAGA: We wszystkich przypadkach korzystasz z piaskownicy, więc będziesz mógł uzyskać tylko czas kompilacji zestawów, które wdrażasz z aplikacją. (tzn. to nie zadziała na niczym w GAC).


Oto jak uzyskać Zgromadzenie w WP 8.1:var assembly = typeof (AnyTypeInYourAssembly).GetTypeInfo().Assembly;
André Fiedler

Co jeśli chcesz uruchomić swój kod na obu systemach? - czy jedna z tych metod ma zastosowanie do obu platform?
bvdb,

10

W 2018 r. Niektóre z powyższych rozwiązań już nie działają lub nie działają z platformą .NET Core.

Korzystam z następującego podejścia, które jest proste i działa w moim projekcie .NET Core 2.0.

Dodaj następujące elementy do .csproj wewnątrz PropertyGroup:

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

Definiuje to funkcję PropertyFunction, do której można uzyskać dostęp w poleceniu przed kompilacją.

Twoja wersja wstępna wygląda tak

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

Ustaw właściwość BuildTimeStamp.txt na Zasób osadzony.

Teraz możesz odczytać znacznik czasu w ten sposób

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

Działa również samo generowanie pliku BuildTimeStamp.txt ze zdarzeń przed kompilacją za pomocą poleceń skryptu wsadowego . Zwróć uwagę, że popełniłeś tam błąd: powinieneś otoczyć swój cel cudzysłowami (np. "$(ProjectDir)BuildTimeStamp.txt"), Bo pęknie, gdy w nazwach folderów będzie spacja.
Nyerguds,

Może warto zastosować format czasu niezmiennego dla kultury. W ten sposób: $([System.DateTime]::Now.tostring("MM/dd/yyyy HH:mm:ss"))zamiast$([System.DateTime]::Now)
Ivan Kochurkin,

9

Nie omawianą tutaj opcją jest wstawienie własnych danych do AssemblyInfo.cs, pole „AssemblyInformationalVersion” wydaje się odpowiednie - mamy kilka projektów, w których robiliśmy coś podobnego jak krok kompilacji (jednak nie jestem do końca zadowolony z sposób, który działa, więc tak naprawdę nie chcę reprodukować tego, co mamy).

Istnieje artykuł na ten temat na temat projektu kodowego: http://www.codeproject.com/KB/dotnet/Customizing_csproj_files.aspx


6

Potrzebowałem uniwersalnego rozwiązania, które działałoby z projektem NETStandard na dowolnej platformie (iOS, Android i Windows). Aby to osiągnąć, postanowiłem automatycznie wygenerować plik CS za pomocą skryptu PowerShell. Oto skrypt 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

Zapisz plik PowerScript jako GenBuildDate.ps1 i dodaj go do swojego projektu. Na koniec dodaj następujący wiersz do wydarzenia przed kompilacją:

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

Upewnij się, że plik BuildDate.cs jest uwzględniony w projekcie. Działa jak mistrz na każdym systemie operacyjnym!


1
Można go również użyć do uzyskania numeru wersji SVN za pomocą narzędzia wiersza polecenia svn. Zrobiłem z tym coś podobnego.
użytkownik169771,

5

Po prostu robię:

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

1
Co ciekawe, w przypadku uruchamiania z debugowania „prawdziwą” datą jest GetLastAccessTime ()
balint

4

Możesz użyć tego projektu: https://github.com/dwcullop/BuildInfo

Wykorzystuje T4 do automatyzacji znacznika czasu kompilacji. Istnieje kilka wersji (różne gałęzie), w tym jedna, która daje Git Hash aktualnie wyewidencjonowanej gałęzi, jeśli lubisz takie rzeczy.

Ujawnienie: Napisałem moduł.


3

Innym podejściem przyjaznym dla PCL byłoby użycie wbudowanego zadania MSBuild do zastąpienia czasu kompilacji ciągiem zwracanym przez właściwość w aplikacji. Z powodzeniem stosujemy to podejście w aplikacji, która ma projekty Xamarin.Forms, Xamarin.Android i Xamarin.iOS.

EDYTOWAĆ:

Uproszczone przez przeniesienie całej logiki do SetBuildDate.targetspliku i użycie Regexzamiast zwykłego ciągu znaków zamień, aby plik mógł być modyfikowany przez każdą kompilację bez „resetowania”.

Definicja wbudowanego zadania MSBuild (zapisana w lokalnym pliku SetBuildDate.targets w projekcie Xamarin.Forms w tym przykładzie):

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

Wywoływanie powyższego zadania wbudowanego w pliku csproj Xamarin.Forms w docelowym 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>

FilePathWłaściwość jest ustawiona do BuildMetadata.cspliku w projekcie Xamarin.Forms który zawiera prostą klasę z właściwości String BuildDate, w których czas budowy zostanie podstawiony:

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

Dodaj ten plik BuildMetadata.csdo projektu. Będzie modyfikowany przy każdej kompilacji, ale w sposób, który pozwala na powtarzane kompilacje (powtarzane zamiany), więc możesz uwzględnić lub pominąć go w kontroli źródła według potrzeb.


2

Możesz użyć zdarzenia po kompilacji projektu, aby zapisać plik tekstowy do katalogu docelowego z bieżącą datetime. Następnie możesz odczytać wartość w czasie wykonywania. Jest trochę zuchwały, ale powinien działać.



2

Mała aktualizacja odpowiedzi „New Way” od Jhon.

Podczas pracy z programem ASP.NET/MVC musisz zbudować ścieżkę zamiast ciągów CodeBase

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

1

Możesz uruchomić dodatkowy krok w procesie kompilacji, który zapisuje znacznik daty do pliku, który można następnie wyświetlić.

Na karcie właściwości projektów spójrz na kartę zdarzeń kompilacji. Istnieje opcja wykonania polecenia przed kompilacją lub po kompilacji.


1

Skorzystałem z sugestii Abdurrahima. Wydawało się jednak, że podało dziwny format czasu, a także dodało skrót dnia jako część daty kompilacji; przykład: nie 12.12.2017 13: 21: 05.43. Potrzebowałem tylko daty, więc musiałem wyeliminować resztę za pomocą podciągów.

Po dodaniu echo %date% %time% > "$(ProjectDir)\Resources\BuildDate.txt"zdarzenia do wersji wstępnej po prostu wykonałem następujące czynności:

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

Dobrą wiadomością jest to, że działało.


Bardzo proste rozwiązanie. Lubię to. A jeśli format przeszkadza, istnieją sposoby na uzyskanie lepszego formatu z wiersza poleceń.
Nyerguds

0

Jeśli jest to aplikacja Windows, możesz po prostu użyć ścieżki wykonywalnej aplikacji: new System.IO.FileInfo (Application.ExecutablePath) .LastWriteTime.ToString („rrrr.MM.dd”)


2
Już i odpowiedz używając tego, a także nie dokładnie kuloodpornego.
crashmstr

0

mogłoby być Assembly execAssembly = Assembly.GetExecutingAssembly(); var creationTime = new FileInfo(execAssembly.Location).CreationTime; // "2019-09-08T14:29:12.2286642-04:00"


Czy to nie robi tego samego co ta inna odpowiedź ?
Wai Ha Lee,
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.