Related
Is there any way to print or use in anyway an actual code piece or snip? Like if the code is-
if(BA == True)
{
Console.Writeline("Okay")
};
Then if it's possible to actually print that entire text "if...} ;" to the console or excel / word file etc.. so the console output will be-
if(BA == True)
{
Console.Writeline("Okay")
};
for example.. Sounds like very simple and basic if possible at all, but i couldn't find it anywhere with many search combos..
Thanks..
As far as I know there's not really an easy way to do this in C#. There are a couple ways around it:
Distribute your source with your program in a known location, then in the code open the source file and read out the contents parsing it for the parts you need.
Distribute a "prepared" version of source, saving only the parts you need in some file, possibly formatted using JSON or something to easily get the important bits. This preparation could be done as part of the build process.
On the cooler yet slightly more ridiculous side of things, C# code is normally compiled to IL, which can very effectively be decompiled (for example, in ILSpy). It might just be that there's a decompilation library out there you can use on the source of the running program, and then there's no need to also package a source file. Note that this won't have great results if your build process has obfuscation. If that's the case, you'll also need to put the decompiled code through a deobfuscator that works with your obfuscator before outputting it to the user.
Out of curiosity, why do you want to do this? I think I tried to find a way to do it too a while back but for the life of me can't remember why.
Yes, that's possible in 8-20 lines of code.
Here's the code:
using System;
using System.IO;
using System.Reflection;
namespace CodeViewer
{
internal class Program
{
public static void Main(string[] args)
{
Console.WriteLine(Read());
}
public static string Read()
{
string res = "";
Assembly a = Assembly.GetExecutingAssembly();
Stream stream = a.GetManifestResourceStream("CodeViewer.Program2.cs");
StreamReader reader = new StreamReader(stream);
res = reader.ReadToEnd();
return res;
}
}
}
CodeViewer is the namespace of the project.
Here's a necessary part of the project's file (.csproj):
<ItemGroup>
<Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<EmbeddedResource Include="Program.cs">
<Link>Program2.cs</Link>
</EmbeddedResource>
</ItemGroup>
I added the link to the Program.cs where is my program's code. After, I accessed it in Read method.
I am currently building a tool which will support the development of an ASP.NET Core project. This tool uses the Roslyn APIs and other methods for verifying some development requirements (such as project-specific attributes being applied on API Controllers, enforcing naming conventions, and generating some source code for the JavaScript SPA which accesses an API written using the ASP.NET Core Web API template).
In order to do that, I am currently using hardcoded paths to generate code for the SPA app. But in the app's *.csproj file there is actually a "SpaRoot" property specifying where the SPA application is located inside the project:
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
<TypeScriptCompileBlocked>true</TypeScriptCompileBlocked>
<TypeScriptToolsVersion>Latest</TypeScriptToolsVersion>
<IsPackable>false</IsPackable>
<SpaRoot>ClientApp\</SpaRoot>
...
</PropertyGroup>
...
</Project>
My question is: how can I read the "SpaRoot" property's value using the Roslyn APIs?
I have written a minimum code sample to create a Workspace, open the Solution, and retrieve the Project's reference, which resembles the following:
static async Task Main(string[] args)
{
string solutionFile = #"C:\Test\my-solution.sln";
using (var workspace = MSBuildWorkspace.Create())
{
var solution = await workspace.OpenSolutionAsync(solutionFile);
string projectName = "some-project";
var project = solution.Projects.Single(p => p.Name == projectName);
// How to extract the value of "SpaRoot" from the Project here?
}
I've tried searching on how to extract the "SpaRoot" property from the Project reference, and even went as far as debugging to see if I could spot a way myself. Unfortunately, I came up with no answers to that, and I'm still using hardcoded paths in my original code.
Is it even possible to retrieve the value of .csproj properties of a Project using the current Roslyn APIs?
This is more difficult that you would think :) The Roslyn apis only know what the compiler knows and the compiler is not going to be given anything regarding the SpaRoot property. We can use the MSBuild apis to figure this out though. specifically the Microsoft.Build.Evaluation.Project class.
Some assumptions I am making
You only want to examine .NET Core projects
You will have the .NET Core SDK installed on which ever system runs this tool
So first we want a project file that looks like this:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<!--NOTE: If the project you are analyzing is .NET Core then the commandline tool must be as well.
.NET Framework console apps cannot load .NET Core MSBuild assemblies which is required
for what we want to do.-->
<TargetFramework>netcoreapp3.1</TargetFramework>
<LangVersion>Latest</LangVersion>
</PropertyGroup>
<ItemGroup>
<!-- NOTE: We put ExcludeAssets="runtime" on all direct MSBuild references so that we pick up whatever
version is being used by the .NET SDK instead. This is accomplished with the Microsoft.Build.Locator
referenced further below. -->
<PackageReference Include="Microsoft.Build" Version="16.4.0" ExcludeAssets="runtime" />
<PackageReference Include="Microsoft.Build.Locator" Version="1.2.6" />
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="2.9.8" PrivateAssets="all" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="3.4.0" />
<PackageReference Include="Microsoft.CodeAnalysis.VisualBasic.Workspaces" Version="3.4.0" />
<PackageReference Include="Microsoft.CodeAnalysis.Workspaces.MSBuild" Version="3.4.0" />
<!-- NOTE: A lot of MSBuild tasks that we are going to load in order to analyze a project file will implicitly
load build tasks that will require Newtonsoft.Json version 9. Since there is no way for us to ambiently
pick these dependencies up like with MSBuild assemblies we explicitly reference it here. -->
<PackageReference Include="Newtonsoft.Json" Version="9.0.1" />
</ItemGroup>
</Project>
and a Program.cs file that looks like this:
using System;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using System.Xml;
using Microsoft.Build.Construction;
using Microsoft.Build.Evaluation;
using Microsoft.Build.Locator;
using Microsoft.CodeAnalysis.MSBuild;
// I use this so I don't get confused with the Roslyn Project type
using MSBuildProject = Microsoft.Build.Evaluation.Project;
namespace loadProject {
class Program {
static async Task Main(string[] args) {
MSBuildWorkspaceSetup();
// NOTE: we need to make sure we call MSBuildLocator.RegisterInstance
// before we ask the CLR to load any MSBuild types. Therefore we moved
// the code that uses MSBuild types to its own method (instead of being in
// Main) so the CLR is not forced to load them on startup.
await DoAnalysisAsync(args[0]);
}
private static async Task DoAnalysisAsync(string solutionPath) {
using var workspace = MSBuildWorkspace.Create();
// Print message for WorkspaceFailed event to help diagnosing project load failures.
workspace.WorkspaceFailed += (o, e) => Console.WriteLine(e.Diagnostic.Message);
Console.WriteLine($"Loading solution '{solutionPath}'");
// Attach progress reporter so we print projects as they are loaded.
var solution = await workspace.OpenSolutionAsync(solutionPath, new ConsoleProgressReporter());
Console.WriteLine($"Finished loading solution '{solutionPath}'");
// We just select the first project as a demo
// you will want to use your own logic here
var project = solution.Projects.First();
// Now we use the MSBuild apis to load and evaluate our project file
using var xmlReader = XmlReader.Create(File.OpenRead(project.FilePath));
ProjectRootElement root = ProjectRootElement.Create(xmlReader, new ProjectCollection(), preserveFormatting: true);
MSBuildProject msbuildProject = new MSBuildProject(root);
// We can now ask any question about the properties or items in our project file
// and get the correct answer
string spaRootValue = msbuildProject.GetPropertyValue("SpaRoot");
}
private static void MSBuildWorkspaceSetup() {
// Attempt to set the version of MSBuild.
var visualStudioInstances = MSBuildLocator.QueryVisualStudioInstances().ToArray();
var instance = visualStudioInstances.Length == 1
// If there is only one instance of MSBuild on this machine, set that as the one to use.
? visualStudioInstances[0]
// Handle selecting the version of MSBuild you want to use.
: SelectVisualStudioInstance(visualStudioInstances);
Console.WriteLine($"Using MSBuild at '{instance.MSBuildPath}' to load projects.");
// NOTE: Be sure to register an instance with the MSBuildLocator
// before calling MSBuildWorkspace.Create()
// otherwise, MSBuildWorkspace won't MEF compose.
MSBuildLocator.RegisterInstance(instance);
}
private static VisualStudioInstance SelectVisualStudioInstance(VisualStudioInstance[] visualStudioInstances) {
Console.WriteLine("Multiple installs of MSBuild detected please select one:");
for (int i = 0; i < visualStudioInstances.Length; i++) {
Console.WriteLine($"Instance {i + 1}");
Console.WriteLine($" Name: {visualStudioInstances[i].Name}");
Console.WriteLine($" Version: {visualStudioInstances[i].Version}");
Console.WriteLine($" MSBuild Path: {visualStudioInstances[i].MSBuildPath}");
}
while (true) {
var userResponse = Console.ReadLine();
if (int.TryParse(userResponse, out int instanceNumber) &&
instanceNumber > 0 &&
instanceNumber <= visualStudioInstances.Length) {
return visualStudioInstances[instanceNumber - 1];
}
Console.WriteLine("Input not accepted, try again.");
}
}
private class ConsoleProgressReporter : IProgress<ProjectLoadProgress> {
public void Report(ProjectLoadProgress loadProgress) {
var projectDisplay = Path.GetFileName(loadProgress.FilePath);
if (loadProgress.TargetFramework != null) {
projectDisplay += $" ({loadProgress.TargetFramework})";
}
Console.WriteLine($"{loadProgress.Operation,-15} {loadProgress.ElapsedTime,-15:m\\:ss\\.fffffff} {projectDisplay}");
}
}
}
}
You have a large Visual Studio Solution with dozens of project files in it. How would you verify that all the projects follow certain rules in their property settings, and enforce these rules if a new project is added. For example check that all projects have:
TargetFrameworkVersion = "v4.5"
Platform = "AnyCPU"
WarningLevel = 4
TreatWarningsAsErrors = true
OutputPath = $(SolutionDir)bin
SignAssembly = true
AssemblyName = $(ProjectFolderName)
I know two methods myself that I will add in an answer below, but I was wondering how people go about doing this type of project test. I'm especially interested to learn about available solutions such as libraries or build tasks for this rather than having to have to invent something new or write it from scratch.
*.sln files are plain text and easily parsable, and *.*proj files are xml.
You can add a dummy project with a prebuild step that parses the sln to retrieve all of the project files, validate their settings, print a report, and fail the build if necessary.
Also, you should check this post to ensure the prebuild step is always executed. Essentially, you specify a blank output in the custom build step to force a rebuild.
The following list identifies the key file types that are automatically added to VSS when a solution is added to source control by using the Visual Studio .NET integrated development environment (IDE):
Solution files (.sln). The key items maintained within these files include a list of constituent projects, dependency information, build configuration details, and source control provider details.
Project files (.csproj or *.vbproj). The key items maintained within these files include assembly build settings, referenced assemblies (by name and path), and a file inventory.
Application configuration files. These are configuration files based on Extensible Markup Language (XML) used to control various aspects of your project's run time behavior.
Use a Single Solution Model Whenever Possible an
Also see : https://msdn.microsoft.com/en-us/library/ee817677.aspx,
https://msdn.microsoft.com/en-us/library/ee817675.aspx
AND For CONTINUOUS INTEGRATION :
there are many tools available like MSBuild, Jenkins, Apache's Continuum, Cruise Control (CC), and Hudson(plugin can be extended to c#)
This is what I have myself:
One way to do this is to create an MSBuild target with error conditions:
<Error Condition="'$(TreatWarningsAsErrors)'!='true'" Text="Invalid project setting" />
I like this approach because it is integrated with MSBuild and gives you early errors, however, you have to modify every project to import it in them or get all your team members to use a special command prompt with environment variables that will inject custom pre-build steps into your projects during the build, which is a pain.
The second approach I know is to use some library like VSUnitTest which provides an API to project properties that you can test against. VSUnitTest is currently not open source and unlisted from the NuGet service.
You could write some code to open the the solution as a text file to identify all of the csproj files referenced, in turn opening each of these as xml files, and then writing unit tests to ensure specific nodes of the project match what you expect.
It's a quick and dirty solution, but works for CI and gives you the flexibility to ignore nodes you don't care about. It actually sounds kinda useful. I have a solution with 35 projects I'd like to scan too.
Let's try something completely different: you could ensure that they are consistent by construction by generating them from a template or by using a build generation tool such as CMake. This might be simpler than attempting to make them consistent after the fact.
In our work we use a powershell script that checks project settings and modified them if they are incorrect. For example, we remove Debug configuration this way, disable C++ optimization and SSE2 support. We run it manually, but definitely it is possible to run it automatically, e.g. as pre\post build step.
Below the example:
`function Prepare-Solution {
param (
[string]$SolutionFolder
)
$files = gci -Recurse -Path $SolutionFolder -file *.vcxproj | select - ExpandProperty fullname
$files | %{
$file = $_
[xml]$xml = get-content $file
#Deleting Debug configurations...
$xml.Project.ItemGroup.ProjectConfiguration | ?{$_.Configuration -eq "Debug"} | %{$_.ParentNode.RemoveChild($_)} | Out-Null
$xml.SelectNodes("//*[contains(#Condition,'Debug')]") |%{$_.ParentNode.RemoveChild($_)} | Out-Null
if($xml.Project.ItemDefinitionGroup.ClCompile) {
$xml.Project.ItemDefinitionGroup.ClCompile | %{
#Disable SSE2
if (-not($_.EnableEnhancedInstructionSet)){
$_.AppendChild($xml.CreateElement("EnableEnhancedInstructionSet", $xml.DocumentElement.NamespaceURI)) | Out-Null
}
if($_.ParentNode.Condition.Contains("Win32")){
$_.EnableEnhancedInstructionSet = "StreamingSIMDExtensions"
}
elseif($_.ParentNode.Condition.Contains("x64")) {
$_.EnableEnhancedInstructionSet = "NotSet"
} else {
Write-Host "Neither x86 nor x64 config. Very strange!!"
}
#Disable Optimization
if (-not($_.Optimization)){
$_.AppendChild($xml.CreateElement("Optimization", $xml.DocumentElement.NamespaceURI)) | Out-Null
}
$_.Optimization = "Disabled"
}
}
$xml.Save($file);
} }`
A file is an assembly if and only if it is managed, and contains an assembly entry in its metadata. For more information on assemblies and metadata, see the topic Assembly Manifest.
How to manually determine if a file is an assembly
Start the Ildasm.exe (IL Disassembler).
Load the file you wish to test.
If ILDASM reports that the file is not a portable executable (PE) file, then it is not an assembly. For more information, see the topic How to: View Assembly Contents.
How to programmatically determine if a file is an assembly
Call the GetAssemblyName method, passing the full file path and name of the file you are testing.
If a BadImageFormatException exception is thrown, the file is not an assembly.
This example tests a DLL to see if it is an assembly.
class TestAssembly
{
static void Main()
{
try
{
System.Reflection.AssemblyName testAssembly = System.Reflection.AssemblyName.GetAssemblyName(#"C:\Windows\Microsoft.NET\Framework\v3.5\System.Net.dll");
System.Console.WriteLine("Yes, the file is an assembly.");
}
catch (System.IO.FileNotFoundException)
{
System.Console.WriteLine("The file cannot be found.");
}
catch (System.BadImageFormatException)
{
System.Console.WriteLine("The file is not an assembly.");
}
catch (System.IO.FileLoadException)
{
System.Console.WriteLine("The assembly has already been loaded.");
}
}
}
// Output (with .NET Framework 3.5 installed):
// Yes, the file is an assembly.
Framework is the highest installed version, SP is the service pack for that version.
RegistryKey installed_versions = Registry.LocalMachine.OpenSubKey(#"SOFTWARE\Microsoft\NET Framework Setup\NDP");
string[] version_names = installed_versions.GetSubKeyNames();
//version names start with 'v', eg, 'v3.5' which needs to be trimmed off before conversion
double Framework = Convert.ToDouble(version_names[version_names.Length - 1].Remove(0, 1), CultureInfo.InvariantCulture);
int SP = Convert.ToInt32(installed_versions.OpenSubKey(version_names[version_names.Length - 1]).GetValue("SP", 0));
For .Net 4.5
using System;
using Microsoft.Win32;
...
private static void Get45or451FromRegistry()
{
using (RegistryKey ndpKey = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry32).OpenSubKey("SOFTWARE\\Microsoft\\NET Framework Setup\\NDP\\v4\\Full\\")) {
int releaseKey = Convert.ToInt32(ndpKey.GetValue("Release"));
if (true) {
Console.WriteLine("Version: " + CheckFor45DotVersion(releaseKey));
}
}
}
...
// Checking the version using >= will enable forward compatibility,
// however you should always compile your code on newer versions of
// the framework to ensure your app works the same.
private static string CheckFor45DotVersion(int releaseKey)
{
if (releaseKey >= 393273) {
return "4.6 RC or later";
}
if ((releaseKey >= 379893)) {
return "4.5.2 or later";
}
if ((releaseKey >= 378675)) {
return "4.5.1 or later";
}
if ((releaseKey >= 378389)) {
return "4.5 or later";
}
// This line should never execute. A non-null release key should mean
// that 4.5 or later is installed.
return "No 4.5 or later version detected";
}
For similar purposes we use custom MSBuild fragments with common properties that we want to share between the projects, like this (build.common.props file):
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="12.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<TargetFrameworkVersion>v2.0</TargetFrameworkVersion>
<PlatformToolset>v90</PlatformToolset>
<OutputPath>$(SolutionDir)..\bin\$(PlatformPath)\$(Configuration)\</OutputPath>
<!-- whatever you need here -->
</PropertyGroup>
</Project>
And then we just include this fragment to real VS projects we want to apply these properties to:
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="12.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<CommonProps>$(SolutionDir)..\Build\build.common.props</CommonProps>
</PropertyGroup>
<Import Project="$(CommonProps)" />
<!-- the rest of the project -->
</Project>
We handle a lot of things using this approach:
common properties, as you mentioned
static analysis (FxCop, StyleCop)
digital sign of assemblies
etc.
The only disadvantage that you need to include these MSBuild fragments into each project file, but once you do that, you have all the benefits of modular build system that is easy to manage and update.
You could go the search & replace Regex way with a handwritten C#, Script, powershell or similar. But it has the following problems:
Difficult to read (Read your pretty regex in three or more months)
Difficult to enhance(New regex for new search/replace/check feature)
Easy to break (a new release/format of ms build project or a not forecast tag may not work)
Harder to test (you must check that no unintended match occurs)
Difficult to maintain (because of the above)
and the following advantages:
Not doing any extra validation which (may) let it work on any kind of project (mono or visual).
Doesn't care about \r :)
The best could be to use the Microsoft.Build.Evaluation
and build a C# tool which does all your testing/checking/fix and so on.
I've done a command line tool that use a sourcefile list (used by Mono) and update sources of csproj and another which dumps on console the csproj content. It was easy to do, pretty straightforward and easy to test also.
However, it may fail (as I've experienced it) on projects modified by "non" Ms tool (like Mono Studio) or because of missing \r....
Anyway, you can always handle it with an exception catch and a good message.
Here a sample by using Microsoft.Build.dll (don't use Microsof.Build.Engine as it is obsolete):
using System;
using Microsoft.Build.Evaluation;
internal class Program
{
private static void Main(string[] args)
{
var project = new Project("PathToYourProject.csproj");
Console.WriteLine(project.GetProperty("TargetFrameworkVersion", true, string.Empty));
Console.WriteLine(project.GetProperty("Platform", true, string.Empty));
Console.WriteLine(project.GetProperty("WarningLevel", true, string.Empty));
Console.WriteLine(project.GetProperty("TreatWarningsAsErrors", true, "false"));
Console.WriteLine(project.GetProperty("OutputPath", false, string.Empty));
Console.WriteLine(project.GetProperty("SignAssembly", true, "false"));
Console.WriteLine(project.GetProperty("AssemblyName", false, string.Empty));
Console.ReadLine();
}
}
public static class ProjectExtensions
{
public static string GetProperty(this Project project, string propertyName, bool afterEvaluation, string defaultValue)
{
var property = project.GetProperty(propertyName);
if (property != null)
{
if (afterEvaluation)
return property.EvaluatedValue;
return property.UnevaluatedValue;
}
return defaultValue;
}
}
I also faced this issue and created a small solution that creates a csv file with details to identifies the inconsistences. You can look at it in this url
https://github.com/gdlmanuv/VSProjectConsistencyChecker
I currently have an app displaying the build number in its title window. That's well and good except it means nothing to most of the users, who want to know if they have the latest build - they tend to refer to it as "last Thursday's" rather than build 1.0.8.4321.
The plan is to put the build date there instead - So "App built on 21/10/2009" for example.
I'm struggling to find a programmatic way to pull the build date out as a text string for use like this.
For the build number, I used:
Assembly.GetExecutingAssembly().GetName().Version.ToString()
after defining how those came up.
I'd like something like that for the compile date (and time, for bonus points).
Pointers here much appreciated (excuse pun if appropriate), or neater solutions...
Jeff Atwood had a few things to say about this issue in Determining Build Date the hard way.
The most reliable method turns out to be retrieving the linker timestamp from the PE header embedded in the executable file -- some C# code (by Joe Spivey) for that from the comments to Jeff's article:
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;
}
Usage example:
var linkTimeLocal = Assembly.GetExecutingAssembly().GetLinkerTime();
Note: this method works for .NET Core 1.0, but stopped working after .NET Core 1.1 - it gives random years in the 1900-2020 range.
Add below to pre-build event command line:
echo %date% %time% > "$(ProjectDir)\Resources\BuildDate.txt"
Add this file as resource,
now you have 'BuildDate' string in your resources.
To create resources, see How to create and use resources in .NET.
The way
As pointed out by #c00000fd in the comments. Microsoft is changing this. And while many people don't use the latest version of their compiler I suspect this change makes this approach unquestionably bad. And while it's a fun exercise I would recommend people to simply embed a build date into their binary through any other means necessary if it's important to track the build date of the binary itself.
This can be done with some trivial code generation which probably is the first step in your build script already. That, and the fact that ALM/Build/DevOps tools help a lot with this and should be preferred to anything else.
I leave the rest of this answer here for historical purposes only.
The new way
I changed my mind about this, and currently use this trick to get the correct build date.
#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
The old way
Well, how do you generate build numbers? Visual Studio (or the C# compiler) actually provides automatic build and revision numbers if you change the AssemblyVersion attribute to e.g. 1.0.*
What will happen is that is that the build will be equal to the number of days since January 1, 2000 local time, and for revision to be equal to the number of seconds since midnight local time, divided by 2.
see Community Content, Automatic Build and Revision numbers
e.g. 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)
Add below to pre-build event command line:
echo %date% %time% > "$(ProjectDir)\Resources\BuildDate.txt"
Add this file as resource, now you have 'BuildDate' string in your resources.
After inserting the file into the Resource (as public text file), I accessed it via
string strCompTime = Properties.Resources.BuildDate;
To create resources, see How to create and use resources in .NET.
One approach which I'm amazed no-one has mentioned yet is to use T4 Text Templates for code generation.
<## 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); } }
}
}
Pros:
Locale-independent
Allows a lot more than just the time of compilation
Cons:
Only applicable to libraries where you control the source
Requires configuring your project (and build server, if that doesn't pick it up) to execute the template in a pre-build step. (See also T4 without VS).
Lots of great answers here but I feel like I can add my own because of simplicity, performance (comparing to resource-related solutions) cross platform (works with Net Core too) and avoidance of any 3rd party tool. Just add this msbuild target to the csproj.
<Target Name="Date" BeforeTargets="BeforeBuild">
<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>
and now you have Builtin.CompileTime in this project, e.g.:
var compileTime = new DateTime(Builtin.CompileTime, DateTimeKind.Utc);
ReSharper is not gonna like it. You can ignore him or add a partial class to the project too but it works anyway.
UPD: Nowadays ReSharper have an option in a first page of Options: "MSBuild access", "Obtain data from MSBuild after each compilation". This helps with visibility of generated code.
For .NET Core projects, I adapted Postlagerkarte's answer to update the assembly Copyright field with the build date.
Directly Edit csproj
The following can be added directly to the first PropertyGroup in the csproj:
<Copyright>Copyright © $([System.DateTime]::UtcNow.Year) Travis Troyer ($([System.DateTime]::UtcNow.ToString("s")))</Copyright>
Alternative: Visual Studio Project Properties
Or paste the inner expression directly into the Copyright field in the Package section of the project properties in Visual Studio:
Copyright © $([System.DateTime]::UtcNow.Year) Travis Troyer ($([System.DateTime]::UtcNow.ToString("s")))
This can be a little confusing, because Visual Studio will evaluate the expression and display the current value in the window, but it will also update the project file appropriately behind the scenes.
Solution-wide via Directory.Build.props
You can plop the <Copyright> element above into a Directory.Build.props file in your solution root, and have it automatically applied to all projects within the directory, assuming each project does not supply its own Copyright value.
<Project>
<PropertyGroup>
<Copyright>Copyright © $([System.DateTime]::UtcNow.Year) Travis Troyer ($([System.DateTime]::UtcNow.ToString("s")))</Copyright>
</PropertyGroup>
</Project>
Directory.Build.props: Customize your build
Output
The example expression will give you a copyright like this:
Copyright © 2018 Travis Troyer (2018-05-30T14:46:23)
Retrieval
You can view the copyright information from the file properties in Windows, or grab it at runtime:
var version = FileVersionInfo.GetVersionInfo(Assembly.GetEntryAssembly().Location);
Console.WriteLine(version.LegalCopyright);
Regarding the technique of pulling build date/version info from the bytes of an assembly PE header, Microsoft has changed the default build parameters beginning with Visual Studio 15.4. The new default includes deterministic compilation, which makes a valid timestamp and automatically incremented version numbers a thing of the past. The timestamp field is still present but it gets filled with a permanent value that is a hash of something or other, but not any indication of the build time.
Some detailed background here
For those who prioritize a useful timestamp over deterministic compilation, there is a way to override the new default. You can include a tag in the .csproj file of the assembly of interest as follows:
<PropertyGroup>
...
<Deterministic>false</Deterministic>
</PropertyGroup>
Update:
I endorse the T4 text template solution described in another answer here. I used it to solve my issue cleanly without losing the benefit of deterministic compilation. One caution about it is that Visual Studio only runs the T4 compiler when the .tt file is saved, not at build time. This can be awkward if you exclude the .cs result from source control (since you expect it to be generated) and another developer checks out the code. Without resaving, they won't have the .cs file. There is a package on nuget (I think called AutoT4) that makes T4 compilation part of every build. I have not yet confronted the solution to this during production deployment, but I expect something similar to make it right.
I am just C# newbie so maybe my answer sound silly - I display the build date from the date the executable file was last written to:
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());
I tried to use File.GetCreationTime method but got weird results: the date from the command was 2012-05-29, but the date from the Window Explorer showed 2012-05-23. After searching for this discrepancy I found that the file was probably created on 2012-05-23 (as shown by Windows Explorer), but copied to the current folder on 2012-05-29 (as shown by File.GetCreationTime command) - so to be on the safe side I am using File.GetLastWriteTime command.
Zalek
In 2018 some of the above solutions do not work anymore or do not work with .NET Core.
I use the following approach which is simple and works for my .NET Core 2.0 project.
Add the following to your .csproj inside the PropertyGroup :
<Today>$([System.DateTime]::Now)</Today>
This defines a PropertyFunction which you can access in your pre build command.
Your pre-build looks like this
echo $(today) > $(ProjectDir)BuildTimeStamp.txt
Set the property of the BuildTimeStamp.txt to Embedded resource.
Now you can read the time stamp like this
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();
}
}
}
I just do:
File.GetCreationTime(GetType().Assembly.Location)
The above method can be tweaked for assemblies already loaded within the process by using the file's image in memory (as opposed to re-reading it from storage):
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;
}
}
For projects on .NET Core (.NET 5+), it can be done like this. Nice in that there are no files to add or embed, no T4, and no pre-build scripts.
Add a class like this to your project:
namespace SuperDuper
{
[AttributeUsage(AttributeTargets.Assembly)]
public class BuildDateTimeAttribute : Attribute
{
public string Date { get; set; }
public BuildDateTimeAttribute(string date)
{
Date = date;
}
}
}
Update the .csproj of your project to include something like this:
<ItemGroup>
<AssemblyAttribute Include="SuperDuper.BuildDateTime">
<_Parameter1>$([System.DateTime]::Now.ToString("s"))</_Parameter1>
</AssemblyAttribute>
</ItemGroup>
Note that _Parameter1 is a magical name - it means the first (and only) argument to the constructor of our BuildDateTime attribute class.
That's all that is needed to record the build datetime in your assembly.
And then to read the build datetime of your assembly, do something like this:
private static DateTime? getAssemblyBuildDateTime()
{
var assembly = System.Reflection.Assembly.GetExecutingAssembly();
var attr = Attribute.GetCustomAttribute(assembly, typeof(BuildDateTimeAttribute)) as BuildDateTimeAttribute;
if (DateTime.TryParse(attr?.Date, out DateTime dt))
return dt;
else
return null;
}
Note 1 (per Flydog57 in the comments): If your .csproj has property GenerateAssemblyInfo listed in it and set to false, the build won't generate assembly info and you'll get no BuildDateTime info in your assembly. So either do not mention GenerateAssemblyInfo in your .csproj (this is the default behaviour for a new project, and GenerateAssemblyInfo defaults to true if not specifically set to false), or explicitly set it to true.
Note 2 (per Teddy in the comments): In the _Parameter1 example given, we're using ::Now to make use of DateTime.Now, which is the local date and time on your computer, subject to Daylight Savings Time when applicable and your local timezone. You could if you want use ::UtcNow to make use of DateTime.UtcNow so that the build date and time is recorded as UTC/GMT.
For anyone that needs to get the compile time in Windows 8 / Windows Phone 8:
public static async Task<DateTimeOffset?> RetrieveLinkerTimestamp(Assembly assembly)
{
var pkg = Windows.ApplicationModel.Package.Current;
if (null == pkg)
{
return null;
}
var assemblyFile = await pkg.InstalledLocation.GetFileAsync(assembly.ManifestModule.Name);
if (null == assemblyFile)
{
return null;
}
using (var stream = await assemblyFile.OpenSequentialReadAsync())
{
using (var reader = new DataReader(stream))
{
const int PeHeaderOffset = 60;
const int LinkerTimestampOffset = 8;
//read first 2048 bytes from the assembly file.
byte[] b = new byte[2048];
await reader.LoadAsync((uint)b.Length);
reader.ReadBytes(b);
reader.DetachStream();
//get the pe header offset
int i = System.BitConverter.ToInt32(b, PeHeaderOffset);
//read the linker timestamp from the PE header
int secondsSince1970 = System.BitConverter.ToInt32(b, i + LinkerTimestampOffset);
var dt = new DateTimeOffset(1970, 1, 1, 0, 0, 0, DateTimeOffset.Now.Offset) + DateTimeOffset.Now.Offset;
return dt.AddSeconds(secondsSince1970);
}
}
}
For anyone that needs to get the compile time in Windows Phone 7:
public static async Task<DateTimeOffset?> RetrieveLinkerTimestampAsync(Assembly assembly)
{
const int PeHeaderOffset = 60;
const int LinkerTimestampOffset = 8;
byte[] b = new byte[2048];
try
{
var rs = Application.GetResourceStream(new Uri(assembly.ManifestModule.Name, UriKind.Relative));
using (var s = rs.Stream)
{
var asyncResult = s.BeginRead(b, 0, b.Length, null, null);
int bytesRead = await Task.Factory.FromAsync<int>(asyncResult, s.EndRead);
}
}
catch (System.IO.IOException)
{
return null;
}
int i = System.BitConverter.ToInt32(b, PeHeaderOffset);
int secondsSince1970 = System.BitConverter.ToInt32(b, i + LinkerTimestampOffset);
var dt = new DateTimeOffset(1970, 1, 1, 0, 0, 0, DateTimeOffset.Now.Offset) + DateTimeOffset.Now.Offset;
dt = dt.AddSeconds(secondsSince1970);
return dt;
}
NOTE: In all cases you're running in a sandbox, so you'll only be able to get the compile time of assemblies that you deploy with your app. (i.e. this won't work on anything in the GAC).
The option not discussed here is to insert your own data into AssemblyInfo.cs, the "AssemblyInformationalVersion" field seems appropriate - we have a couple of projects where we were doing something similar as a build step (however I'm not entirely happy with the way that works so don't really want to reproduce what we've got).
There's an article on the subject on codeproject: http://www.codeproject.com/KB/dotnet/Customizing_csproj_files.aspx
I needed a universal solution that worked with a NETStandard project on any platform (iOS, Android, and Windows.) To accomplish this, I decided to automatically generate a CS file via a PowerShell script. Here is the PowerShell script:
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
Save the PowerScript file as GenBuildDate.ps1 and add it your project. Finally, add the following line to your Pre-Build event:
powershell -File $(ProjectDir)GenBuildDate.ps1 -outputFile $(ProjectDir)BuildDate.cs
Make sure BuildDate.cs is included in your project. Works like a champ on any OS!
A different, PCL-friendly approach would be to use an MSBuild inline task to substitute the build time into a string that is returned by a property on the app. We are using this approach successfully in an app that has Xamarin.Forms, Xamarin.Android, and Xamarin.iOS projects.
EDIT:
Simplified by moving all of the logic into the SetBuildDate.targets file, and using Regex instead of simple string replace so that the file can be modified by each build without a "reset".
The MSBuild inline task definition (saved in a SetBuildDate.targets file local to the Xamarin.Forms project for this example):
<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>
Invoking the above inline task in the Xamarin.Forms csproj file in 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>
The FilePath property is set to a BuildMetadata.cs file in the Xamarin.Forms project that contains a simple class with a string property BuildDate, into which the build time will be substituted:
public class BuildMetadata
{
public static string BuildDate => "This can be any arbitrary string";
}
Add this file BuildMetadata.cs to project. It will be modified by every build, but in a manner that allows repeated builds (repeated replacements), so you may include or omit it in source control as desired.
You can use this project: https://github.com/dwcullop/BuildInfo
It leverages T4 to automate the build date timestamp. There are several versions (different branches) including one that gives you the Git Hash of the currently checked out branch, if you're into that sort of thing.
Disclosure: I wrote the module.
You could use a project post-build event to write a text file to your target directory with the current datetime. You could then read the value at run-time. It's a little hacky, but it should work.
I'm not sure, but maybe the Build Incrementer helps.
A small update on the "New Way" answer from Jhon.
You need to build the path instead of using the CodeBase string when working with ASP.NET/MVC
var codeBase = assembly.GetName().CodeBase;
UriBuilder uri = new UriBuilder(codeBase);
string path = Uri.UnescapeDataString(uri.Path);
You could launch an extra step in the build process that writes a date stamp to a file which can then be displayed.
On the projects properties tab look at the build events tab. There is an option to execute a pre or post build command.
I used Abdurrahim's suggestion. However, it seemed to give a weird time format and also added the abbreviation for the day as part of the build date; example: Sun 12/24/2017 13:21:05.43. I only needed just the date so I had to eliminate the rest using substring.
After adding the echo %date% %time% > "$(ProjectDir)\Resources\BuildDate.txt"to the pre-build event, I just did the following:
string strBuildDate = YourNamespace.Properties.Resources.BuildDate;
string strTrimBuildDate = strBuildDate.Substring(4).Remove(10);
The good news here is that it worked.
A full solution step by step for Visual Studio 2019, like the one I wish I had found when I began years ago.
Add a text resource file
Access the properties of your project: from the solution explorer, select your project, then right-click -> properties, or Alt+Enter. In the Resources tab, choose Files (Ctrl+5). Then Add Resource / Add New Text File. In the popup message, type the name of your resource, for example BuildDate: this will create a new text file BuildDate.txt in your Project/Resources folder, include it as Project file, and register it as a resource, which can then be accessed via Properties.Resources in C#, or My.Resources in VB.
Automatically update the resource file each time you build
Now you can tell Visual Studio to write a date into this file, each time it builds or rebuilds the project. For this, go to the Compile tab of the Project Properties, choose Build Events, and copy/paste the following into the "Pre-Build event command line" textbox:
powershell -Command "((Get-Date).ToUniversalTime()).ToString(\"s\") | Out-File '$(ProjectDir)Resources\BuildDate.txt'"
This line will locate BuildDate.txt and write today/NowUtc's date and time under the ISO8601 format, such as 2021-09-07T16:08:35
Obtain the build date at run-time by reading the file
You can then retrieve this date from your code at run-time, via the following helper (C#):
DateTime CurrentBuildDate = DateTime.Parse(Properties.Resources.BuildDate, null, System.Globalization.DateTimeStyles.RoundtripKind);
Credits
Base idea: https://stackoverflow.com/a/15502932/10794555
Improved through powershell by: https://stackoverflow.com/users/84898/dbruning
Stable parsing of ISO8601: How to create a .NET DateTime from ISO 8601 format
GetLastWriteTime isn't changed if you copy the assembly to another location.
public static class AssemblyExtensions
{
public static DateTime GetLinkerTime(this Assembly assembly)
{
return File.GetLastWriteTime(assembly.Location).ToLocalTime();
}
}
If this is a windows app, you can just use the application executable path:
new System.IO.FileInfo(Application.ExecutablePath).LastWriteTime.ToString("yyyy.MM.dd")
I just added pre-build event command:
powershell -Command Get-Date -Format 'yyyy-MM-ddTHH:mm:sszzz' > Resources\BuildDateTime.txt
in the project properties to generate a resource file that is then easy to read from the code.
I had difficulties with the suggested solutions with my project, a .Net Core 2.1 web application. I combined various suggestions from above and simplified, and also converted the date to my required format.
The echo command:
echo Build %DATE:~-4%/%DATE:~-10,2%/%DATE:~-7,2% %time% > "$(ProjectDir)\BuildDate.txt"
The code:
Logger.Info(File.ReadAllText(#"./BuildDate.txt").Trim());
It seems to work. The output:
2021-03-25 18:41:40,877 [1] INFO Config - Build 2021/03/25 18:41:37.58
Nothing very original, I just combined suggestions from here and other related questions, and simplified.
For .NET 5 I've used this method successfully. (Found here).
Add this to the .csproj file:
<SourceRevisionId>build$([System.DateTime]::UtcNow.ToString("yyyyMMddHHmmss"))</SourceRevisionId>
Method for getting build date:
private static DateTime GetBuildDate(Assembly assembly)
{
const string BuildVersionMetadataPrefix = "+build";
var attribute = assembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>();
if (attribute?.InformationalVersion != null)
{
var value = attribute.InformationalVersion;
var index = value.IndexOf(BuildVersionMetadataPrefix);
if (index > 0)
{
value = value.Substring(index + BuildVersionMetadataPrefix.Length);
if (DateTime.TryParseExact(value, "yyyyMMddHHmmss", CultureInfo.InvariantCulture, DateTimeStyles.None, out var result))
{
return result;
}
}
}
return default;
}
Usage:
var buildTime = GetBuildDate(Assembly.GetExecutingAssembly());
buildTime = buildTime.ToLocalTime();
Use the following code.
File.GetCreationTime(Assembly.GetExecutingAssembly().Location)
It will return the date of creation of last dll. If debug is running, then it will display current date and time. I modified some code from one of the answers, because i couldn't comment on the answer. Comment for further discussions.
I currently have an app displaying the build number in its title window. That's well and good except it means nothing to most of the users, who want to know if they have the latest build - they tend to refer to it as "last Thursday's" rather than build 1.0.8.4321.
The plan is to put the build date there instead - So "App built on 21/10/2009" for example.
I'm struggling to find a programmatic way to pull the build date out as a text string for use like this.
For the build number, I used:
Assembly.GetExecutingAssembly().GetName().Version.ToString()
after defining how those came up.
I'd like something like that for the compile date (and time, for bonus points).
Pointers here much appreciated (excuse pun if appropriate), or neater solutions...
Jeff Atwood had a few things to say about this issue in Determining Build Date the hard way.
The most reliable method turns out to be retrieving the linker timestamp from the PE header embedded in the executable file -- some C# code (by Joe Spivey) for that from the comments to Jeff's article:
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;
}
Usage example:
var linkTimeLocal = Assembly.GetExecutingAssembly().GetLinkerTime();
Note: this method works for .NET Core 1.0, but stopped working after .NET Core 1.1 - it gives random years in the 1900-2020 range.
Add below to pre-build event command line:
echo %date% %time% > "$(ProjectDir)\Resources\BuildDate.txt"
Add this file as resource,
now you have 'BuildDate' string in your resources.
To create resources, see How to create and use resources in .NET.
The way
As pointed out by #c00000fd in the comments. Microsoft is changing this. And while many people don't use the latest version of their compiler I suspect this change makes this approach unquestionably bad. And while it's a fun exercise I would recommend people to simply embed a build date into their binary through any other means necessary if it's important to track the build date of the binary itself.
This can be done with some trivial code generation which probably is the first step in your build script already. That, and the fact that ALM/Build/DevOps tools help a lot with this and should be preferred to anything else.
I leave the rest of this answer here for historical purposes only.
The new way
I changed my mind about this, and currently use this trick to get the correct build date.
#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
The old way
Well, how do you generate build numbers? Visual Studio (or the C# compiler) actually provides automatic build and revision numbers if you change the AssemblyVersion attribute to e.g. 1.0.*
What will happen is that is that the build will be equal to the number of days since January 1, 2000 local time, and for revision to be equal to the number of seconds since midnight local time, divided by 2.
see Community Content, Automatic Build and Revision numbers
e.g. 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)
Add below to pre-build event command line:
echo %date% %time% > "$(ProjectDir)\Resources\BuildDate.txt"
Add this file as resource, now you have 'BuildDate' string in your resources.
After inserting the file into the Resource (as public text file), I accessed it via
string strCompTime = Properties.Resources.BuildDate;
To create resources, see How to create and use resources in .NET.
One approach which I'm amazed no-one has mentioned yet is to use T4 Text Templates for code generation.
<## 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); } }
}
}
Pros:
Locale-independent
Allows a lot more than just the time of compilation
Cons:
Only applicable to libraries where you control the source
Requires configuring your project (and build server, if that doesn't pick it up) to execute the template in a pre-build step. (See also T4 without VS).
Lots of great answers here but I feel like I can add my own because of simplicity, performance (comparing to resource-related solutions) cross platform (works with Net Core too) and avoidance of any 3rd party tool. Just add this msbuild target to the csproj.
<Target Name="Date" BeforeTargets="BeforeBuild">
<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>
and now you have Builtin.CompileTime in this project, e.g.:
var compileTime = new DateTime(Builtin.CompileTime, DateTimeKind.Utc);
ReSharper is not gonna like it. You can ignore him or add a partial class to the project too but it works anyway.
UPD: Nowadays ReSharper have an option in a first page of Options: "MSBuild access", "Obtain data from MSBuild after each compilation". This helps with visibility of generated code.
For .NET Core projects, I adapted Postlagerkarte's answer to update the assembly Copyright field with the build date.
Directly Edit csproj
The following can be added directly to the first PropertyGroup in the csproj:
<Copyright>Copyright © $([System.DateTime]::UtcNow.Year) Travis Troyer ($([System.DateTime]::UtcNow.ToString("s")))</Copyright>
Alternative: Visual Studio Project Properties
Or paste the inner expression directly into the Copyright field in the Package section of the project properties in Visual Studio:
Copyright © $([System.DateTime]::UtcNow.Year) Travis Troyer ($([System.DateTime]::UtcNow.ToString("s")))
This can be a little confusing, because Visual Studio will evaluate the expression and display the current value in the window, but it will also update the project file appropriately behind the scenes.
Solution-wide via Directory.Build.props
You can plop the <Copyright> element above into a Directory.Build.props file in your solution root, and have it automatically applied to all projects within the directory, assuming each project does not supply its own Copyright value.
<Project>
<PropertyGroup>
<Copyright>Copyright © $([System.DateTime]::UtcNow.Year) Travis Troyer ($([System.DateTime]::UtcNow.ToString("s")))</Copyright>
</PropertyGroup>
</Project>
Directory.Build.props: Customize your build
Output
The example expression will give you a copyright like this:
Copyright © 2018 Travis Troyer (2018-05-30T14:46:23)
Retrieval
You can view the copyright information from the file properties in Windows, or grab it at runtime:
var version = FileVersionInfo.GetVersionInfo(Assembly.GetEntryAssembly().Location);
Console.WriteLine(version.LegalCopyright);
Regarding the technique of pulling build date/version info from the bytes of an assembly PE header, Microsoft has changed the default build parameters beginning with Visual Studio 15.4. The new default includes deterministic compilation, which makes a valid timestamp and automatically incremented version numbers a thing of the past. The timestamp field is still present but it gets filled with a permanent value that is a hash of something or other, but not any indication of the build time.
Some detailed background here
For those who prioritize a useful timestamp over deterministic compilation, there is a way to override the new default. You can include a tag in the .csproj file of the assembly of interest as follows:
<PropertyGroup>
...
<Deterministic>false</Deterministic>
</PropertyGroup>
Update:
I endorse the T4 text template solution described in another answer here. I used it to solve my issue cleanly without losing the benefit of deterministic compilation. One caution about it is that Visual Studio only runs the T4 compiler when the .tt file is saved, not at build time. This can be awkward if you exclude the .cs result from source control (since you expect it to be generated) and another developer checks out the code. Without resaving, they won't have the .cs file. There is a package on nuget (I think called AutoT4) that makes T4 compilation part of every build. I have not yet confronted the solution to this during production deployment, but I expect something similar to make it right.
I am just C# newbie so maybe my answer sound silly - I display the build date from the date the executable file was last written to:
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());
I tried to use File.GetCreationTime method but got weird results: the date from the command was 2012-05-29, but the date from the Window Explorer showed 2012-05-23. After searching for this discrepancy I found that the file was probably created on 2012-05-23 (as shown by Windows Explorer), but copied to the current folder on 2012-05-29 (as shown by File.GetCreationTime command) - so to be on the safe side I am using File.GetLastWriteTime command.
Zalek
In 2018 some of the above solutions do not work anymore or do not work with .NET Core.
I use the following approach which is simple and works for my .NET Core 2.0 project.
Add the following to your .csproj inside the PropertyGroup :
<Today>$([System.DateTime]::Now)</Today>
This defines a PropertyFunction which you can access in your pre build command.
Your pre-build looks like this
echo $(today) > $(ProjectDir)BuildTimeStamp.txt
Set the property of the BuildTimeStamp.txt to Embedded resource.
Now you can read the time stamp like this
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();
}
}
}
I just do:
File.GetCreationTime(GetType().Assembly.Location)
The above method can be tweaked for assemblies already loaded within the process by using the file's image in memory (as opposed to re-reading it from storage):
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;
}
}
For projects on .NET Core (.NET 5+), it can be done like this. Nice in that there are no files to add or embed, no T4, and no pre-build scripts.
Add a class like this to your project:
namespace SuperDuper
{
[AttributeUsage(AttributeTargets.Assembly)]
public class BuildDateTimeAttribute : Attribute
{
public string Date { get; set; }
public BuildDateTimeAttribute(string date)
{
Date = date;
}
}
}
Update the .csproj of your project to include something like this:
<ItemGroup>
<AssemblyAttribute Include="SuperDuper.BuildDateTime">
<_Parameter1>$([System.DateTime]::Now.ToString("s"))</_Parameter1>
</AssemblyAttribute>
</ItemGroup>
Note that _Parameter1 is a magical name - it means the first (and only) argument to the constructor of our BuildDateTime attribute class.
That's all that is needed to record the build datetime in your assembly.
And then to read the build datetime of your assembly, do something like this:
private static DateTime? getAssemblyBuildDateTime()
{
var assembly = System.Reflection.Assembly.GetExecutingAssembly();
var attr = Attribute.GetCustomAttribute(assembly, typeof(BuildDateTimeAttribute)) as BuildDateTimeAttribute;
if (DateTime.TryParse(attr?.Date, out DateTime dt))
return dt;
else
return null;
}
Note 1 (per Flydog57 in the comments): If your .csproj has property GenerateAssemblyInfo listed in it and set to false, the build won't generate assembly info and you'll get no BuildDateTime info in your assembly. So either do not mention GenerateAssemblyInfo in your .csproj (this is the default behaviour for a new project, and GenerateAssemblyInfo defaults to true if not specifically set to false), or explicitly set it to true.
Note 2 (per Teddy in the comments): In the _Parameter1 example given, we're using ::Now to make use of DateTime.Now, which is the local date and time on your computer, subject to Daylight Savings Time when applicable and your local timezone. You could if you want use ::UtcNow to make use of DateTime.UtcNow so that the build date and time is recorded as UTC/GMT.
For anyone that needs to get the compile time in Windows 8 / Windows Phone 8:
public static async Task<DateTimeOffset?> RetrieveLinkerTimestamp(Assembly assembly)
{
var pkg = Windows.ApplicationModel.Package.Current;
if (null == pkg)
{
return null;
}
var assemblyFile = await pkg.InstalledLocation.GetFileAsync(assembly.ManifestModule.Name);
if (null == assemblyFile)
{
return null;
}
using (var stream = await assemblyFile.OpenSequentialReadAsync())
{
using (var reader = new DataReader(stream))
{
const int PeHeaderOffset = 60;
const int LinkerTimestampOffset = 8;
//read first 2048 bytes from the assembly file.
byte[] b = new byte[2048];
await reader.LoadAsync((uint)b.Length);
reader.ReadBytes(b);
reader.DetachStream();
//get the pe header offset
int i = System.BitConverter.ToInt32(b, PeHeaderOffset);
//read the linker timestamp from the PE header
int secondsSince1970 = System.BitConverter.ToInt32(b, i + LinkerTimestampOffset);
var dt = new DateTimeOffset(1970, 1, 1, 0, 0, 0, DateTimeOffset.Now.Offset) + DateTimeOffset.Now.Offset;
return dt.AddSeconds(secondsSince1970);
}
}
}
For anyone that needs to get the compile time in Windows Phone 7:
public static async Task<DateTimeOffset?> RetrieveLinkerTimestampAsync(Assembly assembly)
{
const int PeHeaderOffset = 60;
const int LinkerTimestampOffset = 8;
byte[] b = new byte[2048];
try
{
var rs = Application.GetResourceStream(new Uri(assembly.ManifestModule.Name, UriKind.Relative));
using (var s = rs.Stream)
{
var asyncResult = s.BeginRead(b, 0, b.Length, null, null);
int bytesRead = await Task.Factory.FromAsync<int>(asyncResult, s.EndRead);
}
}
catch (System.IO.IOException)
{
return null;
}
int i = System.BitConverter.ToInt32(b, PeHeaderOffset);
int secondsSince1970 = System.BitConverter.ToInt32(b, i + LinkerTimestampOffset);
var dt = new DateTimeOffset(1970, 1, 1, 0, 0, 0, DateTimeOffset.Now.Offset) + DateTimeOffset.Now.Offset;
dt = dt.AddSeconds(secondsSince1970);
return dt;
}
NOTE: In all cases you're running in a sandbox, so you'll only be able to get the compile time of assemblies that you deploy with your app. (i.e. this won't work on anything in the GAC).
The option not discussed here is to insert your own data into AssemblyInfo.cs, the "AssemblyInformationalVersion" field seems appropriate - we have a couple of projects where we were doing something similar as a build step (however I'm not entirely happy with the way that works so don't really want to reproduce what we've got).
There's an article on the subject on codeproject: http://www.codeproject.com/KB/dotnet/Customizing_csproj_files.aspx
I needed a universal solution that worked with a NETStandard project on any platform (iOS, Android, and Windows.) To accomplish this, I decided to automatically generate a CS file via a PowerShell script. Here is the PowerShell script:
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
Save the PowerScript file as GenBuildDate.ps1 and add it your project. Finally, add the following line to your Pre-Build event:
powershell -File $(ProjectDir)GenBuildDate.ps1 -outputFile $(ProjectDir)BuildDate.cs
Make sure BuildDate.cs is included in your project. Works like a champ on any OS!
A different, PCL-friendly approach would be to use an MSBuild inline task to substitute the build time into a string that is returned by a property on the app. We are using this approach successfully in an app that has Xamarin.Forms, Xamarin.Android, and Xamarin.iOS projects.
EDIT:
Simplified by moving all of the logic into the SetBuildDate.targets file, and using Regex instead of simple string replace so that the file can be modified by each build without a "reset".
The MSBuild inline task definition (saved in a SetBuildDate.targets file local to the Xamarin.Forms project for this example):
<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>
Invoking the above inline task in the Xamarin.Forms csproj file in 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>
The FilePath property is set to a BuildMetadata.cs file in the Xamarin.Forms project that contains a simple class with a string property BuildDate, into which the build time will be substituted:
public class BuildMetadata
{
public static string BuildDate => "This can be any arbitrary string";
}
Add this file BuildMetadata.cs to project. It will be modified by every build, but in a manner that allows repeated builds (repeated replacements), so you may include or omit it in source control as desired.
You can use this project: https://github.com/dwcullop/BuildInfo
It leverages T4 to automate the build date timestamp. There are several versions (different branches) including one that gives you the Git Hash of the currently checked out branch, if you're into that sort of thing.
Disclosure: I wrote the module.
You could use a project post-build event to write a text file to your target directory with the current datetime. You could then read the value at run-time. It's a little hacky, but it should work.
I'm not sure, but maybe the Build Incrementer helps.
A small update on the "New Way" answer from Jhon.
You need to build the path instead of using the CodeBase string when working with ASP.NET/MVC
var codeBase = assembly.GetName().CodeBase;
UriBuilder uri = new UriBuilder(codeBase);
string path = Uri.UnescapeDataString(uri.Path);
You could launch an extra step in the build process that writes a date stamp to a file which can then be displayed.
On the projects properties tab look at the build events tab. There is an option to execute a pre or post build command.
I used Abdurrahim's suggestion. However, it seemed to give a weird time format and also added the abbreviation for the day as part of the build date; example: Sun 12/24/2017 13:21:05.43. I only needed just the date so I had to eliminate the rest using substring.
After adding the echo %date% %time% > "$(ProjectDir)\Resources\BuildDate.txt"to the pre-build event, I just did the following:
string strBuildDate = YourNamespace.Properties.Resources.BuildDate;
string strTrimBuildDate = strBuildDate.Substring(4).Remove(10);
The good news here is that it worked.
A full solution step by step for Visual Studio 2019, like the one I wish I had found when I began years ago.
Add a text resource file
Access the properties of your project: from the solution explorer, select your project, then right-click -> properties, or Alt+Enter. In the Resources tab, choose Files (Ctrl+5). Then Add Resource / Add New Text File. In the popup message, type the name of your resource, for example BuildDate: this will create a new text file BuildDate.txt in your Project/Resources folder, include it as Project file, and register it as a resource, which can then be accessed via Properties.Resources in C#, or My.Resources in VB.
Automatically update the resource file each time you build
Now you can tell Visual Studio to write a date into this file, each time it builds or rebuilds the project. For this, go to the Compile tab of the Project Properties, choose Build Events, and copy/paste the following into the "Pre-Build event command line" textbox:
powershell -Command "((Get-Date).ToUniversalTime()).ToString(\"s\") | Out-File '$(ProjectDir)Resources\BuildDate.txt'"
This line will locate BuildDate.txt and write today/NowUtc's date and time under the ISO8601 format, such as 2021-09-07T16:08:35
Obtain the build date at run-time by reading the file
You can then retrieve this date from your code at run-time, via the following helper (C#):
DateTime CurrentBuildDate = DateTime.Parse(Properties.Resources.BuildDate, null, System.Globalization.DateTimeStyles.RoundtripKind);
Credits
Base idea: https://stackoverflow.com/a/15502932/10794555
Improved through powershell by: https://stackoverflow.com/users/84898/dbruning
Stable parsing of ISO8601: How to create a .NET DateTime from ISO 8601 format
GetLastWriteTime isn't changed if you copy the assembly to another location.
public static class AssemblyExtensions
{
public static DateTime GetLinkerTime(this Assembly assembly)
{
return File.GetLastWriteTime(assembly.Location).ToLocalTime();
}
}
If this is a windows app, you can just use the application executable path:
new System.IO.FileInfo(Application.ExecutablePath).LastWriteTime.ToString("yyyy.MM.dd")
I just added pre-build event command:
powershell -Command Get-Date -Format 'yyyy-MM-ddTHH:mm:sszzz' > Resources\BuildDateTime.txt
in the project properties to generate a resource file that is then easy to read from the code.
I had difficulties with the suggested solutions with my project, a .Net Core 2.1 web application. I combined various suggestions from above and simplified, and also converted the date to my required format.
The echo command:
echo Build %DATE:~-4%/%DATE:~-10,2%/%DATE:~-7,2% %time% > "$(ProjectDir)\BuildDate.txt"
The code:
Logger.Info(File.ReadAllText(#"./BuildDate.txt").Trim());
It seems to work. The output:
2021-03-25 18:41:40,877 [1] INFO Config - Build 2021/03/25 18:41:37.58
Nothing very original, I just combined suggestions from here and other related questions, and simplified.
For .NET 5 I've used this method successfully. (Found here).
Add this to the .csproj file:
<SourceRevisionId>build$([System.DateTime]::UtcNow.ToString("yyyyMMddHHmmss"))</SourceRevisionId>
Method for getting build date:
private static DateTime GetBuildDate(Assembly assembly)
{
const string BuildVersionMetadataPrefix = "+build";
var attribute = assembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>();
if (attribute?.InformationalVersion != null)
{
var value = attribute.InformationalVersion;
var index = value.IndexOf(BuildVersionMetadataPrefix);
if (index > 0)
{
value = value.Substring(index + BuildVersionMetadataPrefix.Length);
if (DateTime.TryParseExact(value, "yyyyMMddHHmmss", CultureInfo.InvariantCulture, DateTimeStyles.None, out var result))
{
return result;
}
}
}
return default;
}
Usage:
var buildTime = GetBuildDate(Assembly.GetExecutingAssembly());
buildTime = buildTime.ToLocalTime();
Use the following code.
File.GetCreationTime(Assembly.GetExecutingAssembly().Location)
It will return the date of creation of last dll. If debug is running, then it will display current date and time. I modified some code from one of the answers, because i couldn't comment on the answer. Comment for further discussions.