Click here to Skip to main content
15,860,861 members
Articles / Programming Languages / C#
Article

Timestamping assemblies with Build date and time.

Rate me:
Please Sign up or sign in to vote.
4.96/5 (21 votes)
27 Apr 2012CPOL5 min read 64.5K   520   26   14
It surprised me to find that there is no simple way to find out when a .NET assembly was built. You can work it out from the revision number (provided you only use the "standard" numbering scheme) but it's not obvious. This provides a simple way to timestamp individual assemblies at build time.

Introduction

Recently I was answering a QA question on build time stamping, and realised that it is a surprising omission from .NET - there is no simple way to get the date and time of a build from an assembly. This tip rectifies this, and introduces one of the more advanced Visual Studio features. 

Background

Why would you want to timestamp a build?  If for no other reason than you can easily check when it was produced! You date all your memos, emails and SMS messages, don't you? Or at least, the system does for you, and you almost certainly organise them by that time stamp. Why should assemblies be any different? 

There are a couple of ways you can work out a build date:

  1. From the version number 
  2. From the assembly file CreatedOn attribute
  3. Embed it in your file at build time 

Using the version number 

The first of these options requires you to enable it:

  1. Open the project properties sheet (not the pane - double click "properties" under the project name) 
  2. Under the Application tab,  press the Advanced button.
  3. In the version number field, replace the third digit with an asterisk character, and delete the final number. 

Now, when you build your assembly, you will get versions such as "1.0.4500.17115" where "4500" is the days since Jan 1st, 2000, and the "17115" is the seconds since midnight (midnight, where I don't know - it isn't here!) 

And yes, it is easy to work back to today's date. But there is a problem - this number is the "binding version" - if you are using Strongly Named Assemblies then any change to this number will cause the binding to fail. Needless to say, you can't use this format in the FileVersion, where it could be useful! I do not recommend this approach.

Using the File Date Information 

You can pick up the File created date attribute, and it's not difficult to do:

C#
Assembly asm = Assembly.GetExecutingAssembly();
FileInfo fi = new FileInfo(asm.Location);
Console.WriteLine(fi.CreationTime);
Console.WriteLine(fi.LastWriteTime);

Unfortunately, there are problems here as well:

  1. CreationTime is the first time the file was created - i.e. the first time you built the EXE, not the last. 
  2. LastWriteTime is the last time the file was written - i.e. the last time you built it.
Not all operating systems support LastWriteTime (Windows 98 for example) so it is not infallible.

Embedding in your file 

Embedding is surprisingly easy: Visual studio provides a PreBuild and a PostBuild step for each project which can execute any application. All we have to do is write an application to generate a time stamp as a source file:

  1. Open Visual Studio, and create a new C# Project: Call it GenerateTimeStampFile and make it a WinForms application.
  2. When the project is constructed, highlight the only Form in the project (normally called Form1) and delete it. We don't need it as we will not have a user interface. (I didn't construct it as a Console Application, because that would mean getting rid of the console itself, which is a little harder to do).
  3. Open the Program.cs file and replace it's content: 

C#
using System;
using System.IO;

namespace GenerateTimeStampFile
    {
    static class Program
        {
        /// <summary>
        /// The main entry point for the application.
        /// </summary>
        [STAThread]
        static void Main()
            {
            string[] args = Environment.GetCommandLineArgs();
            if (args.Length > 1)
                {
                // Assume new file required
                string fileName = null;
                string nameSpace = "TimeStamp";
                string[] TimestampFile = new string[]
                    {@"using System;",
                     @"// The namespace can be overidden by the /N option:",
                     @"// GenerateTimeStampFile file.cs /N:MyNameSpace",
                     @"// Such settings will override your value here.",
                     @"namespace TimeStamp",
                     @"    {",
                     @"    /// <summary>",
                     @"    /// Static Timestamp related data.",
                     @"    /// </summary>",
                     @"    /// <remarks>",
                     @"    /// THIS FILE IS CHANGED BY EXTERNAL PROGRAMS.",
                     @"    /// Do not modify the namespace, as it may be overwritten. You can",
                     @"    ///    set the namespace with the /N option.",
                     @"    /// Do not modify the definition of BuildAt as your changes will be discarded.",
                     @"    /// Do not modify the definition of TimeStampedBy as your changes will be discarded.",
                     @"    /// </remarks>",
                     @"    public static class Timestamp",
                     @"        {",
                     @"        /// <summary>",
                     @"        /// The time stamp",
                     @"        /// </summary>",
                     @"        /// <remarks>",
                     @"        /// Do not modify the definition of BuildAt as your changes will be discarded.",
                     @"        /// </remarks>",
                     @"        public static DateTime BuildAt { get { return new DateTime(???); } } //--**",
                     @"        /// <summary>",
                     @"        /// The program that time stamped it.",
                     @"        /// </summary>",
                     @"        /// <remarks>",
                     @"        /// Do not modify the definition of TimeStampedBy as your changes will be discarded.",
                     @"        /// </remarks>",
                     @"        public static string TimeStampedBy { get { return @""???""; } } //--**",
                     @"        }",
                     @"    }" };
                for (int i = 1; i < args.Length; i++)
                    {
                    if (!args[i].StartsWith("/"))
                        {
                        // File name
                        fileName = args[i];
                        if (File.Exists(fileName))
                            {
                            TimestampFile = File.ReadAllLines(fileName);
                            }
                        }
                    else
                        {
                        // It's an option
                        if (args[i].StartsWith("/N:"))
                            {
                            // Set the namespace for the Timestamp class.
                            nameSpace = args[i].Substring(3);
                            }
                        }
                    }
                if (!string.IsNullOrWhiteSpace(fileName))
                    {
                    // We have an output location.
                    // Replace the namespace and timestamp.
                    for(int i = 0; i < TimestampFile.Length; i++)
                        {
                        string line = TimestampFile[i].Trim();
                        if (line.StartsWith("namespace"))
                            {
                            TimestampFile[i] = "namespace " + nameSpace;
                            }
                        else if (line.EndsWith("//--**"))
                            {
                            // Special
                            if (line.Contains("DateTime BuildAt"))
                                {
                                TimestampFile[i] = @"        public static DateTime BuildAt { get { return new DateTime(" +
                                                   DateTime.Now.Ticks.ToString() +
                                                   @"); } } //--**";
                                }
                            else if (line.Contains("string TimeStampedBy"))
                                {
                                TimestampFile[i] = @"        public static string TimeStampedBy { get { return @""GenerateTimeStampFile V1.0""; } } //--**";
                                }
                            }
                        }
                    File.WriteAllLines(fileName, TimestampFile);
                    }
                }
            Environment.ExitCode = 0;       //Set no error
            }
        }
    }  

You can now build the project - preferably as the Release version. 

It is important that you locate the EXE file you just generated - it may help to copy it to the Program Files folder so you can find it easily later. 

Using the code   

  • In the assembly you want to time stamp double click on Properties in the Solution Explorer. 
  • In the Build Events tab, fill in the Pre-build event command line text box: with a single line.
Assuming that you moved the EXE to Program Files:

"C:\Program Files\GenerateTimeStampFile.exe" "$(ProjectDir)Timestamp.cs" "/N:MyNamespace" 

The quote marks are important!

The /N:MyNamespace part is an optional specifier to put the timestamp into your project namespace  - if you do not use it, then it will go in the TimeStamp namespace. Using your own allows you to more easily have separate time stamps for each assembly in a project. Using the default allows them all to refer to the same one. 

Build your application. Nothing will change!

In the solution explorer for your project, right click and select "Add...Existing Item..."

Select the TimeStamp.cs file, and press "Add".

You can now code to use the time stamp:

C#
Console.WriteLine(Timestamp.BuildAt.ToString() + " by " + Timestamp.TimeStampedBy);

If you used the default namespace, then you will need to add the appropriate using statement. 

How does it work? 

When you build your project, Visual Studio executes the commands in the pre-build step. In the example above, it looks in "C:\Program Files" for an executable called "GenerateTimeStampFile.exe" and runs it with the given parameters - having replaced "$(...)" values with Visual Studio project and / or solution related strings. 

So, if in the example above your project was in the folder "C:\Users\Griff\Documents\My Projects\UtilityApplications\SetShares" then it is the equivalent of bringing up a command prompt and manually running GenerateTimeStampFile with two parameters:

  1. "C:\Users\griff\Documents\My Projects\UtilityApplications\SetShares\Timestamp.cs"  
  2. "/N:MyNamespace"
The GenerateTimeStampFile program runs,  checks that it has at least one parameter (other than the program executable path) and then:

  1. Generates a dummy file in case the target destination does not exist. This allows for an easy start up.
  2. Processes the application parameters: it sets the file name and path to output to, and optionally the namespace to use - this defaults to "TimeStamp". If the file exists, the default file is discarded and the new one loaded, allowing you to add items to the Timestamp class if you need to.
  3. It then checks that a file was specified, and if so it loops through each line in the current file content looking for elements to replace: the namespace and any line ending with a "--**" comment - these lines will be replaced completely with the stamped versions. 
  4. The file is then written, and the program exits indicating no error back to VS.
When the pre-build step is complete, Visual studio then compiles all necessary files - which will include the modified Timestamp.cs file, adding the timestamp to the assembly. 

Notes

This started life as a simple quick and dirty application, followed by a Tip/Trick. But as such things do, it "growed like Topsy" so I converted it to a small article - it was way too big for a tip! 

History

Original version (27/04/2012 12:35:53 by GenerateTimeStampFile V1.0) 

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
CEO
Wales Wales
Born at an early age, he grew older. At the same time, his hair grew longer, and was tied up behind his head.
Has problems spelling the word "the".
Invented the portable cat-flap.
Currently, has not died yet. Or has he?

Comments and Discussions

 
QuestionLinker Date/Time Pin
HiAle27-Apr-12 5:26
HiAle27-Apr-12 5:26 
AnswerRe: Linker Date/Time Pin
OriginalGriff27-Apr-12 5:58
mveOriginalGriff27-Apr-12 5:58 
You're welcome! I didn't know about that one - is it documented anywhere?
Ideological Purity is no substitute for being able to stick your thumb down a pipe to stop the water

GeneralRe: Linker Date/Time Pin
Luc Pattyn29-Apr-12 23:21
sitebuilderLuc Pattyn29-Apr-12 23:21 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.