Solved

How to use product version as a constant

Posted on 2014-12-23
16
156 Views
Last Modified: 2014-12-23
Ok, here is my problem. I want to show the assembly version (product version) on an about form. Currently I am doing so by using the Application Class and the ProductVersion property. Everything works fine UNTIL I protect the assembly with an obfuscation system. When I do so the ProductVersion property always returns 1.0.0.0

I know the obfuscation system well and there is no way to resolve this without sacrificing some of the strong protection it provides. I have even strong named the assembly but get the same result. So here is my proposed solution, though I don't know how to implement it, or if it is even possible.

Is there any way to assign the product version to a constant during compilation?

Proposed example:

private const string MyProductVersion = some kind of replaceable parameter during compilation.

This way it would be hard coded (which is fine) and would solve the problem since this is the way I wound up having to do it by hand. I use an auto-incrementing version system so every time I release an update I have to manually check the assembly info and put it into the source code constant. If I forget a previous version number is displayed and creates havoc.

Thanks in advance
0
Comment
Question by:exptech
  • 8
  • 7
16 Comments
 
LVL 62

Expert Comment

by:Fernando Soto
ID: 40514902
Hi exptech;

Have you tried using the version number at runtime with code like this.

// Get the version of the executing assembly
Assembly assem = Assembly.GetEntryAssembly();
AssemblyName assemName = assem.GetName();
Version ver = assemName.Version;
Console.WriteLine("Application {0}, Version {1}", assemName.Name, ver.ToString());

Open in new window

0
 

Author Comment

by:exptech
ID: 40514910
Yes, the problem is after obfuscation the version number is not able to be read properly.
0
 
LVL 32

Expert Comment

by:it_saige
ID: 40514913
*No Points*

Just to add to what Fernando has stated, you can also add a level of *granularity* by getting the build, major, minor and revision.

Some additional methods:
/// <summary>Class used to gather executable and dynamic link library information.</summary>
public sealed class Executables
{
	/// <summary>Gets the Assembly Build Version.</summary>
	public static string AssemblyBuildVersion
	{
		get { return Assembly.GetEntryAssembly().GetName().Version.Build.ToString(); }
	}

	/// <summary>Gets the Assembly Copyright information.</summary>
	public static string AssemblyCopyright
	{
		get
		{
			object[] attributes = Assembly.GetEntryAssembly().GetCustomAttributes(typeof(AssemblyCopyrightAttribute), false);
			return (attributes != null && attributes.Length > 0) && ((AssemblyCopyrightAttribute)attributes[0] != null) ? ((AssemblyCopyrightAttribute)attributes[0]).Copyright : string.Empty;
		}
	}

	/// <summary>Gets the Assembly Guid.</summary>
	public static string AssemblyGuid
	{
		get
		{
			object[] attributes = Assembly.GetEntryAssembly().GetCustomAttributes(typeof(GuidAttribute), false);
			return attributes != null && attributes.Length > 0 ? ((GuidAttribute)attributes[0]).Value : string.Empty;
		}
	}

	/// <summary>Gets the Assembly Major Version.</summary>
	public static string AssemblyMajorVersion
	{
		get { return Assembly.GetEntryAssembly().GetName().Version.Major.ToString(); }
	}

	/// <summary>Gets the Assembly Minor Version.</summary>
	public static string AssemblyMinorVersion
	{
		get { return Assembly.GetEntryAssembly().GetName().Version.Minor.ToString(); }
	}

	/// <summary>Gets the Assembly Revision Version.</summary>
	public static string AssemblyRevisionVersion
	{
		get { return Assembly.GetEntryAssembly().GetName().Version.Revision.ToString(); }
	}

	/// <summary>Gets the Assembly Title.</summary>
	public static string AssemblyTitle
	{
		get
		{
			object[] attributes = Assembly.GetEntryAssembly().GetCustomAttributes(typeof(AssemblyTitleAttribute), false);
			return (attributes != null && attributes.Length > 0) && (((AssemblyTitleAttribute)attributes[0]).Title != "") ? ((AssemblyTitleAttribute)attributes[0]).Title : Path.GetFileNameWithoutExtension(Assembly.GetEntryAssembly().CodeBase);
		}
	}

	/// <summary>Gets the Assembly Version.</summary>
	public static string AssemblyVersion
	{
		get
		{
			object[] attributes = Assembly.GetEntryAssembly().GetCustomAttributes(typeof(AssemblyVersionAttribute), false);
			return attributes != null && attributes.Length > 0 ? ((AssemblyVersionAttribute)attributes[0]).Version : string.Empty;
		}
	}
}

Open in new window


-saige-
0
 
LVL 32

Expert Comment

by:it_saige
ID: 40514918
So exptech, you are stating the the obfuscation is even modifying the assembly metadata?  Interesting.  Which obfuscater are you using?

-saige-
0
 

Author Comment

by:exptech
ID: 40514921
Please read my comment above. I appreciate the answers but they won't solve my problem.
0
 

Author Comment

by:exptech
ID: 40514923
Dot net Reactor. Yes, in fact it even resigns the assembly.
0
 

Author Comment

by:exptech
ID: 40514925
Actually, sometimes it does show the correct version number, but most of the time it does not. I am in touch with the company on the issue but in the meantime I need another solution.
0
 
LVL 32

Expert Comment

by:it_saige
ID: 40514941
According to their site, you can use the ObfuscationAttribute class.
.NET Reactor automatically detects the attribute and excludes the corresponding types and members from obfuscation.
Example code posted from MSDN:
using System;
using System.Reflection;

// Mark this public assembly for obfuscation.
[assembly:ObfuscateAssemblyAttribute(false)]

// This class is marked for obfuscation, because the assembly 
// is marked. 
public class Type1
{

    // Exclude this method from obfuscation. The default value 
    // of the Exclude property is true, so it is not necessary 
    // to specify Exclude=True, although spelling it out makes 
    // your intent clearer.
    [ObfuscationAttribute(Exclude=true)]
    public void MethodA() {}

    // This member is marked for obfuscation because the class 
    // is marked. 
    public void MethodB() {}
}

// Exclude this type from obfuscation, but do not apply that 
// exclusion to members. The default value of the Exclude  
// property is true, so it is not necessary to specify  
// Exclude=true, although spelling it out makes your intent  
// clearer.
[ObfuscationAttribute(Exclude=true, ApplyToMembers=false)]
public class Type2
{

    // The exclusion of the type is not applied to its members, 
    // however in order to mark the member with the "default"  
    // feature it is necessary to specify Exclude=false, 
    // because the default value of Exclude is true. The tool 
    // should not strip this attribute after obfuscation.
    [ObfuscationAttribute(Exclude=false, Feature="default", 
        StripAfterObfuscation=false)]
    public void MethodA() {}

    // This member is marked for obfuscation, because the  
    // exclusion of the type is not applied to its members. 
    public void MethodB() {}

}

// This class only exists to provide an entry point for the 
// assembly and to display the attribute values. 
internal class Test
{

    public static void Main()
    {

        // Display the ObfuscateAssemblyAttribute properties 
        // for the assembly.        
        Assembly assem = typeof(Test).Assembly;
        object[] assemAttrs = assem.GetCustomAttributes(
            typeof(ObfuscateAssemblyAttribute), false);

        foreach( Attribute a in assemAttrs )
        {
            ShowObfuscateAssembly((ObfuscateAssemblyAttribute) a);
        }

        // Display the ObfuscationAttribute properties for each 
        // type that is visible to users of the assembly. 
        foreach( Type t in assem.GetTypes() )
        {
            if (t.IsVisible)
            {
                object[] tAttrs = t.GetCustomAttributes(
                    typeof(ObfuscationAttribute), false);

                foreach( Attribute a in tAttrs )
                {
                    ShowObfuscation(((ObfuscationAttribute) a),
                        t.Name);
                }

                // Display the ObfuscationAttribute properties 
                // for each member in the type. 
                foreach( MemberInfo m in t.GetMembers() )
                {
                    object[] mAttrs = m.GetCustomAttributes(
                        typeof(ObfuscationAttribute), false);

                    foreach( Attribute a in mAttrs )
                    {
                        ShowObfuscation(((ObfuscationAttribute) a), 
                            t.Name + "." + m.Name);
                    }
                }
            }
        }
    }

    private static void ShowObfuscateAssembly(
        ObfuscateAssemblyAttribute ob)
    {
        Console.WriteLine("\r\nObfuscateAssemblyAttribute properties:");
        Console.WriteLine("   AssemblyIsPrivate: {0}", 
            ob.AssemblyIsPrivate);
        Console.WriteLine("   StripAfterObfuscation: {0}",
            ob.StripAfterObfuscation);
    }

    private static void ShowObfuscation(
        ObfuscationAttribute ob, string target)
    {
        Console.WriteLine("\r\nObfuscationAttribute properties for: {0}",
            target);
        Console.WriteLine("   Exclude: {0}", ob.Exclude);
        Console.WriteLine("   Feature: {0}", ob.Feature);
        Console.WriteLine("   StripAfterObfuscation: {0}",
            ob.StripAfterObfuscation);
        Console.WriteLine("   ApplyToMembers: {0}", ob.ApplyToMembers);
    }
}

/* This code example produces the following output:

ObfuscateAssemblyAttribute properties:
   AssemblyIsPrivate: False
   StripAfterObfuscation: True

ObfuscationAttribute properties for: Type1.MethodA
   Exclude: True
   Feature: all
   StripAfterObfuscation: True
   ApplyToMembers: True

ObfuscationAttribute properties for: Type2
   Exclude: True
   Feature: all
   StripAfterObfuscation: True
   ApplyToMembers: False

ObfuscationAttribute properties for: Type2.MethodA
   Exclude: False
   Feature: default
   StripAfterObfuscation: False
   ApplyToMembers: True
 */

Open in new window


-saige-
0
How to improve team productivity

Quip adds documents, spreadsheets, and tasklists to your Slack experience
- Elevate ideas to Quip docs
- Share Quip docs in Slack
- Get notified of changes to your docs
- Available on iOS/Android/Desktop/Web
- Online/Offline

 

Author Comment

by:exptech
ID: 40515632
it_saige,

I was not aware of that class but I am not sure how I would use it. The problem is not the code that reads and displays the Application.ProductVersion property, it is the property itself that is returning the wrong version. Where would I use this attribute?
0
 
LVL 32

Expert Comment

by:it_saige
ID: 40515661
I would first try excluding the method that returns the AssemblyVersion (espcially if it is a part of the Assembly, this is where the obfuscation is probably occuring); i.e. - (Using my implementation above it would look something like):
/// <summary>Class used to gather executable and dynamic link library information.</summary>
[ObfuscationAttribute(Exclude = true, ApplyToMembers = true)]
public sealed class Executables
{
	/// <summary>Gets the Assembly Build Version.</summary>
	public static string AssemblyBuildVersion
	{
		get { return Assembly.GetEntryAssembly().GetName().Version.Build.ToString(); }
	}

	/// <summary>Gets the Assembly Copyright information.</summary>
	public static string AssemblyCopyright
	{
		get
		{
			object[] attributes = Assembly.GetEntryAssembly().GetCustomAttributes(typeof(AssemblyCopyrightAttribute), false);
			return (attributes != null && attributes.Length > 0) && ((AssemblyCopyrightAttribute)attributes[0] != null) ? ((AssemblyCopyrightAttribute)attributes[0]).Copyright : string.Empty;
		}
	}

	/// <summary>Gets the Assembly Guid.</summary>
	public static string AssemblyGuid
	{
		get
		{
			object[] attributes = Assembly.GetEntryAssembly().GetCustomAttributes(typeof(GuidAttribute), false);
			return attributes != null && attributes.Length > 0 ? ((GuidAttribute)attributes[0]).Value : string.Empty;
		}
	}

	/// <summary>Gets the Assembly Major Version.</summary>
	public static string AssemblyMajorVersion
	{
		get { return Assembly.GetEntryAssembly().GetName().Version.Major.ToString(); }
	}

	/// <summary>Gets the Assembly Minor Version.</summary>
	public static string AssemblyMinorVersion
	{
		get { return Assembly.GetEntryAssembly().GetName().Version.Minor.ToString(); }
	}

	/// <summary>Gets the Assembly Revision Version.</summary>
	public static string AssemblyRevisionVersion
	{
		get { return Assembly.GetEntryAssembly().GetName().Version.Revision.ToString(); }
	}

	/// <summary>Gets the Assembly Title.</summary>
	public static string AssemblyTitle
	{
		get
		{
			object[] attributes = Assembly.GetEntryAssembly().GetCustomAttributes(typeof(AssemblyTitleAttribute), false);
			return (attributes != null && attributes.Length > 0) && (((AssemblyTitleAttribute)attributes[0]).Title != "") ? ((AssemblyTitleAttribute)attributes[0]).Title : Path.GetFileNameWithoutExtension(Assembly.GetEntryAssembly().CodeBase);
		}
	}

	/// <summary>Gets the Assembly Version.</summary>
	public static string AssemblyVersion
	{
		get
		{
			object[] attributes = Assembly.GetEntryAssembly().GetCustomAttributes(typeof(AssemblyVersionAttribute), false);
			return attributes != null && attributes.Length > 0 ? ((AssemblyVersionAttribute)attributes[0]).Version : string.Empty;
		}
	}
}

Open in new window


If this does not bear any fruit then creating a property as you were thinking should do the trick:
public static Globals
{
	private readonly static string fMyVersion = "1.0.0.0";

	[ObfuscationAttribute(Exclude = true)]
	public static string MyVersion { get { return fMyVersion; } }
}

Open in new window


-saige-
0
 

Author Comment

by:exptech
ID: 40515670
I am going to try this now.
0
 

Author Comment

by:exptech
ID: 40515718
OK, now this is really crazy. When I use the following line of code you suggested:

get { return Assembly.GetEntryAssembly().GetName().Version.Build.ToString(); }

and I protect the application it will not even run. It just starts and stops.

If I replace it with:

get { return Application.ProductVersion; }

then it runs fine but returns the wrong version #.

For some reason it runs when I use the Application class but not the Assembly class.

Your last idea:

public static Globals
{
      private readonly static string fMyVersion = "1.0.0.0";

      [ObfuscationAttribute(Exclude = true)]
      public static string MyVersion { get { return fMyVersion; } }
}

is what I am doing now, but is there any way to have the version number automatically assigned to the fMyVersion field during complitation? I could make fMyVersion a constant but I don't know of any way to assign a value to a constant when compiling.
0
 
LVL 32

Expert Comment

by:it_saige
ID: 40515726
That particular method returns the build portion of the version.  For the AssemblyVersion you would use:
/// <summary>Gets the Assembly Version.</summary>
public static string AssemblyVersion
{
	get
	{
		object[] attributes = Assembly.GetEntryAssembly().GetCustomAttributes(typeof(AssemblyVersionAttribute), false);
		return attributes != null && attributes.Length > 0 ? ((AssemblyVersionAttribute)attributes[0]).Version : string.Empty;
	}
}

Open in new window


My question would be where is it failing at, if you debug it do you get an exception.  You could always include a try catch around the return.

As for the constant.  If your code is not able to provide you with the version as assigned in the AssemblyInfo.cs, then trying to use a method to return the value to the fMyVersion backing field will result in the same obfuscation.

Matter of fact, now you are making me wonder why the build version caused a failure.  I'm going to build a quick test project (unfortunately I do not have .Net Reactor, I wonder if they have a 30-day trial.  ;) )

-saige-
0
 
LVL 32

Expert Comment

by:it_saige
ID: 40515744
Ok.  Had to make a change to the AssemblyVersion method.  But it all works fine for me:
using System;
using System.IO;
using System.Reflection;
using System.Runtime.InteropServices;

namespace EE_Q28585763
{
	class Program
	{
		static void Main(string[] args)
		{
			Console.WriteLine("Build Version: {0}", Executables.AssemblyBuildVersion);
			Console.WriteLine("Copyright: {0}", Executables.AssemblyCopyright);
			Console.WriteLine("Guid: {0}", Executables.AssemblyGuid);
			Console.WriteLine("Major Version: {0}", Executables.AssemblyMajorVersion);
			Console.WriteLine("Minor Version: {0}", Executables.AssemblyMinorVersion);
			Console.WriteLine("Revision: {0}", Executables.AssemblyRevisionVersion);
			Console.WriteLine("Title: {0}", Executables.AssemblyTitle);
			Console.WriteLine("Version: {0}", Executables.AssemblyVersion);
			Console.ReadLine();
		}
	}

	/// <summary>Class used to gather executable and dynamic link library information.</summary>
	[ObfuscationAttribute(Exclude = true, ApplyToMembers = true)]
	public sealed class Executables
	{
		/// <summary>Gets the Assembly Build Version.</summary>
		public static string AssemblyBuildVersion
		{
			get { return Assembly.GetEntryAssembly().GetName().Version.Build.ToString(); }
		}

		/// <summary>Gets the Assembly Copyright information.</summary>
		public static string AssemblyCopyright
		{
			get
			{
				object[] attributes = Assembly.GetEntryAssembly().GetCustomAttributes(typeof(AssemblyCopyrightAttribute), false);
				return (attributes != null && attributes.Length > 0) && ((AssemblyCopyrightAttribute)attributes[0] != null) ? ((AssemblyCopyrightAttribute)attributes[0]).Copyright : string.Empty;
			}
		}

		/// <summary>Gets the Assembly Guid.</summary>
		public static string AssemblyGuid
		{
			get
			{
				object[] attributes = Assembly.GetEntryAssembly().GetCustomAttributes(typeof(GuidAttribute), false);
				return attributes != null && attributes.Length > 0 ? ((GuidAttribute)attributes[0]).Value : string.Empty;
			}
		}

		/// <summary>Gets the Assembly Major Version.</summary>
		public static string AssemblyMajorVersion
		{
			get { return Assembly.GetEntryAssembly().GetName().Version.Major.ToString(); }
		}

		/// <summary>Gets the Assembly Minor Version.</summary>
		public static string AssemblyMinorVersion
		{
			get { return Assembly.GetEntryAssembly().GetName().Version.Minor.ToString(); }
		}

		/// <summary>Gets the Assembly Revision Version.</summary>
		public static string AssemblyRevisionVersion
		{
			get { return Assembly.GetEntryAssembly().GetName().Version.Revision.ToString(); }
		}

		/// <summary>Gets the Assembly Title.</summary>
		public static string AssemblyTitle
		{
			get
			{
				object[] attributes = Assembly.GetEntryAssembly().GetCustomAttributes(typeof(AssemblyTitleAttribute), false);
				return (attributes != null && attributes.Length > 0) && (((AssemblyTitleAttribute)attributes[0]).Title != "") ? ((AssemblyTitleAttribute)attributes[0]).Title : Path.GetFileNameWithoutExtension(Assembly.GetEntryAssembly().CodeBase);
			}
		}

		/// <summary>Gets the Assembly Version.</summary>
		public static string AssemblyVersion
		{
			get
			{
				Version version = Assembly.GetEntryAssembly().GetName().Version;
				return version != null && version.ToString().Length > 0 ? version.ToString() : string.Empty;
			}
		}
	}
}

Open in new window


Unprotected output -Capture.JPG
Protected output -Capture.JPG
-saige-
0
 
LVL 32

Accepted Solution

by:
it_saige earned 500 total points
ID: 40515762
ILDASM unprotected exe -
.method private hidebysig static void  Main(string[] args) cil managed
{
  .entrypoint
  // Code size       136 (0x88)
  .maxstack  2
  IL_0000:  nop
  IL_0001:  ldstr      "Build Version: {0}"
  IL_0006:  call       string EE_Q28585763.Executables::get_AssemblyBuildVersion()
  IL_000b:  call       void [mscorlib]System.Console::WriteLine(string,
                                                                object)
  IL_0010:  nop
  IL_0011:  ldstr      "Copyright: {0}"
  IL_0016:  call       string EE_Q28585763.Executables::get_AssemblyCopyright()
  IL_001b:  call       void [mscorlib]System.Console::WriteLine(string,
                                                                object)
  IL_0020:  nop
  IL_0021:  ldstr      "Guid: {0}"
  IL_0026:  call       string EE_Q28585763.Executables::get_AssemblyGuid()
  IL_002b:  call       void [mscorlib]System.Console::WriteLine(string,
                                                                object)
  IL_0030:  nop
  IL_0031:  ldstr      "Major Version: {0}"
  IL_0036:  call       string EE_Q28585763.Executables::get_AssemblyMajorVersion()
  IL_003b:  call       void [mscorlib]System.Console::WriteLine(string,
                                                                object)
  IL_0040:  nop
  IL_0041:  ldstr      "Minor Version: {0}"
  IL_0046:  call       string EE_Q28585763.Executables::get_AssemblyMinorVersion()
  IL_004b:  call       void [mscorlib]System.Console::WriteLine(string,
                                                                object)
  IL_0050:  nop
  IL_0051:  ldstr      "Revision: {0}"
  IL_0056:  call       string EE_Q28585763.Executables::get_AssemblyRevisionVersion()
  IL_005b:  call       void [mscorlib]System.Console::WriteLine(string,
                                                                object)
  IL_0060:  nop
  IL_0061:  ldstr      "Title: {0}"
  IL_0066:  call       string EE_Q28585763.Executables::get_AssemblyTitle()
  IL_006b:  call       void [mscorlib]System.Console::WriteLine(string,
                                                                object)
  IL_0070:  nop
  IL_0071:  ldstr      "Version: {0}"
  IL_0076:  call       string EE_Q28585763.Executables::get_AssemblyVersion()
  IL_007b:  call       void [mscorlib]System.Console::WriteLine(string,
                                                                object)
  IL_0080:  nop
  IL_0081:  call       string [mscorlib]System.Console::ReadLine()
  IL_0086:  pop
  IL_0087:  ret
} // end of method Program::Main

Open in new window


ILDASM of protected exe -Capture.JPG
.NET Reflector of protected exe -Capture.JPG
-saige-
0
 

Author Comment

by:exptech
ID: 40515768
It works. Thank you for the help !
0

Featured Post

Better Security Awareness With Threat Intelligence

See how one of the leading financial services organizations uses Recorded Future as part of a holistic threat intelligence program to promote security awareness and proactively and efficiently identify threats.

Join & Write a Comment

Suggested Solutions

Exception Handling is in the core of any application that is able to dignify its name. In this article, I'll guide you through the process of writing a DRY (Don't Repeat Yourself) Exception Handling mechanism, using Aspect Oriented Programming.
Real-time is more about the business, not the technology. In day-to-day life, to make real-time decisions like buying or investing, business needs the latest information(e.g. Gold Rate/Stock Rate). Unlike traditional days, you need not wait for a fe…
The viewer will learn how to clear a vector as well as how to detect empty vectors in C++.
The viewer will be introduced to the member functions push_back and pop_back of the vector class. The video will teach the difference between the two as well as how to use each one along with its functionality.

743 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question

Need Help in Real-Time?

Connect with top rated Experts

11 Experts available now in Live!

Get 1:1 Help Now