You can use some pinvokes...
[DllImport("version.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern int GetFileVersionInfoSize(string lptstrFilename, out int lpdwHandle);
[DllImport("version.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern bool GetFileVersionInfo(string lptstrFilename, int dwHandle, int dwLen, byte[] lpData);
[DllImport("version.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern bool VerQueryValue(byte[] pBlock, string lpSubBlock, out IntPtr lplpBuffer, out int puLen);
public static Tuple<string, string>[] GetVersionInfo(string fileName, params string[] keys)
{
int num;
int size = GetFileVersionInfoSize(fileName, out num);
if (size == 0)
{
throw new Win32Exception();
}
var bytes = new byte[size];
bool success = GetFileVersionInfo(fileName, 0, size, bytes);
if (!success)
{
throw new Win32Exception();
}
int size2;
IntPtr ptr;
success = VerQueryValue(bytes, @"\VarFileInfo\Translation", out ptr, out size2);
uint[] langs;
if (success)
{
langs = new uint[size2 / 4];
for (int i = 0, j = 0; j < size2; i++, j += 4)
{
langs[i] = unchecked((uint)(((ushort)Marshal.ReadInt16(ptr, j) << 16) | (ushort)Marshal.ReadInt16(ptr, j + 2)));
}
}
else
{
// Taken from https://referencesource.microsoft.com/#System/services/monitoring/system/diagnosticts/FileVersionInfo.cs,470
langs = new uint[] { 0x040904B0, 0x040904E4, 0x04090000 };
}
string[] langs2 = Array.ConvertAll(langs, x => @"\StringFileInfo\" + x.ToString("X8") + @"\");
var kv = new Tuple<string, string>[keys.Length];
for (int i = 0; i < kv.Length; i++)
{
string key = keys[i];
string value = null;
foreach (var lang in langs2)
{
success = VerQueryValue(bytes, lang + key, out ptr, out size2);
if (success)
{
value = Marshal.PtrToStringUni(ptr);
break;
}
}
kv[i] = Tuple.Create(key, value);
}
return kv;
}
and then you use:
string name = "Win32Project1.exe";
var infos = GetVersionInfo(name, "LastModified", "Comments", "CompanyName", "FileVersion", "LegalCopyright", "LegalTrademarks", "ProductVersion", "InternalName", "OriginalFilename", "FileDescription", "ProductName", "BuildDate");
var buildDate = infos.Single(x => x.Item1 == "BuildDate").Item2;
Out of curiosity I've began exploring the various structs of the VS_VERSIONINFO
, and I've written some code:
public class VersionInfo
{
[DllImport("version.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern int GetFileVersionInfoSize(string lptstrFilename, out int lpdwHandle);
[DllImport("version.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern bool GetFileVersionInfo(string lptstrFilename, int dwHandle, int dwLen, byte[] lpData);
[DllImport("version.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern bool VerQueryValue(byte[] pBlock, string lpSubBlock, out IntPtr lplpBuffer, out int puLen);
public readonly Version FileVersion;
public readonly Version ProductVersion;
public readonly uint FileFlagsMask;
public readonly uint FileFlags;
public readonly uint FileOS;
public readonly uint FileType;
public readonly uint FileSubtype;
// Always null
public readonly DateTime? FileDate;
protected VersionInfo(Version fileVersion, Version productVersion, uint fileFlagsMask, uint fileFlags, uint fileOS, uint fileType, uint fileSubtype, DateTime? fileDate)
{
FileVersion = fileVersion;
ProductVersion = productVersion;
FileFlagsMask = fileFlagsMask;
FileFlags = fileFlags;
FileOS = fileOS;
FileType = fileType;
FileSubtype = fileSubtype;
FileDate = fileDate;
}
// vi can be null on exit
// Item1 = language | codepage
// Item2 = Key
// Item3 = Value
public static IEnumerable<Tuple<uint, string, string>> ReadVersionInfo(string fileName, out VersionInfo vi)
{
int num;
int size = GetFileVersionInfoSize(fileName, out num);
if (size == 0)
{
throw new Win32Exception();
}
var buffer = new byte[size];
bool success = GetFileVersionInfo(fileName, 0, size, buffer);
if (!success)
{
throw new Win32Exception();
}
return ReadVersionInfo(buffer, out vi);
}
// vi can be null on exit
// Item1 = language | codepage
// Item2 = Key
// Item3 = Value
public static IEnumerable<Tuple<uint, string, string>> ReadVersionInfo(byte[] buffer, out VersionInfo vi)
{
int offset;
// The offset calculated here is unused
var fibs = ReadFileInfoBaseStruct(buffer, 0, out offset);
if (fibs.Key != "VS_VERSION_INFO")
{
throw new Exception(fibs.Key);
}
// Value = VS_FIXEDFILEINFO
if (fibs.ValueLength != 0)
{
uint signature = BitConverter.ToUInt32(buffer, fibs.ValueOffset);
if (signature != 0xFEEF04BD)
{
throw new Exception(signature.ToString("X8"));
}
uint strucVersion = BitConverter.ToUInt32(buffer, fibs.ValueOffset + 4);
var fileVersion = new Version(BitConverter.ToUInt16(buffer, fibs.ValueOffset + 10), BitConverter.ToUInt16(buffer, fibs.ValueOffset + 8), BitConverter.ToUInt16(buffer, fibs.ValueOffset + 14), BitConverter.ToUInt16(buffer, fibs.ValueOffset + 12));
var productVersion = new Version(BitConverter.ToUInt16(buffer, fibs.ValueOffset + 18), BitConverter.ToUInt16(buffer, fibs.ValueOffset + 16), BitConverter.ToUInt16(buffer, fibs.ValueOffset + 22), BitConverter.ToUInt16(buffer, fibs.ValueOffset + 20));
uint fileFlagsMask = BitConverter.ToUInt32(buffer, fibs.ValueOffset + 24);
uint fileFlags = BitConverter.ToUInt32(buffer, fibs.ValueOffset + 28);
uint fileOS = BitConverter.ToUInt32(buffer, fibs.ValueOffset + 32);
uint fileType = BitConverter.ToUInt32(buffer, fibs.ValueOffset + 36);
uint fileSubtype = BitConverter.ToUInt32(buffer, fibs.ValueOffset + 40);
uint fileDateMS = BitConverter.ToUInt32(buffer, fibs.ValueOffset + 44);
uint fileDateLS = BitConverter.ToUInt32(buffer, fibs.ValueOffset + 48);
DateTime? fileDate = fileDateMS != 0 || fileDateLS != 0 ?
(DateTime?)DateTime.FromFileTime((long)fileDateMS << 32 | fileDateLS) :
null;
vi = new VersionInfo(fileVersion, productVersion, fileFlagsMask, fileFlags, fileOS, fileType, fileSubtype, fileDate);
}
else
{
vi = null;
}
return ReadVersionInfoInternal(buffer, fibs);
}
protected static IEnumerable<Tuple<uint, string, string>> ReadVersionInfoInternal(byte[] buffer, FileInfoBaseStruct fibs)
{
int sfiOrValOffset = (fibs.ValueOffset + fibs.ValueLength + 3) & (~3);
while (sfiOrValOffset < fibs.Length)
{
int nextSfiOrValOffset;
var sfiOrVal = ReadFileInfoBaseStruct(buffer, sfiOrValOffset, out nextSfiOrValOffset);
if (sfiOrVal.Key == "StringFileInfo")
{
int stOffset = sfiOrVal.ValueOffset;
while (stOffset < sfiOrVal.EndOffset)
{
int nextStOffset;
var st = ReadFileInfoBaseStruct(buffer, stOffset, out nextStOffset);
uint langCharset = uint.Parse(st.Key, NumberStyles.HexNumber);
int striOffset = st.ValueOffset;
while (striOffset < st.EndOffset)
{
int nextStriOffset;
var stri = ReadFileInfoBaseStruct(buffer, striOffset, out nextStriOffset);
// Here stri.ValueLength is in words!
int len = FindLengthUnicodeSZ(buffer, stri.ValueOffset, stri.ValueOffset + (stri.ValueLength * 2));
string value = Encoding.Unicode.GetString(buffer, stri.ValueOffset, len * 2);
yield return Tuple.Create(langCharset, stri.Key, value);
striOffset = nextStriOffset;
}
stOffset = nextStOffset;
}
}
else if (sfiOrVal.Key == "VarFileInfo")
{
int varOffset = sfiOrVal.ValueOffset;
while (varOffset < sfiOrVal.EndOffset)
{
int nextVarOffset;
var var = ReadFileInfoBaseStruct(buffer, varOffset, out nextVarOffset);
if (var.Key != "Translation")
{
throw new Exception(var.Key);
}
int langOffset = var.ValueOffset;
while (langOffset < var.EndOffset)
{
unchecked
{
// We invert the order suggested by the Var description!
uint high = (uint)BitConverter.ToInt16(buffer, langOffset);
uint low = (uint)BitConverter.ToInt16(buffer, langOffset + 2);
uint lang = (high << 16) | low;
langOffset += 4;
}
}
varOffset = nextVarOffset;
}
}
else
{
Debug.WriteLine("Unrecognized " + sfiOrVal.Key);
}
sfiOrValOffset = nextSfiOrValOffset;
}
}
protected static FileInfoBaseStruct ReadFileInfoBaseStruct(byte[] buffer, int offset, out int nextOffset)
{
var fibs = new FileInfoBaseStruct
{
Length = BitConverter.ToInt16(buffer, offset),
ValueLength = BitConverter.ToInt16(buffer, offset + 2),
Type = BitConverter.ToInt16(buffer, offset + 4)
};
int len = FindLengthUnicodeSZ(buffer, offset + 6, offset + fibs.Length);
fibs.Key = Encoding.Unicode.GetString(buffer, offset + 6, len * 2);
// Padding
fibs.ValueOffset = ((offset + 6 + (len + 1) * 2) + 3) & (~3);
fibs.EndOffset = offset + fibs.Length;
nextOffset = (fibs.EndOffset + 3) & (~3);
return fibs;
}
protected static int FindLengthUnicodeSZ(byte[] buffer, int offset, int endOffset)
{
int offset2 = offset;
while (offset2 < endOffset && BitConverter.ToInt16(buffer, offset2) != 0)
{
offset2 += 2;
}
// In chars
return (offset2 - offset) / 2;
}
// Used internally
protected class FileInfoBaseStruct
{
public short Length { get; set; }
public short ValueLength { get; set; }
public short Type { get; set; }
public string Key { get; set; }
public int ValueOffset { get; set; }
public int EndOffset { get; set; }
}
}
use it like:
string name = "Win32Project1-loc.exe";
// vi could be null on return from ReadVersionInfo
VersionInfo vi;
// Note that it is an IEnumerable<>... If you want to use
// it multipel times, you should .ToArray() it!
var infos = VersionInfo.ReadVersionInfo(name, out vi);
// For example
var buildDate = infos.Single(x => x.Item2 == "BuildDate").Item3;