Отображение даты сборки


260

В настоящее время у меня есть приложение, отображающее номер сборки в окне заголовка. Это хорошо, за исключением того, что это ничего не значит для большинства пользователей, которые хотят знать, есть ли у них последняя сборка - они склонны называть ее «прошлой четвергой», а не сборкой 1.0.8.4321.

План состоит в том, чтобы вместо этого указать дату сборки - например, «Приложение построено 21/10/2009».

Я изо всех сил пытаюсь найти программный способ вытащить дату сборки в виде текстовой строки для использования, как это.

Для номера сборки я использовал:

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

после определения, как они возникли.

Я хотел бы что-то подобное для даты компиляции (и время, для бонусных баллов).

Здесь очень ценятся указатели (извините за каламбур, если это уместно) или более аккуратные решения ...


2
Я попробовал предоставленные способы получения данных сборки сборок, которые работают в простых сценариях, но если две сборки объединяются, я получаю не правильное время сборки, это один час в будущем .. какие-либо предложения?

Ответы:


356

Джеффу Этвуду было что сказать по этому поводу в « Определении даты сборки» .

Оказывается, наиболее надежным методом является получение метки времени компоновщика из PE-заголовка, встроенного в исполняемый файл, - некоторый код C # (Джо Спиви) для этого из комментариев к статье Джеффа:

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

Пример использования:

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

ОБНОВЛЕНИЕ: метод работал для .Net Core 1.0, но перестал работать после выпуска .Net Core 1.1 (дает случайные годы в диапазоне 1900-2020)


8
Я немного изменил свой тон по этому поводу, я все еще буду очень осторожен, когда копаюсь в остром РЕ заголовке. Но, насколько я могу судить, этот PE-материал намного надежнее, чем использование номеров версий, кроме того, я не хочу назначать номера версий отдельно от даты сборки.
Джон Лейдгрен

6
Мне это нравится, и я им пользуюсь, но эта вторая с последней строки .AddHours()довольно хакерская и (я думаю) не будет принимать во внимание DST. Если вы хотите это по местному времени, вы должны использовать dt.ToLocalTime();вместо этого чистящее средство. Средняя часть также может быть значительно упрощена с помощью using()блока.
JLRishe

6
Да, у меня это перестало работать и с ядром .net (1940-е, 1960-е и т. Д.)
eoleary

7
Хотя использование PE-заголовка сегодня может показаться хорошим вариантом, стоит отметить, что MS экспериментирует с детерминированными сборками (что делает этот заголовок бесполезным) и, возможно, даже устанавливает его по умолчанию в будущих версиях C # компилятора (по уважительным причинам). Полезное чтение: blog.paranoidcoding.com/2016/04/05/… и вот ответ, относящийся к .NET Core (TLDR: "все
сделано

13
Для тех, кто считает, что это больше не работает, проблема не является .NET Core. Смотрите мой ответ ниже о новых настройках параметров по умолчанию, начиная с Visual Studio 15.4.
Том

107

Добавьте ниже, чтобы предварительно построить командную строку события:

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

Добавьте этот файл в качестве ресурса, теперь у вас есть строка 'BuildDate' в ваших ресурсах.

Для создания ресурсов см. Как создавать и использовать ресурсы в .NET .


4
+1 от меня, просто и эффективно. Мне даже удалось получить значение из файла с такой строкой кода: String buildDate = <MyClassLibraryName> .Properties.Resources.BuildDate
davidfrancis

11
другой вариант - создать класс: (необходимо включить в проект после первой его компиляции) -> echo namespace My.app.namespace {public static class Build {public static string Timestamp = "% DATE%% TIME%" .Substring (0,16);}}> "$ (ProjectDir) \ BuildTimestamp.cs" - - - -> затем можно вызвать его с помощью Build.Timestamp
FabianSilva

9
Это отличное решение. Единственная проблема заключается в том, что переменные командной строки% date% и% time% локализованы, поэтому выходные данные будут различаться в зависимости от языка Windows пользователя.
VS

2
+1, это лучший метод, чем чтение PE-заголовков - потому что есть несколько сценариев, когда это вообще не будет работать (например, приложение для Windows Phone)
Мэтт Уитфилд,

17
Умная. Вы также можете использовать powershell, чтобы получить более точный контроль над форматом, например, чтобы получить дату и время UTC в формате ISO8601: powershell -Command "((Get-Date) .ToUniversalTime ()). ToString (\" s \ ") | Out-File '$ (ProjectDir) Resources \ BuildDate.txt' "
dbruning

90

Способ

Как указано @ c00000fd в комментариях . Microsoft меняет это. И хотя многие люди не используют последнюю версию своего компилятора, я подозреваю, что это изменение делает этот подход, безусловно, плохим. И хотя это забавное упражнение, я бы порекомендовал людям просто встраивать дату сборки в свой бинарный файл любым другим необходимым способом, если важно отслеживать дату сборки самого бинарного файла.

Это можно сделать с помощью некоторой тривиальной генерации кода, которая, вероятно, уже является первым шагом в вашем скрипте сборки. Это и тот факт, что инструменты ALM / Build / DevOps очень помогают в этом и должны быть предпочтительнее всего остального.

Я оставляю остальную часть этого ответа здесь только в исторических целях.

Новый путь

Я передумал об этом, и в настоящее время использую этот трюк, чтобы получить правильную дату сборки.

#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

По старому

Ну, как вы генерируете номера сборки? Visual Studio (или компилятор C #) фактически предоставляет номера автоматической сборки и ревизии, если вы измените атрибут AssemblyVersion на, например:1.0.*

Что произойдет, так это то, что сборка будет равна количеству дней с 1 января 2000 года по местному времени, а пересмотр будет равен числу секунд с полуночи по местному времени, деленному на 2.

см. контент сообщества, номера автоматической сборки и ревизии

например 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
Я только добавил один час, еслиTimeZone.CurrentTimeZone.IsDaylightSavingTime(buildDateTime) == true
e4rthdog

2
К сожалению, я использовал этот подход без тщательной проверки, и он кусает нас в производстве. Проблема в том, что когда JIT-компилятор запускает PE-информацию, информация изменяется. Отсюда и отрицательный голос. Теперь я собираюсь провести ненужное «исследование», чтобы объяснить, почему мы видим дату установки в качестве даты сборки.
Джейсон Д

8
@JasonD В какой вселенной твоя проблема почему-то становится моей проблемой? Как вы оправдываете понижение рейтинга просто потому, что столкнулись с проблемой, которую эта реализация не приняла во внимание. Вы получили это бесплатно, и вы проверили это плохо. Кроме того, что заставляет вас верить, что заголовок переписывается компилятором JIT? Вы читаете эту информацию из памяти процесса или из файла?
Джон Лейдгрен

6
Я заметил, что если вы работаете в веб-приложении, свойство .Codebase выглядит как URL (file: // c: /path/to/binary.dll). Это приводит к сбою вызова File.Exists. Использование «assembly.Location» вместо свойства CodeBase решило проблему для меня.
mdryden

2
@JohnLeidegren: не полагайтесь на заголовок Windows PE для этого. Начиная с Windows 10 и воспроизводимых сборок , IMAGE_FILE_HEADER::TimeDateStampполе имеет случайное число и больше не является отметкой времени.
c00000fd

51

Добавьте ниже, чтобы предварительно построить командную строку события:

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

Добавьте этот файл в качестве ресурса, теперь у вас есть строка 'BuildDate' в ваших ресурсах.

После вставки файла в ресурс (в виде открытого текстового файла) я получил к нему доступ через

string strCompTime = Properties.Resources.BuildDate;

Для создания ресурсов см. Как создавать и использовать ресурсы в .NET .


1
@DavidGorsline - уценка комментария была правильной, поскольку она цитирует этот другой ответ . У меня недостаточно репутации, чтобы откатить ваши изменения, иначе я бы сделал это сам.
Вай Ха Ли

1
@ Wai Ha Lee - a) ответ, который вы цитируете, не дает кода для фактического получения даты / времени компиляции. б) в то время у меня не было достаточно репутации, чтобы добавить комментарий к этому ответу (что я бы сделал) только для публикации. Итак, в) я написал полный ответ, чтобы люди могли получить все детали в одной области ..
brewmanz

Если вы видите %te% вместо% date%, проверьте здесь: developercommunity.visualstudio.com/content/problem/237752/… В двух словах, сделайте это: echo% 25date% 25% 25time% 25
Qodex

41

Один из подходов, о котором я удивляюсь, еще никто не упомянул, - это использовать текстовые шаблоны T4 для генерации кода.

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

Плюсы:

  • Locale-независимый
  • Позволяет намного больше, чем просто время компиляции

Минусы:


1
Итак, теперь это лучший ответ. 324 балла до того, как он станет самым популярным ответом :). Stackoverflow нужен способ показать самый быстрый альпинист.
pauldendulk

1
@pauldendulk, не очень поможет, потому что наиболее голосуемый и принятый ответ почти всегда набирают голоса быстрее всех. Принятый ответ на этот вопрос имеет + 60 / -2, так как я разместил этот ответ.
Питер Тейлор

Я считаю, что вам нужно добавить .ToString () к вашим тикам (в противном случае я получаю ошибку компиляции). Тем не менее, я сталкиваюсь с крутой кривой обучения здесь, не могли бы вы показать, как использовать это в основной программе тоже?
Энди

@ Энди, ты прав насчет ToString (). Использование просто Constants.CompilationTimestampUtc. Если VS не генерирует файл C # с классом, то вам нужно выяснить, как заставить его это сделать, но ответ зависит (как минимум) от версии VS и типа файла csproj, поэтому слишком много деталей для этого поста.
Питер Тейлор

1
В случае, если другие задаются вопросом, это то, что нужно, чтобы заставить его работать над VS 2017: мне пришлось сделать этот шаблон для Design Time T4 (мне потребовалось время, чтобы понять, я сначала добавил шаблон препроцессора). Мне также нужно было включить эту сборку: Microsoft.VisualStudio.TextTemplating.Interfaces.10.0 в качестве ссылки на проект. Наконец мой шаблон должен был включать «использование системы»; перед пространством имен, или же ссылка на DateTime не удалась.
Энди

20

Что касается техники извлечения информации о дате / версии сборки из байтов PE-заголовка сборки, Microsoft изменила параметры сборки по умолчанию, начиная с Visual Studio 15.4. Новое значение по умолчанию включает детерминированную компиляцию, которая делает действительную временную метку и автоматически увеличивающиеся номера версий ушедшими в прошлое. Поле метки времени все еще присутствует, но оно заполняется постоянным значением, которое является хешем того или иного, но не указанием времени сборки.

Некоторые подробные сведения здесь

Для тех, кто отдает предпочтение временной метке над детерминированной компиляцией, есть способ переопределить новое значение по умолчанию. Вы можете включить тег в файл .csproj интересующей сборки следующим образом:

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

Обновление: я поддерживаю решение текстового шаблона T4, описанное в другом ответе здесь. Я использовал это, чтобы решить мою проблему чисто, не теряя преимущества детерминированной компиляции. Одно предостережение по этому поводу заключается в том, что Visual Studio запускает компилятор T4 только при сохранении файла .tt, а не во время сборки. Это может быть неудобно, если вы исключите результат .cs из управления исходным кодом (поскольку вы ожидаете, что он будет сгенерирован), а другой разработчик проверит код. Без сохранения у них не будет файла .cs. В nuget есть пакет (который называется AutoT4), который делает компиляцию T4 частью каждой сборки. Я еще не сталкивался с решением этой проблемы во время развертывания производства, но я ожидаю, что что-то подобное сделает это правильно.


Это решило мою проблему в sln, который использует самый старый ответ.
pauldendulk

Ваше предостережение относительно T4 совершенно справедливо, но обратите внимание, что оно уже присутствует в моем ответе.
Питер Тейлор

15

Я просто новичок в C #, поэтому, возможно, мой ответ звучит глупо - я отображаю дату сборки с даты, когда исполняемый файл был последний раз записан:

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

Я попытался использовать метод File.GetCreationTime, но получил странные результаты: дата из команды была 2012-05-29, но дата из Window Explorer показала 2012-05-23. После поиска этого несоответствия я обнаружил, что файл, вероятно, был создан 2012-05-23 (как показано в проводнике Windows), но скопирован в текущую папку 2012-05-29 (как показано командой File.GetCreationTime) - так чтобы быть на безопасной стороне, я использую команду File.GetLastWriteTime.

ZAlek


4
Я не уверен, что это пуленепробиваемое копирование исполняемого файла через диски / компьютеры / сети.
Раввин Стелс

это первое, что приходит в голову, но вы знаете, что это ненадежно, существует много программ, используемых для перемещения файлов по сети, которые не обновляют атрибуты после загрузки, я бы согласился с ответом @ Abdurrahim.
Мубашар

Я знаю, что это старая версия, но я только что обнаружил с некоторым похожим кодом, что процесс INSTALL (по крайней мере, при использовании clickonce) обновляет время файла сборки. Не очень полезно Не уверен, что это применимо к этому решению, хотя.
bobwki

Вы, вероятно, действительно хотите LastWriteTime, так как это точно отражает время, когда исполняемый файл был фактически обновлен.
Дэвид Р. Триббл

Извините, но время записи исполняемого файла не является надежным индикатором времени сборки. Метка времени файла может быть переписана из-за всех видов вещей, которые находятся вне сферы вашего влияния.
Том

15

Здесь много хороших ответов, но я чувствую, что могу добавить свой из-за простоты, производительности (по сравнению с решениями, связанными с ресурсами), кроссплатформенности (также работает с Net Core) и отказа от любого стороннего инструмента. Просто добавьте эту цель msbuild в 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>

и теперь у вас есть Builtin.CompileTimeили new DateTime(Builtin.CompileTime, DateTimeKind.Utc)если вам это нужно таким образом.

ReSharper это не понравится. Вы можете игнорировать его или добавить частичный класс в проект, но он все равно работает.


Я могу построить это и разрабатывать локально (запускать веб-сайты) в ASP.NET Core 2.1, но публикация веб-развертывания из VS 2017 завершается с ошибкой «Имя« Встроенный »не существует в текущем контексте». ДОПОЛНЕНИЕ: если я получаю доступ Builtin.CompileTimeиз вида Razor.
Джереми Кук

В этом случае я думаю, что вам просто нужно, BeforeTargets="RazorCoreCompile"но только тогда, когда это в том же проекте
Дмитрий Гусаров

круто, а как мы относимся к сгенерированному объекту? мне кажется, что в ответе отсутствует ключевая часть ...
Маттео

1
@Matteo, как уже упоминалось в ответе, вы можете использовать «Builtin.CompileTime» или «новый DateTime (Builtin.CompileTime, DateTimeKind.Utc)». Visual Studio IntelliSense способен увидеть это сразу. Старый ReSharper может жаловаться во время разработки, но похоже, что они исправили это в новых версиях. clip2net.com/s/46rgaaO
Дмитрий Гусаров

Я использовал эту версию, поэтому нет необходимости в дополнительном коде, чтобы получить дату. Также resharper не жалуется на свою последнюю версию. <WriteLinesToFile File = "$ (IntermediateOutputPath) BuildInfo.cs" Lines = "с использованием внутреннего статического частичного класса System% 3B BuildInfo {public static long DateBuiltTicks = $ ([System.DateTime] :: UtcNow.Ticks)% 3B public static DateTime DateBuilt => новый DateTime (DateBuiltTicks, DateTimeKind.Utc)% 3B} "Overwrite =" true "/>
Softlion

13

Для проектов .NET Core я адаптировал ответ Postlagerkarte, чтобы обновить поле Copyright для сборки, указав дату сборки.

Прямое редактирование csproj

Следующее может быть добавлено непосредственно к первому PropertyGroupв csproj:

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

Альтернатива: свойства проекта Visual Studio

Или вставьте внутреннее выражение непосредственно в поле Copyright в разделе Package свойств проекта в Visual Studio:

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

Это может немного сбивать с толку, потому что Visual Studio оценит выражение и отобразит текущее значение в окне, но также соответствующим образом обновит файл проекта за кулисами.

Комплексное решение через Directory.Build.props

Вы можете поместить <Copyright>вышеуказанный элемент в Directory.Build.propsфайл в корне вашего решения и автоматически применить его ко всем проектам в каталоге, предполагая, что каждый проект не имеет своего значения авторского права.

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

Directory.Build.props: настроить сборку

Вывод

Пример выражения даст вам авторское право, подобное этому:

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

поиск

Вы можете просмотреть информацию об авторских правах в свойствах файла в Windows или получить ее во время выполнения:

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

Console.WriteLine(version.LegalCopyright);

11

Вышеупомянутый метод может быть настроен для сборок, уже загруженных в процессе , используя изображение файла в памяти (в отличие от повторного чтения его из хранилища):

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

Это отлично работает, даже для фреймворка 4.7. Использование: Utils.GetLinkerDateTime (Assembly.GetExecutingAssembly (), null))
real_yggdrasil

Прекрасно работает! Спасибо!
bobwki

10

Для тех, кому нужно время компиляции в 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);
            }
        }
    }

Для тех, кому нужно время компиляции в 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;
    }

ПРИМЕЧАНИЕ. Во всех случаях вы работаете в «песочнице», поэтому вы сможете получить только время компиляции сборок, которые вы развернете вместе с приложением. (т.е. это не будет работать ни на чем в GAC).


Вот как вы получаете сборку в WP 8.1:var assembly = typeof (AnyTypeInYourAssembly).GetTypeInfo().Assembly;
Андре Фидлер

Что если вы хотите запустить свой код в обеих системах? - применим ли один из этих методов для обеих платформ?
17

10

В 2018 году некоторые из вышеперечисленных решений больше не работают или не работают с .NET Core.

Я использую следующий подход, который прост и работает для моего проекта .NET Core 2.0.

Добавьте следующее в ваш .csproj внутри PropertyGroup:

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

Это определяет PropertyFunction, к которой вы можете получить доступ в вашей команде pre build.

Ваша предварительная сборка выглядит так

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

Установите свойство BuildTimeStamp.txt для встроенного ресурса.

Теперь вы можете прочитать отметку времени, как это

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

Простое создание этого BuildTimeStamp.txt из событий предварительной сборки с использованием команд пакетного сценария также работает. Обратите внимание, что вы допустили ошибку: вы должны заключить вашу цель в кавычки (например "$(ProjectDir)BuildTimeStamp.txt"), или она сломается, если в именах папок будут пробелы.
Нергудс

Возможно, имеет смысл использовать формат времени, не зависящий от культуры. Вот так: $([System.DateTime]::Now.tostring("MM/dd/yyyy HH:mm:ss"))вместо$([System.DateTime]::Now)
Иван Кочуркин

9

Опция, не обсуждаемая здесь, заключается в вставке ваших собственных данных в AssemblyInfo.cs, поле «AssemblyInformationalVersion» кажется подходящим - у нас есть несколько проектов, в которых мы выполняли нечто подобное в качестве шага сборки (однако я не совсем доволен способ, который работает, так что на самом деле не хочу воспроизводить то, что у нас есть).

Есть статья на эту тему по codeproject: http://www.codeproject.com/KB/dotnet/Customizing_csproj_files.aspx


6

Мне нужно было универсальное решение, которое работало бы с проектом NETStandard на любой платформе (iOS, Android и Windows). Для этого я решил автоматически сгенерировать файл CS с помощью скрипта PowerShell. Вот скрипт 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

Сохраните файл PowerScript как GenBuildDate.ps1 и добавьте его в свой проект. Наконец, добавьте следующую строку в событие Pre-Build:

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

Убедитесь, что BuildDate.cs включен в ваш проект. Работает как чемпион на любой ОС!


1
Вы также можете использовать это для получения номера ревизии SVN с помощью инструмента командной строки svn. Я сделал что-то подобное с этим.
user169771

5

Я просто:

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

1
Интересно, что при запуске из режима отладки «истинной» датой будет GetLastAccessTime ()
balint

4

Вы можете использовать этот проект: https://github.com/dwcullop/BuildInfo

Он использует T4 для автоматизации метки времени сборки. Существует несколько версий (разных веток), в том числе одна, которая дает вам Git Hash ветки, которую вы в данный момент извлекли, если вы любите подобные вещи.

Раскрытие: я написал модуль.


3

Другой, дружественный к PCL подход заключается в использовании встроенной задачи MSBuild для замены времени сборки в строку, возвращаемую свойством приложения. Мы успешно используем этот подход в приложении с проектами Xamarin.Forms, Xamarin.Android и Xamarin.iOS.

РЕДАКТИРОВАТЬ:

Упрощается путем перемещения всей логики в SetBuildDate.targetsфайл и использования Regexвместо простой замены строк, чтобы файл мог быть изменен каждой сборкой без «перезагрузки».

Определение встроенной задачи MSBuild (для этого примера сохранено в файле SetBuildDate.targets, локальном для проекта Xamarin.Forms):

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

Вызов указанной выше встроенной задачи в файле Xamarin.Forms csproj в target 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>

FilePathСвойство установлено в BuildMetadata.csфайл в проекте Xamarin.Forms , который содержит простой класс со свойством строки BuildDate, в которой время сборки будет заменено:

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

Добавьте этот файл BuildMetadata.csв проект. Он будет изменяться при каждой сборке, но таким образом, чтобы допускались повторные сборки (повторные замены), так что вы можете включить или опустить его в управлении исходным кодом по желанию.


2

Вы можете использовать событие проекта после сборки, чтобы записать текстовый файл в целевой каталог с текущей датой и временем. Затем вы можете прочитать значение во время выполнения. Это немного глупо, но это должно сработать.



2

Небольшое обновление ответа «Новый путь» от Джона.

Вам нужно построить путь вместо использования строки CodeBase при работе с ASP.NET/MVC

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

1

Вы можете запустить дополнительный шаг в процессе сборки, который записывает отметку даты в файл, который затем может быть отображен.

На вкладке свойств проектов посмотрите вкладку событий сборки. Существует возможность выполнить команду до или после сборки.


1

Я использовал предложение Абдуррахима. Однако, это, казалось, дало странный формат времени и также добавило сокращение для дня как часть даты сборки; пример: вс 12/24/2017 13: 21: 05.43. Мне нужна была только дата, поэтому мне пришлось удалить все остальное, используя подстроку.

После добавления echo %date% %time% > "$(ProjectDir)\Resources\BuildDate.txt"события перед сборкой я просто сделал следующее:

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

Хорошая новость в том, что это сработало.


Очень простое решение. Мне это нравится. И если формат беспокоит, есть способы получить лучший формат из командной строки.
Нергудс

0

Если это приложение для Windows, вы можете просто использовать путь к исполняемому файлу приложения: новый System.IO.FileInfo (Application.ExecutablePath) .LastWriteTime.ToString ("yyyy.MM.dd")


2
Уже и ответь с помощью этого, а также не совсем пуленепробиваемого.
crashmstr

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.