Click here to Skip to main content
15,893,487 members
Articles / Programming Languages / C#

VersionInfo++

Rate me:
Please Sign up or sign in to vote.
3.53/5 (5 votes)
28 Aug 2007CPOL2 min read 21.2K   211   11  
Add additional key/value pairs to the VERSIONINFO resource.
using System;
using System.Collections.Generic;
using System.Text;
using System.Collections;
using System.IO;
using System.Text.RegularExpressions;
using System.Globalization;
using Microsoft.Build.Utilities;
using Microsoft.Build.Framework;


namespace CP.MSBuild
{
    /// <summary>
    /// Implements the VersionResource task. Use the VersionResource element in 
    /// your project file to create and execute this task.
    /// 
    /// The task is used to create a temporary res (win32 resource) file that contains
    /// an icon and version-information and embed it into an .NET application.
    /// </summary>
	public class VersionResource : ToolTask
    {
        #region types
        
		
		/// <summary>
		/// Windows\v6.0\Include\WinVer.h
		/// VS_VERSION.dwFileFlags
		/// </summary>
		[Flags]
        enum FileFlags
        {
            VS_FF_DEBUG = 0x00000001,
            VS_FF_PRERELEASE = 0x00000002,
            VS_FF_PATCHED = 0x00000004,
            VS_FF_PRIVATEBUILD = 0x00000008,
            VS_FF_INFOINFERRED = 0x00000010,
            VS_FF_SPECIALBUILD = 0x00000020,
        }

		/// <summary>
		/// Windows\v6.0\Include\WinVer.h
		/// VS_VERSION.dwFileType
		/// </summary>
        enum FileType
        {
            VFT_UNKNOWN = 0x00000000,
            VFT_APP = 0x00000001,
            VFT_DLL = 0x00000002,
            VFT_DRV = 0x00000003,
            VFT_FONT = 0x00000004,
            VFT_VXD = 0x00000005,
            VFT_STATIC_LIB = 0x00000007,
        }
        #endregion

        #region private fields
        private ITaskItem tempRCFile;
		private ITaskItem targetFileName;
		private bool debug;
		private string toolPath;
		private string applicationIcon;
		private string assemblyInfoPath;

		private Dictionary<string, string> valueDictionary = new Dictionary<string, string>();
		private Version fileVersion;
		private Version productVersion;
		#endregion

		#region public properties

		/// <summary>
		/// must be used to set the fully qualified path to the termporary resource (.rc) file.
		/// </summary>
		[Required]
		public ITaskItem TempRCFile
		{
			set { tempRCFile = value; }
		}

		/// <summary>
		/// full path to the resource compiler (rc.exe) tool.
		/// </summary>
		[Required]
		public new string ToolPath
		{
			set { toolPath = value; }
		}

		/// <summary>
		/// must be used to set the name of the file to be generated.
		/// </summary>
		[Required]
		public ITaskItem TargetFileName
		{
			get { return targetFileName; }
			set { targetFileName = value; }
		}

		/// <summary>
		/// can be used to inform the task about the debug type. 
		/// </summary>
		public string DebugType
		{
			set { debug = value == "full"; }
		}

		/// <summary>
		/// can be used to set inform the task about the location of the AssemblyInfo.cs file.
		/// </summary>
		public ITaskItem[] Compile
		{
			set
			{
				foreach (ITaskItem item in value)
				{
					if (item.GetMetadata("Extension").ToLower() == ".cs" && item.GetMetadata("Filename").ToLower() == "assemblyinfo")
					{
						assemblyInfoPath = item.GetMetadata("Fullpath");
					}
				}
			}
		}

		/// <summary>
		/// must be used to retrieve the full path to generated temporary res file
		/// </summary>
		[Output]
		public string Win32Resource
		{
			get 
			{
				return Path.Combine(Path.GetDirectoryName(tempRCFile.ToString()), Path.GetFileNameWithoutExtension(tempRCFile.ToString()) + ".res");
			}
		}

		/// <summary>
		/// must be used to set the full path to the application icon and get an empty string
		/// back upon completion of the task.
		/// </summary>
		[Output]
		public string ApplicationIcon
		{
			get { return ""; }
			set { applicationIcon = value; }
		}
		#endregion

		#region private methods & properties

		private string BuildResourceString()
		{
			StringBuilder sb = new StringBuilder();

			int langId = 0; //0x0807;
			int codePage = 0x04B0;  // unicode

			FileType fileType = FileType.VFT_UNKNOWN;
			switch(targetFileName.GetMetadata("Extension").ToLower())
			{
				case ".dll": fileType = FileType.VFT_DLL; break;
				case ".exe": fileType = FileType.VFT_APP; break;
			}

			if (applicationIcon !=null)
			{
				sb.AppendFormat("0 ICON \"{0}\"\n", applicationIcon);
			}
			sb.Append("1 VERSIONINFO\n");
			sb.AppendFormat(" FILEVERSION {0},{1},{2},{3}\n", FileVersion.Major, FileVersion.Minor, FileVersion.Build, FileVersion.Revision);
			sb.AppendFormat(" PRODUCTVERSION {0},{1},{2},{3}\n", ProductVersion.Major, ProductVersion.Minor, ProductVersion.Build, ProductVersion.Revision);
			sb.Append(" FILEFLAGSMASK 0x17L\n");
			sb.AppendFormat(" FILEFLAGS 0x{0:x2}L\n", (int)(debug?FileFlags.VS_FF_DEBUG:0));
			sb.Append(" FILEOS 0x4L\n");
			sb.AppendFormat(" FILETYPE 0x{0:x2}L\n", (int)fileType);
			sb.Append(" FILESUBTYPE 0x0L\n");
			sb.Append("{\n");
			sb.Append("    BLOCK \"StringFileInfo\"\n");
			sb.Append("    {\n");
			sb.AppendFormat("        BLOCK \"{0:x4}{1:x4}\"\n", langId, codePage);
			sb.Append("        {\n");
			foreach (string key in valueDictionary.Keys)
			{
				sb.AppendFormat("            VALUE \"{0}\", \"{1}\"\n", key, valueDictionary[key]);
			}
			sb.Append("        }\n");
			sb.Append("    }\n");
			sb.Append("    BLOCK \"VarFileInfo\"\n");
			sb.Append("    {\n");
			sb.AppendFormat("        VALUE \"Translation\", 0x{0:x4}, 0x{1:x4}\n", langId, codePage);
			sb.Append("    }\n");
			sb.Append("}\n");

			return sb.ToString();
		}

        /// <summary>
        /// maps .NET attribute names to VERSIONINFO (SDK) names
        /// </summary>
        /// <param name="key">partial attribute name</param>
        /// <returns>sdk name</returns>
		private string MapKey(string key)
		{
			switch (key)
			{
				case "Title": return "FileDescription";
				case "Copyright": return "LegalCopyright";
				case "Version": return "Assembly Version"; // not an SDK name!
				case "Description": return "Comments";
				case "Company": return "CompanyName";
				case "FileVersion": return "FileVersion";
				case "Trademark": return "LegalTrademarks";
				case "Product": return "ProductName";
				case "InformationalVersion": return "ProductVersion";

				case "Culture": return null;
			}
			return key;
		}

		public void ParseAssemblyInfoFile()
		{
            if (File.Exists(assemblyInfoPath))
            {

                //[assembly: AssemblyCompany("Company")]
                //                  (name   )(para   )
                string regex = "\\[\\s*assembly:\\s*Assembly(?<name>\\w+)\\s*\\(\\s*\"(?<para>[^\\\\\"]*(?:\\\\.[^\\\\\"]*)*)\"\\s*\\)\\s*]";
                Regex re = new Regex(regex, RegexOptions.Multiline);
                for (Match m = re.Match(File.ReadAllText(assemblyInfoPath), 0); m.Success; m = m.NextMatch())
                {
                    if (!m.Success) continue;
                    string name = m.Groups["name"].Value;
                    string para = m.Groups["para"].Value;
                    //Debug.WriteLine(string.Format("{0}={1}", name, para));
                    if (para == null || para.Length == 0) continue;
                    valueDictionary.Add(MapKey(name), para);
                }
            }
		}

		private static bool TryParse(string s, out Version result)
		{
			string[] parts = s.Split(',', '.');
			int count = 0;
			int major = 0;
			int minor = 0;
			int build = 0;
			int revision = 0;
			if (parts.Length > 0 && int.TryParse(parts[0], out major)) { count++; };
			if (parts.Length > 1 && int.TryParse(parts[1], out minor)) { count++; };
			if (parts.Length > 2 && int.TryParse(parts[2], out build)) { count++; };
			if (parts.Length > 3 && int.TryParse(parts[3], out revision)) { count++; };
			result = new Version(major, minor, build, revision);
			return count > 0;
		}

        private Version FileVersion
        {
            get
            {
                if (fileVersion == null)
                {
					TryParse(valueDictionary["FileVersion"], out fileVersion);
                }
                return fileVersion;
            }
        }

        private Version ProductVersion
        {
            get
            {
                if (productVersion == null)
                {
					if (!TryParse(valueDictionary["ProductVersion"], out productVersion))
					{
						// use fileversion if productversion is no good
						productVersion = FileVersion;
					}
                }
                return productVersion;
            }
        }
		#endregion

		#region ToolTask overrides
		protected override string ToolName
		{
			get
			{
				return "rc.exe";
			}
		}
		protected override string GenerateFullPathToTool()
		{
			return Path.Combine(toolPath, ToolName);
		}
		protected override string GenerateCommandLineCommands()
		{
			CommandLineBuilder builder = new CommandLineBuilder();
			builder.AppendSwitch("/r");
			builder.AppendFileNameIfNotNull(tempRCFile);
			Log.LogMessage(MessageImportance.High, "compiling resource {0}", tempRCFile);

			return builder.ToString();
		}
		public override bool Execute()
		{
			ParseAssemblyInfoFile();

			// use FileVersion as ProductVersion if no ProductVersion set
			if (!valueDictionary.ContainsKey("ProductVersion") && valueDictionary.ContainsKey("FileVersion"))
			{
				valueDictionary["ProductVersion"] = valueDictionary["FileVersion"];
			}

			if (targetFileName != null && targetFileName.ItemSpec.Length > 0)
			{
				if (!valueDictionary.ContainsKey("InternalName"))
				{
					valueDictionary["InternalName"] = targetFileName.ItemSpec;
				}
				if (!valueDictionary.ContainsKey("OriginalFilename"))
				{
					valueDictionary["OriginalFilename"] = targetFileName.ItemSpec;
				}
			}

            valueDictionary["Build Machine"] = Environment.MachineName;
			valueDictionary["Build Time"] = DateTime.Now.ToString("g", CultureInfo.InvariantCulture);

			File.WriteAllText(tempRCFile.ToString(), BuildResourceString(), Encoding.Unicode);

			bool res = base.Execute();
			File.Delete(tempRCFile.ToString());
			return res;
		}
		#endregion
	}
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

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


Written By
Switzerland Switzerland
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions