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
}
}