Вопрос

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

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

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

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

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

после определения того, как они появились.

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

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

Это было полезно?

Решение

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

Оказывается, наиболее надежным способом является получение метки времени компоновщика из 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)

Другие советы

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

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

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

Чтобы создать ресурсы, см. Как создавать и использовать ресурсы в. NET .

Способ

Как указал @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!

Пример кода.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)

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

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

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

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

string strCompTime = Properties.Resources.BuildDate;

Чтобы создать ресурсы, см. Как создавать и использовать ресурсы в. NET .

Один из подходов, который, как я удивлен, до сих пор никто не упомянул, заключается в использовании Текстовые шаблоны 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); } }
    }
}

Плюсы:

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

Минусы:

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

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

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

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

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

Я всего лишь новичок в 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, но дата из проводника окон показывала 2012-05-23.После поиска этого несоответствия я обнаружил, что файл, вероятно, был создан в 2012-05-23 (как показано в проводнике Windows), но скопирован в текущую папку в 2012-05-29 (как показано в File.Команда GetCreationTime) - поэтому на всякий случай я использую File.Команда GetLastWriteTime.

Zalek

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

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

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

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

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

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

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

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

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

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

Или вставьте внутреннее выражение непосредственно в поле «Авторское право» в разделе «Пакет» свойств проекта в Visual Studio:

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

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

для всего решения через Directory.Build.props

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

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

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

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

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

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

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

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

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

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

Мне нужно было универсальное решение, которое работало с проектом 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 включен в ваш проект. Работает как чемпион на любой ОС!

Я просто делаю:

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

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

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

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

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

Я не уверен, но, возможно, Build Incrementer поможет.

Другой, дружественный к 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.Формирует файл 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, содержащий простой класс со свойством string BuildDate, в который будет заменено время сборки:

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

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

Небольшое обновление " New Way " ответ от Джона.

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

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

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

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

Я использовал предложение Абдуррахима. Однако, это, казалось, дало странный формат времени и также добавило сокращение для дня как часть даты сборки; пример: вс 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);

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top