views:

1586

answers:

9

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, or neater solutions...

Mark

A: 

I'm not sure, but maybe the Build Incrementer helps.

Bobby

Bobby
+1  A: 

It's in vb, but should be fairly easy to follow. Check out this blog post on Coding Horror. http://www.codinghorror.com/blog/archives/000264.html

brad.huffman
+17  A: 

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:

private DateTime RetrieveLinkerTimestamp()
{
    string filePath = System.Reflection.Assembly.GetCallingAssembly().Location;
    const int c_PeHeaderOffset = 60;
    const int c_LinkerTimestampOffset = 8;
    byte[] b = new byte[2048];
    System.IO.Stream s = null;

    try
    {
        s = new System.IO.FileStream(filePath, System.IO.FileMode.Open, System.IO.FileAccess.Read);
        s.Read(b, 0, 2048);
    }
    finally
    {
        if (s != null)
        {
            s.Close();
        }
    }

    int i = System.BitConverter.ToInt32(b, c_PeHeaderOffset);
    int secondsSince1970 = System.BitConverter.ToInt32(b, i + c_LinkerTimestampOffset);
    DateTime dt = new DateTime(1970, 1, 1, 0, 0, 0);
    dt = dt.AddSeconds(secondsSince1970);
    dt = dt.AddHours(TimeZone.CurrentTimeZone.GetUtcOffset(dt).Hours);
    return dt;
}
mdb
+1 Wow that's ... impressive.
JustLoren
very impressive, but I tried John's 3 line one below, and certainly for my system and versions, it appears to work fine. However as per the article there are some limitations in that method, so I'm voting for both of yours and accepting yours so that future readers can benefit from its soundness. Thanks!
Mark Mayo
I would never dig into the PE header in that way, just to get the assembly version information. I've never had an issue with the build number not being update to date, that problem is a thing of the past. Since you're looking at the executable as raw bytes you have no guarantees that the PE header won't change in the future or be a Windows PE header at all (does this work in mono? probably yes). And that's the only reason you should ever need. Besides the format there's an probable issue with endian on the XBOX360 that you'll run into when someone tries to port this code.
John Leidegren
I've changed my tone about this somewhat, I'd still be very careful when digging into the acutal PE header. But as far as I can tell, this PE stuff is a lot more reliable than using the versioning numbers, besides I wan't to assign the version numbers seperate from the build date.
John Leidegren
A: 

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.

Guy van den Berg
A: 

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.

MikeWyatt
+1  A: 

As an extension to Guy's answer, you could use custom AssemblyInfo attributes to store the value.

Vijay Patel
+1  A: 

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%5Fcsproj%5Ffiles.aspx

Murph
+6  A: 

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)
{
    if (File.Exists(assembly.Location))
    {
        var buffer = new byte[Math.Max(Marshal.SizeOf(typeof(_IMAGE_FILE_HEADER)), 4)];
        using (var fileStream = new FileStream(assembly.Location, 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)
John Leidegren
Perfect, was just the limited short answer I needed and it works great. However I feel that for completeness, mdb's answer above will prevent others from shooting themselves in the foot, so accepting his. Gave you a vote tho - thanks!
Mark Mayo
The community content seem to be removed... How can we be sure that MS wont change the algorithm? :) Any official docs on this guys?
jitbit
This method gives me a timestamp that is off by one hour. I guess it's because of daylight savings time or perhaps because my timezone i CET, i.e. +1. I have tried construction the new DateTime with DateTimeKind.Local and DateTimeKind.Utc, but that doesn't help.
Jan Aagaard
Yeah, I've started to think less of this approach after all. Digging into the PE header is just easier, and you get an UTC timestamp that you can do the correct stuff with. I use the version number for things other than determining build date now a days. However, I wrote my code a bit differently, I wouldn't assume that the timestamp is found at a specific offset every time.
John Leidegren
A: 

There's a good article here Reading the Portable Executable (PE) header in C# about reading in the whole PE Header and then getting the header information you want, including the linker date and time.

John Stewien